Deploying a fault tolerant, scalable WordPress Micro-services with EC2 container service on AWS

By Intuitive / Mar 23,2019

In this blog, I have deployed a WordPress microservices with EC2 container service on AWS using Terraform, Packer and Ansible. I will provide overview of EC2 container service, some hands-on stuff I tried. Amazon does not charge separately for the ECS service, instances used for containers will be charged appropriately. I have a AWS free-tier account and I was able to try the ECS with the free-tier account.

Implemented architecture

(NAT and internet gateway are not shown for clarity purposes)

us-west-2
+--------------------------------------------------------------+
|                                                              |
|           +----------------+    +----------------+           |
|           |         +-----------------+          |           |
|           |         |      |ELB |     |          |           |
|public     |         +-----------------+          | public    |
|us-west-2a +----------------+ || +----------------+ us-west-2b|
|                              ||                              |
|           +----------------+ || +----------------+           |
|           |                | || |                |           |
|           | +------------+ | || | +------------+ |           |
|           | |ECS instance| | || | |ECS instance| |           |
|           | |            +^------^+            | |           |
|           | +-----^----^-+ |    | +-----^-----^+ |           |
|private    |       |    |   |    |       |     |  | private   |
|us-west-2a +----------------+    +----------------+ us—west—2b|
|                   |    |                |     |              |
|                +--+--+ +-------------+--+--+  |              |
|                | RDS |               | EFS |  |              |
|                +-----+               +-----+  |              |
|                      +------------------------+              |
+--------------------------------------------------------------+

What I have done?

  • First up I built the docker image with Packer docker builder using local shell provisioner, ansible-local provisioner and docker post-processor to upload the image to private docker registry aka AWS ECR. This image contains WordPress files with apache2 as a webserver.
  • In addition, it has an entry point that copies files to the EFS folder at the first time if necessary, then populate DB config and wpSalt through environment variables. Resulting from the build I get a docker image that can run apache2 with WordPress configuration
  • Afterwards, I’ve done a terraform configuration that creates a AWS ECS cluster in EU-WEST region with base infrastructure components and auto scaling groups configuration using ECS optimized AMI that comes with the ecs agent already installed, ELB was going to load balance WordPress docker containers, RDS for database and Elastic File System which gives me a low latency NFS mount.
  • A WordPress dockerized service runs in a specific port on every container instance and it has mounted an EFS folder. I have splitted the components with modules that will allow me or others to reuse them for other projects or create more environments (Needs more work to achieve 100% of that). I have created a provisioning task for the WordPress service to deploy without downtime.

How components interact between each over?

  • Firstly, I set up an ECR registry for docker images. Then, I built the packer template based on Ubuntu 16.04 docker image with 4 provisioners; a local shell script that install Ansible roles, then a shell script that installs Ansible, also an Ansible playbook that sets up the time zone and installs WordPress, and finally a clean-up shell script that removes ansible and clears off unused ansible tmp files to save a few space in the resulting docker image. The docker post-processor generate a tagged image and then upload to ECR registry.
  • Next, I have created VPC with 2 public subnets and 2 private subnets in different availability zones. The public subnets have a routing table that points to the Internet Gateway. The private subnets have a routing table to get the outgoing internet connection for ec2 container instances through 2 NAT Gateways with elastic ip, set up it in public subnets. I made ELB security group which allows incoming traffic on port 80 and outbound traffic from private network on port 80.
  • An EFS security group to allow connection of NFS points on container instances.
  • An ECS security group which handles incoming traffic from public and private subnets on port 80 and open port 22 for testing purposes. Also, it allows all outgoing traffic.
  • Then I deployed a single RDS instance with security group that only permit traffic from private subnet on port 3306.
  • There are two IAM roles: one for EC2 instances and another one for the ECS services. EC2 instances role has permissions to interact with ECS cluster, such as register itself when a server started or read EFS information.
  • ECS services role have permissions to register/unregister services from ELB, etc. Container instances need to be launched with an EC2 instance IAM role that authenticates to the account and provides the required resource permissions.
  • Next, the ECS cluster has a NFS folder mounted for each instance of the specific subnet, and auto scaling group for the ec2 container instances that are booted on private subnet so they are not externally accessible. This setup allows to scale the system up or down simply by changing the values in terraform configuration or automatically following auto scaling group policies.
  • An ELB will load balance the http request to EC2 container instances on port 80 across multiple availability zones. When the instances are loaded and joined to the cluster using the init script, and service configuration runs a valid container (if required), and the ELB health checks are going well, the ELB register the instance on it, and allows external traffic to the service. Note that, I statically allocate port numbers. This means I can only run one container of this service per instance per port.
  • Finally, I have a WordPress service setting that launch a specific WordPress image, which was generated with packer and ansible. Also, it has a WordPress database hostname where it gets the url from rds module.
  • To summarize, inbound traffic is routed through an ELB exposed to the internet and forwarded to their ECS service and containers.

Here are the components I used to configure a container cluster infrastructure and the WordPress service:

• VPC (/16 ip address range)
• Internet gateway (interface between VPC and the internet).
• 2 public subnet and NAT gateways in 2 availability zone .
• 2 private subnet in 2 availability zone for ecs instances with auto scaling group.
• Elastic ips for nat gateways.
• Route tables and route tables association for subnets.
• Security groups for accessing and/or blocking ELB, container instances, EFS, public and private subnets communications.
• IAM policies, roles and instance profiles.
• ECS: cluster, instances role, services role, container instances in different availability zones in private subnet with auto scaling group configured and security group, running ECS agent.
• ELB to distribute traffic between container instances.
• EFS file system.
• RDS instance.
• ECR repository
• WordPress service task definition.

Making the container stateless

To achieve our goal of easy scalability we want to make our WordPress container stateless, meaning that no particular data are attached to the host the container is running on. WordPress text article content is stored on an external database so we’re good on this side. We’ll use a RDS mysql instance for that. WordPress static content is stored at path /var/www/html/wp-content of the container. We’ll store this on some storage space shared between hosts and mounted in the container. We’ll use the EFS service for that (AWS nfs as a service). We’ll use an internal route53 DNS zone to associate simple names to our services:
• db.wordpress.ael for RDS
• nfs.wordpress.ael for EFS

We also want to put our ECS instances in an autoscaling group and put an ELB with HTTP health check in front of it (cloudwatch alarms for autoscaling not implemented yet).

High Availability

To achieve HA we’ll span our autoscaling group in two different availability zones. Our RDS and NFS services are accessible to those two zones but are not HA, we should add that for production deployment.

Security

We use a dedicated VPC for our project, associate restrictive security groups to instances and put our ECS instances, DB and NFS services in private subnets which access the internet through a NAT (ECS instances need to install nfs-utils at startup and pull ECR repo). Only the ELB resides in the public subnet.

Instructions to implement

As we’re using AWS ECR to store our docker containers and that our ECS cluster is pulling from it, we’ll need to deploy our infrastructure first and then build and push our WordPress container with packer.

What you’ll need on your machine
• packer
• terraform
• docker
• ansible

Export your AWS credentials

export AWS_ACCESS_KEY_ID=your_access_key
export AWS_SECRET_ACCESS_KEY=your_secret_access_key

Deploy the infrastructure and get the ECR url (without the repo name)

terraform apply
export ECR_REPOSITORY=$(terraform output ecr_repository | sed 's/\/.*//')

output

aws_eip.natgw_ip_zoneB: Creating...
  allocation_id:     "" => "<computed>"
  association_id:    "" => "<computed>"
  domain:            "" => "<computed>"
  instance:          "" => "<computed>"
  network_interface: "" => "<computed>"
  private_ip:        "" => "<computed>"
  public_ip:         "" => "<computed>"
  vpc:               "" => "true"
aws_efs_file_system.efs: Creating...
  creation_token:   "" => "wordpress-assets"
  performance_mode: "" => "<computed>"
  reference_name:   "" => "<computed>"
aws_ecr_repository.ecr: Creating...
  arn:            "" => "<computed>"
  name:           "" => "wordpress_ael"
  registry_id:    "" => "<computed>"
  repository_url: "" => "<computed>"
