Step by step ECS Fargate setup from scratch using Ansible part 2

Welcome to the part 2 of our attempt to deploy to ECS using ansible. This is the continuation of the ansible setup we did on in the part 1. In there in part 1, we have set up the project using a specific structure that allows for multi environments and allows for the use of collections from ansible galaxy. In this part 2, we will go through the implementation of ansible to create various AWS services necessary for us to reach our goal. This post is likely to be long based on the objective. There will be a part 3 where we hope to finalise on the usage of ansible for our setup. For those who want to follow along or who are just like me and sometimes just want to checkout the code and read , you can get it from my gitlab account https://gitlab.com/kd-tutorials/ansible-ecs-step-by-step/-/tree/base_infra_and_security

Reusability

In the very first part of this series where we used strictly awscli to achieve the same setup, the usage of environment variables to hold certain values can be argued to have some level of reusability. That can be explained further with the fact that there aren’t too much hard coded values, especially for the outputs. It can easily be copied and pasted and executed.

The whole point of having ansible playbooks broken into a set reusable files is to promote reusability. After many years of using ansible , breaking playbooks into smaller pieces turns out to be one of the practices that had improved our operations in many ways, stability, security etc. The reason behind that is very simple, it boils down to trust issues 😀 . Even though there is a promise of idempotency in ansible, one should not always run a playbook that (re)-creates a whole VPC for example, when the deployment is just for one application many others. Especially when no change is required on the VPC section. So each time there is a deployment of a new update or for a brand new application/service, only the playbook that deploys that service should be the one that runs. This reduces greatly the radius blast when we run into issue or things go south. This post focussing on how to deploy to ECS using ansible shows how to achieve that

Setting up Base infrastructure

Let’s start with the playbook dedicated to the setup of the AWS VPC, its various subnets, NAT gateway and Internet gateways as well. The first playbook we are going to work on will be called aws_setup_base_infra_playbooks.yml.The scope of this base infrastructure playbook will cover the following:

  • Creating the VPC
  • Creating all the subnets
  • Creating the Internet Gateway
  • Creating the NAT Gateway
  • Mapping the public and the private subnets
  • Creating CloudWatch Log group

VPC & network components

The playbook aws_setup_base_infra_playbook.yml will make use of the following variable files:

  • playbook_vars/<environment>/aws_setup_base_infra_vars.yml: this will hold specific variables for the base infra.
  • playbook_vars/<environment>/global_vars.yml: This will hold all the common variables that cuts across multiple playbooks. For example any playbook that needs the id of the vpc will definitely lookup its name in the global_vars and then perform the use the ec2_vpc_net_info based on tag name and retrieve any necessary info returned.

Let’s look into the first iteration of the aws_setup_base_infra_playbooks.yml file

//aws_setup_base_infra_playbooks.yml
---
- name: Execute Ansible from localhost using aws python sdk
  hosts: localhost
  connection: local
  gather_facts: true
  vars_files:
    - playbook_vars/{{ global_var_environment }}/global_vars.yml
    - playbook_vars/{{ global_var_environment }}/aws_setup_base_infra_vars.yml
  tasks:

### Using the module ec2_vpc_net to create the vpc using variables
### from the global variables with tags when the variable aws_setup_base_vpc_create
### is true (which is default meaning I don't have to set it if I want it true)
    - name: Creating the AWS VPC
      amazon.aws.ec2_vpc_net:
        name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_name_sufx }}"
        cidr_block: "{{ global_var_vpc_cidr }}"
        region: "{{ global_var_region }}"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_name_sufx }}"
          ENV: "{{ global_var_environment }}"
      register: reg_vpc_info
      when: aws_setup_base_vpc_create | default(true)

### Using the module ec2_vpc_subnet to create the subnets destined to be
### public subnets using variables from the global_vars file. This uses a looping
### technics to create the subnets making us use only 1 entry to create 3 subnets
### This is created only when the variable aws_setup_base_vpc_public_subnet_details is defined
    - name: Creating public subnets
      amazon.aws.ec2_vpc_subnet:
        cidr: "{{ item.cidr }}"
        vpc_id: "{{ reg_vpc_info.vpc.id }}"
        region: "{{ global_var_region }}"
        az: "{{ item.az_zone }}"
        state: "present"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_public_subnet_sufx }}"
          ENV: "{{ global_var_environment }}"
          AZName: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_public_subnet_sufx }}-{{ item.az_zone }}"
      loop: "{{ aws_setup_base_vpc_public_subnet_details }}"
      register: reg_all_public_subnet_info
      when: aws_setup_base_vpc_public_subnet_details is defined

### Using the module ec2_vpc_subnet to create a single private subnet that should be
### associated with the AZ A. Uses variables from the global_vars file. This is only
### created if the variable aws_setup_base_vpc_private_subnet_a_create is true.
    - name: Creating private subnet A
      amazon.aws.ec2_vpc_subnet:
        cidr: "{{ global_var_vpc_private_subnet_a }}"
        vpc_id: "{{ reg_vpc_info.vpc.id }}"
        region: "{{ global_var_region }}"
        az: "{{ global_var_region }}a"
        state: "present"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_private_subnet_sufx }}"
          ENV: "{{ global_var_environment }}"
          AZName: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_private_subnet_sufx }}-{{ global_var_region }}a"
      register: reg_private_subnet_a_info
      when: aws_setup_base_vpc_private_subnet_a_create | default(true)

### Using the module ec2_vpc_subnet to create a single private subnet that should be
### associated with the AZ B. Uses variables from the global_vars file. This is only
### created if the variable aws_setup_base_vpc_private_subnet_b_create is true.
    - name: Creating private subnet B
      amazon.aws.ec2_vpc_subnet:
        cidr: "{{ global_var_vpc_private_subnet_b }}"
        vpc_id: "{{ reg_vpc_info.vpc.id }}"
        region: "{{ global_var_region }}"
        az: "{{ global_var_region }}b"
        state: "present"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_private_subnet_sufx }}"
          ENV: "{{ global_var_environment }}"
          AZName: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_private_subnet_sufx }}-{{ global_var_region }}b"
      register: reg_private_subnet_b_info
      when: aws_setup_base_vpc_private_subnet_b_create | default(true)

