I have a CloudFormation stack with VPC Peerings, in that case, it’s a peering between VPC of a new Elastic Kubernetes Service cluster and VPC of the Prometheus monitoring stack.
The EKS cluster’s stack and its whole automation creation were described in the AWS Elastic Kubernetes Service: a cluster creation automation, part 1 – CloudFormation and AWS Elastic Kubernetes Service: a cluster creation automation, part 2 – Ansible, eksctl posts.
The task: add an ability to chose if CloudFormation have to create the peering mentioned above – or skip this step.
The solution: use the AWS CloudFormation Conditions: will add a new parameter VPCPeeringCreate
which will accept a true value false from a Jenkins job and then depending on this value CloudFormation will decide if need to create such a peering and related resources – the peering itself and two Routes.
The task is becoming a bit more complicated due to the fact, that I have Nested Stacks used there and resources are created by different stacks, so to create a peering it uses:
- root-stack:
- will create a Region and AvalabilityZones-located stacks
- in the Region-stack:
- will create
AWS::EC2::VPCPeeringConnection
- will update a RouteTable of the remote Monitoring stack with Prometheus – will create a new
AWS::EC2::Route
(a route from the Monitoring VPC into the EKS stack’s VPC) - in its
Outputs
will return a MonitoringProdVPCPeeringConnectionID
- will create
- AvalabilityZones-located stack:
- will grab the MonitoringProdVPCPeeringConnectionID
- will create an
AWS::EC2::Route
(a route from the EKS cluster’s VPC private subnets to the Monitoring stack VPC)
So, let’s add them one-by-one to see how this is working.
Contents
Root stack
In the Parameters of the Root stack add a new parameter called VPCPeeringCreate, which can accept true or 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" ] }
Update the Region-stack resource in the Root-stack template- add VPCPeeringCreate parameter to be passed to the Root-stack template:
"Resources": { "RegionNetworkStack": { "Type": "AWS::CloudFormation::Stack", "Properties": { "TemplateURL": "eks-region-networking.json", "Parameters": { "VPCCIDRBlock": { "Ref": "VPCCIDRBlock" }, "VPCPeeringCreate": { "Ref": "VPCPeeringCreate"} } } }, ...
Region Stack
Will accept the VPCPeeringCreate – remove the default value here as it will be passed from the Root-stack:
{ "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
Now – the main part here: add the Conditions
called DoVPCPeeringCreate where we will check the VPCPeeringCreate‘s value using Fn::Equals
and if it’s true – then return true from the Condition:
... "VPCPeeringCreate": { "Description": "Create or not VPC peering connections", "Type": "String", "AllowedValues": [ "true", "false" ] } }, "Conditions" : { "DoVPCPeeringCreate" : {"Fn::Equals" : [ {"Ref" : "VPCPeeringCreate"}, true] } }, ...
Next, add a check for both MonitoringProdVPCPeeringConnection and MonitoringToEksProdPeeringRoute resources – "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" } } }, ...
So, if the DoVPCPeeringCreate Condition
will return True – then the MonitoringProdVPCPeeringConnection and MonitoringToEksProdPeeringRoute resources will be triggered for creation.
Run to check – and here they are created:
Unresolved resource dependencies and Fn::If
And now – run the same but at this time specify the VPCPeeringCreate == false, and you’ll get an error because of missing value in the Outputs
on th Region-stack:
Why so? Well – because we’ve disabled the peering to be created, but in the Region-stack’s Outpus
we still trying to put an ID of the peering which wasn’t created:
... "MonitoringProdVPCPeeringConnectionID": { "Description" : "MonitoringProdVPCPeeringConnection ID", "Value" : {"Ref" : "MonitoringProdVPCPeeringConnection" } } ...
To resolve this – update the Outputs
and use the Fn::If
to chose what exactly to return:
... "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" ] } } } } ...
I.e. if the DoVPCPeeringCreate
Condition will return True – then we will grab the MonitoringProdVPCPeeringConnection’s ID and will put to the, if DoVPCPeeringCreate
== false – then in the Outputs we will put some other value, here is for example “Zero”.
AvailabilityZones stack
And in the AZ-stack we need to do the same things – add the parameter and add the Conditions
– copy-past it from the Region-stack, and create a check for the AWS::EC2::Route
resource – "Condition" : "DoVPCPeeringCreate"
:
... "EksToMonitoringProdPeeringRoute": { "Type": "AWS::EC2::Route", "Condition" : "DoVPCPeeringCreate", "Properties": { "RouteTableId": { "Ref": "PrivateRouteTable" }, "DestinationCidrBlock": { "Fn::ImportValue" : "monitoring-production-VPC-CIDR" }, "VpcPeeringConnectionId": { "Ref": "MonitoringProdVPCPeeringConnectionID" } } }, ...
In the Root-стеке add the VPCPeeringCreate to be passed to the AZ-stack:
... "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"}, ...
Create stacks – and no peering created:
To check – create again, with the VPCPeeringCreate == true:
Done.