Skip to Main Content

Creating a Fargate ECS Task in AWS using Terraform

This article explains how to run containers with the Fargate technology used in ECS. To carry out this task, Terraform’s infrastructure software is used.

Containers are easily managed using the Amazon Elastic Container Service (ECS) provided by AWS. This tool makes containers faster and more scalable, facilitating the process of running, stopping, and managing them in a cluster. Fargate launch type is a specific ECS technology that enables clusters in a serverless infrastructure. For more control, a different type of launch is required, such as the Amazon EC2 or External launch types. 

What is Terraform and What are the Basic Files for Task Launching

Terraform is an open source software that allows the creation of an infrastructure’s construction plan via another programming language. With this technology, infrastructure can be expressed as code (Infrastructure as Code). Infrastructure management becomes simple through the use of a basic and unified syntax. A great advantage of working with Terraform is the reusability of implemented configurations that can also be shared across various projects.

Before explaining how to create and define the Fargate Launch Type ECS Task, the necessary files for launching the task are mentioned below. An explanation of the task execution and launch is provided later in the article.

First, Terraform’s Provider file is created with the name provider.tf. This file is used to specify a provider configuration (the syntax below demonstrates a provider block) and start AWS or another provider in the project:

provider "aws" {
  version = ">= 1.58.0, <= 2.0.0"
  region = var.aws_region
  access_key = var.aws_access_key
  secret_key = var.aws_secret_key
} 

Note: the version argument shown in the above example is deprecated; this means that it is no longer recommended to be used in provider configurations.

Instead, for Terraform 0.13 and above, the version of the provider is mentioned in the required_providers block. The required_providers block describes the provider requirements or the list of providers that Terraform must download and use within a module. In the example below, the source refers to the location of the provider within the chosen Terraform registry (in our case, Hashicorp), and the version constraint ensures that Terraform only uses the provider version or range of versions that are compatible with a module. The following example specifies as compatible all versions of the AWS provider starting from 4.0.0. Accordingly, 4.0.0 is the minimum provider version that would work agreeably with the module: 

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 4.0.0"
    }
  }
}

After this, a variable definitions file is required. The file’s name will be variables.tf and will define both the authentication variables and the ones that the application needs:

variable "aws_access_key" {} 
variable "aws_secret_key" {} 
variable "aws_region" {}

Then, the values of the above-defined variables will be added to another file named terraform.tfvars. Terraform will automatically load this file:

aws_access_key = "aws-access-key" 
aws_secret_key = "aws-secret-key" 
aws_region = "eu-west-1"

The next step is the creation of all necessary network components: VPC, subnets, and the cluster where our ECS task will be defined. For the sake of brevity, it is assumed that all subnets are public. The file name is network.tf:

resource "aws_ecs_cluster" "cluster" {
  name = "cluster-name"
}
resource "aws_vpc" "aws-vpc" { 
  cidr_block = "10.0.0.0/16" 
  enable_dns_hostnames = true
}

Lastly, defining policies and roles based on the requirements of our task will be necessary to launch the ECS task correctly. This code will be added to a file named iam.tf with the defined roles and policies needed in our task:

resource "aws_iam_role" "ecs_task_execution_role" {
  name = "role-name"
 
  assume_role_policy = <<EOF
{
 "Version": "2012-10-17",
 "Statement": [
   {
     "Action": "sts:AssumeRole",
     "Principal": {
       "Service": "ecs-tasks.amazonaws.com"
     },
     "Effect": "Allow",
     "Sid": ""
   }
 ]
}
EOF
}

resource "aws_iam_role" "ecs_task_role" {
  name = "role-name-task"
 
  assume_role_policy = <<EOF
{
 "Version": "2012-10-17",
 "Statement": [
   {
     "Action": "sts:AssumeRole",
     "Principal": {
       "Service": "ecs-tasks.amazonaws.com"
     },
     "Effect": "Allow",
     "Sid": ""
   }
 ]
}
EOF
}
 
