Skip to main content

AWS Elastic Container Service (ECS) - Create Example Task with the AWS CLI and Access the Container

1304 words·
AWS AWS CLI Elastic Container Service (ECS) Elastic Container Registry CloudWatch Docker
Table of Contents

Prerequisites
#

Security Group
#

Find Default VPC
#

# Find default Subnet
aws ec2 describe-vpcs \
    --filters Name=isDefault,Values=true \
    --region us-east-1 \
    --query "Vpcs[0].VpcId" --output text

# Shell output:
vpc-01c63f56a39efd950

List Security Groups
#

# List security groups
aws ec2 describe-security-groups \
    --filters Name=vpc-id,Values=vpc-01c63f56a39efd950 \
    --region us-east-1 \
    --query "SecurityGroups[?GroupName=='default'].[GroupId,GroupName]" --output table

# Shell output:
-------------------------------------
|      DescribeSecurityGroups       |
+-----------------------+-----------+
|  sg-0de79f0cab5634038 |  default  |
+-----------------------+-----------+

Describe Default Security Group
#

# List security group details
aws ec2 describe-security-groups \
    --group-ids sg-0de79f0cab5634038 \
    --region us-east-1

# Shell output:
{
    "SecurityGroups": [
        {
            "GroupId": "sg-0de79f0cab5634038",
            "IpPermissionsEgress": [
                {
                    "IpProtocol": "-1",
                    "UserIdGroupPairs": [],
                    "IpRanges": [
                        {
                            "CidrIp": "0.0.0.0/0"
                        }
                    ],
                    "Ipv6Ranges": [],
                    "PrefixListIds": []
                }
            ],
            "VpcId": "vpc-01c63f56a39efd950",
            "SecurityGroupArn": "arn:aws:ec2:us-east-1:012345678912:security-group/sg-0de79f0cab5634038",
            "OwnerId": "012345678912",
            "GroupName": "default",
            "Description": "default VPC security group",
            "IpPermissions": [
                {
                    "IpProtocol": "-1",
                    "UserIdGroupPairs": [
                        {
                            "UserId": "012345678912",
                            "GroupId": "sg-0de79f0cab5634038"
                        }
                    ],
                    "IpRanges": [],
                    "Ipv6Ranges": [],
                    "PrefixListIds": []
                }
            ]
        }
    ]
}

Add Inbound Rule
#

# Allow inbound traffic on port "80" / all sources
aws ec2 authorize-security-group-ingress \
    --group-id sg-0de79f0cab5634038 \
    --protocol tcp \
    --port 80 \
    --cidr 0.0.0.0/0 \
    --region us-east-1

Verify the Inbound Rule
#

# Verify the inbound rule
aws ec2 describe-security-groups \
   --group-ids sg-0de79f0cab5634038 \
   --region us-east-1 \
   --query "SecurityGroups[].IpPermissions[*].{Protocol:IpProtocol,From:FromPort,To:ToPort,Source:IpRanges[].CidrIp}" \
   --output table

# Shell output:
------------------------------
|   DescribeSecurityGroups   |
+-------+-------------+------+
| From  |  Protocol   | To   |
+-------+-------------+------+
|  80   |  tcp        |  80  |
+-------+-------------+------+
||          Source          ||
|+--------------------------+|
||  0.0.0.0/0               ||
|+--------------------------+|
+------+------------+--------+
| From | Protocol   |  To    |
+------+------------+--------+
|  None|  -1        |  None  |
+------+------------+--------+



List Default Subnets
#

List the default subnets:

# List the default VPC in the "us-east-1" region
aws ec2 describe-vpcs --query "Vpcs[?IsDefault==\`true\`].VpcId" --output text --region us-east-1

# Shell output:
vpc-01c63f56a39efd950
# List default subnets
aws ec2 describe-subnets --filters Name=vpc-id,Values=vpc-01c63f56a39efd950 --query "Subnets[].SubnetId" --output text --region us-east-1

# Shell output:
subnet-0863f1a46bc384e59        subnet-0a68b63a736166ec5        subnet-018ce0a68d466f6b8        subnet-031bce85422909dd0        subnet-005aa1a09c535126c        subnet-08029536779973d55

Create CloudWatch Log Group
#

Create a log group in AWS CloudWatch logs:

  • A log group in AWS CloudWatch is a logical grouping of log streams.

  • Each ECS task or container generates logs, which are stored in individual log streams within the specified log group.

# Create a Cloudwatch log group
aws logs create-log-group \
    --log-group-name /ecs/example-task \
    --region us-east-1