### Using the module ec2_vpc_subnet to create a single private subnet that should be
### associated with the AZ C. Uses variables from the global_vars file. This is only
### created if the variable aws_setup_base_vpc_private_subnet_c_create is true.
    - name: Creating private subnet C
      amazon.aws.ec2_vpc_subnet:
        cidr: "{{ global_var_vpc_private_subnet_c }}"
        vpc_id: "{{ reg_vpc_info.vpc.id }}"
        region: "{{ global_var_region }}"
        az: "{{ global_var_region }}c"
        state: "present"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_private_subnet_sufx }}"
          ENV: "{{ global_var_environment }}"
          AZName: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_private_subnet_sufx }}-{{ global_var_region }}c"
      register: reg_private_subnet_c_info
      when: aws_setup_base_vpc_private_subnet_c_create | default(true)

### Using the module ec2_vpc_igw to create teh internet gateway for the VPC
### the variables used is from the global_vars file and will only create if the
### variable aws_setup_base_vpc_igw_create is set to true.
    - name: Creating the Internet Gateway
      amazon.aws.ec2_vpc_igw:
        vpc_id: "{{ reg_vpc_info.vpc.id }}"
        region: "{{ global_var_region }}"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-igw"
          ENV: "{{ global_var_environment }}"
      register: reg_internet_gateway_info
      when: aws_setup_base_vpc_igw_create | default(true)

### With the module ec2_vpc_route_table, not only we are creating the route table
### but as well the route itself and the association between the subnet and the
### route table. This route table because associated with the internet gateway
### makes the subnet associated with it a public subnet. Created when aws_setup_base_vpc_public_route_table_create is true
    - name: Creating public route table
      amazon.aws.ec2_vpc_route_table:
        vpc_id: "{{ reg_vpc_info.vpc.id }}"
        region: "{{ global_var_region }}"
        subnets: "{{ reg_all_public_subnet_info.results |
                    map(attribute='subnet') |
                    map(attribute='id') }}"
        routes:
          - dest: 0.0.0.0/0
            gateway_id: "{{ reg_internet_gateway_info.gateway_id }}"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-public-route-table"
          ENV: "{{ global_var_environment }}"
      register: reg_public_route_table_info
      when: aws_setup_base_vpc_public_route_table_create | default(true)

### Using the ec2_eip module to create the Elastic IP, ie a static public IP to be used
### able to pull from the internet and also be able to be used by the aws session manager.
### Created when aws_setup_base_eip_4_natgw_create is true
    - name: Creating EIP for the NAT Gateway
      amazon.aws.ec2_eip:
        region: "{{ global_var_region }}"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-ngw-eip"
          ENV: "{{ global_var_environment }}"
      register: reg_eip_4_natgw_info
      when: aws_setup_base_eip_4_natgw_create | default(true)

### Using the ec2_vpc_nat_gateway module to create the NAT gateway to allow private subnets to be
### able to pull from the internet and also be able to be used by the aws session manager.
### This is created in a single subnet of the VPC. Created when aws_setup_base_natgw_create is true
    - name: Creating the NAT Gateway
      amazon.aws.ec2_vpc_nat_gateway:
        subnet_id: "{{ reg_all_public_subnet_info.results[0].subnet.id }}"
        allocation_id: "{{ reg_eip_4_natgw_info.allocation_id }}"
        region: "{{ global_var_region }}"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-nat-gateway"
          ENV: "{{ global_var_environment }}"
      register: reg_nat_gateway_info
      when: aws_setup_base_natgw_create | default(true)

### Using ec2_vpc_route_table module to create a route table and tie the route
### which uses the NAT Gateway to the subnets. This makes the subnet associated the private subnets.
### This is created when aws_setup_base_vpc_private_route_table_create is true
    - name: Creating private route table
      amazon.aws.ec2_vpc_route_table:
        vpc_id: "{{ reg_vpc_info.vpc.id }}"
        region: "{{ global_var_region }}"
        subnets:
          - "{{ reg_private_subnet_a_info.subnet.id }}"
          - "{{ reg_private_subnet_b_info.subnet.id }}"
          - "{{ reg_private_subnet_c_info.subnet.id }}"
        routes:
          - dest: 0.0.0.0/0
            gateway_id: "{{ reg_nat_gateway_info.nat_gateway_id }}"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-private-route-table"
          ENV: "{{ global_var_environment }}"
      register: reg_private_route_table_info
      when: aws_setup_base_vpc_private_route_table_create | default(true)

### Using the cloudwatchlogs_log_group module to create a log group to hosts
### all the logs from our application layer
    - name: Creating CloudWatch log group
      amazon.aws.cloudwatchlogs_log_group:
        log_group_name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_cloudwatch_loggroup_sufx }}"
        region: "{{ global_var_region }}"
        retention: 30
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_cloudwatch_loggroup_sufx }}"
          ENV: "{{ global_var_environment }}"
      register: reg_cloudwatch_log_group_info
      when: aws_setup_base_cloudwatch_log_group_create | default(true)

All the variables used in the playbook are from eitheraws_setup_base_infra_vars.yml or global_vars.yml . As explained earlier, the global_vars.yml mainly contains names and values that are likely to be needed by more then just the vpc playbook.