aws_iam_role.ecs_role: Creating...
  arn:                "" => "<computed>"
  assume_role_policy: "" => "{\n  \"Version\": \"2008-10-17\", \n  \"Statement\": [\n    {\n      \"Action\": \"sts:AssumeRole\", \n      \"Principal\": {\n        \"Service\": \"ec2.amazonaws.com\"\n      }, \n      \"Effect\": \"Allow\"\n    }\n  ]\n}\n"
  create_date:        "" => "<computed>"
  name:               "" => "ecs_role"
  path:               "" => "/"
  unique_id:          "" => "<computed>"
aws_vpc.vpc_wordpress: Creating...
  assign_generated_ipv6_cidr_block: "" => "false"
  cidr_block:                       "" => "10.0.0.0/16"
  default_network_acl_id:           "" => "<computed>"
  default_route_table_id:           "" => "<computed>"
  default_security_group_id:        "" => "<computed>"
  dhcp_options_id:                  "" => "<computed>"
  enable_classiclink:               "" => "<computed>"
  enable_dns_hostnames:             "" => "true"
  enable_dns_support:               "" => "true"
  instance_tenancy:                 "" => "<computed>"
  ipv6_association_id:              "" => "<computed>"
  ipv6_cidr_block:                  "" => "<computed>"
  main_route_table_id:              "" => "<computed>"
aws_ecs_cluster.ecs: Creating...
  name: "" => "wordpress-cluster"
aws_eip.natgw_ip_zoneA: Creating...
  allocation_id:     "" => "<computed>"
  association_id:    "" => "<computed>"
  domain:            "" => "<computed>"
  instance:          "" => "<computed>"
  network_interface: "" => "<computed>"
  private_ip:        "" => "<computed>"
  public_ip:         "" => "<computed>"
  vpc:               "" => "true"
aws_iam_role.ecs_role: Creation complete (ID: ecs_role)
aws_iam_instance_profile.ecs: Creating...
  arn:             "" => "<computed>"
  create_date:     "" => "<computed>"
  name:            "" => "ecs-instance-profile"
  path:            "" => "/"
  role:            "" => "<computed>"
  roles.#:         "" => "1"
  roles.112154061: "" => "ecs_role"
  unique_id:       "" => "<computed>"
aws_iam_role_policy_attachment.ecs_instance_role_policy: Creating...
  policy_arn: "" => "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
  role:       "" => "ecs_role"
aws_iam_instance_profile.ecs: Creation complete (ID: ecs-instance-profile)
aws_iam_role_policy_attachment.ecs_instance_role_policy: Creation complete (ID: ecs_role-002bb127d71652ab58b16bcc73)
aws_ecr_repository.ecr: Creation complete (ID: wordpress_ael)
data.template_file.wordpress_task: Refreshing state...
aws_ecs_task_definition.ecs: Creating...
  arn:                         "" => "<computed>"
  container_definitions:       "" => "97cbbcbe417f1f5b08da011ea450a68a672d535c"
  family:                      "" => "wordpress"
  network_mode:                "" => "<computed>"
  revision:                    "" => "<computed>"
  volume.#:                    "" => "1"
  volume.3601277827.host_path: "" => "/mnt/wordpress"
  volume.3601277827.name:      "" => "nfs-storage"
aws_eip.natgw_ip_zoneA: Creation complete (ID: eipalloc-07485d33b30046b43)
aws_eip.natgw_ip_zoneB: Creation complete (ID: eipalloc-09c10532268b467b4)
aws_ecs_cluster.ecs: Creation complete (ID: arn:aws:ecs:us-west-2:824846414599:cluster/wordpress-cluster)
data.template_file.ec2_userdata: Refreshing state...
aws_ecs_task_definition.ecs: Creation complete (ID: wordpress)
aws_ecs_service.ecs: Creating...
  cluster:                            "" => "arn:aws:ecs:us-west-2:824846414599:cluster/wordpress-cluster"
  deployment_maximum_percent:         "" => "200"
  deployment_minimum_healthy_percent: "" => "100"
  desired_count:                      "" => "1"
  name:                               "" => "wordpress-service"
  task_definition:                    "" => "wordpress"
aws_ecs_service.ecs: Creation complete (ID: arn:aws:ecs:us-west-2:824846414599:service/wordpress-service)
aws_vpc.vpc_wordpress: Creation complete (ID: vpc-0dc8766851d848dab)
aws_internet_gateway.igw: Creating...
  vpc_id: "" => "vpc-0dc8766851d848dab"
aws_subnet.public_subnet_zoneA: Creating...
  assign_ipv6_address_on_creation: "" => "false"
  availability_zone:               "" => "us-west-2a"
  cidr_block:                      "" => "10.0.0.0/24"
  ipv6_cidr_block:                 "" => "<computed>"
  ipv6_cidr_block_association_id:  "" => "<computed>"
  map_public_ip_on_launch:         "" => "false"
  vpc_id:                          "" => "vpc-0dc8766851d848dab"
aws_subnet.public_subnet_zoneB: Creating...
  assign_ipv6_address_on_creation: "" => "false"
  availability_zone:               "" => "us-west-2b"
  cidr_block:                      "" => "10.0.1.0/24"
  ipv6_cidr_block:                 "" => "<computed>"
  ipv6_cidr_block_association_id:  "" => "<computed>"
  map_public_ip_on_launch:         "" => "false"
  vpc_id:                          "" => "vpc-0dc8766851d848dab"
aws_route53_zone.wordpress_ael: Creating...
  comment:        "" => "Managed by Terraform"
  force_destroy:  "" => "false"
  name:           "" => "wordpress.ael."
  name_servers.#: "" => "<computed>"
  vpc_id:         "" => "vpc-0dc8766851d848dab"
  vpc_region:     "" => "<computed>"
  zone_id:        "" => "<computed>"
aws_subnet.private_subnet_zoneB: Creating...
  assign_ipv6_address_on_creation: "" => "false"
  availability_zone:               "" => "us-west-2b"
  cidr_block:                      "" => "10.0.11.0/24"
  ipv6_cidr_block:                 "" => "<computed>"
  ipv6_cidr_block_association_id:  "" => "<computed>"
  map_public_ip_on_launch:         "" => "false"
  vpc_id:                          "" => "vpc-0dc8766851d848dab"
aws_security_group.ecs: Creating...
  description:                           "" => "Allow http port for wordpress containers"
  egress.#:                              "" => "<computed>"
  ingress.#:                             "" => "1"
  ingress.2214680975.cidr_blocks.#:      "" => "1"
  ingress.2214680975.cidr_blocks.0:      "" => "0.0.0.0/0"
  ingress.2214680975.from_port:          "" => "80"
  ingress.2214680975.ipv6_cidr_blocks.#: "" => "0"
  ingress.2214680975.protocol:           "" => "tcp"
  ingress.2214680975.security_groups.#:  "" => "0"
  ingress.2214680975.self:               "" => "false"
  ingress.2214680975.to_port:            "" => "80"
  name:                                  "" => "http"
  owner_id:                              "" => "<computed>"
  vpc_id:                                "" => "vpc-0dc8766851d848dab"
aws_subnet.private_subnet_zoneA: Creating...
  assign_ipv6_address_on_creation: "" => "false"
  availability_zone:               "" => "us-west-2a"
  cidr_block:                      "" => "10.0.10.0/24"
  ipv6_cidr_block:                 "" => "<computed>"
  ipv6_cidr_block_association_id:  "" => "<computed>"
  map_public_ip_on_launch:         "" => "false"
  vpc_id:                          "" => "vpc-0dc8766851d848dab"
aws_subnet.private_subnet_zoneB: Creation complete (ID: subnet-007fbbbf34deefcd0)
aws_subnet.public_subnet_zoneA: Creation complete (ID: subnet-03f7ba09d2bcb9127)
aws_subnet.private_subnet_zoneA: Creation complete (ID: subnet-00f701d4eb5adb14a)
aws_nat_gateway.natgw_zoneA: Creating...
  allocation_id:        "" => "eipalloc-07485d33b30046b43"
  network_interface_id: "" => "<computed>"
  private_ip:           "" => "<computed>"
  public_ip:            "" => "<computed>"
  subnet_id:            "" => "subnet-03f7ba09d2bcb9127"
