Setting up task-definition, service and task in ECS cluster with Terraform CDK

In the previous article of the terraform CDK series, we have registered an EC2 instance to the ECS cluster.

In this article, we are going to add task-definition, service and task for simple Nginx server. i.e. we are going to create one container and run Nginx docker image in the container in our already created ECS cluster.

We need the cluster arn (Amazon Resource Name) of the cluster we created in the previous article.

To run any application in the ECS container, firstly, we need to create a task-definition. Task-definition is nothing but a provided configuration of your task (like the requirement and the purpose of the task). It includes information like cpu and memory required by the task, what all containers you want to run in the cluster, and what all docker images are required to run those containers.

Register a task-definition:

Let's start with creating one task definition for nginx container. For this, we are using EcsTaskDefinition resource from the AWS provider.

Import the resource.

import { EcsTaskDefinition } from "./.gen/providers/aws/ecs";

Add a task definition.

new EcsTaskDefinition(this, "ecs-task-definition", {
      family: "test-nginx-task",
      memory: "128",
      cpu: "256",
      networkMode: "bridge",
      requiresCompatibilities: ["EC2"],
      executionRoleArn: "arn:aws:iam::XXXX:role/ecsTaskExecutionRole", // replace this with execution role you created
      taskRoleArn: "arn:aws:iam::XXXX:role/AWS_ECS_Custom_Role_S3", // replace this with task role you created
      containerDefinitions: Fn.jsonencode(
          [
              {
                  "name": "nginx",
                  "image": "nginx:latest",
                  "cpu": 0,
                  "memory": null,
                  "essential": true,
                  "portMappings": [
                      {
                          "hostPort": 0,
                          "protocol": "tcp",
                          "containerPort": 80
                      }
                  ]
              }
          ]
      )
    });

Here, we are creating a task-definition for our Nginx image to run in a container. And specified few attributes.

  • family - A unique name for your task definition. It is a required field.
  • memory - Amount of memory (in MiB) used by the task. The field is optional. If the requires_compatibilities is FARGATE this field is required.
  • cpu - Number of cpu units used by the task. The field is optional. If the requires_compatibilities is FARGATE this field is required.
  • networkMode - Docker networking mode to use for the containers in the task. Valid values are none, bridge, awsvpc, and host.
  • requiresCompatibilities - Set of launch types required by the task. This field is optional. The valid values are EC2 and FARGATE.
  • executionRoleArn - ARN of the task execution role that the Amazon ECS container agent and the Docker daemon can assume.
  • taskRoleArn - ARN of IAM role that allows your Amazon ECS container task to make calls to other AWS services.
  • containerDefinitions - A list of valid container definitions provided as a single valid JSON document.

Let's check, what fields the containerDefinition attribute support. The container definition JSON can be used directly from the file.

  • name - name of the container
  • image - image of the container. This can be docker hub image or the path to image from ECR
  • cpu - Number of cpu units allocated to the container.
  • memory - amount of memory required (in MiB) by the container.
  • essential - boolean parameter and is optional.  If the essential parameter of a container is marked as true, and that container fails or stops for any reason, all other containers that are part of the task are stopped. If the essential parameter of a container is marked as false, then its failure doesn't affect the rest of the containers in a task. If this parameter is omitted, a container is assumed to be essential.
  • portMappings - Port mappings allow containers to access ports on the host container instance to send or receive traffic. The mapping of host port to container port. For nginx, container it is 80:80 with port 80 exposed on the host.
📔
Note: we are using dynamic port mapping, hence, the host port is set to "0". Dynamic port mapping allows you to run multiple tasks over the same host using multiple random host ports (in spite of defined host port).

There are some advanced container definition parameters available for health check, environment, network setting, etc. You can find them here.

Create a service:

ECS service helps you to run and maintain a specified number of instances of a task definition simultaneously in an Amazon ECS cluster.

If any of your tasks should fail or stop for any reason, the Amazon ECS scheduler launches another instance of your task definition to replace it in order to maintain the desired number of tasks in the service. For more information on services, see Amazon ECS services.

Import service resource.

import { EcsService } from "./.gen/providers/aws/ecs";

Create service.

return new EcsService(this, "test-nginx-ecs-service", {
      name: "test-nginx-ecs-service",
      cluster: "arn:aws:XXXX", // replace this with the cluster arn
      taskDefinition: "arn:aws:XXXX", // replace this with the task-definition arn
      launchType: "EC2",
      desiredCount: 1,
      orderedPlacementStrategy: [
        {
          type: "spread",
          field: "instanceId"
        }
      ]
    });

To create a service, we need cluster and task-definition arn that we have created earlier. Don't forget to replace them in the code snippet when you copy the snippet.

  • name - Name of the service.
  • cluster - The short name or full Amazon Resource Name (ARN) of the cluster on which to run your service. If not specified default cluster is assumed.
  • taskDefinition - The family and revision (family:revision) or full Amazon Resource Name (ARN) of the task definition to run in your service. If a revision isn't specified, the latest ACTIVE revision of the specified family is used.
  • launchType - The launch type on which to run your service. If not specified, EC2 is used by default. Check this for more information on launchType. We are using "EC2" launch type.
  • desiredCount - The number of instantiations of the specified task definition to place and keep running on your cluster. This parameter is required if the REPLICA scheduling strategy is used. If the service uses the DAEMON scheduling strategy, this parameter is optional.
  • orderedPlacementStrategy - The placement strategy objects to use for tasks in your service. You can specify a maximum of four strategy rules per service. It has three valid values - random | spread | binpack

For more information about the service definition parameters, check this out.

Finally, deploy the changes using: cdktf deploy

This will create task-definition, a service and a task running in container.

So far, we have created cluster, an EC2 instance (ECS optimised), task-definition and the service for our task. In the next article, we'll attach an application load balancer to our task. So that, it'll be available publicly to consume.

Kiran Kamalakar

Kiran Kamalakar

Design and development enthusiast.
Pune, India