//playbook_vars/test/global_vars.yml
global_var_region: us-east-1
global_var_project: yevi
global_var_vpc_name_sufx: "vnet"
global_var_vpc_cidr_root: "172.18"
global_var_vpc_cidr: "{{ global_var_vpc_cidr_root }}.0.0/16"
global_var_vpc_public_subnet_a: "{{ global_var_vpc_cidr_root }}.0.0/24"
global_var_vpc_public_subnet_b: "{{ global_var_vpc_cidr_root }}.1.0/24"
global_var_vpc_public_subnet_c: "{{ global_var_vpc_cidr_root }}.2.0/24"
global_var_vpc_private_subnet_a: "{{ global_var_vpc_cidr_root }}.10.0/24"
global_var_vpc_private_subnet_b: "{{ global_var_vpc_cidr_root }}.11.0/24"
global_var_vpc_private_subnet_c: "{{ global_var_vpc_cidr_root }}.12.0/24"
global_var_vpc_public_subnet_sufx: "public-subnet"
global_var_vpc_private_subnet_sufx: "private-subnet"
global_var_cloudwatch_loggroup_sufx: "log-group"

The setup base infra variables on the other hand, directly influences the way the base infra playbook should run. It allows to toggle from true to false certain aspect , enabling or disabling the execution of some steps.

//aws_setup_base_infra_vars.yml
aws_setup_base_vpc_public_subnet_details:
  - {
    cidr: "{{ global_var_vpc_public_subnet_a }}",
    az_zone: "{{ global_var_region }}a"
  }
  - {
    cidr: "{{ global_var_vpc_public_subnet_b }}",
    az_zone: "{{ global_var_region }}b"
  }
  - {
    cidr: "{{ global_var_vpc_public_subnet_c }}",
    az_zone: "{{ global_var_region }}c"
  }
aws_setup_base_vpc_create: true
aws_setup_base_vpc_private_subnet_a_create: true
aws_setup_base_vpc_private_subnet_b_create: true
aws_setup_base_vpc_private_subnet_c_create: true
aws_setup_base_vpc_igw_create: true
aws_setup_base_eip_4_natgw_create: true
aws_setup_base_vpc_public_route_table_create: true
aws_setup_base_natgw_create: true
aws_setup_base_vpc_lb_secgroup_create: true
aws_setup_base_vpc_app_secgroup_create: true
aws_setup_base_cloudwatch_log_group_create: true

There are number of elements from the playbook that I would like to call out specifically. In order to create the subnets, we used 2 different methods. For the subnets that are supposed to be public, I have used a loop construct and for the private one, a regular construct .

To execute this playbook, from the ansible folder , run the following:

AWS_PROFILE=yevi-test ansible-playbook aws_setup_base_infra_playbook.yml -e "global_var_environment=test"
  • AWS_PROFILE being yevi-test , the same as the one we used before. It’s a combination of the following <product>-<environment>. Yevi being our fiction product and test being the environment of interest right now.
  • aws_setup_base_infra_playbook being the name of the playbook
  • -e being the sub command for the extra variables. This has the highest precedence in case any variable defined here has the same key but different value. The extra variable one always overrides whichever one defined in a variable files.

After executing the playbook above a number of time, it became pretty apparent that each time we run it, to test the idempotency , it create a new Elastic IP and therefore a new NAT Gateway, that’s not what we want. After googling without much pointers , I upgraded my python from pypy3.9-7.3.9 to pypy3.10-7.3.12 so I can install the latest version of the ansible ansible [core 2.15.1].I have also upgraded the collections and my requirements.yml file looks now like the the following:

collections:
  - name: amazon.aws
    version: 6.0.0
    source: https://galaxy.ansible.com
  - name: community.aws
    version: 6.0.0
    source: https://galaxy.ansible.com
  - name: community.general
    version: 6.1.0
    source: https://galaxy.ansible.com

None of that seems to want to fix the broken idempotency. Later on, I narrowed it to the creation of the EIP. Once the EIP creation has been isolated (i.e created manually outside of ansible), the rest of the play became idempotent again.

This little issue has brought forward a practice that I was going to introduce later. This is directly related to our quest to reusability. The fact that files are broken down to a set of concerns in a playbook makes sharing of ids or results used by other playbooks, difficult .To solve that, we will need to introduce a file responsible of collecting facts and info from components already created (by previous playbooks, obviously) and make cross playbooks variables sharing a bit more manageable. I surprisingly named the file aws_setup_fact_gatherer_playbook.yml . Let’s dive into its content below

//aws_setup_base_infra_playbooks.yml
- name: Playbook to gather fact and export them
  hosts: localhost
  connection: local
  gather_facts: false
  vars_files:
    - playbook_vars/{{ global_var_environment }}/global_vars.yml
  tasks:

### Using the ec2_eip_info module to pull back from AWS all the info related to the EIP
### using the tag:Name filter we are able to identify the EIP resource we are aiming at.
### Whatever was retreived is assigned to the variable facts_ngw_eip_info.
    - name: Gather || Get Info about EIP for NAT Gateway
      amazon.aws.ec2_eip_info:
        filters:
          "tag:Name": "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_ngw_eip_name_sufx }}"
        region: "{{ global_var_region }}"
      register: facts_ngw_eip_info

### using set fact to export all variable declared under it
    - name: Gather || Export all collected facts/info
      ansible.builtin.set_fact:
        exported_ngw_eip_info: "{{ facts_ngw_eip_info }}"

The change above will need an update of the following to the global_vars.yml . The EIP has been created manually using the same tags Name: <product>-<environment>-<name_sufx> i.e. yevi-test-ngw-eip . The set_fact module is exporting into a global ansible execution context, the value of the variables declared under it.

##... entries above
global_var_ngw_eip_name_sufx: "ngw-eip"

Now that we have the idempotency fixed. It’s time to work on the second iteration of our aws_setup_base_infra_playbooks.yml file. I apologise for creating or posting the same playbook twice. That has contributed to the length of this tutorial but , it’s a necessary step to show how in real world, we change or adjust our code base and adapt to new solutions.

//aws_setup_base_infra_playbooks.yml second iteration
---
- name: Importing Fact Gatherer playbook
  ansible.builtin.import_playbook: aws_setup_fact_gatherer_playbook.yml

