AWS: CloudFormation — создание шаблона для VPC, EC2, NAT и Internet Gateway

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

aws-logo-square-02Пошаговый процесс написания шаблона для AWS CloudFormation, который создаёт VPC, подсети и инстансы для Docker Swarm-кластера.

Первая часть — AWS: VPC – EC2 в public и private подсетях, NAT и Internet Gateway.

Вторая часть: Docker: Docker Swarm кластер в AWS step-by-step.

Общие сведения по AWS CloudFormation — AWS: CloudFormation.

Готовый шаблон доступен тут>>>.

VPC

Начнём с VPC:

{
  "AWSTemplateFormatVersion" : "2010-09-09",

  "Description" : "AWS CloudFormation Template Docker Swarm cluster in VPC",

  "Parameters" : {

    "VPCCidrBlock" : {
      "Description" : "VPC CIDR IP addresses block",
      "Type" : "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default": "11.0.0.0/16",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
    }
  },

  "Resources" : {

    "VPC" : {
      "Type" : "AWS::EC2::VPC",
      "Properties" : {
        "CidrBlock" : { "Ref" : "VPCCidrBlock" },
        "EnableDnsSupport" : "false",
        "EnableDnsHostnames" : "false",
        "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
      }
    }
  },

  "Outputs" : {
    "VPCCIDR" : {
      "Value" : { "Fn::GetAtt" : ["VPC", "CidrBlock"] },
      "Description" : "VPC CIDR IP addresses block"
    }
  }
}

Запускаем:

$ aws cloudformation create-stack --stack-name VPConly1 --template-body file:////home//setevoy//PycharmProjects//Swarm-cluster/VPC_alone.template
{
    "StackId": "arn:aws:cloudformation:eu-west-1:264418146286:stack/VPConly1/c7cc9d40-6383-11e6-9d18-50faeb52a4d2"
}

Security Group

Для конфигурирования Security Group — добавим параметр, через который можно указать IP, с которого будет разрешён SSH к Мастеру:

...
    "SSHLocation" : {
      "Description" : " The IP address range that can be used to SSH to the EC2 instances",
      "Type": "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default": "0.0.0.0/0",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
    }
...

Добавляем ресурс MasterSecurityGroup:

...
    "MasterSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "VpcId" : { "Ref": "VPC"},
        "GroupDescription" : "Enable HTTP access via port 80 and SSH access",
        "SecurityGroupIngress" : [
          {"IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0"},
          {"IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : { "Ref" : "SSHLocation"}}
        ]
      }
    }
...

В котором мы открываем порт 22 для доступа с IP, переданного  в параметре {"Ref" : "SSHLocation"}, и порт 80 — отовсюду.

Сохраняем, удаляем старый стек и создаём его заново (с другим именем, пока удаляется старый):

$ aws cloudformation delete-stack --stack-name VPConly1
$ aws cloudformation create-stack --stack-name VPConly2 --template-body file:////home//setevoy//PycharmProjects//Swarm-cluster/VPC_alone.template
{
    "StackId": "arn:aws:cloudformation:eu-west-1:264418146286:stack/VPConly2/f63414a0-6384-11e6-8c8c-500c3cd570d2"
}

Проверим.

Находим описание всех созаднных в стеке ресурсов:

$ aws cloudformation list-stack-resources --stack-name VPConly2
{
    "StackResourceSummaries": [
        {
            "ResourceType": "AWS::EC2::SecurityGroup", 
            "PhysicalResourceId": "VPConly2-MasterSecurityGroup-1TCMP3SIJOV5N", 
            "LastUpdatedTimestamp": "2016-08-16T07:42:39.218Z", 
            "ResourceStatus": "CREATE_COMPLETE", 
            "LogicalResourceId": "MasterSecurityGroup"
        }, 
        {
            "ResourceType": "AWS::EC2::VPC", 
            "PhysicalResourceId": "vpc-72ab1516", 
            "LastUpdatedTimestamp": "2016-08-16T07:42:40.951Z", 
            "ResourceStatus": "CREATE_COMPLETE", 
            "LogicalResourceId": "VPC"
        }
    ]
}

И по PhysicalResourceId группы безопасности — проверяем правила:

$ aws ec2 describe-security-groups --group-names VPConly2-MasterSecurityGroup-1TCMP3SIJOV5N --filters --query '[SecurityGroups[*].IpPermissions[*].{Port:FromPort,FromIP:IpRanges}]' --output text
80
FROMIP  0.0.0.0/0
22
FROMIP  0.0.0.0/0