resource "aws_iam_role_policy_attachment" "ecs-task-execution-role-policy-attachment" {
  role       = aws_iam_role.ecs_task_execution_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

resource "aws_iam_role_policy_attachment" "task_s3" {
  role       = "${aws_iam_role.ecs_task_role.name}"
  policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
}

Creating a Fargate Launch Type ECS Task 

AWS Fargate is a serverless compute engine that works with both ECS and Amazon Elastic Kubernetes Service (EKS). This technology removes the need to equip and manage servers. This means that we only use the resources needed by the application, which in turn improves security through application isolation.

AWS Fargate isn’t available in all regions. To work with it, verify its availability in your working region. Check the AWS documentation for more information regarding this matter.  This article uses the European (Ireland) region as an example: eu-west-1.

AWS

With Terraform, the ECS task definition will be implemented in order to run Docker containers:

resource "aws_ecs_task_definition" "definition" {}

The task definition of an ECS task uses a series of parameters. While some are mandatory, others are optional but useful in this case:

  • family: a mandatory string-type parameter. This parameter is the name of the task definition to which AWS will also assign a revision number.
  • taskRoleArn: an optional and string type parameter. To define a task with this parameter, an IAM role is provided which enables the containers to have the required permissions and then activate other AWS services.
  • executionRoleArn: an optional and string type parameter. A task execution role can be provided through this parameter to enable containers to  extract images and publish logs on CloudWatch on its behalf. 
  • networkMode: string type parameter which is not required. This parameter is the Docker network mode that is going to be used on this task’s containers.  There are different types, and in this case we will use awsvpc. With this type, an elastic network interface is assigned. 
  • cpu: integer type parameter which is not required. The number of cpu units that Amazon ECS will reserve for the container is defined via this parameter.
  • memory: an optional, integer type parameter. It defines the amount (in MiB) of memory that will be reserved for the container. 

To specify the type of launch that will be used in defining the task, the following parameter will be used:

  • requiresCompatibilities: it will be called FARGATE in this case. It is an array type of string parameter which is not required. Through this, it is possible to guarantee that all the used parameters in its definition are meeting their launch type requirements.
resource "aws_ecs_task_definition" "definition" {
  family                   = "task_definition_name"
  task_role_arn            = "${var.ecs_task_role}"
  execution_role_arn       = "${var.ecs_task_execution_role}"
  network_mode             = "awsvpc"
  cpu                      = "256"
  memory                   = "1024"
  requires_compatibilities = ["FARGATE"]
  }

Once all the optional requirements and parameters to be used are defined, we then define the containers through which our task will be executed. It is a compulsory parameter type and a list type provided with a single JSON document. 

A series of permitted parameters will be specified in the container definition. The two most important, mandatory definitions are: 

  • name: a string type parameter. It is the name of a container that supports up to 255 characters.
  • image: a string type parameter. It will define the image that is used to start the container. In our case, the URL of a previously uploaded Amazon Elastic Container Registry (ECR) repository image and its version will be specified.

The image used by the container is created from a Docker file. This image is stored in the docker container registry provided by Amazon through the ECR.

docker

Other optional but very useful parameters for any container definition is:

  • logConfiguration: an object type Log Configuration parameter. In order to configure the container registration, we have to take into account that for Fargate-type launch tasks, additional software has to be installed outside the task.  In our case, the registry controller awslogs will be used.
  • environment: an object array type parameter. Through this parameter we transfer the environment variables to our container.
  • secrets: an object array type parameter: name and valueFrom. The secrets parameter together with the valueFrom allows the transfer of a value to the container provided with a full ARN. It then allows us to make use of AWS Secrets Manager in our example.

Here is a complete definition of a container as a JSON document:

container_definitions = <<DEFINITION
[
  {
    "image": "${var.account}.dkr.ecr.eu-west-1.amazonaws.com/project:latest",
    "name": "project-container",
    "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-region" : "eu-west-1",
                    "awslogs-group" : "stream-to-log-fluentd",
                    "awslogs-stream-prefix" : "project"
                }
            },
    "secrets": [{
        "name": "secret_variable_name",
        "valueFrom": "arn:aws:ssm:region:acount:parameter/parameter_name"
    }],           
    "environment": [
            {
                "name": "bucketName",
                "value": "${var.bucket_name}"
            },
            {
                "name": "folder",
                "value": "${var.folder}"
            }
        ]
    }
  
]
DEFINITION
}

Terraform Execution and Launch of the ECS Task

In order to launch the ECS task, follow these steps:

  1. We position ourselves where our code is located, then run the terraform init command from the terminal: 
  2. The terraform plan command can then be executed to obtain the execution plan as a result.  This is very useful to verify if it meets your expectations without making any changes.
  3. To conclude, we run the terraform apply command on the command line, thus building the entire infrastructure:

Conclusion

As we have seen, thanks to AWS Fargate together with the power of Terraform, an application inside a container can be lifted, managed, and launched very quickly and easily. This is just an example of one of the many benefits provided by AWS.

 References

Reach out to Chakray if you need support throughout your integration project, whether that be choosing technologies like Terraform for your organisation’s needs. Feel free to connect with us today for specialised insights on how to plan your integration strategy from start to finish, without a hitch in sight.

Talk to our experts!

Contact our team and discover the cutting-edge technologies that will empower your business.

contact us