aws_subnet.public_subnet_zoneB: Creation complete (ID: subnet-0fbb10ab2ddad10a8)
aws_security_group.elb: Creating...
  description:                          "" => "Allow http from elb to ecs instances"
  egress.#:                             "" => "1"
  egress.3705613968.cidr_blocks.#:      "" => "2"
  egress.3705613968.cidr_blocks.0:      "" => "10.0.10.0/24"
  egress.3705613968.cidr_blocks.1:      "" => "10.0.11.0/24"
  egress.3705613968.from_port:          "" => "80"
  egress.3705613968.ipv6_cidr_blocks.#: "" => "0"
  egress.3705613968.prefix_list_ids.#:  "" => "0"
  egress.3705613968.protocol:           "" => "tcp"
  egress.3705613968.security_groups.#:  "" => "0"
  egress.3705613968.self:               "" => "false"
  egress.3705613968.to_port:            "" => "80"
  ingress.#:                            "" => "<computed>"
  name:                                 "" => "http-egress"
  owner_id:                             "" => "<computed>"
  vpc_id:                               "" => "vpc-0dc8766851d848dab"
aws_db_subnet_group.rds: Creating...
  arn:                   "" => "<computed>"
  description:           "" => "Managed by Terraform"
  name:                  "" => "subnet_group"
  name_prefix:           "" => "<computed>"
  subnet_ids.#:          "" => "2"
  subnet_ids.2684848430: "" => "subnet-007fbbbf34deefcd0"
  subnet_ids.2916683848: "" => "subnet-00f701d4eb5adb14a"
aws_security_group.ec2_egress: Creating...
  description:                          "" => "Every needed rules for the ec2 instances (nfs, mysql, http/S for yum install)"
  egress.#:                             "" => "4"
  egress.2214680975.cidr_blocks.#:      "" => "1"
  egress.2214680975.cidr_blocks.0:      "" => "0.0.0.0/0"
  egress.2214680975.from_port:          "" => "80"
  egress.2214680975.ipv6_cidr_blocks.#: "" => "0"
  egress.2214680975.prefix_list_ids.#:  "" => "0"
  egress.2214680975.protocol:           "" => "tcp"
  egress.2214680975.security_groups.#:  "" => "0"
  egress.2214680975.self:               "" => "false"
  egress.2214680975.to_port:            "" => "80"
  egress.2418635525.cidr_blocks.#:      "" => "2"
  egress.2418635525.cidr_blocks.0:      "" => "10.0.10.0/24"
  egress.2418635525.cidr_blocks.1:      "" => "10.0.11.0/24"
  egress.2418635525.from_port:          "" => "2049"
  egress.2418635525.ipv6_cidr_blocks.#: "" => "0"
  egress.2418635525.prefix_list_ids.#:  "" => "0"
  egress.2418635525.protocol:           "" => "tcp"
  egress.2418635525.security_groups.#:  "" => "0"
  egress.2418635525.self:               "" => "false"
  egress.2418635525.to_port:            "" => "2049"
  egress.2617001939.cidr_blocks.#:      "" => "1"
  egress.2617001939.cidr_blocks.0:      "" => "0.0.0.0/0"
  egress.2617001939.from_port:          "" => "443"
  egress.2617001939.ipv6_cidr_blocks.#: "" => "0"
  egress.2617001939.prefix_list_ids.#:  "" => "0"
  egress.2617001939.protocol:           "" => "tcp"
  egress.2617001939.security_groups.#:  "" => "0"
  egress.2617001939.self:               "" => "false"
  egress.2617001939.to_port:            "" => "443"
  egress.2705982278.cidr_blocks.#:      "" => "2"
  egress.2705982278.cidr_blocks.0:      "" => "10.0.10.0/24"
  egress.2705982278.cidr_blocks.1:      "" => "10.0.11.0/24"
  egress.2705982278.from_port:          "" => "3306"
  egress.2705982278.ipv6_cidr_blocks.#: "" => "0"
  egress.2705982278.prefix_list_ids.#:  "" => "0"
  egress.2705982278.protocol:           "" => "tcp"
  egress.2705982278.security_groups.#:  "" => "0"
  egress.2705982278.self:               "" => "false"
  egress.2705982278.to_port:            "" => "3306"
  ingress.#:                            "" => "<computed>"
  name:                                 "" => "ec2_egress"
  owner_id:                             "" => "<computed>"
  vpc_id:                               "" => "vpc-0dc8766851d848dab"
aws_security_group.efs: Creating...
  description:                           "" => "Allow nfs port"
  egress.#:                              "" => "<computed>"
  ingress.#:                             "" => "1"
  ingress.2418635525.cidr_blocks.#:      "" => "2"
  ingress.2418635525.cidr_blocks.0:      "" => "10.0.10.0/24"
  ingress.2418635525.cidr_blocks.1:      "" => "10.0.11.0/24"
  ingress.2418635525.from_port:          "" => "2049"
  ingress.2418635525.ipv6_cidr_blocks.#: "" => "0"
  ingress.2418635525.protocol:           "" => "tcp"
  ingress.2418635525.security_groups.#:  "" => "0"
  ingress.2418635525.self:               "" => "false"
  ingress.2418635525.to_port:            "" => "2049"
  name:                                  "" => "nfs"
  owner_id:                              "" => "<computed>"
  vpc_id:                                "" => "vpc-0dc8766851d848dab"
aws_security_group.rds: Creating...
  description:                           "" => "Allow mysql port"
  egress.#:                              "" => "<computed>"
  ingress.#:                             "" => "1"
  ingress.2705982278.cidr_blocks.#:      "" => "2"
  ingress.2705982278.cidr_blocks.0:      "" => "10.0.10.0/24"
  ingress.2705982278.cidr_blocks.1:      "" => "10.0.11.0/24"
  ingress.2705982278.from_port:          "" => "3306"
  ingress.2705982278.ipv6_cidr_blocks.#: "" => "0"
  ingress.2705982278.protocol:           "" => "tcp"
  ingress.2705982278.security_groups.#:  "" => "0"
  ingress.2705982278.self:               "" => "false"
  ingress.2705982278.to_port:            "" => "3306"
  name:                                  "" => "mysql"
  owner_id:                              "" => "<computed>"
  vpc_id:                                "" => "vpc-0dc8766851d848dab"
aws_internet_gateway.igw: Creation complete (ID: igw-0f1fe38bc616b8a02)
aws_nat_gateway.natgw_zoneB: Creating...
  allocation_id:        "" => "eipalloc-09c10532268b467b4"
  network_interface_id: "" => "<computed>"
  private_ip:           "" => "<computed>"
  public_ip:            "" => "<computed>"
  subnet_id:            "" => "subnet-0fbb10ab2ddad10a8"
aws_efs_file_system.efs: Creation complete (ID: fs-43e591ea)
aws_route_table.public: Creating...
  route.#:                                   "" => "1"
  route.284702497.cidr_block:                "" => "0.0.0.0/0"
  route.284702497.egress_only_gateway_id:    "" => ""
  route.284702497.gateway_id:                "" => "igw-0f1fe38bc616b8a02"
  route.284702497.instance_id:               "" => ""
  route.284702497.ipv6_cidr_block:           "" => ""
  route.284702497.nat_gateway_id:            "" => ""
  route.284702497.network_interface_id:      "" => ""
  route.284702497.vpc_peering_connection_id: "" => ""
  vpc_id:                                    "" => "vpc-0dc8766851d848dab"
aws_db_subnet_group.rds: Creation complete (ID: subnet_group)
aws_security_group.ecs: Creation complete (ID: sg-00365c79ffd108583)
aws_route_table.public: Creation complete (ID: rtb-0884aa071111b2c44)
aws_route_table_association.public_zoneA: Creating...
  route_table_id: "" => "rtb-0884aa071111b2c44"
  subnet_id:      "" => "subnet-03f7ba09d2bcb9127"
aws_route_table_association.public_zoneB: Creating...
  route_table_id: "" => "rtb-0884aa071111b2c44"
  subnet_id:      "" => "subnet-0fbb10ab2ddad10a8"