- name: Execute Ansible from localhost using aws python sdk
  hosts: localhost
  connection: local
  gather_facts: true
  vars_files:
    - playbook_vars/{{ global_var_environment }}/global_vars.yml
    - playbook_vars/{{ global_var_environment }}/aws_setup_base_infra_vars.yml
  tasks:

### Using the module ec2_vpc_net to create the vpc using variables
### from the global variables with tags when the variable aws_setup_base_vpc_create
### is true (which is default meaning I don't have to set it if I want it true)
    - name: Creating the AWS VPC
      amazon.aws.ec2_vpc_net:
        name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_name_sufx }}"
        cidr_block: "{{ global_var_vpc_cidr }}"
        region: "{{ global_var_region }}"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_name_sufx }}"
          ENV: "{{ global_var_environment }}"
      register: reg_vpc_info
      when: aws_setup_base_vpc_create | default(true)

### Using the module ec2_vpc_subnet to create the subnets destined to be
### public subnets using variables from the global_vars file. This uses a looping
### technics to create the subnets making us use only 1 entry to create 3 subnets
### This is created only when the variable aws_setup_base_vpc_public_subnet_details is defined
    - name: Creating public subnets
      amazon.aws.ec2_vpc_subnet:
        cidr: "{{ item.cidr }}"
        vpc_id: "{{ reg_vpc_info.vpc.id }}"
        region: "{{ global_var_region }}"
        az: "{{ item.az_zone }}"
        state: "present"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_public_subnet_sufx }}"
          ENV: "{{ global_var_environment }}"
          AZName: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_public_subnet_sufx }}-{{ item.az_zone }}"
      loop: "{{ aws_setup_base_vpc_public_subnet_details }}"
      register: reg_all_public_subnet_info
      when: aws_setup_base_vpc_public_subnet_details is defined

### Using the module ec2_vpc_subnet to create a single private subnet that should be
### associated with the AZ A. Uses variables from the global_vars file. This is only
### created if the variable aws_setup_base_vpc_private_subnet_a_create is true.
    - name: Creating private subnet A
      amazon.aws.ec2_vpc_subnet:
        cidr: "{{ global_var_vpc_private_subnet_a }}"
        vpc_id: "{{ reg_vpc_info.vpc.id }}"
        region: "{{ global_var_region }}"
        az: "{{ global_var_region }}a"
        state: "present"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_private_subnet_sufx }}"
          ENV: "{{ global_var_environment }}"
          AZName: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_private_subnet_sufx }}-{{ global_var_region }}a"
      register: reg_private_subnet_a_info
      when: aws_setup_base_vpc_private_subnet_a_create | default(true)

### Using the module ec2_vpc_subnet to create a single private subnet that should be
### associated with the AZ B. Uses variables from the global_vars file. This is only
### created if the variable aws_setup_base_vpc_private_subnet_b_create is true.
    - name: Creating private subnet B
      amazon.aws.ec2_vpc_subnet:
        cidr: "{{ global_var_vpc_private_subnet_b }}"
        vpc_id: "{{ reg_vpc_info.vpc.id }}"
        region: "{{ global_var_region }}"
        az: "{{ global_var_region }}b"
        state: "present"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_private_subnet_sufx }}"
          ENV: "{{ global_var_environment }}"
          AZName: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_private_subnet_sufx }}-{{ global_var_region }}b"
      register: reg_private_subnet_b_info
      when: aws_setup_base_vpc_private_subnet_b_create | default(true)

### Using the module ec2_vpc_subnet to create a single private subnet that should be
### associated with the AZ C. Uses variables from the global_vars file. This is only
### created if the variable aws_setup_base_vpc_private_subnet_c_create is true.
    - name: Creating private subnet C
      amazon.aws.ec2_vpc_subnet:
        cidr: "{{ global_var_vpc_private_subnet_c }}"
        vpc_id: "{{ reg_vpc_info.vpc.id }}"
        region: "{{ global_var_region }}"
        az: "{{ global_var_region }}c"
        state: "present"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_private_subnet_sufx }}"
          ENV: "{{ global_var_environment }}"
          AZName: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_private_subnet_sufx }}-{{ global_var_region }}c"
      register: reg_private_subnet_c_info
      when: aws_setup_base_vpc_private_subnet_c_create | default(true)

### Using the module ec2_vpc_igw to create teh internet gateway for the VPC
### the variables used is from the global_vars file and will only create if the
### variable aws_setup_base_vpc_igw_create is set to true.
    - name: Creating the Internet Gateway
      amazon.aws.ec2_vpc_igw:
        vpc_id: "{{ reg_vpc_info.vpc.id }}"
        region: "{{ global_var_region }}"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-igw"
          ENV: "{{ global_var_environment }}"
      register: reg_internet_gateway_info
      when: aws_setup_base_vpc_igw_create | default(true)

### With the module ec2_vpc_route_table, not only we are creating the route table
### but as well the route itself and the association between the subnet and the
### route table. This route table because associated with the internet gateway
### makes the subnet associated with it a public subnet. Created when aws_setup_base_vpc_public_route_table_create is true
    - name: Creating public route table
      amazon.aws.ec2_vpc_route_table:
        vpc_id: "{{ reg_vpc_info.vpc.id }}"
        region: "{{ global_var_region }}"
        subnets: "{{ reg_all_public_subnet_info.results |
                    map(attribute='subnet') |
                    map(attribute='id') }}"
        routes:
          - dest: 0.0.0.0/0
            gateway_id: "{{ reg_internet_gateway_info.gateway_id }}"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-public-route-table"
          ENV: "{{ global_var_environment }}"
      register: reg_public_route_table_info
      when: aws_setup_base_vpc_public_route_table_create | default(true)

