SaltyCrane Blog — Notes on JavaScript and web development

Amazon ECS notes

These are my notes for creating a Docker image, pushing it to Amazon ECR (Elastic Container Registry), and deploying it to Amazon ECS (Elastic Container Service) using AWS Fargate (serverless for containers) using command line tools.

Create docker image on local machine

  • install docker (macOS)

    brew install homebrew/cask/docker
    
  • create directory

    mkdir /tmp/my-project
    cd /tmp/my-project
    
  • create /tmp/my-project/Dockerfile:

    FROM python:3.9-alpine3.13
    WORKDIR /app
    RUN echo 'Hello' > ./index.html
    EXPOSE 80
    CMD ["python", "-m", "http.server", "80"]
    
  • create Docker image

    docker build -t my-image .
    
  • test running the Docker image locally

    docker run -p 80:80 my-image
    
  • go to http://localhost in the browser and see the text "Hello"

Install and configure AWS command line tools

  • install AWS command line tools

    brew install awscli
    
  • create an IAM user

  • run aws configure and enter:

    • AWS Access Key ID
    • AWS Secret Access Key

    This creates the file ~/.aws/credentials

Create ECR repository and push image to it

From https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html#use-ecr

  • create an Amazon ECR repository using aws ecr create-repository

    aws ecr create-repository --repository-name my-repository --region us-east-1
    

    output:

    {
        "repository": {
            "repositoryArn": "arn:aws:ecr:us-east-1:AAAAAAAAAAAA:repository/my-repository",
            "registryId": "AAAAAAAAAAAA",
            "repositoryName": "my-repository",
            "repositoryUri": "AAAAAAAAAAAA.dkr.ecr.us-east-1.amazonaws.com/my-repository",
            "createdAt": "2021-03-17T10:48:18-07:00",
            "imageTagMutability": "MUTABLE",
            "imageScanningConfiguration": {
                "scanOnPush": false
            },
            "encryptionConfiguration": {
                "encryptionType": "AES256"
            }
        }
    }
    

    Take note of the "registryId" and use it in place of "AAAAAAAAAAAA" below.

  • tag the docker image with the repositoryUri

    docker tag my-image AAAAAAAAAAAA.dkr.ecr.us-east-1.amazonaws.com/my-repository
    
  • log in to the Amazon ECR registry using aws ecr get-login-password

    aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin AAAAAAAAAAAA.dkr.ecr.us-east-1.amazonaws.com
    
  • push the docker image to the Amazon ECR repository

    docker push AAAAAAAAAAAA.dkr.ecr.us-east-1.amazonaws.com/my-repository
    
  • see the image in AWS console https://console.aws.amazon.com/ecr/repositories?region=us-east-1

Install ECS command line tools

  • install ecs-cli. Note there is ecs-cli in addition to aws ecs tools. The reason is probably similar to why some services are named "Amazon Service" and some are named "AWS Service". (It seems like ecs-cli provides higher level commands.)
    brew install amazon-ecs-cli
    

Create Amazon ECS Fargate cluster

From https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-cli-tutorial-fargate.html

  • create a cluster using ecs-cli up
    ecs-cli up --cluster my-cluster --launch-type FARGATE --region us-east-1
    
    output:
    INFO[0001] Created cluster                               cluster=my-cluster region=us-east-1
    INFO[0002] Waiting for your cluster resources to be created...
    INFO[0002] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
    VPC created: vpc-BBBBBBBBBBBBBBBBB
    Subnet created: subnet-CCCCCCCCCCCCCCCCC
    Subnet created: subnet-DDDDDDDDDDDDDDDDD
    Cluster creation succeeded.
    
    Take note of the VPC (virtual private cloud), and two subnet IDs to use later. See the cluster in the AWS console UI: https://console.aws.amazon.com/ecs/home?region=us-east-1#/clusters

Gather parameters required to deploy to ECS cluster

From https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-cli-tutorial-fargate.html

Create task execution IAM role
  • create a file /tmp/my-project/task-execution-assume-role.json

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "",
          "Effect": "Allow",
          "Principal": {
            "Service": "ecs-tasks.amazonaws.com"
          },
          "Action": "sts:AssumeRole"
        }
      ]
    }
    
  • create the task execution role using aws iam create-role

    aws iam create-role --role-name my-task-execution-role --assume-role-policy-document file:///tmp/my-project/task-execution-assume-role.json --region us-east-1
    
  • attach the task execution role policy using aws iam attach-role-policy

    aws iam attach-role-policy --role-name my-task-execution-role --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy --region us-east-1
    