Подсети

Далее — добавим две подсети — публичную 11.0.1.0/24, и приватную — 11.0.2.0/24. При этом — их блоки адресов надо иметь возможность переопределить во время создания стека, поэтому — вынесем их отдельными параметрами.

Добавляем два параметра для новых подсетей — PubNetCIDR и PrivNetCIDR:

...
    "PubNetCIDR": {
      "Description" : "The IP address range tin VPC block for Public subnet",
      "Type": "String",
      "Default": "11.0.1.0/24",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."  
    },
    "PrivNetCIDR": {
      "Description" : "The IP address range tin VPC block for Private subnet",
      "Type": "String",
      "Default": "11.0.2.0/24",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."  
    }
...

Теперь — два ресурса:

...
    "PubSubnet" : {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "VpcId": { "Ref": "VPC"},
        "CidrBlock": { "Ref": "PubNetCIDR"},
        "AvailabilityZone": { "Fn::Select" : [ "0", { "Fn::GetAZs" : "" } ]}
      }
    },

    "PrivSubnet" : {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "VpcId": { "Ref": "VPC"},
        "CidrBlock": { "Ref": "PrivNetCIDR"},
        "AvailabilityZone": { "Fn::Select" : [ "0", { "Fn::GetAZs" : "" } ]}
      }
    }
...

VpcId мы получим из значения, возвращаемого ресурсом VPC, CidrBlock — из определенных нами выше параметров, а AvailabilityZone — с помощью функции GetAZs.

Проверяем шаблон:

$ aws cloudformation validate-template --template-body file:////home//setevoy//PycharmProjects//Swarm-cluster/VPC_alone.template
{
    "Description": "AWS CloudFormation Template Docker Swarm cluster in VPC", 
    "Parameters": [
        {
            "DefaultValue": "0.0.0.0/0", 
            "NoEcho": false, 
            "Description": "The IP address range that can be used to SSH to the EC2 instances", 
            "ParameterKey": "SSHLocation"
        }, 
        {
            "DefaultValue": "11.0.2.0/24", 
            "NoEcho": false, 
            "Description": "The IP address range tin VPC block for Public subnet", 
            "ParameterKey": "PrivNetCIDR"
        }, 
        {
            "DefaultValue": "11.0.1.0/24", 
            "NoEcho": false, 
            "Description": "The IP address range tin VPC block for Public subnet", 
            "ParameterKey": "PubNetCIDR"
        }, 
        {
            "DefaultValue": "11.0.0.0/16", 
            "NoEcho": false, 
            "Description": "VPC CIDR IP addresses block", 
            "ParameterKey": "VPCCidrBlock"
        }
    ]
}

Запускаем создание:

$ aws cloudformation delete-stack --stack-name VPConly2
$ aws cloudformation create-stack --stack-name VPConly3 --template-body file:////home//setevoy//PycharmProjects//Swarm-cluster/VPC_alone.template
{
    "StackId": "arn:aws:cloudformation:eu-west-1:264418146286:stack/VPConly3/b3a9bf00-6392-11e6-9731-500c4233b6d2"
}

Проверяем созданные ресурсы:

$ aws cloudformation list-stack-resources --stack-name VPConly3
{
    "StackResourceSummaries": [
        {
            "ResourceType": "AWS::EC2::SecurityGroup", 
            "PhysicalResourceId": "VPConly3-MasterSecurityGroup-QFU149RV4XM8", 
            "LastUpdatedTimestamp": "2016-08-16T09:21:01.300Z", 
            "ResourceStatus": "CREATE_COMPLETE", 
            "LogicalResourceId": "MasterSecurityGroup"
        }, 
        {
            "ResourceType": "AWS::EC2::Subnet", 
            "PhysicalResourceId": "subnet-e39d0895", 
            "LastUpdatedTimestamp": "2016-08-16T09:21:26.706Z", 
            "ResourceStatus": "CREATE_COMPLETE", 
            "LogicalResourceId": "PrivSubnet"
        }, 
        {
            "ResourceType": "AWS::EC2::Subnet", 
            "PhysicalResourceId": "subnet-e49d0892", 
            "LastUpdatedTimestamp": "2016-08-16T09:21:22.010Z", 
            "ResourceStatus": "CREATE_COMPLETE", 
            "LogicalResourceId": "PubSubnet"
        }, 
        {
            "ResourceType": "AWS::EC2::VPC", 
            "PhysicalResourceId": "vpc-7f9a241b", 
            "LastUpdatedTimestamp": "2016-08-16T09:21:01.928Z", 
            "ResourceStatus": "CREATE_COMPLETE", 
            "LogicalResourceId": "VPC"
        }
    ]
}

