AWS: миграция RTFM, часть #3: CloudFormation – инфрастуктура

Автор: | 26/09/2016
 

aws-logo-square-02Напомню – описывается процесс создания инфрастуктуры для миграции RTFM – два EC2 (Bastion и Zeus) в VPC, приватная и публичная сети, NAT-gateway.

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

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

В этой части – возьмём CloudFormation шаблон из поста AWS: CloudFormation – создание шаблона для VPC, EC2, NAT и Internet Gateway – пару ресурсов удалим, пару параметров добавим.

Сейчас создадим шаблон для сети, а шаблоны для создания и провижена всяких EC2/RDS etc – потом сделаем вложенными, что бы потом можно будет подключать их в основной по мере готовности/необходимости.

Аналогичный пост – AWS: CloudFormation – создание шаблона для VPC, EC2, NAT и Internet Gateway.

Шаблон, получившийся в результате написания этого поста – доступен тут>>>.

Подготовка шаблона

Копируем старый шаблон:

[simterm]

$ mkdir ~/Work/RTFM/rtfm/12615
$ cp ./Work/RTFM/rtfm/11990/vpc_ec2_nat_private_public_subnets.template ~/Work/RTFM/rtfm/12615/rtfm_migrate_p3.template

[/simterm]

Добавим несколько параметров.

Добавим возможность указать AMI-ID (без mapping):

...
    "BastionAMIid": {
      "Description" : "OpenBSD AMI ID for the StackRegion",
      "Type" : "String",
      "Default":  "ami-170b6064"
    },

    "ZeusAMIid": {
      "Description" : "CoreOS AMI ID for the StackRegion",
      "Type" : "String",
      "Default":  "ami-068bfe75"
    },
...

Так же обновим подсети – 12.*/24 вместо 11.*/24:

...
    "VPCCidrBlock" : {
...
      "Default": "12.0.0.0/16",
...
    "PubNetCIDR": {
...      "Default": "12.0.1.0/24",
    "PrivNetCIDR": {
...
      "Default": "12.0.2.0/24",
...

И пробуем со всем этим завестись.

[simterm]

$ cd Work/RTFM/rtfm/12615/
$ aws cloudformation create-stack --stack-name VPConly1 --template-body file://rtfm_migrate_p3.template
{
    "StackId": "arn:aws:cloudformation:eu-west-1:264418146286:stack/VPConly1/c995de90-83cf-11e6-bc28-50fae9b818d2"
}

[/simterm]

Структура шаблона

Вспомним стуктуру шаблона:

  • Parameters
    • StackRegion: регион для создания стека;
    • BastionAMIid: AMI ID для OpenBSD на Bastion;
    • ZeusAMIid: AMI ID для CoreOS на Zeus;
    • VPCCidrBlock: сеть для VPC (/16);
    • SSHLocation: сеть, из которой доступен SSH;
    • PubNetCIDR: публичная подсеть в VPC (/24);
    • PrivNetCIDR: приватная подсеть в VPC (/24);
    • InstanceType: тип инстанса (t2.nano);
  • Resources
    • VPC:
      • тип ресурса: AWS::EC2::VPC
      • используемые параметры:
        • “CidrBlock” : { “Ref” : “VPCCidrBlock” }
        • “Tags”: { “Ref” : “AWS::StackId”}
    • MasterSecurityGroup:
      • тип ресурса: AWS::EC2::SecurityGroup
      • используемые параметры:
        • “VpcId” : { “Ref”: “VPC”}
        • “SecurityGroupIngress”: { “Ref” : “SSHLocation”}
    • NodesSecurityGroup:
      • тип ресурса: AWS::EC2::SecurityGroup
      • используемые параметры:
        • “VpcId” : { “Ref” : “VPC” }
    • PubSubnet:
      • тип ресурса: AWS::EC2::Subnet
      • используемые параметры:
        • VpcId”: { “Ref”: “VPC”}
        • “CidrBlock”: { “Ref”: “PubNetCIDR”}
        • “AvailabilityZone”: { “Fn::Select” : [ “0”, { “Fn::GetAZs” : “” } ]}
    • PrivSubnet:
      • тип ресурса: AWS::EC2::Subnet
      • используемые параметры:
        • VpcId”: { “Ref”: “VPC”}
        • “CidrBlock”: { “Ref”: “PrivNetCIDR”},
        • “AvailabilityZone”: { “Fn::Select” : [ “0”, { “Fn::GetAZs” : “” } ]}
    • PubNetIGW:
      • тип ресурса: AWS::EC2::InternetGateway
    • AttachGateway:
      • тип ресурса: AWS::EC2::VPCGatewayAttachment
      • используемые параметры:
        • “VpcId”: { “Ref”: “VPC” }
        • “InternetGatewayId”: { “Ref”: “PubNetIGW” }
    • NatEIP:
      • тип ресурса: AWS::EC2::NatGateway
      • используемые параметры:
        • “AllocationId” : { “Fn::GetAtt” : [“NatEIP”, “AllocationId”]}
        • “SubnetId” : { “Ref”: “PubSubnet”}
    • NatGW:
      • тип ресурса: AWS::EC2::NatGateway
    • PublicRouteTable:
      • тип ресурса: AWS::EC2::RouteTable
      • используемые параметры:
        • “AllocationId” : { “Fn::GetAtt” : [“NatEIP”, “AllocationId”]}
        • “SubnetId” : { “Ref”: “PubSubnet”}
    • PrivateRouteTable:
      • тип ресурса: AWS::EC2::RouteTable
      • используемые параметры:
        • “VpcId”: { “Ref”: “VPC” }
    • PublicRoute:
      • тип ресурса: AWS::EC2::Route
      • используемые параметры:
        • “RouteTableId”: { “Ref”: “PublicRouteTable” },
        • “GatewayId”: { “Ref”: “PubNetIGW” }
    • PrivateRoute:
      • тип ресурса: AWS::EC2::Route
      • используемые параметры:
        • “RouteTableId”: { “Ref”: “PrivateRouteTable” }
        • “NatGatewayId”: { “Ref”: “NatGW” }
    • PublicRouteAssociate:
      • тип ресурса: AWS::EC2::SubnetRouteTableAssociation
      • используемые параметры:
        • “RouteTableId” : { “Ref”: “PublicRouteTable”}
        • “SubnetId” : { “Ref”: “PubSubnet”}
    • PrivateRouteAssociate:
      • тип ресурса: AWS::EC2::SubnetRouteTableAssociation
      • используемые параметры:
        • “RouteTableId” : { “Ref”: “PrivateRouteTable”},
        • “SubnetId” : { “Ref”: “PrivSubnet”}
    • SwarmMaster0EIP:
      • тип ресурса: AWS::EC2::EIP
      • используемые параметры:
        • “Properties” : { “Domain” : “vpc” }
    • SwarmMaster0:
      • тип ресурса: AWS::EC2::Instance
      • используемые параметры:
        • “Tags” : [ {“Key” : “Name”, “Value” : “SwarmMaster0” } ],
        • “InstanceType” : { “Ref” : “InstanceType” }
        • “ImageId” : “ami-a7412ad4” (сейчас указан явно, переделаем через параметры ,которые определили выше)
        • “AvailabilityZone”: { “Fn::Select” : [ “0”, { “Fn::GetAZs” : “” } ]}
        • “KeyName”: “my-cluster” (тоже надо вынести в параметры)
        • “SubnetId”: {“Ref”: “PubSubnet”}
        • “GroupSet”: [ {“Ref”: “MasterSecurityGroup”} ]
    • SwarmMaster0AssociateEIP:
      • тип ресурса: AWS::EC2::EIPAssociation
      • используемые параметры:
        • “EIP”: { “Ref”: “SwarmMaster0EIP”},
        • “InstanceId”: { “Ref”: “SwarmMaster0”}
    • SwarmMaster1EIP: удалить
    • SwarmMaster1: удалить
    • SwarmMaster1AssociateEIP: удалить
    • SwarmNode0:
      • тип ресурса:  AWS::EC2::Instance
      • используемые параметры:
        • “Tags” : [ {“Key” : “Name”, “Value” : “SwarmNode0” } ]
        • “InstanceType” : { “Ref” : “InstanceType” }
        • “ImageId” : “ami-a7412ad4” (в параметры)
        • “AvailabilityZone”: { “Fn::Select” : [ “0”, { “Fn::GetAZs” : “” } ]}
        • “KeyName”: “my-cluster” (в параметры)
        • “SubnetId”: {“Ref”: “PrivSubnet”}
        • “GroupSet”: [ {“Ref”: “NodesSecurityGroup”} ]
    • SwarmNode1: удалить
    • Consul0: удалить
  • Outputs
    • “Value” : { “Fn::GetAtt” : [“VPC”, “CidrBlock”] }

Обновление шаблона

Удаляем ресурсы: SwarmMaster1EIP, SwarmMaster1, SwarmMaster1AssociateEIP, SwarmNode1, Consul0.

Проверяем:

[simterm]

$ aws cloudformation validate-template --template-body file://rtfm_migrate_p3.template
{
    "Description": "AWS CloudFormation Template Docker Swarm cluster in VPC", 
    "Parameters": [
        {
            "DefaultValue": "ami-170b6064", 
...

[/simterm]

Вроде ОК, удаляем первый стек, создаём новый:

[simterm]

$ aws cloudformation delete-stack --stack-name VPConly1
$ aws cloudformation create-stack --stack-name MigrateP3 --template-body file://rtfm_migrate_p3.template
{
    "StackId": "arn:aws:cloudformation:eu-west-1:264418146286:stack/MigrateP3/f1019220-83da-11e6-a465-500c426596d2"
}

[/simterm]

Stack Update

Следующим шагом – переименовываем всё 🙂

Общая идея – всё, что касается SwarmMaster0 – становится Bastion, всё касаемо SwarmNode0 – в ZeusSwarmMaster1, SwarmNode1 и Consul0 – уже удалены:

  1. MasterSecurityGroup > BastionSecurityGroup
  2. NodesSecurityGroup > ZeusSecurityGroup
  3. SwarmMaster0EIP > BastionEIP
  4. SwarmMaster0 > BastionEC2
  5. SwarmMaster0AssociateEIP > BastionAssociateEIP
  6. SwarmNode0 > ZeusEC2

Используем Update Stack:

[simterm]

$ aws cloudformation update-stack --stack-name MigrateP3 --template-body file://rtfm_migrate_p3.template
{
    "StackId": "arn:aws:cloudformation:eu-west-1:264418146286:stack/MigrateP3/f1019220-83da-11e6-a465-500c426596d2"
}

[/simterm]

Проверяем.

Создаются новые ресурсы:

rtfm_aws_migrate_p3

Удаляются старые:

rtfm_aws_migrate_p4

Или так:

[simterm]

$ aws cloudformation describe-stack-events --stack-name MigrateP3 --query 'StackEvents[*].{ResourceName:LogicalResourceId,Type:ResourceType,Status:ResourceStatus}'                                                       
[                                                                                                                                                                                                                                                                              
    {
        "Status": "UPDATE_COMPLETE",
        "ResourceName": "MigrateP3",
        "Type": "AWS::CloudFormation::Stack"
    },
    {
        "Status": "DELETE_COMPLETE",
        "ResourceName": "MasterSecurityGroup",
        "Type": "AWS::EC2::SecurityGroup"
    },
    {
        "Status": "DELETE_COMPLETE",
        "ResourceName": "SwarmMaster0EIP",
        "Type": "AWS::EC2::EIP"
    },
    {                                                                                                                                                                                                                                                                          
        "Status": "DELETE_IN_PROGRESS",
        "ResourceName": "MasterSecurityGroup",
        "Type": "AWS::EC2::SecurityGroup"
    },
    {
        "Status": "DELETE_IN_PROGRESS",
        "ResourceName": "SwarmMaster0EIP",
        "Type": "AWS::EC2::EIP"
    },
    {
...

[/simterm]

Готово:

rtfm_aws_migrate_p5

Теперь – добавим параметров.

Обновление параметров

Два новых параметра уже добавили в начале (BastionAMIid, ZeusAMIid).

Создадим новый – для указания ключа:

...
    "StackKeyName": {
      "Description" : "Existing key pair name to used with stack resources",
      "Type" : "String",
      "Default": "my-cluster"
    },
...

Далее – обновляем ресурсы.

Используем созданные ранее параметры. Из *подготовки* нам требуется изменить:

“ImageId” : “ami-a7412ad4”
“KeyName”: “my-cluster” (в параметры)

Добавляем параметр для ключа:

...

    "KeyName": {
      "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the instance",
      "Type": "AWS::EC2::KeyPair::KeyName",
      "ConstraintDescription" : "must be the name of an existing EC2 KeyPair.",
      "Default":  "my-cluster"
    },
...

Редактируем ресурс BastionEC2:

...
    "BastionEC2": {
      "DependsOn" : "BastionEIP",
      "Type" : "AWS::EC2::Instance",
...
        "ImageId" : { "Ref" : "BastionAMIid"},
        "AvailabilityZone": { "Fn::Select" : [ "0", { "Fn::GetAZs" : "" } ]},
        "KeyName": { "Ref" : "KeyName" },
        ...
    },
...

Аналогично – ZeusEC2:

...
    "ZeusEC2": {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "Tags" : [ {"Key" : "Name", "Value" : "Zeus" } ],
        "InstanceType" : { "Ref" : "InstanceType" },
        "ImageId" : { "Ref" : "ZeusAMIid" },
        "AvailabilityZone": { "Fn::Select" : [ "0", { "Fn::GetAZs" : "" } ]},
        "KeyName": { "Ref" : "KeyName" },
...

Проверяем:

[simterm]

$ aws cloudformation validate-template --template-body file://rtfm_migrate_p3.template
{
    "Description": "AWS CloudFormation Template Docker Swarm cluster in VPC", 
    "Parameters": [
        {
            "DefaultValue": "ami-170b6064", 
            "NoEcho": false, 
            "Description": "OpenBSD AMI ID for the StackRegion", 
            "ParameterKey": "BastionAMIid"
        }, 
        {
...

[/simterm]

Обновляем стек:

[simterm]

$ aws cloudformation update-stack --stack-name MigrateP3 --template-body file://rtfm_migrate_p3.template

[/simterm]

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

Bastion должен быть достпен “снаружи”:

[simterm]

$ ssh [email protected] -i ~/.ssh/my-cluster.pem
The authenticity of host '52.18.160.157 (52.18.160.157)' can't be established.
ECDSA key fingerprint is 8c:77:df:71:4e:3c:8f:c5:0b:10:fe:39:72:15:b6:1f.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '52.18.160.157' (ECDSA) to the list of known hosts.
OpenBSD 6.0-current (GENERIC.MP) #2337: Mon Aug  8 16:15:11 MDT 2016

Welcome to OpenBSD: The proactively secure Unix-like operating system.

...

#

[/simterm]

И Zeus:

[simterm]

$ scp -i ~/.ssh/my-cluster.pem /home/setevoy/.ssh/my-cluster.pem [email protected]:/root
my-cluster.pem                                                                                                                                                                                                                               100% 1675     1.6KB/s   00:00
$ ssh [email protected] -i ~/.ssh/my-cluster.pem
Last login: Mon Sep 26 13:40:18 2016 from 194.105.145.45
OpenBSD 6.0-current (GENERIC.MP) #2337: Mon Aug  8 16:15:11 MDT 2016

Welcome to OpenBSD: The proactively secure Unix-like operating system.

...

# ssh [email protected] -i /root/my-cluster.pem "uname -a"                                                                                                                                                                                                                 
The authenticity of host '12.0.2.42 (12.0.2.42)' can't be established.
ECDSA key fingerprint is SHA256:kovFZFRr/11VrJnVQKqnGiBEdqFzMdykq0IafQDkfF4.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '12.0.2.42' (ECDSA) to the list of known hosts.
Linux ip-12-0-2-42.eu-west-1.compute.internal 4.6.3-coreos #2 SMP Fri Aug 5 04:51:16 UTC 2016 x86_64 Intel(R) Xeon(R) CPU E5-2676 v3 @ 2.40GHz GenuineIntel GNU/Linux

[/simterm]

Добавление тегов

Добавляем теги для создаваемых ресурсов:

  1. Name: имя ресурса;
  2. stack_name: для добавления всем ресурсам, создаваемым в этом стеке;
  3. rtfm_env: все ресурсы в рамках подготовки к миграции – будут со значением “migrate“.

Например:

...
"Type" : "AWS::EC2::VPC",
...
        "Tags" : [
          { "Key" : "Name", "Value" : "Migrate-VPC"},
          { "Key" : "stack_name", "Value" : { "Ref" : "AWS::StackName"} },
          { "Key" : "rtfm_env", "Value" : "migrate" }
        ]
...
"Type" : "AWS::EC2::SecurityGroup"
...
        "Tags" : [
          { "Key" : "Name", "Value" : "Migrate-BastionSecurityGroup"},
          { "Key" : "stack_name", "Value" : { "Ref" : "AWS::StackName"} },
          { "Key" : "rtfm_env", "Value" : "migrate" }
        ]
...

Повторяем для всех ресурсов, которые поддерживают теги, в данном шаблоне это будут: AWS::EC2::VPC, AWS::EC2::SecurityGroup, AWS::EC2::Subnet, AWS::EC2::InternetGateway, AWS::EC2::RouteTable, AWS::EC2::Instance.

Обновляем стек:

[simterm]

$ aws cloudformation validate-template --template-body file://rtfm_migrate_p3.template
$ aws cloudformation update-stack --stack-name MigrateP3 --template-body file://rtfm_migrate_p3.template
{
   "StackId": "arn:aws:cloudformation:eu-west-1:264418146286:stack/MigrateP3/a1548350-83f7-11e6-8f33-50a68645b236"
}

[/simterm]

Проверяем:

[simterm]

$ aws cloaws ec2 describe-instances --instance-ids  i-cea2b90f --query '[Reservations[*].Instances[*].Tags[*]]' --output text
Name    MigrateZeusEC2
aws:cloudformation:stack-id     arn:aws:cloudformation:eu-west-1:264418146286:stack/MigrateP3/a1548350-83f7-11e6-8f33-50a68645b236
aws:cloudformation:logical-id   ZeusEC2
aws:cloudformation:stack-name   MigrateP3
stack_name      MigrateP3
rtfm_env        migrate

rtfm_aws_migrate_p6

[/simterm]

Добавление outputs

Добавим немного больше информации в вывод после создания стека.

Текущий блок Outputs:

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

Выведем:

  1. VPCID, блок IP
  2. BastionIntance ID, Public DNS, Public IP, Private IP
  3. ZeusIntance ID, Private DNS, Private IP

Например, возвращаемые данные для EC2 можно найти тут>>>.

Public DNS для Bastion сейчас будет пустым, т.к. созданная VPC не назначет имена автоматом.

Добавляем:

...
  "Outputs" : {
    "VPCID" : {
      "Value" : { "Ref" : "VPC"},
      "Description" : "VPC ID"
    },
    "VPCCIDR" : {
      "Value" : { "Fn::GetAtt" : ["VPC", "CidrBlock"] },
      "Description" : "VPC CIDR IP addresses block"
    },
    "BastionID" : {
      "Value" : { "Ref" : "BastionEC2"},
      "Description": "Bastion EC2 instance ID"
    },
    "BastionPublicDNS" : {
      "Value" : { "Fn::GetAtt" : ["BastionEC2", "PublicDnsName"] },
      "Description": "Bastion public DNS name"
    },
    "BastionPublicIP" : {
      "Value" : { "Fn::GetAtt" : ["BastionEC2", "PublicIp"] },
      "Description": "Bastion public DNS IP address"
    },
    "BastionPrivateIP" : {
      "Value" : { "Fn::GetAtt" : ["BastionEC2", "PrivateIp"] },
      "Description": "Bastion private DNS IP address"
    },
    "ZeusID" : {
      "Value" : { "Ref" : "ZeusEC2" },
      "Description": "Zeus EC2 instance ID"
    },
    "ZeusPrivateDNS" : {
      "Value" : { "Fn::GetAtt" : ["ZeusEC2", "PrivateDnsName"] },
      "Description": "Zeus private DNS name "
    },
    "ZeusPrivateI" : {
      "Value" : { "Fn::GetAtt" : ["ZeusEC2", "PrivateIp"] },
      "Description": "Zeus private IP address"
    }
  }
...

Запускаем:

[simterm]

$ aws cloudformation update-stack --stack-name MigrateP3 --template-body file://rtfm_migrate_p3.template
{
    "StackId": "arn:aws:cloudformation:eu-west-1:264418146286:stack/MigrateP3/a1548350-83f7-11e6-8f33-50a68645b236"
}

[/simterm]

Проверяем:

[simterm]

$ aws cloudformation describe-stacks --stack-name MigrateP3 --query '[Stacks[*].Outputs[*]]' --output text
Bastion private DNS IP address  BastionPrivateIP        12.0.1.20
Bastion EC2 instance ID BastionID       i-cda2b90c
Zeus private IP address ZeusPrivateI    12.0.2.199
VPC ID  VPCID   vpc-bd53c1d9
VPC CIDR IP addresses block     VPCCIDR 12.0.0.0/16
Zeus private DNS name   ZeusPrivateDNS  ip-12-0-2-199.eu-west-1.compute.internal
Bastion public DNS name BastionPublicDNS
Zeus EC2 instance ID    ZeusID  i-cea2b90f
Bastion public DNS IP address   BastionPublicIP 52.211.232.8

[/simterm]

rtfm_aws_migrate_p7

Готово.

Почитать по теме:

19 Best Practices for Creating Amazon CloudFormation Templates

AWS CloudFormation Best Practices

Using Tags to Organize AWS Resources