aws_route_table_association.public_zoneB: Creation complete (ID: rtbassoc-0f5aa8436843c492c)
aws_route_table_association.public_zoneA: Creation complete (ID: rtbassoc-0bbeba0a4b49c0112)
aws_security_group.ec2_egress: Creation complete (ID: sg-0c5d1254c3cad9928)
aws_launch_configuration.ec2: Creating...
  associate_public_ip_address: "" => "false"
  ebs_block_device.#:          "" => "<computed>"
  ebs_optimized:               "" => "<computed>"
  enable_monitoring:           "" => "true"
  iam_instance_profile:        "" => "ecs-instance-profile"
  image_id:                    "" => "ami-022b9262"
  instance_type:               "" => "t2.micro"
  key_name:                    "" => "<computed>"
  name:                        "" => "ecs-configuration"
  root_block_device.#:         "" => "<computed>"
  security_groups.#:           "" => "2"
  security_groups.2702182474:  "" => "sg-0c5d1254c3cad9928"
  security_groups.3785074950:  "" => "sg-00365c79ffd108583"
  user_data:                   "" => "9630257743c6c41d34fdceae5dd30d4668e9f802"
aws_security_group.efs: Creation complete (ID: sg-03fd23f12285fa30c)
aws_efs_mount_target.efs_private_zoneB: Creating...
  dns_name:                   "" => "<computed>"
  file_system_id:             "" => "fs-43e591ea"
  ip_address:                 "" => "<computed>"
  network_interface_id:       "" => "<computed>"
  security_groups.#:          "" => "1"
  security_groups.2140710823: "" => "sg-03fd23f12285fa30c"
  subnet_id:                  "" => "subnet-007fbbbf34deefcd0"
aws_efs_mount_target.efs_private_zoneA: Creating...
  dns_name:                   "" => "<computed>"
  file_system_id:             "" => "fs-43e591ea"
  ip_address:                 "" => "<computed>"
  network_interface_id:       "" => "<computed>"
  security_groups.#:          "" => "1"
  security_groups.2140710823: "" => "sg-03fd23f12285fa30c"
  subnet_id:                  "" => "subnet-00f701d4eb5adb14a"
aws_security_group.elb: Creation complete (ID: sg-0ee3c4b36cc3d73f9)
aws_elb.ec2: Creating...
  availability_zones.#:                   "" => "<computed>"
  connection_draining:                    "" => "false"
  connection_draining_timeout:            "" => "300"
  cross_zone_load_balancing:              "" => "true"
  dns_name:                               "" => "<computed>"
  health_check.#:                         "" => "1"
  health_check.0.healthy_threshold:       "" => "2"
  health_check.0.interval:                "" => "30"
  health_check.0.target:                  "" => "HTTP:80/wp-admin/install.php"
  health_check.0.timeout:                 "" => "3"
  health_check.0.unhealthy_threshold:     "" => "2"
  idle_timeout:                           "" => "60"
  instances.#:                            "" => "<computed>"
  internal:                               "" => "<computed>"
  listener.#:                             "" => "1"
  listener.3057123346.instance_port:      "" => "80"
  listener.3057123346.instance_protocol:  "" => "http"
  listener.3057123346.lb_port:            "" => "80"
  listener.3057123346.lb_protocol:        "" => "http"
  listener.3057123346.ssl_certificate_id: "" => ""
  name:                                   "" => "wordpress-elb"
  security_groups.#:                      "" => "2"
  security_groups.1666258149:             "" => "sg-0ee3c4b36cc3d73f9"
  security_groups.3785074950:             "" => "sg-00365c79ffd108583"
  source_security_group:                  "" => "<computed>"
  source_security_group_id:               "" => "<computed>"
  subnets.#:                              "" => "2"
  subnets.1121773161:                     "" => "subnet-03f7ba09d2bcb9127"
  subnets.3293901821:                     "" => "subnet-0fbb10ab2ddad10a8"
  zone_id:                                "" => "<computed>"
aws_security_group.rds: Creation complete (ID: sg-06a9b028185bf1aab)
aws_db_instance.rds: Creating...
  address:                           "" => "<computed>"
  allocated_storage:                 "" => "5"
  apply_immediately:                 "" => "<computed>"
  arn:                               "" => "<computed>"
  auto_minor_version_upgrade:        "" => "true"
  availability_zone:                 "" => "<computed>"
  backup_retention_period:           "" => "<computed>"
  backup_window:                     "" => "<computed>"
  character_set_name:                "" => "<computed>"
  copy_tags_to_snapshot:             "" => "false"
  db_subnet_group_name:              "" => "subnet_group"
  endpoint:                          "" => "<computed>"
  engine:                            "" => "mysql"
  engine_version:                    "" => "5.6.27"
  hosted_zone_id:                    "" => "<computed>"
  identifier:                        "" => "<computed>"
  identifier_prefix:                 "" => "<computed>"
  instance_class:                    "" => "db.t2.micro"
  kms_key_id:                        "" => "<computed>"
  license_model:                     "" => "<computed>"
  maintenance_window:                "" => "<computed>"
  monitoring_interval:               "" => "0"
  monitoring_role_arn:               "" => "<computed>"
  multi_az:                          "" => "<computed>"
  name:                              "" => "wordpress"
  option_group_name:                 "" => "<computed>"
  parameter_group_name:              "" => "<computed>"
  password:                          "<sensitive>" => "<sensitive>"
  port:                              "" => "<computed>"
  publicly_accessible:               "" => "false"
  replicas.#:                        "" => "<computed>"
  resource_id:                       "" => "<computed>"
  skip_final_snapshot:               "" => "false"
  status:                            "" => "<computed>"
  storage_type:                      "" => "<computed>"
  timezone:                          "" => "<computed>"
  username:                          "" => "wp"
  vpc_security_group_ids.#:          "" => "1"
  vpc_security_group_ids.3973349117: "" => "sg-06a9b028185bf1aab"
