Break a Monolithic Application into Microservices with AWS Copilot, Amazon ECS, Docker, and AWS Fargate

TUTORIAL

Module Two: Containerize and Deploy the Monolith

Overview

Containers allow you to easily package an application's code, configurations, and dependencies into easy-to-use building blocks that deliver environmental consistency, operational efficiency, developer productivity, and version control. Containers can help ensure that applications deploy quickly, reliably, and consistently, regardless of deployment environment.

Why use containers?

Launching a container with a new release of code can be done without significant deployment overhead. Operational speed is improved, because code built in a container on a developer’s local machine can be easily moved to a test server by simply moving the container. At build time, this container can be linked to other containers required to run the application stack.

Dependency control and improved pipeline

A Docker container image is a point-in-time capture of an application's code and dependencies. This allows an engineering organization to create a standard pipeline for the application lifecycle. For example:

  1. Developers build and run the container locally.
  2. Continuous integration server runs the same container and executes integration tests against it to make sure it passes expectations.
  3. The same container is shipped to a staging environment where its runtime behavior can be checked using load tests or manual QA.
  4. The same container is shipped to production.

Being able to build, test, ship, and run the exact same container through all stages of the integration and deployment pipeline makes delivering a high quality, reliable application considerably easier.

Density and resource efficiency

Containers facilitate enhanced resource efficiency by allowing multiple heterogeneous processes to run on a single system. Resource efficiency is a natural result of the isolation and allocation techniques that containers use. Containers can be restricted to consume certain amounts of a host's CPU and memory. By understanding what resources a container needs and what resources are available from the underlying host server, you can right-size the compute resources you use with smaller hosts or increase the density of processes running on a single large host, increasing availability and optimizing resource consumption.

Flexibility

The flexibility of Docker containers is based on their portability, ease of deployment, and small size. In contrast to the installation and configuration required on a VM, packaging services inside of containers allows them to be easily moved between hosts, isolated from failure of other adjacent services, and protected from errant patches or software upgrades on the host system.

Application overview

a. Client – The client makes a request over port 80 to the load balancer.

b. Load Balancer – The load balancer distributes requests across all available ports.

c. Target Groups – Instances are registered in the application's target group.

d. Container Ports – Each container runs a single application process which binds the Node.js cluster parent to port 80 within its namespace.

e. Containerized Node.js Monolith – The Node.js cluster parent is responsible for distributing traffic to the workers within the monolithic application. This architecture is containerized, but still monolithic because each container has all the same features of the rest of the containers.

What is Amazon ECS?

Amazon ECS is a highly scalable, high performance container management service that supports Docker containers and allows you to easily run applications on a managed cluster of Amazon EC2 instances. With simple API calls, you can launch and stop Docker-enabled applications, query the complete state of your cluster, and access many familiar features like security groups, Elastic Load Balancing, EBS volumes, and IAM roles.

You can use Amazon ECS to schedule the placement of containers across your cluster based on your resource needs and availability requirements. You can also integrate your own scheduler or third-party schedulers to meet business or application-specific requirements.

There is no additional charge for Amazon ECS. You pay for the AWS resources (for example, EC2 instances or EBS volumes) you create to store and run your application.

What you will accomplish

In this module, you will use Amazon ECS to instantiate a managed cluster of EC2 compute instances and deploy your image as a container running on the cluster.

Implementation

Follow these step-by-step instructions to deploy the Node.js application using AWS Copilot.

Step 1: Create an AWS Copilot application

An AWS Copilot Application is a group of services and environments. Think of it as a label for what you are building. In this example, it’s an API built as a monolithic Node.js application. In this step, you will create a new empty application. In the terminal or command prompt, enter the following and name the Application api.

$ cd ./amazon-ecs-nodejs-microservices/
$ copilot app init

An AWS Copilot Application creates an empty application which consists of roles to administrate StackSets, ECR repositories, KMS keys, and S3 buckets. It also creates a local directory in your repository to hold configuration files for your application and services.