### Using the ec2_eip module to create the Elastic IP, ie a static public IP to be used
### able to pull from the internet and also be able to be used by the aws session manager.
### Created when aws_setup_base_eip_4_natgw_create is true
### Due to the broken idempotency issue with the EIP. We will have to manually create the EIP
### and reference it through the aws_setup_fact_gatherer file
    # - name: Creating EIP for the NAT Gateway
    #   amazon.aws.ec2_eip:
    #     region: "{{ global_var_region }}"
    #     reuse_existing_ip_allowed: false
    #     release_on_disassociation: false
    #     allow_reassociation: false
    #     tags:
    #       Name: "{{ global_var_project }}-{{ global_var_environment }}-ngw-eip"
    #       ENV: "{{ global_var_environment }}"
    #   register: reg_eip_4_natgw_info
    #   when: aws_setup_base_eip_4_natgw_create | default(true)

### Using the ec2_vpc_nat_gateway module to create the NAT gateway to allow private subnets to be
### able to pull from the internet and also be able to be used by the aws session manager.
### This is created in a single subnet of the VPC. Created when aws_setup_base_natgw_create is true
    - name: Creating the NAT Gateway
      amazon.aws.ec2_vpc_nat_gateway:
        subnet_id: "{{ reg_all_public_subnet_info.results[0].subnet.id }}"
        allocation_id: "{{ hostvars.localhost.exported_ngw_eip_info.addresses.0.allocation_id }}"
        region: "{{ global_var_region }}"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-nat-gateway"
          ENV: "{{ global_var_environment }}"
      register: reg_nat_gateway_info
      when: aws_setup_base_natgw_create | default(true)

### Using ec2_vpc_route_table module to create a route table and tie the route
### which uses the NAT Gateway to the subnets. This makes the subnet associated the private subnets.
### This is created when aws_setup_base_vpc_private_route_table_create is true
    - name: Creating private route table
      amazon.aws.ec2_vpc_route_table:
        vpc_id: "{{ reg_vpc_info.vpc.id }}"
        region: "{{ global_var_region }}"
        subnets:
          - "{{ reg_private_subnet_a_info.subnet.id }}"
          - "{{ reg_private_subnet_b_info.subnet.id }}"
          - "{{ reg_private_subnet_c_info.subnet.id }}"
        routes:
          - dest: 0.0.0.0/0
            gateway_id: "{{ reg_nat_gateway_info.nat_gateway_id }}"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-private-route-table"
          ENV: "{{ global_var_environment }}"
      register: reg_private_route_table_info
      when: aws_setup_base_vpc_private_route_table_create | default(true)

### Using the cloudwatchlogs_log_group module to create a log group to hosts
### all the logs from our application layer
    - name: Creating CloudWatch log group
      amazon.aws.cloudwatchlogs_log_group:
        log_group_name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_cloudwatch_loggroup_sufx }}"
        region: "{{ global_var_region }}"
        retention: 30
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_cloudwatch_loggroup_sufx }}"
          ENV: "{{ global_var_environment }}"
      register: reg_cloudwatch_log_group_info
      when: aws_setup_base_cloudwatch_log_group_create | default(true)

There are a few things I would like to draw your attention to.

The first is that we have included the fact gatherer playbook into our base infrastructure playbook at its top. This means that the fact gatherer playbook will execute before the rest of the base infrastructure. It is at this point exporting exported_ngw_eip_info .

the second is that, At the line 155 of aws_setup_base_infra_playbooks.yml, allocation_id: "{{ hostvars.localhost.exported_ngw_eip_info.addresses.0.allocation_id }}" we are retrieving the value of the allocation_id from the exported exported_ngw_eip_info that was pushed by the fact gatherer. This allocation_id is crucial to the creation of our NAT Gateway.

Setting up container and permission resources

In this section of the post will focus on setting up resources such as IAM policies and roles as as well security groups. To deploy to ECS using ansible, we have to setup the container registry, since it’s all about containers in ECS (just in case we have not noticed 😀 ). Our containers will need security groups, execution roles etc. The file aws_setup_container_permission_playbook.yml will create all the aforementioned resources and will make use of the global variable file, the ansible vault variable file test_variables.vault as well as the aws_setup_cont_and_perm_vars.yml variable file.

Container resources

There are situations where a container needs to be passed environment variable securely. In AWS that can be done using 2 services. Parameter Store and Secrets manager. We will assume that our applications will require to use database credentials from the secrets manager for example (another service from AWS). We will also create the container registry that will keep our images from which we pull ,create and run containers.

The credentials will have to be stored somewhere in our ansible structure and eventually checked into our git repository. This, on the face value is just the right amount of recipe for a disaster. Anyone with access to your repo will also have access to the creds. To avoid that we will need to leverage the ansible vault utility. Let’s get started with the vault. We will need a place to store the vault file and a place for the password file as well. Some may decide not to use the password file and rather just use some password preferably stored in a environment variable on your computer. For now, let’s say we store our file inside a Dropbox folder.

Let’s create our password file. It can be generated with openssl as follows. But then I have a little utility pwgen that I love using for this kind of stuff

mkdir -p  ~/Dropbox/tutorials/ansible_vault/ansible_ecs_step_by_step/{prod,test,uat}
touch ~/Dropbox/tutorials/ansible_vault/ansible_ecs_step_by_step/test/sensitive.password
openssl rand -hex 500 > ~/Dropbox/tutorials/ansible_vault/ansible_ecs_step_by_step/test/sensitive.password

you could use openssl as the following to create the password file

pwgen -s -1   500 >  ~/Dropbox/tutorials/ansible_vault/ansible_ecs_step_by_step/test/sensitive.password

But if you prefer the pwgen like I do then you could do it like the following

We will create the ansible vault file location and the vault file. From the root folder do the following. the ansible-vault command will want to use a cli text editor. I can’t remember the prompt very well but I used vim. So it will open up the vim editor for you to add the first variables in the vault

mkdir playbook_vars/test/vault ; cd playbook_vars/test/vault
ansible-vault create test_variables.vault --vault-password-file ~/Dropbox/tutorials/ansible_vault/ansible_ecs_step_by_step/test/sensitive.password 
# if it's vim. press i to to into the insert mode and paste the following
vault_db_username: app_db_user
vault_db_password: 98daf!si-UTyoyis
# after pasting press esc to cancel the insert mode. Press colon then wq to show :wq to write and quit and press enter 