aws_launch_configuration.ec2: Creation complete (ID: ecs-configuration)
aws_route53_zone.wordpress_ael: Still creating... (10s elapsed)
aws_elb.ec2: Creation complete (ID: wordpress-elb)
aws_nat_gateway.natgw_zoneA: Still creating... (10s elapsed)
aws_nat_gateway.natgw_zoneB: Still creating... (10s elapsed)
aws_efs_mount_target.efs_private_zoneB: Still creating... (10s elapsed)
aws_efs_mount_target.efs_private_zoneA: Still creating... (10s elapsed)
aws_db_instance.rds: Still creating... (10s elapsed)
aws_route53_zone.wordpress_ael: Still creating... (20s elapsed)
aws_nat_gateway.natgw_zoneA: Still creating... (20s elapsed)
aws_nat_gateway.natgw_zoneB: Still creating... (20s elapsed)
aws_efs_mount_target.efs_private_zoneB: Still creating... (20s elapsed)
aws_efs_mount_target.efs_private_zoneA: Still creating... (20s elapsed)
aws_db_instance.rds: Still creating... (20s elapsed)
aws_route53_zone.wordpress_ael: Still creating... (30s elapsed)
aws_nat_gateway.natgw_zoneA: Still creating... (30s elapsed)
aws_nat_gateway.natgw_zoneB: Still creating... (30s elapsed)
aws_efs_mount_target.efs_private_zoneB: Still creating... (30s elapsed)
aws_efs_mount_target.efs_private_zoneA: Still creating... (30s elapsed)
aws_db_instance.rds: Still creating... (30s elapsed)
aws_route53_zone.wordpress_ael: Still creating... (40s elapsed)
aws_nat_gateway.natgw_zoneA: Still creating... (40s elapsed)
aws_nat_gateway.natgw_zoneB: Still creating... (40s elapsed)
aws_efs_mount_target.efs_private_zoneB: Still creating... (40s elapsed)
aws_efs_mount_target.efs_private_zoneA: Still creating... (40s elapsed)
aws_db_instance.rds: Still creating... (40s elapsed)
aws_route53_zone.wordpress_ael: Still creating... (50s elapsed)
aws_nat_gateway.natgw_zoneA: Still creating... (50s elapsed)
aws_nat_gateway.natgw_zoneB: Still creating... (50s elapsed)
aws_efs_mount_target.efs_private_zoneB: Still creating... (50s elapsed)
aws_efs_mount_target.efs_private_zoneA: Still creating... (50s elapsed)
aws_db_instance.rds: Still creating... (50s elapsed)
aws_route53_zone.wordpress_ael: Still creating... (1m0s elapsed)
aws_nat_gateway.natgw_zoneA: Still creating... (1m0s elapsed)
aws_nat_gateway.natgw_zoneB: Still creating... (1m0s elapsed)
aws_efs_mount_target.efs_private_zoneB: Still creating... (1m0s elapsed)
aws_efs_mount_target.efs_private_zoneA: Still creating... (1m0s elapsed)
aws_db_instance.rds: Still creating... (1m0s elapsed)
aws_route53_zone.wordpress_ael: Creation complete (ID: Z1OUVW1SXQYCFO)
aws_nat_gateway.natgw_zoneA: Still creating... (1m10s elapsed)
aws_nat_gateway.natgw_zoneB: Still creating... (1m10s elapsed)
aws_efs_mount_target.efs_private_zoneB: Still creating... (1m10s elapsed)
aws_efs_mount_target.efs_private_zoneA: Still creating... (1m10s elapsed)
aws_db_instance.rds: Still creating... (1m10s elapsed)
aws_nat_gateway.natgw_zoneA: Still creating... (1m20s elapsed)
aws_nat_gateway.natgw_zoneB: Still creating... (1m20s elapsed)
aws_efs_mount_target.efs_private_zoneB: Still creating... (1m20s elapsed)
aws_efs_mount_target.efs_private_zoneA: Still creating... (1m20s elapsed)
aws_db_instance.rds: Still creating... (1m20s elapsed)
aws_nat_gateway.natgw_zoneA: Still creating... (1m30s elapsed)
aws_nat_gateway.natgw_zoneB: Still creating... (1m30s elapsed)
aws_efs_mount_target.efs_private_zoneB: Still creating... (1m30s elapsed)
aws_efs_mount_target.efs_private_zoneA: Still creating... (1m30s elapsed)
aws_db_instance.rds: Still creating... (1m30s elapsed)
aws_nat_gateway.natgw_zoneA: Still creating... (1m40s elapsed)
aws_nat_gateway.natgw_zoneB: Still creating... (1m40s elapsed)
aws_efs_mount_target.efs_private_zoneB: Still creating... (1m40s elapsed)
aws_efs_mount_target.efs_private_zoneA: Still creating... (1m40s elapsed)
aws_db_instance.rds: Still creating... (1m40s elapsed)
aws_nat_gateway.natgw_zoneA: Still creating... (1m50s elapsed)
aws_nat_gateway.natgw_zoneB: Still creating... (1m50s elapsed)
aws_nat_gateway.natgw_zoneB: Creation complete (ID: nat-0d65bcf10d71ffe67)
aws_route_table.private_zoneB: Creating...
  route.#:                                    "" => "1"
  route.2463091106.cidr_block:                "" => "0.0.0.0/0"
  route.2463091106.egress_only_gateway_id:    "" => ""
  route.2463091106.gateway_id:                "" => "nat-0d65bcf10d71ffe67"
  route.2463091106.instance_id:               "" => ""
  route.2463091106.ipv6_cidr_block:           "" => ""
  route.2463091106.nat_gateway_id:            "" => ""
  route.2463091106.network_interface_id:      "" => ""
  route.2463091106.vpc_peering_connection_id: "" => ""
  vpc_id:                                     "" => "vpc-0dc8766851d848dab"
aws_nat_gateway.natgw_zoneA: Creation complete (ID: nat-013230dc005201969)
aws_route_table.private_zoneA: Creating...
  route.#:                                    "" => "1"
  route.2627915174.cidr_block:                "" => "0.0.0.0/0"
  route.2627915174.egress_only_gateway_id:    "" => ""
  route.2627915174.gateway_id:                "" => "nat-013230dc005201969"
  route.2627915174.instance_id:               "" => ""
  route.2627915174.ipv6_cidr_block:           "" => ""
  route.2627915174.nat_gateway_id:            "" => ""
  route.2627915174.network_interface_id:      "" => ""
  route.2627915174.vpc_peering_connection_id: "" => ""
  vpc_id:                                     "" => "vpc-0dc8766851d848dab"
aws_autoscaling_group.ec2: Creating...
  arn:                            "" => "<computed>"
  availability_zones.#:           "" => "<computed>"
  default_cooldown:               "" => "<computed>"
  desired_capacity:               "" => "1"
  force_delete:                   "" => "false"
  health_check_grace_period:      "" => "300"
  health_check_type:              "" => "<computed>"
  launch_configuration:           "" => "ecs-configuration"
  load_balancers.#:               "" => "1"
  load_balancers.1851460066:      "" => "wordpress-elb"
  max_size:                       "" => "3"
  metrics_granularity:            "" => "1Minute"
  min_size:                       "" => "1"
  name:                           "" => "ecs-autoscale"
  protect_from_scale_in:          "" => "false"
  target_group_arns.#:            "" => "<computed>"
  vpc_zone_identifier.#:          "" => "2"
  vpc_zone_identifier.2684848430: "" => "subnet-007fbbbf34deefcd0"
  vpc_zone_identifier.2916683848: "" => "subnet-00f701d4eb5adb14a"
  wait_for_capacity_timeout:      "" => "10m"
aws_route_table.private_zoneB: Creation complete (ID: rtb-080c73dfcd2e3180d)
aws_route_table_association.private_zoneB: Creating...
  route_table_id: "" => "rtb-080c73dfcd2e3180d"
  subnet_id:      "" => "subnet-007fbbbf34deefcd0"
aws_route_table_association.private_zoneB: Creation complete (ID: rtbassoc-039b8e4b7feac3bc8)
aws_efs_mount_target.efs_private_zoneB: Still creating... (1m50s elapsed)
aws_efs_mount_target.efs_private_zoneA: Still creating... (1m50s elapsed)
aws_db_instance.rds: Still creating... (1m50s elapsed)
aws_route_table.private_zoneA: Creation complete (ID: rtb-035a74380195ad160)
aws_route_table_association.private_zoneA: Creating...
  route_table_id: "" => "rtb-035a74380195ad160"
  subnet_id:      "" => "subnet-00f701d4eb5adb14a"
aws_route_table_association.private_zoneA: Creation complete (ID: rtbassoc-0d2893388c8168e1d)
aws_autoscaling_group.ec2: Still creating... (10s elapsed)
aws_efs_mount_target.efs_private_zoneB: Still creating... (2m0s elapsed)
aws_efs_mount_target.efs_private_zoneA: Still creating... (2m0s elapsed)
aws_db_instance.rds: Still creating... (2m0s elapsed)
aws_autoscaling_group.ec2: Still creating... (20s elapsed)
aws_efs_mount_target.efs_private_zoneB: Still creating... (2m10s elapsed)
aws_efs_mount_target.efs_private_zoneA: Still creating... (2m10s elapsed)
aws_db_instance.rds: Still creating... (2m10s elapsed)
aws_autoscaling_group.ec2: Still creating... (30s elapsed)
aws_efs_mount_target.efs_private_zoneB: Still creating... (2m20s elapsed)
aws_efs_mount_target.efs_private_zoneA: Still creating... (2m20s elapsed)
aws_db_instance.rds: Still creating... (2m20s elapsed)
aws_autoscaling_group.ec2: Still creating... (40s elapsed)
aws_efs_mount_target.efs_private_zoneB: Still creating... (2m30s elapsed)
aws_efs_mount_target.efs_private_zoneA: Still creating... (2m30s elapsed)
aws_db_instance.rds: Still creating... (2m30s elapsed)
aws_autoscaling_group.ec2: Still creating... (50s elapsed)
aws_efs_mount_target.efs_private_zoneA: Creation complete (ID: fsmt-0b5a29a2)
aws_route53_record.nfs_wordpress_ael: Creating...
  fqdn:              "" => "<computed>"
  name:              "" => "nfs.wordpress.ael"
  records.#:         "" => "1"
  records.919881341: "" => "fs-43e591ea.efs.us-west-2.amazonaws.com"
  ttl:               "" => "300"
  type:              "" => "CNAME"
  zone_id:           "" => "Z1OUVW1SXQYCFO"
