AWS Copilot : The easiest way to host your APIs with high availability
In this blog post I will show you how to host your web application with auto-scaling of containers behind a load balancer. By the end you will be able to create as many environments as you want such as staging, pre-production, production, …., store all of your sensitive secrets encrypted and inject them in your applications at startup. You will also be able to add any service in private subnet and automate deployment with Gitlab CI / CD or Github Action.
All this with basic knowledge in cloud engineering.
Cloud engineering has been coming the main topic when company wants to expose their web services to its customers. There are many tools to address these issues but if no one with appropriate skills can compare them, the wrong decision cannot be avoided.
Many containers orchestration tools need advanced knowledge to be used such as :
- Kubernetes
- Openshift
- Hashicorp Nomad
- Docker Swarm
- Rancher
With nobody to maintain your infrastructure, these tools cannot be considered.
AWS Copilot is an open source command line interface that makes it easy for developers to build, release, and operate production ready containerized applications on AWS App Runner, Amazon ECS, and AWS Fargate. As their website says.
In this post we are going to deploy infrastructure through Amazon ECS service, if you are interested with other Copilot’s features here is the documentation
Prérequisites :
You need to configure authentication to your AWS account : how to
Once it’s done, make sure your IAM user have AdministratorAccess policy permission. Copilot will creates many resources in your account such as Virtual Private Cloud, Elastic Load Balancer, ECR, ECS cluster and roles.
Deep dive into container orchestrator AWS managed
Every applications in your ECS cluster need to be containerized, it simplifies everything.
All my examples will be in NodeJS but it doesn’t matter, all you have to do is to adapt the Dockerfile for your needs. The next steps do not depend on the programming language.
FROM node:16.10-alpine
EXPOSE 80
WORKDIR '/app'
RUN npm install -g npm@8.18.0 --upgrade
ENV PORT=80
COPY . .
RUN npm install
ENTRYPOINT [ "node", "app.js" ]
Once is done you have to initialize your Copilot application
copilot app init <application-name>
And get this output
As you can see, Copilot creates one CloudFormation stack and IAM role to create AWS resources for next steps.
Now let’s create one environment such as staging
copilot env init --name staging
Choose your AWS account that you configured before and leave the default settings.
Then you have to deploy your newly created environment
copilot env deploy staging
Our environment is now ready to host applications.
Note that every environments will create a new ECS Cluster for more isolation and best control on each
As recommended two step before, we initialize Copilot application.
copilot init <service-name>
Request-Driven Web Service
An AWS App Runner service that autoscales your instances based on incoming traffic and scales down to a baseline instance when there’s no traffic.
Load Balanced Web Service
An ECS Service running tasks on Fargate with an Application Load Balancer, a Network Load Balancer or both, as ingress. This option is suitable for HTTP or TCP services with steady request volumes that need to access resources in a VPC or require advanced configuration.
Note that an Application Load Balancer is an environment-level resource, and is shared by all Load Balanced Web Services within the environment.
Backend Service
If you want a service that can’t be accessed externally, but only from other services within your application, you can create a Backend Service. Copilot will provision an ECS Service running on AWS Fargate, but won’t set up any internet-facing endpoints
Worker Service
Worker Services allow you to implement asynchronous service-to-service communication with pub/sub architectures.
Scheduled Job
As his name says scheduled jobs are useful to complete task based on cron expressions alongside web services in cluster.
In this article we will create Load Balanced Web Service and resolve it by domain name. Select it and choose a name for your application then set the path to your Dockerfile
You can edit the manifest created at ./copilot/<application-name>/manifest.yml according to your needs here is mine :
# The manifest for the "application" service.
# Read the full specification for the "Load Balanced Web Service" type at:
# https://aws.github.io/copilot-cli/docs/manifest/lb-web-service/
name: application
type: Load Balanced Web Service
# Distribute traffic to your service.
http:
# Requests to this path will be forwarded to your service from load balancer.
# To match all requests you can use the "/" path.
path: '/'
healthcheck: '/healthcheck'
# Configuration for your containers and service.
image:
build:
dockerfile: src/Dockerfile
cache_from:
- 741609059380.dkr.ecr.eu-west-3.amazonaws.com/application/application
port: 80
# Default values
cpu: 256
memory: 512
# Configuration by environement
# Overwrites default values
environments:
staging:
cpu: 512
memory: 1024
# Range of scale
count:
range:
min: 1
max: 6
spot_from: 3
cpu_percentage: 70
memory_percentage: 80
variables:
ENVIRONMENT: STAGING
To create secrets and inject them at every deployment we will use AWS System Manager and more precisely Parameter Store. You can create secrets directly by the web console but don’t forget to add the correct tags. AWS Copilot use these tags to know which ones secret to use.
With AWS cli, ssm option gives you many choice to interact with their API
ENVIRONMENT_NAME=staging
APPLICATION_NAME=<application-name>
SECRET_NAME=STAGING_MONGO_URI.....
SECRET_VALUE=mongo://......
aws ssm put-parameter --name ${SECRET_NAME} --value ${SECRET_VALUE} --type SecureString --tags Key=copilot-environment,Value=${ENVIRONMENT_NAME} Key=copilot-application,Value=${APPLICATION_NAME}
And you can specify it by updating the manifest
The value of MONGO_URI will be recorvered by the name of the previsouly created secret.
environments:
staging:
cpu: 512
memory: 1024
count:
range:
min: 1
max: 6
spot_from: 3
cpu_percentage: 70
memory_percentage: 80
variables:
ENVIRONMENT: STAGING
secrets:
MONGO_URI: STAGING_MONGO_URI
It works the same way of environment variables except that values are encrypted in AWS System Manager.
Now, you can deploy the newly created manifest by doing this command
copilot svc deploy --env staging --tag 0.0.1
That’s it ! Your application is now responding on the host in command output.
Note : if you create Backend Service it can be resolved only by other services in the cluster
Domain Name configuration
If your domain is registered with AWS Route53, Copilot allows you to specify the domain in your Load Balanced Web Service manifest by doing this
http:
# Requests to this path will be forwarded to your service.
# To match all requests you can use the "/" path.
path: '/'
healthcheck: '/healthcheck'
alias:
- name: api.domain.com
Note that you can also attach SSL certificate in http section by adding associated ARN in AWS Certificate Manager more details here
If you use another registar than AWS Route53 you have to make this manually. Create record into domain name configuration which is a CNAME to the host where your Load Balanced Web Application is responding.
Automate deployment with CI / CD pipelines
In this section we gonna see how to automate application deployment with Copilot cli. It’s globally the same actions that we made before.
Gitlab CI / CD
To auto deploy your application, first of all you have to create .gitlab-ci.yml file at the root of your project and paste the following into it.
stages:
- deployment
variables:
COPILOT_SERVICE: <service-name>
copilot-staging:
stage: deployment
only:
- master
image: ubuntu:latest
script:
- apt update && apt install -y wget
- wget -O /usr/local/bin/copilot https://github.com/aws/copilot-cli/releases/download/v1.27.0/copilot-linux && chmod +x /usr/local/bin/copilot && copilot -v
- copilot svc deploy --env staging -n ${COPILOT_SERVICE} --tag "$(git describe --tags --always)-$(date +"%Y%m%d-%H%M%S" -u)"
Note that you have to set AWS_SECRET_ACCESS_KEY / AWS_DEFAULT_REGION / AWS_ACCESS_KEY_ID in your Gitlab repository secrets, without Copilot cannot authenticate itself to AWS
Github Action
Github CI / CD manifests have to be created in .github/workflows/
Let’s create file called deploy-ECS.yaml in this folder and paste the following manifest into it
name: Deploy ECS
run-name: ${{ github.actor }} trigger staging deployment 🚀
on:
push:
branch:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
- uses: ksivamuthu/aws-copilot-github-action@v0.0.1
with:
command: install
- run: |
copilot --version
- uses: ksivamuthu/aws-copilot-github-action@v0.0.1
with:
command: deploy
app: application
env: staging
force: false
Note that, as Gitlab you have to set secrets as AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_DEFAULT_REGION in your Github repository.
Congrats, you’ve created your first AWS hosted web application with Copilot !