AWS: VPC – EC2 в public и private подсетях, NAT и Internet Gateway

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

aws-logo-square-02Задача – создать два EC2 инстанса в одной VPC: один Master в public (с внешним IP) подсети, и второй, Slave – в private подсети, доступ к которому будет только с Master-хоста. Master будет “ходить в интернеты” через Internet Gateway, Slave – через NAT Gateway.

Схема примерно такая:

nat-gateway-diagram

Без особых деталей, просто step-by-step HowTo. Документация – тут>>>.

Фактически – это пост для подготовки инфраструктуры для дальнейшего развёртывания Swarm-кластера, которая описана в посте Docker: Docker Swarm кластер в AWS step-by-step. А “третья часть” – AWS: CloudFormation – создание шаблона для VPC, EC2, NAT и Internet Gateway.

Все действи выполняются через AWS CLI.

VPC

Подробнее о VPC в AWS – в посте AWS: VPC – введение, примеры и документации.

Создаём VPC для EC2 инстансов:

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

Проверяем:

$ aws ec2 describe-vpcs --vpc-ids vpc-24279540
{
    "Vpcs": [
        {
            "VpcId": "vpc-24279540", 
            "InstanceTenancy": "default", 
            "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-24279540
{
    "SecurityGroups": [
        {
            "IpPermissionsEgress": [
                {
                    "IpProtocol": "-1", 
                    "IpRanges": [
                        {
                            "CidrIp": "0.0.0.0/0"
                        }
                    ], 
                    "UserIdGroupPairs": [], 
                    "PrefixListIds": []
                }
            ], 
            "Description": "default VPC security group", 
            "IpPermissions": [
                {
                    "IpProtocol": "-1", 
                    "IpRanges": [], 
                    "UserIdGroupPairs": [
                        {
                            "UserId": "264418146286", 
                            "GroupId": "sg-cbbb9eac"
                        }
                    ], 
                    "PrefixListIds": []
                }
            ], 
            "GroupName": "default", 
            "VpcId": "vpc-24279540", 
            "OwnerId": "264418146286", 
            "GroupId": "sg-cbbb9eac"
        }
    ]
}

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

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

Или просто “всё и всем”:

$ aws ec2 authorize-security-group-ingress --group-id sg-cbbb9eac --protocol all --port all --cidr 0.0.0.0/0

Подсети

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

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

И вторую подсеть – приватную, для Slave:

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

Проверяем:

$ aws ec2 describe-subnets --filters Name=vpc-id,Values=vpc-24279540
{
    "Subnets": [
        {
            "VpcId": "vpc-24279540", 
            "CidrBlock": "10.0.2.0/24", 
            "MapPublicIpOnLaunch": false, 
            "DefaultForAz": false, 
            "State": "available", 
            "AvailabilityZone": "eu-west-1c", 
            "SubnetId": "subnet-c3c35da7", 
            "AvailableIpAddressCount": 251
        }, 
        {
            "VpcId": "vpc-24279540", 
            "CidrBlock": "10.0.1.0/24", 
            "MapPublicIpOnLaunch": false, 
            "DefaultForAz": false, 
            "State": "available", 
            "AvailabilityZone": "eu-west-1c", 
            "SubnetId": "subnet-f6c35d92", 
            "AvailableIpAddressCount": 251
        }
    ]
}

Ещё раз про подсети.

Public, для Master:

            "CidrBlock": "10.0.1.0/24", 
            ...
            "SubnetId": "subnet-f6c35d92",

Private, для Slave:

            "CidrBlock": "10.0.2.0/24", 
            ...
            "SubnetId": "subnet-c3c35da7",

VPC Internet Gateway

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

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

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

$ aws ec2 attach-internet-gateway --internet-gateway-id igw-6e6d6e0b --vpc-id vpc-24279540

Проверяем:

$ aws ec2 describe-internet-gateways --filters Name=internet-gateway-id,Values=igw-6e6d6e0b
{
    "InternetGateways": [
        {
            "Tags": [], 
            "InternetGatewayId": "igw-6e6d6e0b", 
            "Attachments": [
                {
                    "State": "available", 
                    "VpcId": "vpc-24279540"
                }
            ]
        }
    ]
}

NAT Gateway

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

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

$ aws ec2 allocate-address --domain vpc
{
    "PublicIp": "52.209.79.242", 
    "Domain": "vpc", 
    "AllocationId": "eipalloc-8d96c8e8"
}

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

$ aws ec2 create-nat-gateway --subnet-id subnet-f6c35d92 --allocation-id eipalloc-8d96c8e8
{
    "NatGateway": {
        "NatGatewayAddresses": [
            {
                "AllocationId": "eipalloc-8d96c8e8"
            }
        ], 
        "VpcId": "vpc-24279540", 
        "State": "pending", 
        "NatGatewayId": "nat-02b563f68fb5e6bce", 
        "SubnetId": "subnet-f6c35d92", 
        "CreateTime": "2016-08-11T07:29:47.287Z"
    }
}

Route table

Находим ID таблицы машрутизации нашей VPC:

$ aws ec2 describe-route-tables --filters Name=vpc-id,Values=vpc-24279540
{
    "RouteTables": [
        {
            "Associations": [
                {
                    "RouteTableAssociationId": "rtbassoc-b9be45de", 
                    "Main": true, 
                    "RouteTableId": "rtb-b90271dd"
                }
            ], 
            "RouteTableId": "rtb-b90271dd", 
            "VpcId": "vpc-24279540", 
            "PropagatingVgws": [], 
            "Tags": [], 
            "Routes": [
                {
                    "GatewayId": "local", 
                    "DestinationCidrBlock": "10.0.0.0/16", 
                    "State": "active", 
                    "Origin": "CreateRouteTable"
                }
            ]
        }
    ]
}

Добавляем правило маршрутизации для публичной сети (10.0.1.0/24) в сеть 0.0.0.0/0 (весь Интернет) через Intenet Gateway:

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

Для закрытой подсети – создаём новую таблицу маршрутизации:

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

В ней создаём новое правило для маршрутизации трафика из подсети 10.0.2.0/24 через NAT Gateway, который расположен в публичной посети:

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

Подключаем эту таблицу маршрутизации к закрытой подсети 10.0.2.0/24, в которой будет наш Slave-хост:

$ aws ec2 associate-route-table --route-table-id rtb-03007367 --subnet-id subnet-c3c35da7
{
    "AssociationId": "rtbassoc-e0b34887"
}

Проверяем.

NAT Gateway:

$ aws ec2 describe-route-tables --route-table-ids rtb-03007367 --query '[RouteTables[*].Routes[*].NatGatewayId]'
[
    [
        [
            "nat-02b563f68fb5e6bce"
        ]
    ]
]

Internet Gateway:

$ aws ec2 describe-route-tables --route-table-ids rtb-b90271dd --query '[RouteTables[*].Routes[*].GatewayId]'
[
    [
        [
            "local", 
            "igw-6e6d6e0b"
        ]
    ]
]

Про форматирование вывода с помощью --query – см. тут>>>.

EC2 инстансы

Создаём 2 инстанса – Master и Slave.

Key pairs

Создаём RSA ключи доступа:

$ aws ec2 create-key-pair --key-name my-cluster --query 'KeyMaterial' --output text > my-cluster.pem

Проверяем:

$ head -n 3 my-cluster.pem 
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAgpBI74z1uF5rN6JVtvcNnZZj5t4Pv931Vc61nda/ItUpCppqB5Z1ZBTevhZr
jSXN7bXtBabl+CUJ82N1IYyevdzFMlKVfmylnVa4U8E4vocXiygm3jMyMthHrRkRs025Hs7wGMPw

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

$ chmod 400 my-cluster.pem

Создание EC2

Далее – создаём Master EC2 инстанс в публичной подсети.

Нам потребуется указать AMI ID, который будем запускать, имя ключей, которые мы создали, ID Security-группы и ID подсети в VPC.

Находим AMI, например – Ubuntu:

$ aws ec2 describe-images --owners 099720109477 --filters Name=platform,Values=linux,Name=name,Values='*ubuntu-trusty-14.04-amd64-server*' --query 'sort_by(Images, &Name)' --output text | head
x86_64 2014-06-27T00:18:25.000Z xen ami-8f6ca7f8 099720109477/ubuntu/images/ebs-io1/ubuntu-trusty-14.04-amd64-server-20140607.1 machine aki-52a34525 ubuntu/images/ebs-io1/ubuntu-trusty-14.04-amd64-server-20140607.1 099720109477 True /dev/sda1 ebs available paravirtual
BLOCKDEVICEMAPPINGS /dev/sda1
EBS True False 300 snap-e20ed21b 8 io1
BLOCKDEVICEMAPPINGS /dev/sdb ephemeral0
x86_64 2014-07-25T08:25:51.000Z xen ami-f36cbd84 099720109477/ubuntu/images/ebs-io1/ubuntu-trusty-14.04-amd64-server-20140724 machine aki-52a34525 ubuntu/images/ebs-io1/ubuntu-trusty-14.04-amd64-server-20140724 099720109477 True /dev/sda1 ebs available paravirtual
BLOCKDEVICEMAPPINGS /dev/sda1
EBS True False 200 snap-5ed700a6 8 io1
BLOCKDEVICEMAPPINGS /dev/sdb ephemeral0
x86_64 2014-08-14T00:44:47.000Z xen ami-7615c901 099720109477/ubuntu/images/ebs-io1/ubuntu-trusty-14.04-amd64-server-20140813 machine aki-52a34525 ubuntu/images/ebs-io1/ubuntu-trusty-14.04-amd64-server-20140813 099720109477 True /dev/sda1 ebs available paravirtual
BLOCKDEVICEMAPPINGS /dev/sda1

Теперь можно создать Master в сети 10.0.1.0/24 (ID subnet-f6c35d92):

$ aws ec2 run-instances --image-id ami-7615c901 --count 1 --instance-type t1.micro --key-name my-cluster --security-group-ids sg-cbbb9eac --subnet-id subnet-f6c35d92                                                    
{
    "OwnerId": "264418146286"
    "ReservationId": "r-43ef55fa",
    "Groups": [],
    "Instances": [
        {
            "Monitoring": {
                "State": "disabled"
            },
            "PublicDnsName": "",
            "KernelId": "aki-52a34525",
            "State": {
                "Code": 0,
                "Name": "pending"
            },                                                                                                                                                                                                                                                                 
            "EbsOptimized": false,
            "LaunchTime": "2016-08-11T07:36:54.000Z",
            "PrivateIpAddress": "10.0.1.103",
            "ProductCodes": [],
            "VpcId": "vpc-24279540",
            "StateTransitionReason": "",
            "InstanceId": "i-46e228cd",
            "ImageId": "ami-7615c901",
            "PrivateDnsName": "ip-10-0-1-103.eu-west-1.compute.internal",
            "KeyName": "my-cluster", 
            "SecurityGroups": [
                {
                    "GroupName": "default", 
                    "GroupId": "sg-cbbb9eac"
                }
            ], 
            "ClientToken": "", 
            "SubnetId": "subnet-f6c35d92", 
            "InstanceType": "t1.micro", 
            "NetworkInterfaces": [
                {
                    "Status": "in-use", 
                    "MacAddress": "02:f7:03:6e:6c:45", 
                    "SourceDestCheck": true, 
                    "VpcId": "vpc-24279540", 
                    "Description": "", 
                    "NetworkInterfaceId": "eni-c839beb3", 
                    "PrivateIpAddresses": [
                        {
                            "Primary": true, 
                            "PrivateIpAddress": "10.0.1.103"
                        }
                    ], 
                    "Attachment": {
                        "Status": "attaching", 
                        "DeviceIndex": 0, 
                        "DeleteOnTermination": true, 
                        "AttachmentId": "eni-attach-9c924e40", 
                        "AttachTime": "2016-08-11T07:36:54.000Z"
                    }, 
                    "Groups": [
                        {
                            "GroupName": "default", 
                            "GroupId": "sg-cbbb9eac"
                        }
                    ], 
                    "SubnetId": "subnet-f6c35d92", 
                    "OwnerId": "264418146286", 
                    "PrivateIpAddress": "10.0.1.103"
                }
            ], 
            "SourceDestCheck": true, 
            "Placement": {
                "Tenancy": "default", 
                "GroupName": "", 
                "AvailabilityZone": "eu-west-1c"
            }, 
            "Hypervisor": "xen", 
            "BlockDeviceMappings": [], 
            "Architecture": "x86_64", 
            "StateReason": {
                "Message": "pending", 
                "Code": "pending"
            }, 
            "RootDeviceName": "/dev/sda1", 
            "VirtualizationType": "paravirtual", 
            "RootDeviceType": "ebs", 
            "AmiLaunchIndex": 0
        }
    ]
}

Создаём Slave в сети 10.0.2.0/24 (ID subnet-c3c35da7):

$ aws ec2 run-instances --image-id ami-7615c901 --count 1 --instance-type t1.micro --key-name my-cluster --security-group-ids sg-cbbb9eac --subnet-id subnet-c3c35da7                                                    
{
    "OwnerId": "264418146286",
    "ReservationId": "r-92ef552b",
    "Groups": [],                                                                                                                                                                                                                                                              
    "Instances": [
        {
            "Monitoring": {
                "State": "disabled"
            },
            "PublicDnsName": "",
            "KernelId": "aki-52a34525",
            "State": {
                "Code": 0,
                "Name": "pending"
            },
            "EbsOptimized": false,
            "LaunchTime": "2016-08-11T07:38:10.000Z",
            "PrivateIpAddress": "10.0.2.33",
            "ProductCodes": [],
            "VpcId": "vpc-24279540",
            "StateTransitionReason": "",
            "InstanceId": "i-f9e32972",
            "ImageId": "ami-7615c901",
            "PrivateDnsName": "ip-10-0-2-33.eu-west-1.compute.internal", 
            "KeyName": "my-cluster", 
            "SecurityGroups": [
                {
                    "GroupName": "default", 
                    "GroupId": "sg-cbbb9eac"
                }
            ], 
            "ClientToken": "", 
            "SubnetId": "subnet-c3c35da7", 
            "InstanceType": "t1.micro", 
            "NetworkInterfaces": [
                {
                    "Status": "in-use", 
                    "MacAddress": "02:39:e4:a0:03:0b", 
                    "SourceDestCheck": true, 
                    "VpcId": "vpc-24279540", 
                    "Description": "", 
                    "NetworkInterfaceId": "eni-03c74178", 
                    "PrivateIpAddresses": [
                        {
                            "Primary": true, 
                            "PrivateIpAddress": "10.0.2.33"
                        }
                    ], 
                    "Attachment": {
                        "Status": "attaching", 
                        "DeviceIndex": 0, 
                        "DeleteOnTermination": true, 
                        "AttachmentId": "eni-attach-33914def", 
                        "AttachTime": "2016-08-11T07:38:10.000Z"
                    }, 
                    "Groups": [
                        {
                            "GroupName": "default", 
                            "GroupId": "sg-cbbb9eac"
                        }
                    ], 
                    "SubnetId": "subnet-c3c35da7", 
                    "OwnerId": "264418146286", 
                    "PrivateIpAddress": "10.0.2.33"
                }
            ], 
            "SourceDestCheck": true, 
            "Placement": {
                "Tenancy": "default", 
                "GroupName": "", 
                "AvailabilityZone": "eu-west-1c"
            }, 
            "Hypervisor": "xen", 
            "BlockDeviceMappings": [], 
            "Architecture": "x86_64", 
            "StateReason": {
                "Message": "pending", 
                "Code": "pending"
            }, 
            "RootDeviceName": "/dev/sda1", 
            "VirtualizationType": "paravirtual", 
            "RootDeviceType": "ebs", 
            "AmiLaunchIndex": 0
        }
    ]
}

Public IP

Для доступа к мастеру – нам потребуется Public IP.

Получаем адрес:

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

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

$ aws ec2 associate-address --public-ip 52.210.51.198 --instance-id i-46e228cd
{
    "AssociationId": "eipassoc-60196c06"
}

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

$ telnet 52.210.51.198 22
Trying 52.210.51.198...
Connected to 52.210.51.198.
Escape character is '^]'.
SSH-2.0-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2
quit
Protocol mismatch.
Connection closed by foreign host.

Копируем созданный ранее RSA ключ на мастер:

$ scp -i my-cluster.pem my-cluster.pem [email protected]:/home/ubuntu
The authenticity of host '52.210.51.198 (52.210.51.198)' can't be established.
ECDSA key fingerprint is cf:09:53:70:9b:8d:07:51:3b:16:f5:33:c4:bf:4c:0c.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '52.210.51.198' (ECDSA) to the list of known hosts.
my-cluster.pem                         100% 1675     1.6KB/s   00:00

Подключаемся к мастеру:

$ ssh [email protected] -i my-cluster.pem
...
ubuntu@ip-10-0-1-103:~$

И с него – на slave ("PrivateIpAddress": "10.0.2.33"):

$ ssh [email protected] -i my-cluster.pem
...
ubuntu@ip-10-0-2-33:~$

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

$ ping ya.ru
PING ya.ru (213.180.193.3) 56(84) bytes of data.
64 bytes from www.yandex.ru (213.180.193.3): icmp_seq=1 ttl=51 time=63.1 ms

Готово.