aws_efs_mount_target.efs_private_zoneB: Creation complete (ID: fsmt-0a5a29a3)
aws_db_instance.rds: Still creating... (2m40s elapsed)
aws_autoscaling_group.ec2: Still creating... (1m0s elapsed)
aws_route53_record.nfs_wordpress_ael: Still creating... (10s elapsed)
aws_db_instance.rds: Still creating... (2m50s elapsed)
aws_autoscaling_group.ec2: Still creating... (1m10s elapsed)
aws_db_instance.rds: Creation complete (ID: terraform-002bb127d71652ab58b16bcc75)
aws_route53_record.db_wordpress_ael: Creating...
  fqdn:               "" => "<computed>"
  name:               "" => "db.wordpress.ael"
  records.#:          "" => "1"
  records.3748033750: "" => "terraform-002bb127d71652ab58b16bcc75.cbkk7dkn9qy1.us-west-2.rds.amazonaws.com"
  ttl:                "" => "300"
  type:               "" => "CNAME"
  zone_id:            "" => "Z1OUVW1SXQYCFO"
aws_route53_record.nfs_wordpress_ael: Still creating... (20s elapsed)
aws_autoscaling_group.ec2: Still creating... (1m20s elapsed)
aws_route53_record.db_wordpress_ael: Still creating... (10s elapsed)
aws_route53_record.nfs_wordpress_ael: Still creating... (30s elapsed)
aws_autoscaling_group.ec2: Still creating... (1m30s elapsed)
aws_route53_record.db_wordpress_ael: Still creating... (20s elapsed)
aws_route53_record.nfs_wordpress_ael: Still creating... (40s elapsed)
aws_autoscaling_group.ec2: Still creating... (1m40s elapsed)
aws_route53_record.db_wordpress_ael: Still creating... (30s elapsed)
aws_route53_record.nfs_wordpress_ael: Still creating... (50s elapsed)
aws_autoscaling_group.ec2: Still creating... (1m50s elapsed)
aws_route53_record.db_wordpress_ael: Still creating... (40s elapsed)
aws_route53_record.nfs_wordpress_ael: Still creating... (1m0s elapsed)
aws_route53_record.nfs_wordpress_ael: Creation complete (ID: Z1OUVW1SXQYCFO_nfs.wordpress.ael_CNAME)
aws_autoscaling_group.ec2: Still creating... (2m0s elapsed)
aws_route53_record.db_wordpress_ael: Still creating... (50s elapsed)
aws_autoscaling_group.ec2: Still creating... (2m10s elapsed)
aws_autoscaling_group.ec2: Creation complete (ID: ecs-autoscale)
aws_route53_record.db_wordpress_ael: Still creating... (1m0s elapsed)
aws_route53_record.db_wordpress_ael: Creation complete (ID: Z1OUVW1SXQYCFO_db.wordpress.ael_CNAME)

Apply complete! Resources: 40 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path:

Outputs:

ecr_repository = 824846414599.dkr.ecr.us-west-2.amazonaws.com/wordpress_ael
elb_dns = wordpress-elb-611979141.us-west-2.elb.amazonaws.com

Build and push our WordPress container to ECR

cd packer
packer build wordpress-packer.json

Output

[ec2-user@ip-172-31-16-136 packer]$ packer.io build wordpress-packer.json
docker output will be in this color.

==> docker: Creating a temporary directory for sharing data...
==> docker: Pulling Docker image: wordpress:4.7
    docker: 4.7: Pulling from library/wordpress
    docker: Digest: sha256:64d2ad26106b464b0d722185341cf242036a8d46af62a95de24ebc6dc566e2db
    docker: Status: Image is up to date for wordpress:4.7
==> docker: Starting docker container...
    docker: Run command: docker run -v /home/ec2-user/.packer.d/tmp/packer-docker428692306:/packer-files -d -i -t wordpress:4.7 /bin/bash
    docker: Container ID: e5694afc42c60d1ddd7e84d7d09030bc2f350ccff00d047f88f39642248d7baa
==> docker: Provisioning with shell script: /tmp/packer-shell724420956
    docker: Get:1 http://security.debian.org jessie/updates InRelease [44.9 kB]
    docker: Get:2 http://security.debian.org jessie/updates/main amd64 Packages [592 kB]
    docker: Ign http://deb.debian.org jessie InRelease
    docker: Get:3 http://deb.debian.org jessie-updates InRelease [145 kB]
    docker: Get:4 http://deb.debian.org jessie Release.gpg [2420 B]
    docker: Get:5 http://deb.debian.org jessie-updates/main amd64 Packages [23.0 kB]
    docker: Get:6 http://deb.debian.org jessie Release [148 kB]
    docker: Get:7 http://deb.debian.org jessie/main amd64 Packages [9098 kB]
    docker: Fetched 10.1 MB in 1s (6251 kB/s)
    docker: Reading package lists...
    docker: Reading package lists...
    docker: Building dependency tree...
    docker: Reading state information...
    docker: The following extra packages will be installed:
    docker: libpython2.7-minimal libpython2.7-stdlib python2.7-minimal
    docker: Suggested packages:
    docker: python2.7-doc binfmt-support
    docker: The following NEW packages will be installed:
    docker: libpython2.7-minimal libpython2.7-stdlib python2.7 python2.7-minimal
    docker: 0 upgraded, 4 newly installed, 0 to remove and 70 not upgraded.
    docker: Need to get 3878 kB of archives.
    docker: After this operation, 15.6 MB of additional disk space will be used.
    docker: Get:1 http://deb.debian.org/debian/ jessie/main libpython2.7-minimal amd64 2.7.9-2+deb8u1 [378 kB]
    docker: Get:2 http://deb.debian.org/debian/ jessie/main python2.7-minimal amd64 2.7.9-2+deb8u1 [1401 kB]
    docker: Get:3 http://deb.debian.org/debian/ jessie/main libpython2.7-stdlib amd64 2.7.9-2+deb8u1 [1847 kB]
    docker: Get:4 http://deb.debian.org/debian/ jessie/main python2.7 amd64 2.7.9-2+deb8u1 [252 kB]
    docker: debconf: delaying package configuration, since apt-utils is not installed
    docker: Fetched 3878 kB in 0s (26.9 MB/s)
    docker: Selecting previously unselected package libpython2.7-minimal:amd64.
    docker: (Reading database ... 13654 files and directories currently installed.)
    docker: Preparing to unpack .../libpython2.7-minimal_2.7.9-2+deb8u1_amd64.deb ...
    docker: Unpacking libpython2.7-minimal:amd64 (2.7.9-2+deb8u1) ...
    docker: Selecting previously unselected package python2.7-minimal.
    docker: Preparing to unpack .../python2.7-minimal_2.7.9-2+deb8u1_amd64.deb ...
    docker: Unpacking python2.7-minimal (2.7.9-2+deb8u1) ...
    docker: Selecting previously unselected package libpython2.7-stdlib:amd64.
    docker: Preparing to unpack .../libpython2.7-stdlib_2.7.9-2+deb8u1_amd64.deb ...
    docker: Unpacking libpython2.7-stdlib:amd64 (2.7.9-2+deb8u1) ...
    docker: Selecting previously unselected package python2.7.
    docker: Preparing to unpack .../python2.7_2.7.9-2+deb8u1_amd64.deb ...
    docker: Unpacking python2.7 (2.7.9-2+deb8u1) ...
    docker: Processing triggers for mime-support (3.58) ...
    docker: Setting up libpython2.7-minimal:amd64 (2.7.9-2+deb8u1) ...
    docker: Setting up python2.7-minimal (2.7.9-2+deb8u1) ...
    docker: Linking and byte-compiling packages for runtime python2.7...
    docker: Setting up libpython2.7-stdlib:amd64 (2.7.9-2+deb8u1) ...
    docker: Setting up python2.7 (2.7.9-2+deb8u1) ...
