In addition to 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 – now I’d like to pass a Parameter as a List with multiply values to a CloudForamtion stack.
The idea is to get all AvailabilityZones of a region in Ansible, and then use this list for the eksctl
, which will create WorkerNodes groups in dedicated AvailabilityZones, and use the same list for CloudFormation to create child stacks in right AvailabilityZone.
With Jenkins and Ansible it will work in a next way:
- from a Jenkins, job Parameters take a
$REGION
with value eu-west-3 - a Jenkins job will spin up a Docker container with Ansible passing the
$REGION
variable as an environment variable - Ansible using its
{{ region }}
variable will get a list of the AvailabilityZones of the region specified from Jenkins - and will pass this list to the cloudformation role to create two nested stacks in two AvailabilityZones
- and will pass this list to the eksctl role to create NodeGroups in two AvailabilityZones
At this moment in my CloudFormation AvailabilityZones are taken via the Fn::GetAZs - { "Fn::Select": ["0", { "Fn::GetAZs": "" }] }
:
...
"AZNetworkStackA": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "eks-azs-networking.json",
"Parameters": {
"VPCID": { "Fn::GetAtt": ["RegionNetworkStack", "Outputs.VPCID"] },
"AZ": { "Fn::Select": [ "0", { "Fn::GetAZs": "" } ] },
...
So, instead of using { "Fn::Select": ["0", { "Fn::GetAZs": "" }] }
– let’s add a new task in Ansible to obtain all AvailabilityZones of a region where a CloudFormation stack will be created and then both its roles – eksctl и cloudformation – will use the same list with the same values.
The initial example was googled long time ago here – How do I use multiple values for individual parameters in an AWS CloudFormation template, and now I got a chance to use it in a real example.
Ansible
The most difficult task was to create the list in such a form, that CloudForamtion can use it in a correct way, so let’s start with this.
Also, as this list will be used by two separated roles in Ansible – worth to move it in a dedicated task and call it from the roles directly, to avoid duplicating the code.
So, create a common role:
$ mkdir -p roles/common/tasks
Create a new file roles/common/tasks/main.yml
and create a task here to obtain all AvailabilityZones and save the result to a cluster_azs
variable:
- name: "Getting AvailabilityZones list"
command: "aws ec2 describe-availability-zones --region {{ region }} --query 'AvailabilityZones[*].ZoneName' --output text"
register: cluster_azs
The cluster_azs
will return us a structure like the next:
...
ok: [localhost] => {
"msg": {
"changed": true,
"cmd": [
"aws",
"ec2",
"describe-availability-zones",
"--region",
"eu-west-2",
"--query",
"AvailabilityZones[*].ZoneName",
"--output",
"text"
],
"delta": "0:00:00.993216",
"end": "2020-04-08 15:55:56.357234",
"failed": false,
"rc": 0,
"start": "2020-04-08 15:55:55.364018",
"stderr": "",
"stderr_lines": [],
"stdout": "eu-west-2a\teu-west-2b\teu-west-2c",
"stdout_lines": [
"eu-west-2a\teu-west-2b\teu-west-2c"
]
}
}
...
Now, from the stdout
string with the “eu-west-2a\teu-west-2b\teu-west-2c” value need to create a comma-separated list.
The elements here are divided with the TAB, so we can use the split()
function to separate them, and then pass the data via a pipe to the join()
which will concatenate them to a new list – but with the comma as a separator:
...
- set_fact:
cluster_azs_names: "{{ cluster_azs.stdout.split('\t') | join(',') }}"
And add a debug at the end to check the result:
...
- debug:
msg: "cluster_azs_names: {{ cluster_azs_names }}"
Check:
...
TASK [cloudformation : debug] ****
ok: [localhost] => {
"msg": "cluster_azs_names: eu-west-2a,eu-west-2b,eu-west-2c"
}
...
Cool.
CloudFormation Parameter
List<AWS::EC2::AvailabilityZone::Name
Go to your CloudFormation template and add a new parameter with the “List<AWS::EC2::AvailabilityZone::Name>
” type. See the full list in the AWS-Specific Parameter Types:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "AWS CloudFormation stack for Kubernetes cluster",
"Parameters": {
"VPCCIDRBlock": {
"Description": "VPC CidrBlock",
"Type": "String",
"Default": "10.0.0.0/16"
},
"AvailabilityZones": {
"Type": "List<AWS::EC2::AvailabilityZone::Name>",
"Description": "The list of the AvailabilityZones in a current Region",
"Default": "eu-west-2a, eu-west-2b"
}
},
...
Nett, add nested stacks and instead of the { "Fn::Select": ["0", { "Fn::GetAZs": "" }] }
use { "Fn::Select": ["0", { "Ref": "AvailabilityZones" }] }
– to obtain the first element from the list passed to the AvailabilityZones Parameter:
...
"AZNetworkStackA": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "eks-azs-networking.json",
"Parameters": {
"VPCID": { "Fn::GetAtt": ["RegionNetworkStack", "Outputs.VPCID"] },
"AZ": { "Fn::Select": [ "0", { "Ref": "AvailabilityZones" } ] },
...
Pack them (see the AvailabilityZonesAWS: CloudFormation – Nested Stacks and stacks parameters Import/Export):
$ aws --region eu-west-2 cloudformation package --template-file eks-root.json --output-template /tmp/packed-eks-stacks.json --s3-bucket eks-cloudformation-eu-west-2 --use-json
Run:
$ ansible-playbook eks-cluster.yml --tags infra
...
TASK [cloudformation : Getting AvailabilityZones list] ****
changed: [localhost]
TASK [cloudformation : set_fact] ****
ok: [localhost]
TASK [cloudformation : debug] ****
ok: [localhost] => {
"msg": "cluster_azs_names: eu-west-2a,eu-west-2b,eu-west-2c"
}
TASK [cloudformation : Create EKS EKSCTL-BTTRM-EKS-DEV-3-STACK CloudFormation stack] ****
changed: [localhost]
PLAY RECAP ****
localhost : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Check:
Done.
Top comments (0)