Имеется стек, в котором среди прочего создаются VPC Peerings, в данном примере – пиринг между VPC нового кластера Elastic Kubernetes Service и VPC стека с Prometheus.
Создание стеков и вообще всей этой автоматизации есть в постах AWS Elastic Kubernetes Service: автоматизация создания кластера, часть 1 — CloudFormation и AWS Elastic Kubernetes Service: — автоматизация создания кластера, часть 2 — Ansible, eksctl.
Задача: добавить возможность выбора – создавать пиринги, или нет.
Решение: используем AWS CloudFormation Conditions: создадим параметр VPCPeering
, который из параметров Jenkins-джобы будет принимать значение true или false, а затем в зависимости от этого значения – будем принимать решение о создании ресурсов пиринга (сам пиринг и два роута).
Пример немного усложняется тем, что у нас используются Nested Stacks, и ресурсы разбросаны между ними, например, для создания пиринга:
- root-stack:
- создаёт Region и AZ-стеки
- Region-stack:
- создаёт ресурс
AWS::EC2::VPCPeeringConnection
- обновляет RouteTable в стеке Мониторинга – добавляет ресурс
AWS::EC2::Route
(маршрут из сети монитоирнга в сеть EKS) - в
Outputs
возвращает MonitoringProdVPCPeeringConnectionID
- создаёт ресурс
- AZ-стек:
- принимает MonitoringProdVPCPeeringConnectionID
- создаёт ресурс
AWS::EC2::Route
(маршрут из приватных подсетей EKS в сеть мониторинга)
Доабвляем по очереди, и смотрим как всё это можно организовать.
Содержание
Root stack
В Parameters добавляем новый параметр, назовём его VPCPeeringCreate, который принимает true или false:
{ "AWSTemplateFormatVersion": "2010-09-09", "Description": "AWS CloudFormation stack for Kubernetes cluster", "Parameters": { ... "VPCPeeringCreate": { "Description": "Create or not VPC peering connections", "Type": "String", "Default": true, "AllowedValues": [ "true", "false" ] }
Обновляем ресурс region-стека – добавляем передачу параметра VPCPeeringCreate:
"Resources": { "RegionNetworkStack": { "Type": "AWS::CloudFormation::Stack", "Properties": { "TemplateURL": "eks-region-networking.json", "Parameters": { "VPCCIDRBlock": { "Ref": "VPCCIDRBlock" }, "VPCPeeringCreate": { "Ref": "VPCPeeringCreate"} } } }, ...
Region Stack
Принимаем параметр VPCPeeringCreate – тут уже убираем дефолтное значение, так как оно должно придти из рутового стека:
{ "AWSTemplateFormatVersion" : "2010-09-09", "Description" : "AWS CloudFormation Region Networking stack for Kubernetes cluster", "Parameters" : { ... "VPCPeeringCreate": { "Description": "Create or not VPC peering connections", "Type": "String", "AllowedValues": [ "true", "false" ] } }, ...
Создаём Conditions
, в котором с помощью Fn::Equals
проверяем значение из параметра VPCPeeringCreate, и если оно true – то возвращаем тоже true:
... "VPCPeeringCreate": { "Description": "Create or not VPC peering connections", "Type": "String", "AllowedValues": [ "true", "false" ] } }, "Conditions" : { "DoVPCPeeringCreate" : {"Fn::Equals" : [ {"Ref" : "VPCPeeringCreate"}, true] } }, ...
Добавляем проверку обоим ресурсам MonitoringProdVPCPeeringConnection и MonitoringToEksProdPeeringRoute – "Condition" : "DoVPCPeeringCreate"
:
... "MonitoringProdVPCPeeringConnection": { "Type": "AWS::EC2::VPCPeeringConnection", "Condition" : "DoVPCPeeringCreate", "Properties": { "VpcId": { "Ref": "VPC" }, "PeerVpcId": { "Fn::ImportValue" : "monitoring-production-VPC-ID" }, "PeerRegion": { "Fn::ImportValue" : "monitoring-production-StackRegion" }, "Tags": [ { "Key": "Name", "Value": { "Fn::Join": [ "-", [ {"Ref": "AWS::StackName"}, "vpc-monitoring-prod"] ] } } ] } }, "MonitoringToEksProdPeeringRoute": { "Type": "AWS::EC2::Route", "Condition" : "DoVPCPeeringCreate", "Properties": { "RouteTableId": { "Fn::ImportValue" : "monitoring-production-VPC-PublicRouteTable" }, "DestinationCidrBlock": { "Ref": "VPCCIDRBlock" }, "VpcPeeringConnectionId": { "Ref": "MonitoringProdVPCPeeringConnection" } } }, ...
Запускаем – всё создалось:
Unresolved resource dependencies и Fn::If
А теперь запускаем с VPCPeeringCreate == false, и получаем ошибку отсутствия данных для Outputs
нашего регион-стека:
Логично – мы отключили создание пиринга, а в Outpus
шаблона регион-стека по-прежнему пытаемся получить ID не созданного пиринга:
... "MonitoringProdVPCPeeringConnectionID": { "Description" : "MonitoringProdVPCPeeringConnection ID", "Value" : {"Ref" : "MonitoringProdVPCPeeringConnection" } } ...
Обновляем Outputs
, и используем Fn::If
для выбора того – какие данные возвращать:
... "Outputs" : { "VPCID" : { "Description" : "EKS VPC ID", "Value" : { "Ref" : "VPC" } }, "IGWID" : { "Description" : "InternetGateway ID", "Value" : { "Ref" : "InternetGateway" } }, "MonitoringProdVPCPeeringConnectionID": { "Description" : "MonitoringProdVPCPeeringConnection ID", "Value" : { "Fn::If" : [ "DoVPCPeeringCreate", {"Ref" : "MonitoringProdVPCPeeringConnection" }, "Zero" ] } } } } ...
Т.е. если DoVPCPeeringCreate
вернёт значение false – просто передаём что угодно, тут для примера слово “Zero”.
AvailabilityZones stack
В AZ-стеке надо сделать тоже самое – добавит параметр, добавить Conditions
– копируем из region-стека, и для ресурса AWS::EC2::Route
добавляем проверку условия "Condition" : "DoVPCPeeringCreate"
:
... "EksToMonitoringProdPeeringRoute": { "Type": "AWS::EC2::Route", "Condition" : "DoVPCPeeringCreate", "Properties": { "RouteTableId": { "Ref": "PrivateRouteTable" }, "DestinationCidrBlock": { "Fn::ImportValue" : "monitoring-production-VPC-CIDR" }, "VpcPeeringConnectionId": { "Ref": "MonitoringProdVPCPeeringConnectionID" } } }, ...
В root-стеке добавляем передчу параметра VPCPeeringCreate в AZ-стек:
... "AZNetworkStackA": { "Type": "AWS::CloudFormation::Stack", "Properties": { "TemplateURL": "eks-azs-networking.json", "Parameters": { "VPCID": { "Fn::GetAtt": ["RegionNetworkStack", "Outputs.VPCID"] }, "AZ": { "Fn::Select": [ "0", { "Ref": "AvailabilityZones" } ] }, "IGWID": { "Fn::GetAtt": ["RegionNetworkStack", "Outputs.IGWID"] }, "VPCPeeringCreate": { "Ref": "VPCPeeringCreate"}, ...
Создаём стеки – и нашего пиринга нет:
Для проверки – ещё раз запускаем с VPCPeeringCreate == true:
Готово.