Get security group ID
  • get the default security group ID for the virtual private cloud (VPC) created when creating the ECS cluster using aws ec2 describe-security-groups. Replace "vpc-BBBBBBBBBBBBBBBBB" with your VPC ID

    aws ec2 describe-security-groups --filters Name=vpc-id,Values=vpc-BBBBBBBBBBBBBBBBB --region us-east-1
    

    output:

    {
        "SecurityGroups": [
            {
                "Description": "default VPC security group",
                "GroupName": "default",
                "IpPermissions": [
                    {
                        "IpProtocol": "-1",
                        "IpRanges": [],
                        "Ipv6Ranges": [],
                        "PrefixListIds": [],
                        "UserIdGroupPairs": [
                            {
                                "GroupId": "sg-EEEEEEEEEEEEEEEEE",
                                "UserId": "AAAAAAAAAAAA"
                            }
                        ]
                    }
                ],
                "OwnerId": "AAAAAAAAAAAA",
                "GroupId": "sg-EEEEEEEEEEEEEEEEE",
                "IpPermissionsEgress": [
                    {
                        "IpProtocol": "-1",
                        "IpRanges": [
                            {
                                "CidrIp": "0.0.0.0/0"
                            }
                        ],
                        "Ipv6Ranges": [],
                        "PrefixListIds": [],
                        "UserIdGroupPairs": []
                    }
                ],
                "VpcId": "vpc-BBBBBBBBBBBBBBBBB"
            }
        ]
    }
    

    Take note of the "GroupId" to be used later

Deploy to Amazon ECS cluster

From https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-cli-tutorial-fargate.html

  • create /tmp/my-project/ecs-params.yml replacing "subnet-CCCCCCCCCCCCCCCCC", "subnet-DDDDDDDDDDDDDDDDD", and "sg-EEEEEEEEEEEEEEEEE" with appropriate IDs from above. ECS Parameters docs

    version: 1
    task_definition:
      task_execution_role: my-task-execution-role
      ecs_network_mode: awsvpc
      task_size:
        mem_limit: 0.5GB
        cpu_limit: 256
    run_params:
      network_configuration:
        awsvpc_configuration:
          subnets:
            - "subnet-CCCCCCCCCCCCCCCCC"
            - "subnet-DDDDDDDDDDDDDDDDD"
          security_groups:
            - "sg-EEEEEEEEEEEEEEEEE"
          assign_public_ip: ENABLED
    
  • create /tmp/my-project/docker-compose.yml replacing AAAAAAAAAAAA with the registryId:

    version: '3'
    services:
      web:
        image: 'AAAAAAAAAAAA.dkr.ecr.us-east-1.amazonaws.com/my-repository'
        ports:
          - '80:80'
    
  • deploy to the ECS cluster using ecs-cli compose service up. This creates a task definition and service. This uses the docker-compose.yml file in the current directory.

    ecs-cli compose --cluster my-cluster --project-name my-project --ecs-params ecs-params.yml --region us-east-1 service up --launch-type FARGATE
    

    see the service in the web UI: https://console.aws.amazon.com/ecs/home?region=us-east-1#/clusters/my-cluster/services

Hit the server in the browser

  • configure security group to allow inbound access on port 80 using aws ec2 authorize-security-group-ingress

    aws ec2 authorize-security-group-ingress --group-id sg-EEEEEEEEEEEEEEEEE --protocol tcp --port 80 --cidr 0.0.0.0/0 --region us-east-1
    
  • get the IP address using ecs-cli compose service ps

    ecs-cli compose --cluster my-cluster --project-name my-project --region us-east-1 service ps
    

    output:

    Name                                             State    Ports                    TaskDefinition  Health
    my-cluster/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/web  RUNNING  FF.FF.FF.FF:80->80/tcp  my-project:1    UNKNOWN
    

    Take note of the IP address under "Ports"

  • visit in the browser: http://FF.FF.FF.FF replacing "FF.FF.FF.FF" with your IP address

Destroy

  • delete the ECS service using ecs-cli compose service down

    ecs-cli compose --cluster my-cluster --project-name my-project --region us-east-1 service down
    
  • delete the ECS cluster using ecs-cli down

    ecs-cli down --force --cluster my-cluster --region us-east-1
    
  • delete the ECR repository using aws ecr delete-repository

    aws ecr delete-repository --repository-name my-repository --region us-east-1 --force
    

References

Comments