==> docker: Provisioning with Ansible...
    docker:
    docker: PLAY [Customize wordpress container] *******************************************
    docker:
    docker: TASK [Gathering Facts] *********************************************************
    docker: ok: [default]
    docker:
    docker: TASK [shell] *******************************************************************
    docker: changed: [default]
    docker:
    docker: PLAY RECAP *********************************************************************
    docker: default                    : ok=2    changed=1    unreachable=0    failed=0
    docker:
==> docker: Committing the container
    docker: Image ID: sha256:42c78acb1e0e2750463f64eedf160dd4c62311694e930fa1c8a9cd1f688d3803
==> docker: Killing the container: e5694afc42c60d1ddd7e84d7d09030bc2f350ccff00d047f88f39642248d7baa
==> docker: Running post-processor: docker-tag
    docker (docker-tag): Tagging image: sha256:42c78acb1e0e2750463f64eedf160dd4c62311694e930fa1c8a9cd1f688d3803
    docker (docker-tag): Repository: /wordpress_ael:1.0

output

Source Code

aws.tf

provider "aws" {        
    region = "ap-south-1"
}

ec2.tf

resource "aws_launch_configuration" "ec2" {
  name                 = "ecs-configuration"
  instance_type        = "t2.micro"
  image_id             = "ami-022b9262"
  security_groups      = ["${aws_security_group.ecs.id}", "${aws_security_group.ec2_egress.id}"]
  iam_instance_profile = "${aws_iam_instance_profile.ecs.name}"
  user_data            = "${data.template_file.ec2_userdata.rendered}"
}

# We need this 'depend_on' line otherwise we may not be able to reach internet at first terraform apply command
resource "aws_autoscaling_group" "ec2" {
  depends_on           = ["aws_nat_gateway.natgw_zoneA", "aws_nat_gateway.natgw_zoneB"]
  name                 = "ecs-autoscale"
  vpc_zone_identifier  = ["${aws_subnet.private_subnet_zoneA.id}", "${aws_subnet.private_subnet_zoneB.id}"]
  launch_configuration = "${aws_launch_configuration.ec2.name}"
  min_size             = 1
  max_size             = 3
  desired_capacity     = 1
  load_balancers       = ["${aws_elb.ec2.name}"]
}

resource "aws_elb" "ec2" {
  name               = "wordpress-elb"
  security_groups    = ["${aws_security_group.ecs.id}", "${aws_security_group.elb.id}"]
  subnets            = ["${aws_subnet.public_subnet_zoneA.id}", "${aws_subnet.public_subnet_zoneB.id}"]
  listener {
    instance_port     = 80
    instance_protocol = "http"
    lb_port           = 80
    lb_protocol       = "http"
  }
  health_check {
    healthy_threshold   = 2
    unhealthy_threshold = 2
    timeout             = 3
    target              = "HTTP:80/wp-admin/install.php"
    interval            = 30
  }
}

data "template_file" "ec2_userdata" {
  template = "${file("userdata.sh")}"
  vars {
    ecs_cluster = "${aws_ecs_cluster.ecs.name}"
    nfs_fqdn    = "${var.nfs_fqdn}"
  }
}

ecr.tf

resource "aws_ecr_repository" "ecr" {
  name = "wordpress_ael"
}

ecs.tf

resource "aws_ecs_cluster" "ecs" {
  name = "wordpress-cluster"
}

resource "aws_ecs_service" "ecs" {
  name = "wordpress-service"
  cluster = "${aws_ecs_cluster.ecs.id}"
  desired_count = 1
  task_definition = "${aws_ecs_task_definition.ecs.family}"
}

resource "aws_ecs_task_definition" "ecs" {
  family = "wordpress"
  container_definitions = "${data.template_file.wordpress_task.rendered}"
  volume {
    name = "nfs-storage"
    host_path = "/mnt/wordpress"
  }
}

data "template_file" "wordpress_task" {
  template = "${file("wordpress_task.json")}"
  vars {
    repository_url = "${aws_ecr_repository.ecr.repository_url}"
  }
}

ecs-role.json

{
  "Version": "2008-10-17", 
  "Statement": [
    {
      "Action": "sts:AssumeRole", 
      "Principal": {
        "Service": "ec2.amazonaws.com"
      }, 
      "Effect": "Allow"
    }
  ]
}

efs.tf

resource "aws_efs_file_system" "efs" {
  creation_token = "wordpress-assets"
}

resource "aws_efs_mount_target" "efs_private_zoneA" {
  file_system_id  = "${aws_efs_file_system.efs.id}"
  security_groups = ["${aws_security_group.efs.id}"]
  subnet_id       = "${aws_subnet.private_subnet_zoneA.id}"
}

resource "aws_efs_mount_target" "efs_private_zoneB" {
  file_system_id  = "${aws_efs_file_system.efs.id}"
  security_groups = ["${aws_security_group.efs.id}"]
  subnet_id       = "${aws_subnet.private_subnet_zoneB.id}"
}

iam.tf

resource "aws_iam_role" "ecs_role" {
  name               = "ecs_role"
  assume_role_policy = "${file("ecs-role.json")}"
}

resource "aws_iam_role_policy_attachment" "ecs_instance_role_policy" {
  policy_arn    = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role"
  role          = "${aws_iam_role.ecs_role.id}"
}

resource "aws_iam_instance_profile" "ecs" {
  name = "ecs-instance-profile"
  path = "/"
  roles = ["${aws_iam_role.ecs_role.name}"]
}

output.tf

output "ecr_repository" {
  value = "${aws_ecr_repository.ecr.repository_url}"
}

output "elb_dns" {
  value = "${aws_elb.ec2.dns_name}"
}

rds.tf

resource "aws_db_instance" "rds" {
  allocated_storage    = 5
  engine               = "mysql"
  engine_version       = "5.6.27"
  instance_class       = "db.t2.micro"
  name                 = "wordpress"
  username             = "wp"
  password             = "myverystrongpassword"
  db_subnet_group_name = "${aws_db_subnet_group.rds.name}"
  vpc_security_group_ids   = ["${aws_security_group.rds.id}"]
}

resource "aws_db_subnet_group" "rds" {
  name       = "subnet_group"
  subnet_ids = ["${aws_subnet.private_subnet_zoneA.id}", "${aws_subnet.private_subnet_zoneB.id}"]
}

route53.tf

resource "aws_route53_zone" "wordpress_ael" {
  name          = "wordpress.ael."
  vpc_id        = "${aws_vpc.vpc_wordpress.id}"
}

resource "aws_route53_record" "db_wordpress_ael" {
    zone_id = "${aws_route53_zone.wordpress_ael.zone_id}"
    name    = "${var.db_fqdn}"
    type    = "CNAME"
    ttl     = "300"
    records = [
        "${aws_db_instance.rds.address}"
    ]
}

# zoneA and zoneB should have same dns name (?)
resource "aws_route53_record" "nfs_wordpress_ael" {
    zone_id = "${aws_route53_zone.wordpress_ael.zone_id}"
    name    = "${var.nfs_fqdn}"
    type    = "CNAME"
    ttl     = "300"
    records = [
        "${aws_efs_mount_target.efs_private_zoneA.dns_name}"
    ]
}

variable "db_fqdn" {
  default = "db.wordpress.ael"
}

variable "nfs_fqdn" {
  default = "nfs.wordpress.ael"
}

security group.tf