Step 2: Create the environment

Copilot Environments are infrastructure where applications run. AWS Copilot provisions a secure VPC, an Amazon ECS cluster, a Load Balancer, and all the other resources required by the application. Enter copilot env init and choose profile default to use your AWS credentials. Name the Environment api.

$ copilot env init
Environment name: api

  Which credentials would you like to use to create api?  [Use arrows to move, type to filter, ? for more help]
    Enter temporary credentials
  > [profile default]

Choose Yes, use default.

Environment name: api
Credential source: [profile default]

  Would you like to use the default configuration for a new environment?
    - A new VPC with 2 AZs, 2 public subnets and 2 private subnets
    - A new ECS Cluster
    - New IAM Roles to manage services and jobs in your environment
  [Use arrows to move, type to filter]
  > Yes, use default.
    Yes, but I'd like configure the default resources (CIDR ranges, AZs).
    No, I'd like to import existing resources (VPC, subnets).

AWS Copilot creates a manifest.yml that is used to configure the environment.

Environment name: api
Credential source: [profile default]
Default environment configuration? Yes, use default.
✔ Wrote the manifest for environment api at copilot/environments/api/manifest.yml
- Update regional resources with stack set "api-infrastructure"  [succeeded]  [0.0s]
- Update regional resources with stack set "api-infrastructure"  [succeeded]        [130.8s]
  - Update resources in region "us-east-1"                       [create complete]  [130.4s]
    - ECR container image repository for "monolith"              [create complete]  [2.5s]
    - KMS key to encrypt pipeline artifacts between stages       [create complete]  [124.5s]
    - S3 Bucket to store local artifacts                         [create complete]  [2.4s]
✔ Proposing infrastructure changes for the api-api environment.
- Creating the infrastructure for the api-api environment.  [create complete]  [56.0s]
  - An IAM Role for AWS CloudFormation to manage resources  [create complete]  [22.4s]
  - An IAM Role to describe resources in your environment   [create complete]  [25.0s]
✔ Provisioned bootstrap resources for environment api in region us-east-1 under application api.

Step 3: Deploy the environment

The next step is to deploy the environment and provision the services for the application. To deploy, enter copilot env deploy --name api in the terminal.

$ copilot env deploy --name api

✔ Proposing infrastructure changes for the api-api environment.
- Creating the infrastructure for the api-api environment.                    [update complete]  [78.3s]
  - An ECS cluster to group your services                                     [create complete]  [7.5s]
  - A security group to allow your containers to talk to each other           [create complete]  [1.4s]
  - An Internet Gateway to connect to the public internet                     [create complete]  [16.0s]
  - Private subnet 1 for resources with no internet access                    [create complete]  [1.7s]
  - Private subnet 2 for resources with no internet access                    [create complete]  [1.7s]
  - A custom route table that directs network traffic for the public subnets  [create complete]  [10.9s]
  - Public subnet 1 for resources that can access the internet                [create complete]  [3.1s]
  - Public subnet 2 for resources that can access the internet                [create complete]  [6.0s]
  - A private DNS namespace for discovering services within the environment   [create complete]  [46.3s]
  - A Virtual Private Cloud to control networking of your AWS resources       [create complete]  [11.6s]

Step 4: Create the monolith AWS Copilot Service

A Copilot Service runs containers. Internet-facing services can be a Request-Driven Web Service that uses AWS App Runner or a Load Balanced Web Service that provisions an Application or Network Load Balancer with appropriate security groups, and runs on Amazon ECS or Fargate.

Other Service types include a Backend Service that lets AWS services communicate within the application but not to the Internet. A Worker Service is used for asynchronous service-to-service messaging with Amazon Simple Queue Service (Amazon SQS).

This tutorial uses a Load Balanced Web Service for the Internet-facing monolith. To create the monolithic service, enter copilot svc init and choose Load Balanced Web Service.

$ copilot svc init

