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