DEV Community

Arseny Zinchenko
Arseny Zinchenko

Posted on • Originally published at rtfm.co.ua on

2 1

AWS: CloudFormation – using lists in Parameters

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:

  1. from a Jenkins, job Parameters take a $REGION with value eu-west-3
  2. a Jenkins job will spin up a Docker container with Ansible passing the $REGION variable as an environment variable
  3. Ansible using its {{ region }} variable will get a list of the AvailabilityZones of the region specified from Jenkins
  4. and will pass this list to the cloudformation role to create two nested stacks in two AvailabilityZones
  5. 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.

Similar posts

Image of Stellar post

🚀 Stellar Dev Diaries Series: Episode 1 is LIVE!

Ever wondered what it takes to build a web3 startup from scratch? In the Stellar Dev Diaries series, we follow the journey of a team of developers building on the Stellar Network as they go from hackathon win to getting funded and launching on mainnet.

Read more

Top comments (0)

Billboard image

Try REST API Generation for Snowflake

DevOps for Private APIs. Automate the building, securing, and documenting of internal/private REST APIs with built-in enterprise security on bare-metal, VMs, or containers.

  • Auto-generated live APIs mapped from Snowflake database schema
  • Interactive Swagger API documentation
  • Scripting engine to customize your API
  • Built-in role-based access control

Learn more

👋 Kindness is contagious

DEV works best when you're signed in—unlocking a more customized experience with features like dark mode and personalized reading settings!

Okay