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 theec2_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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
//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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
//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:
1 |
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:
1 2 3 4 5 6 7 8 9 10 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
//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.
1 2 |
##... 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
//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
1 2 |
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 |
1 |
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
1 |
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
1 2 3 4 5 6 |
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 <code>:wq</code> 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
//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
1 2 3 4 5 6 7 8 9 |
// 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" } |
1 2 3 |
// 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
1 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
// 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
// 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.
1 2 3 4 5 6 |
// 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
1 2 3 4 |
// 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
//<code>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) |
1 2 3 4 |
//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 |
1 2 3 4 5 6 7 8 9 |
//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.
1 |
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:
1 |
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.