IAM Role & Permissions
#

ECS Task Execution Role
#

  • The ecsTaskExecutionRole is required by ECS to pull the container images from the ECR
# Create the role "ecsTaskExecutionRole"
aws iam create-role \
    --role-name ecsTaskExecutionRole \
    --assume-role-policy-document '{
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "Service": "ecs-tasks.amazonaws.com"
          },
          "Action": "sts:AssumeRole"
        }
      ]
    }'

Attach Policy
#

Attach the managed AmazonECSTaskExecutionRolePolicy policy to the role:

# Attach policy to role
aws iam attach-role-policy \
    --role-name ecsTaskExecutionRole \
    --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

Verify the Role
#

# List role details
aws iam get-role --role-name ecsTaskExecutionRole

# Shell output:
{
    "Role": {
        "Path": "/",
        "RoleName": "ecsTaskExecutionRole",
        "RoleId": "AROARCHUALIN46RDYSU3J",
        "Arn": "arn:aws:iam::012345678912:role/ecsTaskExecutionRole",
        "CreateDate": "2024-12-12T17:27:12+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "ecs-tasks.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        },
        "MaxSessionDuration": 3600,
        "RoleLastUsed": {}
    }
}



AWS Elastic Container Registry (ECR)
#

Create ECR Repository
#

# Create a repository
aws ecr create-repository --repository-name example-application --region us-east-1

# Shell output:
{
    "repository": {
        "repositoryArn": "arn:aws:ecr:us-east-1:012345678912:repository/example-application",
        "registryId": "012345678912",
        "repositoryName": "example-application",
        "repositoryUri": "012345678912.dkr.ecr.us-east-1.amazonaws.com/example-application",
        "createdAt": "2024-12-12T16:10:00.189000+00:00",
        "imageTagMutability": "MUTABLE",
        "imageScanningConfiguration": {
            "scanOnPush": false
        },
        "encryptionConfiguration": {
            "encryptionType": "AES256"
        }
    }
}

Docker Login
#

Login the new registry repository:

# Retrieve an authentication token and authenticate the Docker client
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 012345678912.dkr.ecr.us-east-1.amazonaws.com/example-application

Push Image to ECR
#

Example Application
#

  • Dockerfile
# Use the official NGINX base image
FROM nginx:alpine

# Set working directory
WORKDIR /usr/share/nginx/html

