AWS: миграция RTFM, часть #1: ручное создание инфраструктуры – VPC, подсети, IGW, NAT GW, маршруты и EC2

Автор: | 25/08/2016

aws-logo-square-02В рамках подготовки переезда RTFM на нормальную инфраструктуру (вместо простого EC2 с NGINX/PHP-FPM/MySQL) – описание процесса ручного создания этой ифрастуктуры.

По сути – тут описываются те же шаги, что и в посте AWS: VPC – EC2 в public и private подсетях, NAT и Internet Gateway, плюс интересные примеры применения AWS CLI.

Использоваться будут EC2 с OpenBSD и CoreOS.

Причина выбора – интерес.

FreeBSD я не использовал со времени переезда RTFM на CentOS – осени 2014, когда в версии 9.1 ввели pkgng.

А CoreOS – из-за Docker. Хотя вполне вероятно, что окончательным решением для обеих (и будущих) машин станет всё-таки Debian. Почитать о CoreOS можно тут>>>, а найти номер последней стабильной версии – тут>>>.

EC2 инстансы:

  • Bastion – “фронтенд” с NGINX, Fail2ban, PSAD etc;
  • Zeus – “бекенд” с Docker.

Схема планируется следующая:

  • В публичной сети:
    • EC2:
      • Name: Bastion
      • OS: OpenBSD;
      • Soft: NGINX, Fail2ban, PSAD
  • В приватной сети:
    • EC2:
      • Name: Zeus
      • OS: CoreOS
      • Soft: Docker:
        • PHP-FPM (WordPress)

Позже – к EC2 подключим EBS-разделы и создадим RDS MySQL и S3 корзины для файлов и CloudFront CDN.

Процесс создания инфраструктуры включает в себя шаги:

  1. [AWS] ручное создание “голого” окружения с помощью AWS CLI (этот пост);
  2. [AWS] обновление “голого” окружения – добавление S3, CNDRDS;
  3. [AWS] создание шаблона CloudFormation для автоматизации развёртывания “голого” окружения;
  4. [CHEF] (Ansible?) создание рецептов для провижена Bastion;
  5. [AWS] обновление шаблона CloudFormation для провижена Bastion (NGINX, Fail2Ban, PSAD с хранением логов в S3);
  6. [CHEF] создание рецептов для провижена Zeus;
  7. [AWS] обновление шаблона CloudFormation для провижена Zeus;

Содержание этой части:

  1. VPC
    1. Security group
    2. Subnets
    3. Internet gateway
    4. NAT gateway
    5. Routes
  2. EC2

VPC

Начинаем с создания VPC:

$ aws ec2 create-vpc --cidr-block 10.0.0.0/16
{
    "Vpc": {
        "VpcId": "vpc-1cc04778", 
        "InstanceTenancy": "default", 
        "State": "pending", 
        "DhcpOptionsId": "dopt-8645a9e3", 
        "CidrBlock": "10.0.0.0/16", 
        "IsDefault": false
    }
}

И сразу ставим теги:

$ aws ec2 create-tags --resources vpc-1cc04778 --tags Key=Name,Value=rtfm_migrate_vpc

Проверяем:

$ aws ec2 describe-vpcs --vpc-ids vpc-1cc04778
{
    "Vpcs": [
        {
            "VpcId": "vpc-1cc04778", 
            "InstanceTenancy": "default", 
            "Tags": [
                {
                    "Value": "rtfm_migrate_vpc", 
                    "Key": "Name"
                }
            ], 
            "State": "available", 
            "DhcpOptionsId": "dopt-8645a9e3", 
            "CidrBlock": "10.0.0.0/16", 
            "IsDefault": false
        }
    ]
}

Security Group

По vpc-id находим Security Group, которая была создана при добавлении VPC:

$ aws ec2 describe-security-groups --filters Name=vpc-id,Values=vpc-1cc04778 --query '[SecurityGroups[*].GroupId]' --output text
sg-914e7ef6

Используя ID группы sg-914e7ef6 добавляем три правила – доступ по 80, 443 и 22 портам:

$ aws ec2 authorize-security-group-ingress --group-id sg-914e7ef6 --protocol tcp --port 80 --cidr 0.0.0.0/0
$ aws ec2 authorize-security-group-ingress --group-id sg-914e7ef6 --protocol tcp --port 443 --cidr 0.0.0.0/0
$ aws ec2 authorize-security-group-ingress --group-id sg-914e7ef6 --protocol tcp --port 22 --cidr 194.***.***.0/24