Now we can create the playbook that will make use of the ansible vault in order to create the aws secrets manager item from its content. We will also add the creation of the ECR to it.

//aws_setup_container_permission_playbook.yml
---
- name: Importing Fact Gatherer playbook
  ansible.builtin.import_playbook: aws_setup_fact_gatherer_playbook.yml

- name: Playbook to create container and permission resources
  hosts: localhost
  connection: local
  gather_facts: true
  vars_files:
    - playbook_vars/{{ global_var_environment }}/global_vars.yml
    - playbook_vars/{{ global_var_environment }}/aws_setup_cont_and_perm_vars.yml
    - playbook_vars/{{ global_var_environment }}/vault/{{ global_var_environment }}_variables.vault
  tasks:

### Using the secretsmanager_secret module to create a json secret with values from the ansible vault.
### The name of the secrets and its tag name Name are the same.
### The creation of this resource is conditioned by the variable aws_setup_security_db_secretsmanager_create.
    - name: Creating credentials in AWS Secrets Manager
      community.aws.secretsmanager_secret:
        name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_db_secretsmanager_sufx }}"
        region: "{{ global_var_region }}"
        description: json document with details on db connection
        json_secret:
          username: "{{ vault_db_username }}"
          password: "{{ vault_db_password }}"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_db_secretsmanager_sufx }}"
          ENV: "{{ global_var_environment }}"
      register: reg_db_secretsmanager_info
      when: aws_setup_security_db_secretsmanager_create | default(true)

### Using ecs_ecr module to create the registry needed for all the containers we are getting ready to deploy.
### The use of loop construct helps using only one entry to create multiple resource sahring the same context.
### The creaion of this resouce is conditioned by the existence of the variable aws_setup_security_ecr_repositories_details
    - name: Creating container registry repository
      community.aws.ecs_ecr:
        name: "{{ item.name }}"
        region: "{{ global_var_region }}"
        scan_on_push: true
        state: present
      register: reg_ecr_repositories_info
      when: aws_setup_security_ecr_repositories_details is defined
      loop: "{{ aws_setup_security_ecr_repositories_details }}"

It’s corresponding variable file is aws_setup_cont_and_perm_vars.yml shows as below but as well it’s worth noting that we need to update the global_vars.yml

// aws_setup_cont_and_perm_vars.yml
aws_setup_security_db_secretsmanager_create: true
aws_setup_security_ecr_repositories_details:
  - { 
      name: "{{ global_var_project }}-{{ global_var_environment }}-ecr-repo/java-app" 
  }
  - { 
      name: "{{ global_var_project }}-{{ global_var_environment }}-ecr-repo/golang-app" 
  }
// global_vars.yml
# .. all previous existing content above are hidden for brevity
global_var_db_secretsmanager_sufx: "sensitive-details"

Because we have introduced the vault file, to execute the playbook now, it has to include the vault file as shown in the following

 AWS_PROFILE=yevi-test ansible-playbook aws_setup_container_permission_playbook.yml -e "global_var_environment=test" --vault-password-file  ~/Dropbox/tutorials/ansible_vault/ansible_ecs_step_by_step/test/sensitive.password

Permissions (Security Groups and IAM)

We know we want to deploy to ECS using ansible, also know that we will need to create a set of security groups and at least an ecs task execution role with enough privileges to either pull the image from the ECR or read the secrets from the secrets manager.

IAM

We will use the same playbook to compete the rest of our requirements. Interestingly to achieve the goal we will have to introduce the concept of files and templates. In our root directory we will be creating the following 2 folders:

  • files : This will hold static files that don’t change or the resulting file out of the execution of a template fed with data.
  • templates: this is where we keep the jinja2 template files

We will put in the files, the assume role json document that we have to create for ECS

// files/yevi-ecs-task-trust-policy.json
{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": {
          "Service": "ecs-tasks.amazonaws.com"
        },
        "Action": "sts:AssumeRole"
      }
    ]
}

We will leverage the template file to create the IAM json policy we need the ECS task to have. Below is how the template looks like

// templates/yevi-ecs-task-policy.json.j2
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecr:GetAuthorizationToken"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ecr:BatchCheckLayerAvailability",
                "ecr:GetDownloadUrlForLayer",
                "ecr:BatchGetImage"
            ],
            "Resource": [
{% if vars_template_ecr_arns is defined %}
    {% for arn in  vars_template_ecr_arns %}
            "{{ arn }}"{% if not loop.last %},{% endif %}

    {% endfor %}
{% endif %}            
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
{% if vars_template_log_arns is defined %}
    {% for arn in  vars_template_log_arns %}
            "{{ arn }}"{% if not loop.last %},{% endif %}

    {% endfor %}
{% endif %} 
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "ssm:GetParameters",
                "secretsmanager:GetSecretValue"
            ],
            "Resource": [
{% if vars_template_secretstore_arns is defined %}
    {% for arn in  vars_template_secretstore_arns %}
            "{{ arn }}"{% if not loop.last %},{% endif %}

    {% endfor %}
{% endif %}
            ]
           
        }
    ]
}

In the template above we are created the following access to our ecs tacks:

  • Ability to retrieve images from the ECR
  • Ability to write logs in to the cloudwatch streams
  • Ability to read from parameter stores and secrets manager

We need the arn of the secrets manager to add the policy but it turns out that we won’t be able to get that from the returned output from it’s creation. We will need to compose the full arn of the secrets manager. We will need to know the AWS account id for that. It also turns out that we will need to find the arn of the log group to allow the write permission to. But we have already created that in the previous section. We will have to update the aws_setup_fact_gatherer_playbook.yml to get the missing information. We will also sneak in a way of retrieving the vpc id that we will pretty much soon need.