И созданные подсети:

$ aws ec2 describe-subnets --filters Name=vpc-id,Values=vpc-7f9a241b
{
    "Subnets": [
        {
            "VpcId": "vpc-7f9a241b", 
            "Tags": [
                {
                    "Value": "arn:aws:cloudformation:eu-west-1:264418146286:stack/VPConly3/b3a9bf00-6392-11e6-9731-500c4233b6d2", 
                    "Key": "aws:cloudformation:stack-id"
                }, 
                {
                    "Value": "VPConly3", 
                    "Key": "aws:cloudformation:stack-name"
                }, 
                {
                    "Value": "PubSubnet", 
                    "Key": "aws:cloudformation:logical-id"
                }
            ], 
            "CidrBlock": "11.0.1.0/24", 
            "MapPublicIpOnLaunch": false, 
            "DefaultForAz": false, 
            "State": "available", 
            "AvailabilityZone": "eu-west-1a", 
            "SubnetId": "subnet-e49d0892", 
            "AvailableIpAddressCount": 251
        }, 
        {
            "VpcId": "vpc-7f9a241b", 
            "Tags": [
                {
                    "Value": "arn:aws:cloudformation:eu-west-1:264418146286:stack/VPConly3/b3a9bf00-6392-11e6-9731-500c4233b6d2", 
                    "Key": "aws:cloudformation:stack-id"
                }, 
                {
                    "Value": "PrivSubnet", 
                    "Key": "aws:cloudformation:logical-id"
                }, 
                {
                    "Value": "VPConly3", 
                    "Key": "aws:cloudformation:stack-name"
                }
            ], 
            "CidrBlock": "11.0.2.0/24", 
            "MapPublicIpOnLaunch": false, 
            "DefaultForAz": false, 
            "State": "available", 
            "AvailabilityZone": "eu-west-1a", 
            "SubnetId": "subnet-e39d0895", 
            "AvailableIpAddressCount": 251
        }
    ]
}

VPC Internet Gateway

Далее создаем VPC Internet Gateway.

В ресурсы добавляем:

...
    "PubNetIGW" : {
      "Type": "AWS::EC2::InternetGateway",
      "Properties": {}
    }
...

И подключение этого IGW к VPC по VpcID:

...
    "AttachGateway" : {
      "Type": "AWS::EC2::VPCGatewayAttachment",
      "Properties": {
        "VpcId": { "Ref": "VPC" },
        "InternetGatewayId": { "Ref": "PubNetIGW" }
      }
    }
...

Запускаем:

$ aws cloudformation delete-stack --stack-name VPConly3
$ aws cloudformation create-stack --stack-name VPConly4 --template-body file:////home//setevoy//PycharmProjects//Swarm-cluster/VPC_alone.template
{
    "StackId": "arn:aws:cloudformation:eu-west-1:264418146286:stack/VPConly4/f5034280-6394-11e6-8cff-500c42421e36"
}

После создания — проверяем по VPC ID:

$ aws ec2 describe-internet-gateways --filters Name=attachment.vpc-id,Values=vpc-9c9927f8
{
    "InternetGateways": [
        {
            "Tags": [
                {
                    "Value": "PubNetIGW", 
                    "Key": "aws:cloudformation:logical-id"
                }, 
                {
                    "Value": "VPConly4", 
                    "Key": "aws:cloudformation:stack-name"
                }, 
                {
                    "Value": "arn:aws:cloudformation:eu-west-1:264418146286:stack/VPConly4/f5034280-6394-11e6-8cff-500c42421e36", 
                    "Key": "aws:cloudformation:stack-id"
                }
            ], 
            "InternetGatewayId": "igw-f42d2091", 
            "Attachments": [
                {
                    "State": "available", 
                    "VpcId": "vpc-9c9927f8"
                }
            ]
        }
    ]
}

NAT Gateway и Elastic IP

Для NAT-инстанса нам потребуется публичный IP.

Добавляем ресурс для получения Elastic IP:

...
    "NatEIP": {
      "Type" : "AWS::EC2::EIP",
      "Properties" : { "Domain" : "vpc" }
    }
...

И создание реcурса NAT Gateway:

...
    "NatGW": {
      "Type" : "AWS::EC2::NatGateway",
      "Properties" : {
        "AllocationId" : { "Fn::GetAtt" : ["NatEIP", "AllocationId"]},
        "SubnetId" : { "Ref": "PubSubnet"}
      }
    }