Note: It's best to run this command in the root of your Git repository.
Welcome to the Copilot CLI! We're going to walk you through some questions
to help you get set up with a containerized application on AWS. An application is a collection of
containerized services that operate together.


  Which workload type best represents your architecture?  [Use arrows to move, type to filter, ? for more help]
    Request-Driven Web Service  (App Runner)
  > Load Balanced Web Service   (Internet to ECS on Fargate)
    Backend Service             (ECS on Fargate)
    Worker Service              (Events to SQS to ECS on Fargate)
    Scheduled Job               (Scheduled event to State Machine to Fargate)
Name the service monolith.
Workload type: Load Balanced Web Service

What do you want to name this service? [? for help] monolith

Choose Enter custom path for your Dockerfile.

Workload type: Load Balanced Web Service
Service name: monolith

  Which Dockerfile would you like to use for monolith?  [Use arrows to move, type to filter, ? for more help]
  > Enter custom path for your Dockerfile

Enter the path to 2-containerized/services/api/Dockerfile

Service type: Load Balanced Web Service
Service name: monolith
Dockerfile: Enter custom path for your Dockerfile
Dockerfile: 2-containerized/services/api/Dockerfile

✔ Wrote the manifest for service monolith at copilot/monolith/manifest.yml

Change directory to ./amazon-ecs-nodejs-microservices/copilot/monolith and examine manifest.yml to see how the service is configured. Note that the parameter http defines the path for the app. The Node.js application, server.js, defines the base route as /.

The manifest for the "monolith" service.
Read the full specification for the "Load Balanced Web Service" type at:
 https://aws.github.io/copilot-cli/docs/manifest/lb-web-service/
Your service name will be used in naming your resources like log groups, ECS services, etc.
name: monolith
type: Load Balanced Web Service

Distribute traffic to your service.
http:
  # Requests to this path will be forwarded to your service.
  # To match all requests you can use the "/" path.
  path: '/'
  # You can specify a custom health check path. The default is "/".
  # healthcheck: '/'

Step 5: Deploy the monolith AWS Copilot Service

To deploy the monolith Service, enter copilot svc deploy --name monolith in the terminal. When you deploy the service, the container is built locally by Docker and pushed to your Elastic Container Registry. The service pulls the container from the registry and deploys it in the environment. The monolith application is running when the deployment is complete.

$ copilot svc deploy --name monolith
Only found one service, defaulting to: monolith
Only found one environment, defaulting to: api
Building your container image: docker build -t 837028011264.dkr.ecr.us-east-1.amazonaws.com/api/monolith --platform linux/x86_64 /Users/sparaaws/github/spara/amazon-ecs-nodejs-microservices/2-containerized/services/api -f /Users/sparaaws/github/spara/amazon-ecs-nodejs-microservices/2-containerized/services/api/Dockerfile
[+] Building 43.3s (10/10) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                                          0.0s
 => => transferring dockerfile: 36B                                                                                                                                           0.0s
 => [internal] load .dockerignore                                                                                                                                             0.0s
 => => transferring context: 2B                                                                                                                                               0.0s
 => [internal] load metadata for docker.io/mhart/alpine-node:7.10.1                                                                                                           5.6s
 => [auth] mhart/alpine-node:pull token for registry-1.docker.io                                                                                                              0.0s
 => [internal] load build context                                                                                                                                             0.0s
 => => transferring context: 392B                                                                                                                                             0.0s
 => [1/4] FROM docker.io/mhart/alpine-node:7.10.1@sha256:d334920c966d440676ce9d1e6162ab544349e4a4359c517300391c877bcffb8c                                                     0.0s
 => => resolve docker.io/mhart/alpine-node:7.10.1@sha256:d334920c966d440676ce9d1e6162ab544349e4a4359c517300391c877bcffb8c                                                     0.0s
 => CACHED [2/4] WORKDIR /srv                                                                                                                                                 0.0s
 => [3/4] ADD . .                                                                                                                                                             0.0s
 => [4/4] RUN npm install                                                                                                                                                    37.1s
 => exporting to image                                                                                                                                                        0.3s
 => => exporting layers                                                                                                                                                       0.2s
 => => writing image sha256:26ea1872922a12bd3a297c2dd003d1fc71de93e0e5895d2264acca4db3963fbb                                                                                  0.0s
 => => naming to 837028011264.dkr.ecr.us-east-1.amazonaws.com/api/monolith                                                                                                    0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Login Succeeded

