AWS: CloudFormation — using Conditions, Fn::Equals, and Fn::If — an example
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 itsOutputs will return a MonitoringProdVPCPeeringConnectionID
- 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.
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.
Originally published at RTFM: Linux, DevOps and system administration.
Top comments (0)