Subnets

Создаём новую подсеть в VPC – это будет наша публичная сеть для Bastion-хоста:

$ aws ec2 create-subnet --vpc-id vpc-1cc04778 --cidr-block 10.0.1.0/24
{
    "Subnet": {
        "VpcId": "vpc-1cc04778", 
        "CidrBlock": "10.0.1.0/24", 
        "State": "pending", 
        "AvailabilityZone": "eu-west-1b", 
        "SubnetId": "subnet-04ae0d5c", 
        "AvailableIpAddressCount": 251
    }
}

Добавляем теги:

$ aws ec2 create-tags --resources subnet-04ae0d5c --tags Key=Name,Value=rtfm_migrate_pub_net

Добавляем приватную сеть, для RDS и Docker-хоста Zeus:

$ aws ec2 create-subnet --vpc-id vpc-1cc04778 --cidr-block 10.0.2.0/24
{
    "Subnet": {
        "VpcId": "vpc-1cc04778", 
        "CidrBlock": "10.0.2.0/24", 
        "State": "pending", 
        "AvailabilityZone": "eu-west-1b", 
        "SubnetId": "subnet-f7ae0daf", 
        "AvailableIpAddressCount": 251
    }
}

Добавляем теги:

$ aws ec2 create-tags --resources subnet-f7ae0daf --tags Key=Name,Value=rtfm_migrate_priv_net

Проверяем:

$ aws ec2 describe-subnets --filters Name=vpc-id,Values=vpc-1cc04778
{
    "Subnets": [
        {
            "VpcId": "vpc-1cc04778", 
            "Tags": [
                {
                    "Value": "rtfm_migrate_pub_net", 
                    "Key": "Name"
                }
            ], 
            "CidrBlock": "10.0.1.0/24", 
            "MapPublicIpOnLaunch": false, 
            "DefaultForAz": false, 
            "State": "available", 
            "AvailabilityZone": "eu-west-1b", 
            "SubnetId": "subnet-04ae0d5c", 
            "AvailableIpAddressCount": 251
        }, 
        {
            "VpcId": "vpc-1cc04778", 
            "Tags": [
                {
                    "Value": "rtfm_migrate_priv_net", 
                    "Key": "Name"
                }
            ], 
            "CidrBlock": "10.0.2.0/24", 
            "MapPublicIpOnLaunch": false, 
            "DefaultForAz": false, 
            "State": "available", 
            "AvailabilityZone": "eu-west-1b", 
            "SubnetId": "subnet-f7ae0daf", 
            "AvailableIpAddressCount": 251
        }
    ]
}

VPC Internet Gateway

Создаём Internet Gateway дл VPC – через него будет работать Bastion:

$ aws ec2 create-internet-gateway
{
    "InternetGateway": {
        "Tags": [], 
        "InternetGatewayId": "igw-a82821cd", 
        "Attachments": []
    }
}

Подключаем его к VPC:

$ aws ec2 attach-internet-gateway --internet-gateway-id igw-a82821cd --vpc-id vpc-1cc04778

Добавляем теги:

$ aws ec2 create-tags --resources igw-a82821cd --tags Key=Name,Value=rtfm_migrate_igw

NAT Gateway

Для доступа из private подсети в Интернет – добавляем NAT Gateway. Его создаём в открытой подсети (subnet-04ae0d5c), в которой будет и Bastion-хост.

Получаем новый публичный IP для этого шлюза:

$ aws ec2 allocate-address --domain vpc
{
    "PublicIp": "52.210.114.208", 
    "Domain": "vpc", 
    "AllocationId": "eipalloc-f86a5c9d"
}

И создаём шлюз, указав allocation-id и subnet-id для подключения к шлюзу внешнего IP:

$ aws ec2 create-nat-gateway --subnet-id subnet-04ae0d5c --allocation-id eipalloc-f86a5c9d
{
    "NatGateway": {
        "NatGatewayAddresses": [
            {
                "AllocationId": "eipalloc-f86a5c9d"
            }
        ], 
        "VpcId": "vpc-1cc04778", 
        "State": "pending", 
        "NatGatewayId": "nat-0c045849a609991d3", 
        "SubnetId": "subnet-04ae0d5c", 
        "CreateTime": "2016-08-24T11:58:18.868Z"
    }
}

