AWS: CloudFormation – VPC peering и Fn::ImportValue

By | 06/13/2018
 

В продолжение/дополнение постов Prometheus: AWS EC2 service discovery и AWS: настройка VPC peering – пример настройки VPC peering в шаблонах стеков CloudFormation.

Идея следующая:

  • создадим стек monitor – он будет имитировать стек мониторинга
  • создадим стек application – он будет имитировать стек приложения
  • application должен будет запросить пиринг с VPC из стека monitor, что бы Prometheus в стеке monitor смог использовать service discovery ec2_sd_configs в стеке application

Документация – тут>>> и тут>>>.

Что бы сделать запрос на создание peering connection – надо найти VPC ID из стека monitor.

Для этого – добавим в Outputs шаблона monitor вывод VPC ID, а потом используем Fn::ImportValue в шаблоне application, что бы выполнить запрос на пиринг.

Используем существующий шаблон vpc_ec2_stack.json, копируем его:

cp ~/Work/RTFM/Github/setevoy-aws-templates/vpc_ec2_stack.json vpc_monitor_stack.json
cp ~/Work/RTFM/Github/setevoy-aws-templates/vpc_ec2_stack.json vpc_application_stack.json

Получившиеся в результате шаблоны доступны в Github:

Не забываем поменять блоки адресов для VPC в обоих шаблонах, что они не пересекались, иначе получим ошибку “VpcPeeringConnection failed to stabilize. State: [failed]” и “Failed due to incorrect VPC-ID, Account ID, or overlapping CIDR range“.

Например для стека monitor:

...
    "VPCCIDRBlock": {
      "Description": "VPC CidrBlock",
      "Type": "String",
      "Default": "10.0.0.0/16"
    },

    "PublicSubnetCIDR": {
      "Description": "Public Subnet CIDR",
      "Type": "String",
      "Default": "10.0.1.0/24"
    },
...

И для стека application:

...
    "VPCCIDRBlock": {
      "Description": "VPC CidrBlock",
      "Type": "String",
      "Default": "10.1.0.0/16"
    },

    "PublicSubnetCIDR": {
      "Description": "Public Subnet CIDR",
      "Type": "String",
      "Default": "10.1.0.0/24"
    },
...

Запрос на создание пиринга будет выполняться из стека application к стеку monitor.

Стек monitor будет “статичным”, т.е. – он “уже есть” (сначала будет разворачиваться мониторинг, а потом к нему – добавляться стеки с приложениями), и его параметры известы, поэтому их мы будем передавать в создаваемые стеки приложений через параметры в шаблоне monitor.

Параметры же создаваемых стеков (application  в данном случае) – неизвестны, поэтому их мы будем экспортировать в Outputs новых стеков, а потом эти значения использовать в Fn::ImportValue для стека monitor.

Перед тем, как начать – небольшая заметка по импортам и экспортам данных: при первой попытке создания стеков – у меня получилось залочить два стека так, что ни изменить, ни удалить их нельзя было. Проблема заключалась в том, что стек monitor экспортировал в Outputs свой VPC ID и свою подсеть, которые импортировались в стеке application. А стек application, в свою очередь, экспортировал свою подсеть и VpcPeeringConnectionId, которая импортировались в стеке monitor для создания маршрута. В результате – получилась взаминая связь между стеками, которая не позволяла удалить один из стеков, т.к. его данные использовались в другом. Подробнее про ограничения см. в документации Fn::ImportValue.

AWS::EC2::VPCPeeringConnection

В шаблоне стека application добавляем параметр MonitorVPCID:

...
    "MonitorVPCID": {
      "Description": "Monitoring VPC ID to be used to request peering connection",
      "Type": "String",
      "Default": "vpc-00d98968"
    },
...

Добавляем ресурс AWS::EC2::VPCPeeringConnection:

...
    "VPCPeeringConnection": {
      "Type": "AWS::EC2::VPCPeeringConnection",
      "Properties": {
        "VpcId": {
          "Ref": "VPC"
        },
        "PeerVpcId": {
          "Ref": "MonitorVPCID"
        }
      }
    },
...

Стек monitor уже создан.

Создаём стек appliсation:

aws --profile btrm-mon cloudformation create-stack --stack-name application --template-body file://vpc_application_stack.json --disable-rollback

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

aws --profile btrm-mon ec2 describe-vpc-peering-connections --filters Name=vpc-peering-connection-id,Values=pcx-a8427bc1 --query '[VpcPeeringConnections[*].Status.Message]' --output text
Active

Т.к. обе VPC принадлежат одному пользователю – то запрос на пиринг принимается автоматически.

В панели управления:

Теперь надо обновить таблицы маршрутизации обоих стеков.

AWS::EC2::Route

Для этого потребуется:

  • в стек monitor добавить параметр с именем стека application
  • в стек monitor добавить ресурс AWS::EC2::Route, который будет:
    • с помощью Fn::ImportValue получать значение для DestinationCidrBlock из стека application – его параметр PublicSubnetCIDR
    • с помощью Fn::ImportValue получать VpcPeeringConnectionId из стека application – его ресурс VPCPeeringConnection
  • в стек application добавить параметр с CIDR подсети (или всей VPC) стека monitor
  • в стек application добавить в Outputs экспорт его PublicSubnetCIDR, что бы его мог использовать стек monitor
  • в стек application добавить в Outputs экспорт его VpcPeeringConnectionId
  • в стек application добавить ещё один ресурс AWS::EC2::Route, который будет:
    • использоват параметр DestinationCidrBlock , в котором указывается сеть стека monitor – его PublicSubnetCIDR
    • использовать VpcPeeringConnectionId из стека monitor – его ресурс VPCPeeringConnection