Logging in with your password grants your terminal complete access to your account.
For better security, log in with a limited-privilege personal access token. Learn more at https://docs.docker.com/go/access-tokens/
Using default tag: latest
The push refers to repository [837028011264.dkr.ecr.us-east-1.amazonaws.com/api/monolith]
bc590299ddf7: Pushed
947736d9bac9: Pushed
5f70bf18a086: Pushed
3e893534526a: Pushed
040fd7841192: Pushed
latest: digest: sha256:721adc83096c12f21d61bb73d7ea6d296269cce607178676492aaa1f6cdad6bc size: 1365

Step 6: Confirm the deployment

When deployment completes, AWS Copilot prints the URL to the service in the output. 

✔ Proposing infrastructure changes for stack api-api-monolith
- Creating the infrastructure for stack api-api-monolith                          [create complete]    [360.8s]
  - Service discovery for your services to communicate within the VPC             [create complete]    [0.0s]
  - Update your environment's shared resources                                    [update complete]    [149.9s]
    - A security group for your load balancer allowing HTTP traffic               [create complete]    [5.9s]
    - An Application Load Balancer to distribute public traffic to your services  [create complete]    [123.0s]
    - A load balancer listener to route HTTP traffic                              [create in progress]  [198.5s]
  - An IAM role to update your environment stack                                  [create complete]    [23.5s]
  - An IAM Role for the Fargate agent to make AWS API calls on your behalf        [create complete]    [19.9s]
  - A HTTP listener rule for forwarding HTTP traffic                              [create complete]    [3.5s]
  - A custom resource assigning priority for HTTP listener rules                  [create complete]    [5.1s]
  - A CloudWatch log group to hold your service logs                              [create complete]    [1.5s]
  - An IAM Role to describe load balancer rules for assigning a priority          [create complete]    [23.5s]
  - An ECS service to run and maintain your tasks in the environment cluster      [create complete]    [117.0s]
    Deployments
               Revision  Rollout      Desired  Running  Failed  Pending
      PRIMARY  1         [completed]  1        1        0       0
  - A target group to connect the load balancer to your service                   [create complete]    [15.0s]
  - An ECS task definition to group your containers and run them on ECS           [create complete]    [0.0s]
  - An IAM role to control permissions for the containers in your tasks           [create complete]    [23.5s]
✔ Deployed service monolith.
Recommended follow-up action:
  - You can access your service at http://api-a-Publi-DU44D9VOSXLA-792918025.us-east-1.elb.amazonaws.com over the internet.

You can test the application deployment by entering queries in a browser, such as:

http://<application-string>.us-east-1.elb.amazonaws.com/api/users/3

{"id":3,"username":"pb","name":"Bonnibel Bubblegum","bio":"Scientist, bearer of candy power, ruler of the candy kingdom"}
{"id":2,"title":"Party at the candy kingdom tomorrow","createdBy":3}
[{"thread":1,"text":"Has anyone checked on the lich recently?","user":4},{"thread":1,"text":"I'll stop by and see how he's doing tomorrow!","user":2},{"thread":2,"text":"Come party with the candy people tomorrow!","user":3},{"thread":2,"text":"Mathematical!","user":2},{"thread":2,"text":"I'll bring my guitar","user":1},{"thread":3,"text":"I need a new guitar to play the most savory licks in Ooo","user":1}]
[{"thread":1,"text":"Has anyone checked on the lich recently?","user":4},{"thread":1,"text":"I'll stop by and see how he's doing tomorrow!","user":2}]

Up Next: Break the Monolith

Was this page helpful?