...

SubnetId мы получаем из ресурса PubSubnet (NAT инстанс будет в публичной сети), а AllocationId — через атрибут AllocationId ресурса NatEIP.

Запускаем:

$ aws cloudformation delete-stack --stack-name VPConly4
$ aws cloudformation create-stack --stack-name VPConly5 --template-body file:////home//setevoy//PycharmProjects//Swarm-cluster/VPC_alone.template
{
    "StackId": "arn:aws:cloudformation:eu-west-1:264418146286:stack/VPConly5/7b518e20-6398-11e6-9a5d-50faeb59c0d2"
}

Route tables

Последним шагом в создании сети будет настройка маршрутизации из подсетей.

Публичная сеть — будет ходить через Internet Gateway PubNetIGW, а приватная — через NAT NatGW, который будет в публичной сети PubSubnet.

Для этого — создаём две таблицы маршрутизации:

...
    "PublicRouteTable" : {
      "Type" : "AWS::EC2::RouteTable",
      "Properties" : {
        "VpcId": { "Ref": "VPC" }
      }
    },
    
    "PrivateRouteTable" : {
      "Type" : "AWS::EC2::RouteTable",
      "Properties" : {
        "VpcId": { "Ref": "VPC" }
      }
    },
...

И добавляем в них правила. Правило для таблицы маршрутизации публичной сети — пускать трафик через созданный Internet Gataway, для приватной — всё через NAT Gateway:

...
    "PublicRoute" : {
      "Type": "AWS::EC2::Route",
      "Properties": {
        "RouteTableId": { "Ref": "PublicRouteTable" },
        "DestinationCidrBlock": "0.0.0.0/0",
        "GatewayId": { "Ref": "PubNetIGW" }
      }
    },
    
    "PrivateRoute" : {
      "Type": "AWS::EC2::Route",
      "Properties": {
        "RouteTableId": { "Ref": "PrivateRouteTable" },
        "DestinationCidrBlock": "0.0.0.0/0",
        "NatGatewayId": { "Ref": "NatGW" }
      }
    }
...

Таблицу мо-умолчанию, которая создаётся вместе с VPC — можно вообще не трогать.

Добавляем подключение этих таблиц маршрутизации к подсетям:

...
    
    "PublicRouteAssociate": {
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "RouteTableId" : { "Ref": "PublicRouteTable"},
        "SubnetId" : { "Ref": "PubSubnet"}
      }
    },
    
    "PrivateRouteAssociate": {
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "RouteTableId" : { "Ref": "PrivateRouteTable"},
        "SubnetId" : { "Ref": "PrivSubnet"}
      }
    }
...

Запускаем:

$ aws cloudformation delete-stack --stack-name VPConly5
$ aws cloudformation create-stack --stack-name VPConly6 --template-body file:////home//setevoy//PycharmProjects//Swarm-cluster/VPC_alone.template
{
    "StackId": "arn:aws:cloudformation:eu-west-1:264418146286:stack/VPConly6/81f38640-639b-11e6-a87d-50faeb59c036"
}

Проверяем:

$ aws ec2 describe-route-tables --filters Name=vpc-id,Values=vpc-608f3104 --query '[RouteTables[*].[RouteTableId, Routes[*]]]'
[
    [
        [
            "rtb-c5710ea1", 
            [
                {
                    "GatewayId": "local", 
                    "DestinationCidrBlock": "11.0.0.0/16", 
                    "State": "active", 
                    "Origin": "CreateRouteTable"
                }, 
                {
                    "Origin": "CreateRoute", 
                    "DestinationCidrBlock": "0.0.0.0/0", 
                    "NatGatewayId": "nat-03e3bc9d817d3ed1c", 
                    "State": "active"
                }
            ]
        ], 
        [
            "rtb-c6710ea2", 
            [
                {
                    "GatewayId": "local", 
                    "DestinationCidrBlock": "11.0.0.0/16", 
                    "State": "active", 
                    "Origin": "CreateRouteTable"
                }, 
                {
                    "GatewayId": "igw-122b2677", 
                    "DestinationCidrBlock": "0.0.0.0/0", 
                    "State": "active", 
                    "Origin": "CreateRoute"
                }
            ]
        ], 
        [
            "rtb-f9710e9d", 
            [
                {
                    "GatewayId": "local", 
                    "DestinationCidrBlock": "11.0.0.0/16", 
                    "State": "active", 
                    "Origin": "CreateRouteTable"
                }
            ]
        ]
    ]
]