Route table

Создаём новую таблицу маршрутизации в VPC:

$ aws ec2 create-route-table --vpc-id vpc-1cc04778
{
    "RouteTable": {
        "Associations": [], 
        "RouteTableId": "rtb-b7f8bcd3", 
        "VpcId": "vpc-1cc04778", 
        "PropagatingVgws": [], 
        "Tags": [], 
        "Routes": [
            {
                "GatewayId": "local", 
                "DestinationCidrBlock": "10.0.0.0/16", 
                "State": "active", 
                "Origin": "CreateRouteTable"
            }
        ]
    }
}

Используя route-table-id и gateway-id – добавляем правило маршрутизации для публичной сети (10.0.1.0/24, subnet-04ae0d5c) в сеть 0.0.0.0/0 (весь Интернет) через Internet Gateway:

$ aws ec2 create-route --route-table-id rtb-b7f8bcd3 --destination-cidr-block 0.0.0.0/0 --gateway-id igw-a82821cd
{
    "Return": true
}

Подключаем эту таблицу к публичной подсети (не забываем это делать 🙂 ):

$ aws ec2 associate-route-table --route-table-id rtb-b7f8bcd3 --subnet-id subnet-04ae0d5c
{
    "AssociationId": "rtbassoc-c84194af"
}

Аналогично – создаём таблицу маршрутизации для приватной сети:

$ aws ec2 create-route-table --vpc-id vpc-1cc04778
{
    "RouteTable": {
        "Associations": [], 
        "RouteTableId": "rtb-dbf9bdbf", 
        "VpcId": "vpc-1cc04778", 
        "PropagatingVgws": [], 
        "Tags": [], 
        "Routes": [
            {
                "GatewayId": "local", 
                "DestinationCidrBlock": "10.0.0.0/16", 
                "State": "active", 
                "Origin": "CreateRouteTable"
            }
        ]
    }
}

Добавляем правило для маршрутизации трафика из приватной сети через созданный NAT Gateway в интернет:

$ aws ec2 create-route --route-table-id rtb-dbf9bdbf --nat-gateway-id nat-0c045849a609991d3 --destination-cidr-block 0.0.0.0/0
{
    "Return": true
}

Подключаем таблицу к приватной подсети:

$ aws ec2 associate-route-table --route-table-id rtb-dbf9bdbf --subnet-id subnet-f7ae0daf
{
    "AssociationId": "rtbassoc-6f409508"
}

EC2

Переходим к созданию инстансов. Сначала – Bastion.

Создаём пару ключей для использования в этом стеке:

$ aws ec2 create-key-pair --key-name rtfm_migrate --query 'KeyMaterial' --output text > ~/.ssh/rtfm_migrate.pem

Проверяем и устанавливаем права доступа:

$ head -n 3 ~/.ssh/rtfm_migrate.pem
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAgM5rUbkNL8ZYaZayRVNy7Vf1Gqp0bGgv9W7IEHGvFcQbomFxQ2DekfVgjvet
HRCfPR+hJ42+r+BE3J1fC+Iae5oBQb7b6z0TZX3oSHq36ehrSTTocvlkl9K8QGWHZ7kcB+xGFi4g
$ chmod 400 ~/.ssh/rtfm_migrate.pem
Создаём EC2 Bastion:
  • --image-id ami-170b6064: AMI с OpenBSD;
  • --instance-type t2.nano: описание типов можно посмотреть тут>>>;
  • --subnet-id subnet-04ae0d5c: созданная публичная подсеть.

Запускаем:

$ aws ec2 run-instances --image-id ami-170b6064 --count 1 --instance-type t2.nano --key-name rtfm_migrate --security-group-ids sg-914e7ef6 --subnet-id subnet-04ae0d5c                                                          
{
    "OwnerId": "264418146286",
    "ReservationId": "r-40bbd9cd",
    "Groups": [],
    "Instances": [
        {
            "Monitoring": {
                "State": "disabled"
            },
            "PublicDnsName": "",
            "RootDeviceType": "ebs",
            "State": {
                "Code": 0,
                "Name": "pending"
            },
            "EbsOptimized": false,
            "LaunchTime": "2016-08-24T12:22:33.000Z",
            "PrivateIpAddress": "10.0.1.243",
            "ProductCodes": [],
            "VpcId": "vpc-1cc04778",
            "StateTransitionReason": "",                                                                                                                                                                                                                                       
            "InstanceId": "i-8c7a7c00",
            "ImageId": "ami-170b6064", 
            "PrivateDnsName": "ip-10-0-1-243.eu-west-1.compute.internal",
            "KeyName": "rtfm_migrate",
            "SecurityGroups": [
                {
                    "GroupName": "default",
                    "GroupId": "sg-914e7ef6"
                }
            ],
            "ClientToken": "",
            "SubnetId": "subnet-04ae0d5c",
            "InstanceType": "t2.nano",
            "NetworkInterfaces": [
                {
                    "Status": "in-use",
                    "MacAddress": "0a:a7:06:1e:63:97",
                    "SourceDestCheck": true,
                    "VpcId": "vpc-1cc04778", 
                    "Description": "",
                    "NetworkInterfaceId": "eni-cbbcd994",
                    "PrivateIpAddresses": [
                        {
                            "Primary": true,
                            "PrivateIpAddress": "10.0.1.243"
                        }
                    ],
                    "Attachment": {
                        "Status": "attaching",
                        "DeviceIndex": 0,
                        "DeleteOnTermination": true,
                        "AttachmentId": "eni-attach-a194877e",
                        "AttachTime": "2016-08-24T12:22:33.000Z"
                    },
                    "Groups": [
                        {
                            "GroupName": "default",
                            "GroupId": "sg-914e7ef6"
                        }
                    ],
                    "SubnetId": "subnet-04ae0d5c",
                    "OwnerId": "264418146286",
                    "PrivateIpAddress": "10.0.1.243"
                }
            ],
            "SourceDestCheck": true,
            "Placement": {
                "Tenancy": "default",
                "GroupName": "",
                "AvailabilityZone": "eu-west-1b"
            },
            "Hypervisor": "xen",
            "BlockDeviceMappings": [],
            "Architecture": "x86_64",
            "StateReason": {
                "Message": "pending", 
                "Code": "pending"
            },
            "RootDeviceName": "/dev/sda1",
            "VirtualizationType": "hvm",
            "AmiLaunchIndex": 0
        }
    ]
}

Добавляем теги:

$ aws ec2 create-tags --resources i-8c7a7c00 --tags Key=Name,Value=rtfm_migrate_ec2_bastion

Получаем Elastic IP для этой машины:

$ aws ec2 allocate-address --domain vpc
{
    "PublicIp": "52.31.64.42", 
    "Domain": "vpc", 
    "AllocationId": "eipalloc-3f5e685a"
}

Подключаем его к созданному инстансу:

$ aws ec2 associate-address --public-ip 52.31.64.42 --instance-id i-8c7a7c00
{
    "AssociationId": "eipassoc-df2670b9"
}

После того, как машина поднялась – проверяем её доступность:

$ aws ec2telnet 52.31.64.42 22
Trying 52.31.64.42...
Connected to 52.31.64.42.
Escape character is '^]'.
SSH-2.0-OpenSSH_7.3
...

Находим подходящий AMI:

$ aws ec2 describe-images --owners aws-marketplace  --filters Name=architectureValues=x86_64,Name=virtualization-type,Values=hvm,Name=name,Values=CoreOS-stable-1068* --query '[Images[*].{Name:Name,ImageId:ImageId,Description:Description}]' --output text
CoreOS stable 1068.6.0 (HVM)    ami-03cea870    CoreOS-stable-1068.6.0-hvm-0d1e0bd0-eaea-4397-9a3a-c56f861d2a14-ami-edc744fa.3
CoreOS stable 1068.9.0 (HVM)    ami-068bfe75    CoreOS-stable-1068.9.0-hvm-0d1e0bd0-eaea-4397-9a3a-c56f861d2a14-ami-6d138f7a.3

Запускаем второй инстанс – Zeus, в приватной подсети subnet-f7ae0daf:

$ aws ec2 run-instances --image-id ami-068bfe75 --count 1 --instance-type t2.nano --key-name rtfm_migrate --security-group-ids sg-914e7ef6 --subnet-id subnet-f7ae0daf                                                                         
{
    "OwnerId": "264418146286",
    "ReservationId": "r-b589eb38",
    "Groups": [],
    "Instances": [
        {
            "Monitoring": {
                "State": "disabled"
            },
            "PublicDnsName": "",
            "RootDeviceType": "ebs",
            "State": {
                "Code": 0,
                "Name": "pending"
            },
            "EbsOptimized": false,
            "LaunchTime": "2016-08-24T13:51:21.000Z",
            "PrivateIpAddress": "10.0.2.101",
            "ProductCodes": [],
            "VpcId": "vpc-1cc04778",                                                                                                                                                                                                                                           
            "StateTransitionReason": "",
            "InstanceId": "i-0113158d",
            "ImageId": "ami-068bfe75", 
            "PrivateDnsName": "ip-10-0-2-101.eu-west-1.compute.internal", 
            "KeyName": "rtfm_migrate", 
            "SecurityGroups": [
                {
                    "GroupName": "default", 
                    "GroupId": "sg-914e7ef6"
                }
            ], 
            "ClientToken": "", 
            "SubnetId": "subnet-f7ae0daf", 
            "InstanceType": "t2.nano", 
            "NetworkInterfaces": [
                {
                    "Status": "in-use", 
                    "MacAddress": "0a:26:1d:6a:a4:cf", 
                    "SourceDestCheck": true, 
                    "VpcId": "vpc-1cc04778", 
                    "Description": "", 
                    "NetworkInterfaceId": "eni-03c3a65c", 
                    "PrivateIpAddresses": [
                        {
                            "Primary": true, 
                            "PrivateIpAddress": "10.0.2.101"
                        }
                    ], 
                    "Attachment": {
                        "Status": "attaching", 
                        "DeviceIndex": 0, 
                        "DeleteOnTermination": true, 
                        "AttachmentId": "eni-attach-be5a4861", 
                        "AttachTime": "2016-08-24T13:51:21.000Z"
                    }, 
                    "Groups": [
                        {
                            "GroupName": "default", 
                            "GroupId": "sg-914e7ef6"
                        }
                    ], 
                    "SubnetId": "subnet-f7ae0daf", 
                    "OwnerId": "264418146286", 
                    "PrivateIpAddress": "10.0.2.101"
                }
            ], 
            "SourceDestCheck": true, 
            "Placement": {
                "Tenancy": "default", 
                "GroupName": "", 
                "AvailabilityZone": "eu-west-1b"
            }, 
            "Hypervisor": "xen", 
            "BlockDeviceMappings": [], 
            "Architecture": "x86_64", 
            "StateReason": {
                "Message": "pending", 
                "Code": "pending"
            }, 
            "RootDeviceName": "/dev/xvda", 
            "VirtualizationType": "hvm", 
            "AmiLaunchIndex": 0
        }
    ]
}

Добавляем теги:

$ aws ec2 create-tags --resources i-0113158d --tags Key=Name,Value=rtfm_migrate_ec2_zeus

Проверяем.

Копируем ключ на Bastion:

$ scp -i ~/.ssh/rtfm_migrate.pem ~/.ssh/rtfm_migrate.pem [email protected]:/root/.ssh/
The authenticity of host '52.31.64.42 (52.31.64.42)' can't be established.
ECDSA key fingerprint is 50:e8:0f:71:e2:68:55:18:d5:eb:49:e8:75:ae:ee:ce.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '52.31.64.42' (ECDSA) to the list of known hosts.
rtfm_migrate.pem                           100% 1671     1.6KB/s   00:00

Подключаемся на Bastion, и с него – на Zeus:

# ssh [email protected] -i .ssh/rtfm_migrate.pem  
The authenticity of host '10.0.2.101 (10.0.2.101)' can't be established.
ECDSA key fingerprint is SHA256:AuRuYQ10QzHAMU9C3mDu2AxVK4ruajAHMrjZR2O5OfQ.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '10.0.2.101' (ECDSA) to the list of known hosts.
CoreOS stable (1068.9.0)
Last login: Wed Aug 24 13:57:57 2016 from 10.0.1.243
core@ip-10-0-2-101 ~ $

Проверяем Интернет с Zeus:

core@ip-10-0-2-101 ~ $ ping ya.ru
PING ya.ru (213.180.204.3) 56(84) bytes of data.
64 bytes from www.yandex.ru (213.180.204.3): icmp_seq=1 ttl=46 time=67.0 ms
64 bytes from www.yandex.ru (213.180.204.3): icmp_seq=2 ttl=46 time=66.9 ms

Готово.

Продолжение – AWS: миграция RTFM, часть #2: ручное создание инфраструктуры – AIM, S3, RDS и EBS.