// aws_setup_fact_gatherer_playbook.yml
# .. all previous existing content above are hidden for brevity
### Using the aws_caller_info module to retrieve account details, especially the account id
    - name: Gather || Get AWS Account information
      amazon.aws.aws_caller_info:
        region: "{{ global_var_region }}"
      register: facts_aws_caller_info

### Using ec2_vpc_net_info to pull vpc information back from AWS
    - name: Gather || information about the vpc
      amazon.aws.ec2_vpc_net_info:
        region: "{{ global_var_region }}"
        filters:
          "tag:Name": "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_vpc_name_sufx }}"
      register: facts_vpc_info

### Using cloudwatchlogs_log_group_info module to pull our log group information back
    - name: Gather || information about cloudwatch log group info
      amazon.aws.cloudwatchlogs_log_group_info:
        log_group_name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_cloudwatch_loggroup_sufx }}"
        region: "{{ global_var_region }}"
      register: facts_vpc_cloudwatchlogs_group_info

### using set fact to export all variable declared under it
    - name: Gather || Export all collected facts/info
      ansible.builtin.set_fact:
        exported_ngw_eip_info: "{{ facts_ngw_eip_info }}"
        exported_facts_aws_caller_info: "{{ facts_aws_caller_info }}"
        exported_facts_vpc_id: "{{ facts_vpc_info.vpcs[0].id }}"
        exported_facts_vpc_cloudwatchlogs_group_arn: "{{ facts_vpc_cloudwatchlogs_group_info.log_groups[0].arn }}"

Putting everything together about the IAM we will create the aws_setup_container_permission_playbook.yml with the following content

// aws_setup_container_permission_playbook.yml
 name: Importing Fact Gatherer playbook
  ansible.builtin.import_playbook: aws_setup_fact_gatherer_playbook.yml

- name: Playbook to create container and permission resources
  hosts: localhost
  connection: local
  gather_facts: true
  vars_files:
    - playbook_vars/{{ global_var_environment }}/global_vars.yml
    - playbook_vars/{{ global_var_environment }}/aws_setup_cont_and_perm_vars.yml
    - playbook_vars/{{ global_var_environment }}/vault/{{ global_var_environment }}_variables.vault
  tasks:

### Using the secretsmanager_secret module to create a json secret with values from the ansible vault.
### The name of the secrets and its tag name Name are the same.
### The creation of this resource is conditioned by the variable aws_setup_security_db_secretsmanager_create.
    - name: Creating credentials in AWS Secrets Manager
      community.aws.secretsmanager_secret:
        name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_db_secretsmanager_sufx }}"
        region: "{{ global_var_region }}"
        description: json document with details on db connection
        json_secret:
          username: "{{ vault_db_username }}"
          password: "{{ vault_db_password }}"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_db_secretsmanager_sufx }}"
          ENV: "{{ global_var_environment }}"
      register: reg_db_secretsmanager_info
      when: aws_setup_security_db_secretsmanager_create | default(true)

### Using ecs_ecr module to create the registry needed for all the containers we are getting ready to deploy.
### The use of loop construct helps using only one entry to create multiple resource sahring the same context.
### The creaion of this resouce is conditioned by the existence of the variable aws_setup_security_ecr_repositories_details
    - name: Creating container registry repository
      community.aws.ecs_ecr:
        name: "{{ item.name }}"
        region: "{{ global_var_region }}"
        scan_on_push: true
        state: present
      register: reg_ecr_repositories_info
      when: aws_setup_security_ecr_repositories_details is defined
      loop: "{{ aws_setup_security_ecr_repositories_details }}"

### Using template module to generate the actualy policy file out of a jinja2 template for the ECS task permission
### as you could see the arn of the secrets manager is composed based on a known pattern using account id.
    - name: Creating the custom policy json file
      ansible.builtin.template:
        src: "templates/{{ global_var_project }}-{{ aws_setup_security_iam_ecs_task_policy_template_sufx }}"
        dest: "files/{{ global_var_project }}-{{ global_var_environment }}-{{ aws_setup_security_iam_ecs_task_policy_file_sufx }}"
        mode: 0644
      vars:
        vars_template_ecr_arns: "{{ reg_ecr_repositories_info.results | map(attribute='repository') | map(attribute='repositoryArn' ) | list }}"
        vars_template_log_arns:
          - "{{ hostvars.localhost.exported_facts_vpc_cloudwatchlogs_group_arn }}"
        vars_template_secretstore_arns:
          - "{{ reg_db_secretsmanager_info.secret.arn }}"
          - "arn:aws:ssm:\
            {{ global_var_region }}:\
            {{ hostvars.localhost.exported_facts_aws_caller_info.account }}:\
            parameter/{{ global_var_project }}-\
            {{ global_var_environment }}*"
      register: reg_custom_policy_file_generation
      when: aws_setup_security_custom_iam_policy_template_generate | default(true)

### Using iam_managed_policy to create a policy to be attached to the role used by ECS tasks
    - name: Creating a custom IAM policy
      community.aws.iam_managed_policy:
        policy_name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_custom_iam_policy_sufx }}"
        region: "{{ global_var_region }}"
        policy: "{{ lookup('ansible.builtin.file',vars_custom_policy_file) }}"
      vars:
        vars_custom_policy_file: "files/{{ global_var_project }}-{{ global_var_environment }}-{{ aws_setup_security_iam_ecs_task_policy_file_sufx }}"
      register: reg_custom_iam_policy
      when: aws_setup_security_custom_iam_policy_create | default(true)

 ### Using iam_role module to create a role for the ecs task but also to assign permission created
 ### through the policy created just above
    - name: Creating ECS Task IAM Role
      community.aws.iam_role:
        name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_iam_task_role_sufx }}"
        region: "{{ global_var_region }}"
        assume_role_policy_document: "{{ lookup('ansible.builtin.file', vars_task_assume_policy ) }}"
        purge_policies: true
        managed_policies:
          - "{{ reg_custom_iam_policy.policy.arn }}"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_iam_task_role_sufx }}"
          ENV: "{{ global_var_environment }}"
      vars:
        vars_task_assume_policy: "files/{{ global_var_project }}-{{ aws_setup_security_iam_trust_policy_file_sufx }}"
      register: reg_iam_task_role
      when: aws_setup_security_iam_role_create | default(true)