EC2

Теперь можно создавать инстансы EC2.

Нам потребуется: 2 ноды для мастер, 2 ноды для воркеров и 1 нода — для Consul.

Начнём с Master, остальные — по аналогии.

Добавляем параметр InstanceType для определения типа интанса:

...
    "InstanceType" : {
      "Description" : "Swarm hosts EC2 instance type",
      "Type" : "String",
      "Default" : "t2.nano",
      "AllowedValues" : [ "t2.nano", "t2.micro", "t2.small" ],
      "ConstraintDescription" : "must be a valid EC2 instance type."
    }
...

Ресурс группы безопасности у нас уже есть:

...
    "MasterSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "VpcId" : { "Ref": "VPC"},
        "GroupDescription" : "Enable HTTP access via port 80 and SSH access",
        "SecurityGroupIngress" : [
          {"IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0"},
          {"IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : { "Ref" : "SSHLocation"}}
        ]
      }
    },
...

Добавляем ресурс SwarmMaster0EIP для получения Public IP:

...
    "SwarmMaster0EIP": {
      "Type" : "AWS::EC2::EIP",
      "Properties" : { "Domain" : "vpc" }
    },
...

Сам EC2 инстанс:

...
    "SwarmMaster0": {
      "DependsOn" : "SwarmMaster0EIP",
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "Tags" : [ {"Key" : "Name", "Value" : "SwarmMaster0" } ],
        "InstanceType" : { "Ref" : "InstanceType" },
        "ImageId" : "ami-a7412ad4",
        "AvailabilityZone": { "Fn::Select" : [ "0", { "Fn::GetAZs" : "" } ]},
        "KeyName": "my-cluster",
        "NetworkInterfaces" : [ {
          "DeleteOnTermination": "true",
          "DeviceIndex" : "0",
          "SubnetId": {"Ref": "PubSubnet"},
          "GroupSet": [ {"Ref": "MasterSecurityGroup"} ]
        }]
      }
    },
...

И подключение EIP к этому EC2:

...
    "SwarmMaster0AssociateEIP": {
      "DependsOn" : "SwarmMaster0",
      "Type": "AWS::EC2::EIPAssociation",
      "Properties": {
        "EIP": { "Ref": "SwarmMaster0EIP"},
        "InstanceId": { "Ref": "SwarmMaster0"}
      }
    }
...

Запускаем:

$ aws cloudformation delete-stack --stack-name VPConly6
$ aws cloudformation create-stack --stack-name VPConly7 --template-body file:////home//setevoy//PycharmProjects//Swarm-cluster/VPC_alone.template
{
    "StackId": "arn:aws:cloudformation:eu-west-1:264418146286:stack/VPConly8/38f73870-63c2-11e6-9b1a-50faeb542cd2"
}

Проверяем:

$ aws cloudformation describe-stack-events --stack-name VPConly7 --query '[StackEvents[8].[ResourceType,PhysicalResourceId,LogicalResourceId]]'
[
    [
        "AWS::EC2::Instance", 
        "i-4f768bc2", 
        "SwarmMaster0"
    ]
]

Пробуем SSH. находим IP инстанса:

$ aws ec2 describe-instances --instance-ids i-4f768bc2 --query '[Reservations[0].Instances[0].PublicIpAddress]' --output text
52.210.87.238

Проверяем:

$ telnet 52.210.87.238 22
Trying 52.210.87.238...
Connected to 52.210.87.238.
Escape character is '^]'.
SSH-2.0-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2.7

Добавляем ещё один инстанс под второй мастер:

...
    "SwarmMaster1EIP": {
      "Type" : "AWS::EC2::EIP",
      "Properties" : { "Domain" : "vpc" }
    },

    "SwarmMaster1": {
      "DependsOn" : "SwarmMaster1EIP",
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "Tags" : [ {"Key" : "Name", "Value" : "SwarmMaster1" } ],
        "InstanceType" : { "Ref" : "InstanceType" },
        "ImageId" : "ami-a7412ad4",
        "AvailabilityZone": { "Fn::Select" : [ "0", { "Fn::GetAZs" : "" } ]},
        "KeyName": "my-cluster",
        "NetworkInterfaces" : [ {
          "DeleteOnTermination": "true",
          "DeviceIndex" : "0",
          "SubnetId": {"Ref": "PubSubnet"},
          "GroupSet": [ {"Ref": "MasterSecurityGroup"} ]
        }]
      }
    },

    "SwarmMaster1AssociateEIP": {
      "DependsOn" : "SwarmMaster1",
      "Type": "AWS::EC2::EIPAssociation",
      "Properties": {
        "EIP": { "Ref": "SwarmMaster1EIP"},
        "InstanceId": { "Ref": "SwarmMaster1"}
      }
...

Для инстансов в приватной сети — создадим второй Security Group, в котором разрешено всем и всё:

...
    "NodesSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "GroupDescription" : "Enable SSH access via port 22",
        "SecurityGroupIngress" : [ {
            "IpProtocol": "tcp",
            "FromPort": "0",
            "ToPort": "65535",
            "CidrIp": "0.0.0.0/0"
          }
        ],
        "SecurityGroupEgress": [
          {
            "IpProtocol": "tcp",
            "FromPort": "0",
            "ToPort": "65535",
            "CidrIp": "0.0.0.0/0"
          }
        ]
      }
    },
...

И три инстанса в приватной сети — под воркеры и Consul:

...
    "SwarmNode0": {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "Tags" : [ {"Key" : "Name", "Value" : "SwarmNode0" } ],
        "InstanceType" : { "Ref" : "InstanceType" },
        "ImageId" : "ami-a7412ad4",
        "AvailabilityZone": { "Fn::Select" : [ "0", { "Fn::GetAZs" : "" } ]},
        "KeyName": "my-cluster",
        "NetworkInterfaces" : [ {
          "DeleteOnTermination": "true",
          "DeviceIndex" : "0",
          "SubnetId": {"Ref": "PrivSubnet"},
          "GroupSet": [ {"Ref": "NodesSecurityGroup"} ]
        }]
      }
    },

    "SwarmNode1": {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "Tags" : [ {"Key" : "Name", "Value" : "SwarmNode1" } ],
        "InstanceType" : { "Ref" : "InstanceType" },
        "ImageId" : "ami-a7412ad4",
        "AvailabilityZone": { "Fn::Select" : [ "0", { "Fn::GetAZs" : "" } ]},
        "KeyName": "my-cluster",
        "NetworkInterfaces" : [ {
          "DeleteOnTermination": "true",
          "DeviceIndex" : "0",
          "SubnetId": {"Ref": "PrivSubnet"},
          "GroupSet": [ {"Ref": "NodesSecurityGroup"} ]
        }]
      }
    },

    "Consul0": {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "Tags" : [ {"Key" : "Name", "Value" : "Consul0" } ],
        "InstanceType" : { "Ref" : "InstanceType" },
        "ImageId" : "ami-a7412ad4",
        "AvailabilityZone": { "Fn::Select" : [ "0", { "Fn::GetAZs" : "" } ]},
        "KeyName": "my-cluster",
        "NetworkInterfaces" : [ {
          "DeleteOnTermination": "true",
          "DeviceIndex" : "0",
          "SubnetId": {"Ref": "PrivSubnet"},
          "GroupSet": [ {"Ref": "NodesSecurityGroup"} ]
        }]
      }
    }
...

Запускаем:

$ aws cloudformation create-stack --stack-name VPCfinalizing --template-body file:////home//setevoy//PycharmProjects//Swarm-cluster/VPC_alone.template
{
    "StackId": "arn:aws:cloudformation:eu-west-1:264418146286:stack/VPCfinalizing/430ad7b0-63c5-11e6-ae9a-50a686326636"
}

Готово, проверяем:

$ ssh ubuntu@52.50.104.231 -i ~/Temp/aws_cluster_prod/my-cluster.pem
The authenticity of host '52.50.104.231 (52.50.104.231)' can't be established.
ECDSA key fingerprint is 92:1c:8d:b9:d8:97:07:33:7e:81:c2:fb:d1:6e:2c:70.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '52.50.104.231' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 14.04.5 LTS (GNU/Linux 3.13.0-93-generic x86_64)
...
ubuntu@ip-11-0-1-121:~$

Проверяем машину из приватной сети, например — Consul0:

ubuntu@ip-11-0-1-121:~$ telnet 11.0.2.110 22
Trying 11.0.2.110...
Connected to 11.0.2.110.
Escape character is '^]'.
SSH-2.0-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2.7

aws-swarm-cloudformation-1

aws-swarm-cloudformation-2

 

Что можно улучшить:

  1. добавить outputs;
  2. вынести создание EC2 в отдельный шаблон, и подключить его через AWS CloudFormation Template Snippets;
  3. вынести ImageId и KeyName в параметры.