resource "aws_security_group" "ecs" {
  name = "http"
  vpc_id      = "${aws_vpc.vpc_wordpress.id}"
  description = "Allow http port for wordpress containers"
  ingress {
    from_port = 80
    to_port   = 80
    protocol  = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_security_group" "ec2_egress" {
  name = "ec2_egress"
  vpc_id      = "${aws_vpc.vpc_wordpress.id}"
  description = "Every needed rules for the ec2 instances (nfs, mysql, http/S for yum install)"
  egress {
    from_port = 80
    to_port   = 80
    protocol  = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port = 443
    to_port   = 443
    protocol  = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port = 2049
    to_port   = 2049
    protocol  = "tcp"
    cidr_blocks = ["${aws_subnet.private_subnet_zoneA.cidr_block}", "${aws_subnet.private_subnet_zoneB.cidr_block}"]
  }
  egress {
    from_port = 3306
    to_port   = 3306
    protocol  = "tcp"
    cidr_blocks = ["${aws_subnet.private_subnet_zoneA.cidr_block}", "${aws_subnet.private_subnet_zoneB.cidr_block}"]
  }
}

resource "aws_security_group" "elb" {
  name = "http-egress"
  vpc_id      = "${aws_vpc.vpc_wordpress.id}"
  description = "Allow http from elb to ecs instances"
  egress {
    from_port = 80
    to_port   = 80
    protocol  = "tcp"
    cidr_blocks = ["${aws_subnet.private_subnet_zoneA.cidr_block}", "${aws_subnet.private_subnet_zoneB.cidr_block}"]
  }
}

resource "aws_security_group" "rds" {
  name        = "mysql"
  vpc_id      = "${aws_vpc.vpc_wordpress.id}"
  description = "Allow mysql port"
  ingress {
    from_port = 3306
    to_port   = 3306
    protocol  = "tcp"
    cidr_blocks = ["${aws_subnet.private_subnet_zoneA.cidr_block}", "${aws_subnet.private_subnet_zoneB.cidr_block}"]
  }
}

resource "aws_security_group" "efs" {
  name              = "nfs"
  vpc_id            = "${aws_vpc.vpc_wordpress.id}"
  description       = "Allow nfs port"
  ingress {
    from_port       = 2049
    to_port         = 2049
    protocol        = "tcp"
    cidr_blocks     = ["${aws_subnet.private_subnet_zoneA.cidr_block}", "${aws_subnet.private_subnet_zoneB.cidr_block}"]
  }
}

subnet.tf

resource "aws_subnet" "public_subnet_zoneA" {
  vpc_id = "${aws_vpc.vpc_wordpress.id}"
  availability_zone = "ap-south-1a"
  cidr_block = "10.0.0.0/24"
}

resource "aws_subnet" "public_subnet_zoneB" {
  vpc_id = "${aws_vpc.vpc_wordpress.id}"
  availability_zone = "ap-south-1b"
  cidr_block = "10.0.1.0/24"
}

resource "aws_subnet" "private_subnet_zoneA" {
  vpc_id = "${aws_vpc.vpc_wordpress.id}"
  availability_zone = "ap-south-1a"
  cidr_block = "10.0.10.0/24"
}

resource "aws_subnet" "private_subnet_zoneB" {
  vpc_id = "${aws_vpc.vpc_wordpress.id}"
  availability_zone = "ap-south-1b"
  cidr_block = "10.0.11.0/24"
}

user data.sh

#!/bin/bash
echo ECS_CLUSTER=${ecs_cluster} > /etc/ecs/ecs.config
yum install -y nfs-utils
echo "nfs.wordpress.ael:/ /mnt/ nfs4  defaults  0   0" >> /etc/fstab
mount -a
service docker restart

vpc.tf

resource "aws_vpc" "vpc_wordpress" {
  enable_dns_hostnames = true
  cidr_block = "10.0.0.0/16"
}

resource "aws_internet_gateway" "igw" {
  vpc_id = "${aws_vpc.vpc_wordpress.id}"
}

resource "aws_eip" "natgw_ip_zoneA" {
  vpc      = true
}

resource "aws_eip" "natgw_ip_zoneB" {
  vpc      = true
}

resource "aws_nat_gateway" "natgw_zoneA" {
  allocation_id = "${aws_eip.natgw_ip_zoneA.id}"
  subnet_id = "${aws_subnet.public_subnet_zoneA.id}"
}

resource "aws_nat_gateway" "natgw_zoneB" {
  allocation_id = "${aws_eip.natgw_ip_zoneB.id}"
  subnet_id = "${aws_subnet.public_subnet_zoneB.id}"
}

resource "aws_route_table" "public" {
  vpc_id = "${aws_vpc.vpc_wordpress.id}"
  route {
      cidr_block = "0.0.0.0/0"
      gateway_id = "${aws_internet_gateway.igw.id}"
  }
}

resource "aws_route_table" "private_zoneA" {
  vpc_id = "${aws_vpc.vpc_wordpress.id}"
  route {
      cidr_block = "0.0.0.0/0"
      gateway_id = "${aws_nat_gateway.natgw_zoneA.id}"
  }
}

resource "aws_route_table" "private_zoneB" {
  vpc_id = "${aws_vpc.vpc_wordpress.id}"
  route {
      cidr_block = "0.0.0.0/0"
      gateway_id = "${aws_nat_gateway.natgw_zoneB.id}"
  }
}

resource "aws_route_table_association" "public_zoneA" {
  subnet_id = "${aws_subnet.public_subnet_zoneA.id}"
  route_table_id = "${aws_route_table.public.id}"
}

resource "aws_route_table_association" "public_zoneB" {
  subnet_id = "${aws_subnet.public_subnet_zoneB.id}"
  route_table_id = "${aws_route_table.public.id}"
}

resource "aws_route_table_association" "private_zoneA" {
  subnet_id = "${aws_subnet.private_subnet_zoneA.id}"
  route_table_id = "${aws_route_table.private_zoneA.id}"
}

resource "aws_route_table_association" "private_zoneB" {
  subnet_id = "${aws_subnet.private_subnet_zoneB.id}"
  route_table_id = "${aws_route_table.private_zoneB.id}"
}

wordpress_task.json

[
  {
    "environment": [{
      "name": "WORDPRESS_DB_USER",
      "value": "wp"
    },
    {
      "name": "WORDPRESS_DB_PASSWORD",
      "value": "myverystrongpassword"
    },
    {
      "name": "WORDPRESS_DB_NAME",
      "value": "wordpress"
    },
    {
      "name": "WORDPRESS_DB_HOST",
      "value": "db.wordpress.ael"
    }
    ],
    "memory": 800,
    "cpu": 1024,
    "image": "${repository_url}:1.0",
    "name": "wordpress",
    "command": ["apache2-foreground"],
    "mountPoints": [
      {
        "ContainerPath": "/var/www/html/",
        "SourceVolume": "nfs-storage"
      }
    ],
    "portMappings": [
      {
        "hostPort": 80,
        "containerPort": 80,
        "protocol": "tcp"
      }
    ]
  }
]

playbook.yml

name: Customize wordpress container
  hosts: all

  tasks:
    - shell: "echo we can do what we want here!"

terraform.tfstate

{
    "version": 3,
    "terraform_version": "0.11.7",
    "serial": 1,
    "lineage": "0d34841c-75d5-406f-583c-0dbc56a517a9",
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {},
            "depends_on": []
        }
    ]
}

wordpress-packer.json

{
  "variables": { 
    "aws_access_key_id": "{{env `AWS_ACCESS_KEY_ID`}}", 
    "aws_secret_access_key": "{{env `AWS_SECRET_ACCESS_KEY`}}",
    "ecr_repository": "{{env `ECR_REPOSITORY`}}"
  }, 
  "builders": [
    {
      "type": "docker",
      "image": "wordpress:4.7",
      "commit": true,
      "run_command": []
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "inline": ["apt-get update", "apt-get install -y python2.7", "ln -s /usr/bin/python2.7 /usr/bin/python"]
    },
    {
      "type": "ansible",
      "playbook_file": "playbook.yml"
    }
  ],
  "post-processors": [
    [
      {
        "type": "docker-tag",
        "repository": "{{user `ecr_repository`}}/wordpress_ael",
        "tag": "1.0"
      },
      {
        "type": "docker-push",
        "ecr_login": true,
        "aws_access_key": "{{user `aws_access_key_id`}}",
        "aws_secret_key": "{{user `aws_secret_access_key`}}",
        "login_server": "https://{{user `ecr_repository`}}/"
      }
    ]
  ]
}

To improve

For production deployments, the following should be implemented:
• extract logs from WordPress containers (push to elasticsearch/cloudwatch logs…)
• increase instance capacity (t2.micro currently)
• increase DB size, monitor remaining space and make backups (5GB at the moment)
• set up CDN to serve static content (AWS one, clouflare, MaxCDN…)
• set up Cloudwatch alarms on the ASG so we can really autoscale
• customize WordPress image for performance (use nginx, php fpm, tweak perf parameters…)

Main Logo
Rocket