WordPress on ECS

WordPress on ECS

WordPress is used by over 60 million users, it has powerful features and runs various workloads across the planet. We wanted to run WordPress on ECS as efficiently as possible, this post covers how we attempted that. We’ll also dive into how to customise the example solution to fit your requirements.

Why ECS?

ECS is a highly scalable container orchestration service. It allows you to run and scale containerised applications on AWS. ECS natively integrates into other AWS services which allows us to fulfill most requirements with ease.

Terraform Module

There are of course multiple ways in which to create an ECS cluster and all of the associated resources. In order to make this process as easy as possible we have published a Terraform module. This allows full cluster creation whilst allowing configuration of all aspects with minimal lines of Terraform.

WordPress on ECS
ECS Cluster with an ASG Capacity Provider

In this example we create an ECS cluster with an auto scaling group for the capacity provider along with the role for the instances. We also create the WordPress task definition & associated service. The EFS volume will mount at /var/www/html in the containers running WordPress for persistent data storage.

module "wordpress" {
  source = "coresolutions-ltd/ecs/aws"

  name = "Wordpress"

  create_cluster                              = true
  create_services                            = true
  create_capacity_provider             = true
  create_capacity_provider_role     = true
  cp_instance_type                         = "t2.micro"
  cp_min_size                                 = 1
  cp_desired_capacity                    = 1
  cp_max_size                                = 1
  cp_security_group_ids                 = [aws_security_group.ecs_sg.id]
  cp_vpc_zone_identifier                = [data.aws_subnet.public_a.id]
  cp_associate_public_ip_address = true

  tasks = [{
    family                            = "wordpress"
    container_definitions     = file("wordpress.json")
    desired_count                = 1
    force_new_deployment  = true
    task_role_arn                 = aws_iam_role.ecs_task_role.arn

    volumes = [{
      name = "wordpress"
      efs_volume_configuration = {
        file_system_id        = aws_efs_file_system.efs_fs.id
        transit_encryption = "ENABLED"
      }
    }]
  }]
}

When dealing with secrets and sensitive information it’s recommended to store them in a secure key value store. We can then pull them down at runtime. We have stored our secrets in SSM in the same region as the ECS cluster. This allows us to access them by specifying the parameter name instead of the full ARN.

[{
  "name": "wordpress",
  "image": "wordpress",
  "memory": 256,
  "portMappings": [
      {
          "hostPort": 80,
          "containerPort": 80,
          "protocol": "tcp"
      }
  ],
  "essential": true,
  "mountPoints": [
    {
        "containerPath": "/var/www/html",
        "sourceVolume": "wordpress"
    }
  ],
  "secrets": [
    {
        "name": "WORDPRESS_DB_HOST",
        "valueFrom": "/wordpress/db_host"
    },
    {
        "name": "WORDPRESS_DB_USER",
        "valueFrom": "/wordpress/db_user"
    },
    {
        "name": "WORDPRESS_DB_PASSWORD",
        "valueFrom": "/wordpress/db_password"
    },
    {
        "name": "WORDPRESS_DB_NAME",
        "valueFrom": "/wordpress/db_name"
    }
]
}]

You’ll need to create a task policy to allow the task to get the SSM parameters.

A working example can be found here. You’ll just need to ensure the data blocks for the VPC and subnet IDs align with your AWS environment.

The above example creates the auto scaling group and capacity provider for ECS. If you’d prefer you can run WordPress on ECS using Fargate. An example of running tasks in Fargate can be found here. You could also split load across Fargate & Fargate spot or various ASG capacity providers using strategies.

Making the capacity provider run spot instances is simply a case of passing in two additional variables to the Terraform module. This tells the ASG to leverage spot instances and what to use as the max price for the given instance type.

cp_spot                   = true
cp_spot_max_price = 0.02

FPM & Nginx

If you’re using WordPress FPM you’ll need to use an accompanying reverse proxy. Environment variables are not applicable on when running WordPress on FPM. You could tweak the FPM configuration to allow parsing of environment variables but generally this isn’t recommended.

tasks = [{
    family                        = "nginx"
    container_definitions = file("nginx.json")
    desired_count            = 1
    network_mode          = "host"
  },
  {
    family                        = "wordpress"
    container_definitions = file("wordpress_fpm.json")
    desired_count            = 1
    network_mode           = "host"
  }]

The above example uses the host network mode. This bypasses dockers built in virtual network and maps container ports directly onto the EC2 host. This improves performance but does mean only a single instance of the container can be ran on a single host.

The above limitation does not apply to tasks that use the Fargate launch type. This is because each task that uses the awsvpc network mode with Fargate receives its own ENI.

SSL

When it comes to SSL you have various options to encrypt traffic heading towards your cluster. These of course may differ depending on your exact requirements. If load balancing you could simply terminate SSL on the load balancer. Ideally you would also install your SSL certificates directly on the WordPress containers. This can be achieved by either adding the certificates into a Docker image or by leveraging EFS to mount them in the correct directory.

You can specify different root directories in EFS to mount via the root_directory parameter in the efs_volume_configuration block.

volumes = [{
      name = "certs"
      efs_volume_configuration = {
        file_system_id     = aws_efs_file_system.efs_fs.id
        transit_encryption = "ENABLED"
        root_directory     = "certs"
      }
    },
    {
      name = "wordpress"
      efs_volume_configuration = {
        file_system_id     = aws_efs_file_system.efs_fs.id
        transit_encryption = "ENABLED"
        root_directory     = "wordpress"
      }
    }]

The contents of these directories are then mounted to the containerPath location specified in the container definitions JSON. This saves us creating new EFS volumes for only a handful of shared files. We then end up with an EFS volume per stack with various root directories for each required mount point.

Conclusion

Running WordPress on ECS gives us a huge amount of flexibility. The example above show how easy it is to get up and running. With a few adjustments you can create a production ready, highly available cluster running on Fargate or EC2 instances. If you have any existing WordPress workloads there is no reason not to look at migrating them to ECS.

 

No Comments

Add your comment