Route стека application

Сначала обновляем стек application – добавляем параметр с подсетью стека monitor:

...
    "MonitorPublicSubnet": {
      "Description": "Stack Name to be used to add routes",
      "Type": "String",
      "Default": "10.0.1.0/24"
    },
...

И добавляем новый роут:

...
    "PeeringRouteToMonitorStackSubnet": {
      "Type": "AWS::EC2::Route",
      "Properties": {
        "RouteTableId": {
          "Ref": "PublicRouteTable"
        },
        "DestinationCidrBlock": {
          "Ref": "MonitorPublicSubnet"
        },
        "VpcPeeringConnectionId": {
          "Ref": "VPCPeeringConnection"
        }
      }
    },
...

Обновляем его Outputs, добавляем экспорт значений для импорта в стеке monitorPublicSubnetCIDR и VPCPeeringConnectionID:

...
    "PublicSubnetCIDR" : {
      "Description" : "Own Subnet CIDR to be used in the monitor's stack route",
      "Value" : {"Ref" : "PublicSubnetCIDR"},
      "Export" : {
        "Name" : {"Fn::Sub": "${AWS::StackName}-PublicSubnetCIDR" }
      }
    },
    "VPCPeeringConnectionID" : {
      "Description" : "VPD ID to a Peering connection with",
      "Value" : {"Ref" : "VPCPeeringConnection"},
      "Export" : {
        "Name" : {"Fn::Sub": "${AWS::StackName}-VPCPeeringConnectionID" }
      }
    }
...

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

aws --profile btrm-mon cloudformation update-stack --stack-name application --template-body file://vpc_application_stack.json

Проверяем Outputs:

Проверяем маршруты:

Route стека monitor

Обновляем шаблон для стека monitor.

Добавляем параметр с именем стека, который будет подключаться (при доабвлении новых стеков с новыми приложениями – просто добавим новые параметры в стек monitor с их именами и новые ресурсы роутов):

...
    "ApplicationRouteStackName": {
      "Description": "Stack Name to be used to add routes",
      "Type": "String",
      "Default": "application"
    },
...

Далее – ресурс AWS::EC2::Route будет аналогичен ресурсу стека application, но тут можем использовать cross-stack ссылки на Outputs стека application:

...
    "PeeringRouteToApplicationSubnet": {
      "Type": "AWS::EC2::Route",
      "Properties": {
        "RouteTableId": {
          "Ref": "PublicRouteTable"
        },
        "DestinationCidrBlock": {
          "Fn::ImportValue": {"Fn::Sub" : "${ApplicationRouteStackName}-PublicSubnetCIDR"}
        },
        "VpcPeeringConnectionId": {
          "Fn::ImportValue": {"Fn::Sub" : "${ApplicationRouteStackName}-VPCPeeringConnectionID"}
        }
      }
    },
...

Тут через Fn::ImportValue мы получаем значения PublicSubnetCIDR и VPCPeeringConnectionID из Outputs стека application.

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

aws --profile btrm-mon cloudformation update-stack --stack-name monitor --template-body file://vpc_monitor_stack.json

Проверяем его маршруты:

Пробуем пинг с EC2 в стеке monitor к EC2 в стеке application, используя приватный IP:

ssh admin@18.217.222.6 -i setevoy-testing-ohio.pem 'ip a s eth0 | grep -w inet; ping -c 1 10.1.0.141'
The authenticity of host '18.217.222.6 (18.217.222.6)' can't be established.
ECDSA key fingerprint is SHA256:0L83Mz5D2DFGBrIzov7E7YLWwOvgZNCMycLLkgp7x20.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '18.217.222.6' (ECDSA) to the list of known hosts.
inet 10.0.1.221/24 brd 10.0.1.255 scope global eth0
PING 10.1.0.141 (10.1.0.141) 56(84) bytes of data.
64 bytes from 10.1.0.141: icmp_seq=1 ttl=64 time=0.477 ms
--- 10.1.0.141 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.477/0.477/0.477/0.000 ms

И обратно – с EC2 в стеке application к EC2 в стеке monitor:

ssh admin@18.219.246.166 -i setevoy-testing-ohio.pem 'ip a s eth0 | grep -w inet; ping -c 1 10.0.1.221'
The authenticity of host '18.219.246.166 (18.219.246.166)' can't be established.
ECDSA key fingerprint is SHA256:Gnz/KbfZ1hw2p6eKuo4WuVFSstzkBRMgTFmNhD9c6uU.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '18.219.246.166' (ECDSA) to the list of known hosts.
inet 10.1.0.141/24 brd 10.1.0.255 scope global eth0
PING 10.0.1.221 (10.0.1.221) 56(84) bytes of data.
64 bytes from 10.0.1.221: icmp_seq=1 ttl=64 time=0.408 ms
--- 10.0.1.221 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.408/0.408/0.408/0.000 ms

Готово.