# Remove default NGINX static files
RUN rm -rf ./*

# Copy your custom index.html file to the NGINX root directory
COPY index.html ./

# Expose port 80 for the web server
EXPOSE 80

# Start NGINX
CMD ["nginx", "-g", "daemon off;"]
  • index.html
<!DOCTYPE html>
<html>

<head>
    <title>jklug.work</title>
</head>

<body>
    <h1>Some HTML</h1>
    <p>Example Application<br></p>

</body>

</html>

Build the Image
#

# Build the image for the application
docker build -t example-application .

Tag the Image
#

# Tag the local Docker image to associate it with the ECR repository
docker tag example-application:latest 012345678912.dkr.ecr.us-east-1.amazonaws.com/example-application:latest
  • Docker images deed a fully qualified name to push

Verify the image:

# List Docker images
docker images

# Shell output:
REPOSITORY                                                         TAG       IMAGE ID       CREATED          SIZE
012345678912.dkr.ecr.us-east-1.amazonaws.com/example-application   latest    7044f8e660dc   32 seconds ago   11.8MB
example-application                                                latest    7044f8e660dc   32 seconds ago   11.8MB

Push the Image to ECR
#

# Push the image to ECR
docker push 012345678912.dkr.ecr.us-east-1.amazonaws.com/example-application:latest

Verify / List Image in ECR
#

# List container images in the ECR repository
aws ecr list-images --repository-name example-application --region us-east-1

# Shell output:
{
    "imageIds": [
        {
            "imageDigest": "sha256:a3589b2487adbbbc6a4d03a9c2cd6c90227d5b67dab7a6925c508fbd3cae5763",
            "imageTag": "latest"
        }
    ]
}



ECS Cluster
#

Create an ECS Cluster
#

  • Create a AWS ECS cluster with the default “Networking only” template, designed to use “AWS Fargate (serverless)”
# Create a AWS ECS cluster
aws ecs create-cluster --cluster-name example-cluster --region us-east-1 --no-cli-pager

# Shell output:
{
    "cluster": {
        "clusterArn": "arn:aws:ecs:us-east-1:012345678912:cluster/example-cluster",
        "clusterName": "example-cluster",
        "status": "ACTIVE",
        "registeredContainerInstancesCount": 0,
        "runningTasksCount": 0,
        "pendingTasksCount": 0,
        "activeServicesCount": 0,
        "statistics": [],
        "tags": [],
        "settings": [
            {
                "name": "containerInsights",
                "value": "disabled"
            }
        ],
        "capacityProviders": [],
        "defaultCapacityProviderStrategy": []
    }
}

Task Definition
#

Create a Task Definition
#

  • The task definition focuses on container configuration, like image, CPU, memory and ports.

  • Task definitions are not directly tied to a cluster when they are created, they are later referenced when deploying a task or service to a specific cluster.

# Create a configuration file for the task definition
vi task-definition.json
{
  "family": "example-application-task",
  "executionRoleArn": "arn:aws:iam::012345678912:role/ecsTaskExecutionRole",
  "networkMode": "awsvpc",
  "containerDefinitions": [
    {
      "name": "example-application",
      "image": "012345678912.dkr.ecr.us-east-1.amazonaws.com/example-application:latest",
      "memory": 512,
      "cpu": 256,
      "portMappings": [
        {
          "containerPort": 80,
          "hostPort": 80,
          "protocol": "tcp"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/example-task",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "example"
        }
      }
    }
  ],
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512"
}
# Register the task definition
aws ecs register-task-definition --cli-input-json file://task-definition.json --region us-east-1 --no-cli-pager

Verify / List Task Definition
#

# List ECS task definitions
aws ecs list-task-definitions --region us-east-1

# Shell output:
{
    "taskDefinitionArns": [
        "arn:aws:ecs:us-east-1:012345678912:task-definition/example-application-task:1"
    ]
}



Example Task
#

Create a Task
#

# Create the task
aws ecs run-task \
    --cluster example-cluster \
    --task-definition example-application-task \
    --launch-type FARGATE \
    --network-configuration "awsvpcConfiguration={subnets=[subnet-0863f1a46bc384e59],assignPublicIp=ENABLED}" \
    --region us-east-1

Verify the Task
#

# Verify the task
aws ecs list-tasks \
    --cluster example-cluster \
    --region us-east-1

# Shell output:
{
    "taskArns": [
        "arn:aws:ecs:us-east-1:012345678912:task/example-cluster/923b4662733f4118966f6889c7279d11"
    ]
}

Describe the Task / List ENI
#

# List task details
aws ecs describe-tasks \
    --cluster example-cluster \
    --tasks arn:aws:ecs:us-east-1:012345678912:task/example-cluster/923b4662733f4118966f6889c7279d11 \
    --region us-east-1 \
    --no-cli-pager

Copy the network interface (ENI) ID of the task, it should look like this:

"name": "networkInterfaceId",
"value": "eni-08b4927774642caab"

Get Public IP of the ENI
#

# List the network interface (ENI) ID of the task
aws ec2 describe-network-interfaces \
    --network-interface-ids eni-08b4927774642caab \
    --region us-east-1 \
    --query "NetworkInterfaces[0].Association.PublicIp" \
    --output text

# Shell output:
44.222.253.140

Curl Container via Public IP
#

# Curl the container
curl 44.222.253.140:80

# Shell output:
<!DOCTYPE html>
<html>

<head>
    <title>jklug.work</title>
</head>

<body>
    <h1>Some HTML</h1>
    <p>Example Application<br></p>

</body>

</html>

Delete the Task
#

# Delete the Task
aws ecs stop-task \
    --cluster example-cluster \
    --task arn:aws:ecs:us-east-1:012345678912:task/example-cluster/923b4662733f4118966f6889c7279d11 \
    --region us-east-1



Cleanup
#

Delete ECS Cluster
#

aws ecs delete-cluster \
    --cluster example-cluster \
    --region us-east-1

Delete Task Definition
#

List Task Definitions
#

# List ECS task definitions
aws ecs list-task-definitions --region us-east-1

# Shell output:
{
    "taskDefinitionArns": [
        "arn:aws:ecs:us-east-1:012345678912:task-definition/example-application-task:1"
    ]
}

Deregister Task Definition
#

# Deregister the task definition
aws ecs deregister-task-definition \
    --task-definition example-application-task:1 \
    --region us-east-1 \
    --no-cli-pager

Delete ECR Repository
#

# Delete the ECR repository
aws ecr delete-repository --repository-name example-application --region us-east-1 --force

Delete the CloudWatch Log Group
#

# Delete the log group
aws logs delete-log-group \
    --log-group-name /ecs/example-task \
    --region us-east-1