It’s corresponding variable file aws_setup_cont_and_perm_vars.yml needs to be updated with the following, with all previous content hidden of course to prevent repetition.

// aws_setup_cont_and_perm_vars.yml
# .. all previous existing content above are hidden for brevity
aws_setup_security_custom_iam_policy_template_generate: true
aws_setup_security_iam_trust_policy_file_sufx: "ecs-task-trust-policy.json"
aws_setup_security_iam_ecs_task_policy_template_sufx: "ecs-task-policy.json.j2"
aws_setup_security_iam_ecs_task_policy_file_sufx: "ecs-task-policy.json"

The global_vars file also needs to be updated with the following

// global_vars.yml
# .. all previous existing content above are hidden for brevity
global_var_iam_task_role_sufx: "task-role"
global_var_custom_iam_policy_sufx: "custom-policy"

You can choose to execute the file using the same command involving the ansible vault as shown earlier or wait till the end of the next section to do it.

Security Groups

We already know the flow of the traffic from internet to our containers. We know that for serving our microservices ,we will the help of a load balancer. It will be an application load balancer. When the load balancer receives the traffic it needs to pass it on to the containers. Therefore the containers also need to allow traffic from the load balancer to themselves . Based on that, the following security groups are ones we will create:

  • A security group for the load balancer to accept request from the internet
  • A security group for the ecs task to accept request from the load balancer

To add the 2 security groups, let’s edit the aws_setup_container_permission_playbook.yml file its associated aws_setup_cont_and_perm_vars.yml variable file and of course the global_var.yml

//aws_setup_container_permission_playbook.yml
# .. all previous existing content above are hidden for brevity

### Using the module ec2_security_group to create the security group for the load balancer. It creates 2 rules:
### opens port http (80) to the worl and as port port https (443) to the world
### The vpc id is retrieved from the exported_factss_vpc_id collected and exported by the fact gatherer playbook.
    - name: Creating Load Balancer security group
      amazon.aws.ec2_security_group:
        vpc_id: "{{ hostvars.localhost.exported_facts_vpc_id }}"
        name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_secgroup_elb_sufx }}"
        region: "{{ global_var_region }}"
        description: "Allowing 0.0.0.0/0 to the LB port"
        rules:
          - proto: tcp
            from_port: "{{ global_var_lb_http_port }}"
            to_port: "{{ global_var_lb_http_port }}"
            cidr_ip: 0.0.0.0/0
          - proto: tcp
            from_port: "{{ global_var_lb_https_port }}"
            to_port: "{{ global_var_lb_https_port }}"
            cidr_ip: 0.0.0.0/0
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_secgroup_elb_sufx }}"
          ENV: "{{ global_var_environment }}"
      register: reg_lb_secgroup_info
      when: aws_setup_security_vpc_lb_secgroup_create | default(true)

### Using the module ec2_security_group to  create the security group for the applicaiton, i.e. the containers
### This uses the variable registered above for the creation of the security group for the load balancer as
### source security group. So it opens the same port http and https to the load balancer
### The vpc id is retrieved from the exported_factss_vpc_id collected and exported by the fact gatherer playbook.
    - name: Creating Application security group
      amazon.aws.ec2_security_group:
        vpc_id: "{{ hostvars.localhost.exported_facts_vpc_id }}"
        name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_secgroup_app_sufx }}"
        region: "{{ global_var_region }}"
        description: "Allowing traffic from the LB and to self "
        rules:
          - proto: tcp
            from_port: "{{ global_var_app_from_port }}"
            to_port: "{{ global_var_app_to_port }}"
            group_id: "{{ reg_lb_secgroup_info.group_id }}"
          - proto: tcp
            from_port: "{{ global_var_app_from_port }}"
            to_port: "{{ global_var_app_to_port }}"
            group_name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_secgroup_app_sufx }}"
        tags:
          Name: "{{ global_var_project }}-{{ global_var_environment }}-{{ global_var_secgroup_app_sufx }}"
          ENV: "{{ global_var_environment }}"
      register: reg_app_secgroup_info
      when: aws_setup_security_vpc_app_secgroup_create | default(true)
//aws_setup_cont_and_perm_vars.yml
# .. all previous existing content above are hidden for brevity
aws_setup_security_vpc_lb_secgroup_create: true
aws_setup_security_vpc_app_secgroup_create: true
//global_vars.yml
# .. all previous existing content above are hidden for brevity
global_var_lb_http_port: 80
global_var_lb_https_port: 443
global_var_app_from_port: 8000
global_var_app_to_port: 10000

global_var_secgroup_app_sufx: "app-secgroup"
global_var_secgroup_elb_sufx: "lb-secgroup"

The same ansible playbook with vault command applies here as well to execute this final section of the part 2 of our tutorial.

AWS_PROFILE=yevi-test ansible-playbook aws_setup_container_permission_playbook.yml -e "global_var_environment=test" --vault-password-file  ~/Dropbox/tutorials/ansible_vault/ansible_ecs_step_by_step/test/sensitive.password

The snapshot of this particular section is tagged as base_infra_and_security in gitlab: https://gitlab.com/kd-tutorials/ansible-ecs-step-by-step.git

To checkout this tag, you will have to run the following:

git checkout tags/base_infra_and_security -b branchname

We have arrived at the end of the part 2 of our post. I hope I have been able to show you how to deploy to ECS using ansible and other few things that can help you better structure your ansible project and even build on that. Feel free to share any alternative to ideas discussed here.

Leave a Reply

Your email address will not be published. Required fields are marked *

captcha * Time limit is exhausted. Please reload the CAPTCHA.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to top