Quick jump:
- Workshop overview
- Setting up the VPC
- Setting up the IAM Roles
- Configuring the AWS CLI
- Creating the Container registries with ECR
- Prepping our Docker Images
- Pushing our tested images to ECR
- Creating the ALB
- Creating the Task Definitions
- Create your Services
- Testing our Service deployments from the Console and ALB
- More in-depth logging with Cloudwatch
This workshop introduces the basics of working with ECS. This includes: setting up the initial ECS cluster, and deploying two services, with traffic routed through an ALB.
Prior to beginning the workshop, you'll need to complete a few set up steps:
- Have a working AWS account
- Have a working Github account
- Install the AWS CLI
- Have Docker installed locally
To check if you have the AWS CLI installed and configured:
$ aws --version
This should return something like:
$ aws --version
aws-cli/1.11.36 Python/2.7.13 Darwin/16.4.0 botocore/1.4.93
To check if you have Docker installed:
$ which docker
This should return something like:
$ which docker
/usr/bin/docker
Once you've completed these steps, you're ready to start the workshop!
Once you've signed into your AWS account, navigate to the ECS console. If you've never used ECS before, you can use the first run wizard which will set up your cluster, a dummy service, and a VPC for you. If you don't want to use the wizard, you can use the Cloudformation template included in this repo, and create a new VPC.
Running the Cloudformation template or the first run wizard should result in something like this:
In order to work with the AWS CLI, you'll need an IAM role with the proper permissions set up. To do this, we'll create both an IAM Group, and an IAM user.
To create the group, naviate to the IAM console, and select Groups > Create New Group. Name the group "ecs-demo". From the list of managed policies, add the following policies:
Once you've created your group, you need to attach it to a user. If you already have an existing user, you can add it to the ecs-group. If you don't have a user, or need to create a new one, you can do so by going to Users > Add User:
Name your user something like "ecs-demo-user". From the next step in the wizard, add your user to the "ecs-demo" group that we created in the previous step:
When the wizard finishes, make sure to copy or download your access key and secret key. You'll need them in the next step.
If you've never configured the AWS CLI, the easiest way is by running:
$ aws configure
This should drop you into a setup wizard:
$ aws configure
AWS Access Key ID [****************K2JA]:
AWS Secret Access Key [****************Oqx+]:
Default region name [us-east-1]:
Default output format [json]:
If you already have a profile setup with the AWS CLI, you can also add a new profile to your credentials file.
You can test that your IAM user has the correct permissions, and that your CLI is setup to connect to your AWS account by running the command to obtain an ECR authentication token. This will allow us to pull our registries in the next step:
$ aws ecr get-login --region us-east-1
This should output something like:
$ docker login -u AWS -p AQECAHhwm0YaISJeRtJm5n1G6uqeekXuoXXPe5UFce9Rq8/14wAAAy0wggMpBgkqhkiG9w0BBwagggMaMIIDFgIBADCCAw8GCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM+76slnFaYrrZwLJyAgEQgIIC4LJKIDmvEDtJyr7jO661//6sX6cb2jeD/RP0IA03wh62YxFKqwRMk8gjOAc89ICxlNxQ6+cvwjewi+8/W+9xbv5+PPWfwGSAXQJSHx3IWfrbca4WSLXQf2BDq0CTtDc0+payiDdsXdR8gzvyM7YWIcKzgcRVjOjjoLJpXemQ9liPWe4HKp+D57zCcBvgUk131xCiwPzbmGTZ+xtE1GPK0tgNH3t9N5+XA2BYYhXQzkTGISVGGL6Wo1tiERz+WA2aRKE+Sb+FQ7YDDRDtOGj4MwZ3/uMnOZDcwu3uUfrURXdJVddTEdS3jfo3d7yVWhmXPet+3qwkISstIxG+V6IIzQyhtq3BXW/I7pwZB9ln/mDNlJVRh9Ps2jqoXUXg/j/shZxBPm33LV+MvUqiEBhkXa9cz3AaqIpc2gXyXYN3xgJUV7OupLVq2wrGQZWPVoBvHPwrt/DKsNs28oJ67L4kTiRoufye1KjZQAi3FIPtMLcUGjFf+ytxzEPuTvUk4Xfoc4A29qp9v2j98090Qx0CHD4ZKyj7bIL53jSpeeFDh9EXubeqp6idIwG9SpIL9AJfKxY7essZdk/0i/e4C+481XIM/IjiVkh/ZsJzuAPDIpa8fPRa5Gc8i9h0bioSHgYIpMlRkVmaAqH/Fmk+K00yG8USOAYtP6BmsFUvkBqmRtCJ/Sj+MHs+BrSP7VqPbO1ppTWZ6avl43DM0blG6W9uIxKC9SKBAqvPwr/CKz2LrOhyqn1WgtTXzaLFEd3ybilqhrcNtS16I5SFVI2ihmNbP3RRjmBeA6/QbreQsewQOfSk1u35YmwFxloqH3w/lPQrY1OD+kySrlGvXA3wupq6qlphGLEWeMC6CEQQKSiWbbQnLdFJazuwRUjSQlRvHDbe7XQTXdMzBZoBcC1Y99Kk4/nKprty2IeBvxPg+NRzg+1e0lkkqUu31oZ/AgdUcD8Db3qFjhXz4QhIZMGFogiJcmo= -e none https://<account_id>.dkr.ecr.us-east-1.amazonaws.com
To login to ECR, copy and paste that output or just run `aws ecr get-login --region us-east-1`
which will tell your shell to execute the output of that command. That should return something like:
WARNING: login credentials saved in /home/vagrant/.dockercfg.
Login Succeeded
Note: if you are running Ubuntu, it is possible that you will need to preface your Docker commands with sudo
. For more information on this, see the Docker documentation.
If you are unable to login to ECR, check your IAM user group permissions.
Before we can build and push our images, we need somewhere to push them to. In this case, we're going to create two repositories in ECR.
To create a repository, navigate to the ECS console, and select Repositories. From there, choose Create repository.
Name your first repository ecs-demo-web:
Once you've created the repository, it will display the push commands. Take note of these, as you'll need them in the next step. The push commands should like something like this:
Once you've created the ecs-demo-web, repeat the process for a second repository. This one should be named ecs-demo-api. Take note of the push commands for this second repository, also. Push commands are unique per repository.
If you haven't already, clone this repository:
$ git clone git@github.com:abby-fuller/ecs-demo.git
Our first step is the build and test our containers locally. If you've never worked with Docker before, there are a few basic commands that we'll use in this workshop, but you can find a more thorough list in the Docker "Getting Started" documentation.
To start your first container, go to the web
directory in the project:
$ cd <path/to/project>/ecs-demo/web
To build the container:
$ docker build -t ecs-demo-web .
This should output steps that look something like this:
$ docker build -t ecs-demo-web .
Sending build context to Docker daemon 4.096 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:latest
---> 6aa0b6d7eb90
Step 1 : MAINTAINER abbyfull@amazon.com
---> Using cache
---> 3f2b91d4e7a9
If the container builds successfully, the output should end with something like this:
Removing intermediate container d2cd523c946a
Successfully built ec59b8b825de
To run your container:
$ docker run -d -p 3000:3000 ecs-demo-web
To check if your container is running:
$ docker ps
This should return a list of all the currently running containers. In this example, it should just return a single container, the one that we just started:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fa922a2376d5 ecs-demo-web:latest "python app.py" About a minute ago Up About a minute 3000/tcp, 0.0.0.0:3000->3000/tcp clever_shockley
To test the actual container output:
$ curl localhost:3000/web
This should return:
hi! i'm served via Python + Flask. i'm a web endpoint.
To test the api container, repeat the same process from the /api
directory:
$ cd <path/to/project>/ecs-demo/api
$ docker build -t ecs-demo-api .
$ docker run -d -p 8000:8000 ecs-demo-api
$ curl localhost:8000/api
The API container should return:
hi! i'm ALSO served via Python + Flask. i'm a second web endpoint.
Now that we've tested our images locally, we need to tag them again, and push them to ECR. This will allow us to use them in TaskDefinitions that can be deployed to an ECS cluster.
You'll need your push commands that you saw during registry creation. If you've misplaced your push commands, you can find them again by going back to the repository (ECS Console > Repositories > Select the Repository you want to see the commands for > View Push Commands.
To tag and push the web repository:
$ docker tag ecs-demo-web:latest <account_id>.dkr.ecr.us-east-1.amazonaws.com/ecs-demo-web:latest
$ docker push <account_id>.dkr.ecr.us-east-1.amazonaws.com/ecs-demo-web:latest
This should return something like this:
The push refers to a repository [<account_id>.ecr.us-east-1.amazonaws.com/ecs-demo-web] (len: 1)
ec59b8b825de: Image already exists
5158f10ac216: Image successfully pushed
860a4e60cdf8: Image successfully pushed
6fb890c93921: Image successfully pushed
aa78cde6a49b: Image successfully pushed
Digest: sha256:fa0601417fff4c3f3e067daa7e533fbed479c95e40ee96a24b3d63b24938cba8
To tag and push the api repository:
$ docker tag ecs-demo-api:latest <account_id>.dkr.ecr.us-east-1.amazonaws.com/ecs-demo-api:latest
$ docker push <account_id>.dkr.ecr.us-east-1.amazonaws.com/ecs-demo-api:latest
Note: why :latest
? This is the actual image tag. In most production environments, you'd tag images for different schemes: for example, you might tag the most up-to-date image with :latest
, and all other versions of the same container with a commit SHA from a CI job. If you push an image without a specific tag, it will default to :latest
, and untag the previous image with that tag. For more information on Docker tags, see the Docker documentation.
You can see your pushed images by viewing the repository in the AWS Console. Alternatively, you can use the CLI:
$ aws ecr list-images --repository-name=ecs-demo-api
{
"imageIds": [
{
"imageTag": "latest",
"imageDigest": "sha256:f0819d27f73c7fa6329644efe8110644e23c248f2f3a9445cbbb6c84a01e108f"
}
]
}
Now that we've pushed our images, we need an Application Load Balancer (ALB)[https://aws.amazon.com/elasticloadbalancing/applicationloadbalancer/] to route traffic to our endpoints. Compared to a traditional load balancer, an ALB lets you direct traffic between different endpoints. In our example, we'll use two separate endpoints: /web
and /api
.
To create the ALB:
Navigate to the EC2 Service Console, and select Load Balancers from the left-hand menu. Choose Create Load Balancer:
Name your ALB ecs-demo and add an HTTP listener on port 80:
Note: in a production environment, you should also have a secure listener on port 443. This will require an SSL certificate, which can be obtained from AWS Certificate Manager, or from your registrar/any CA. For the purposes of this demo, we will only create the insecure HTTP listener. DO NOT RUN THIS IN PRODUCTION.
Next, select your VPC and add at least two subnets for high availability. Make sure to choose the VPC that we created during the ECS first-run wizard (or with the Cloudformation template). If you have multiple VPC, and you're not sure which VPC is the correct one, you can find its ID from the VPC console, or by viewing the outputs of the Cloudformation stack.
Next, add a security group. If you ran the ECS first run wizard, you should have an existing group called something like EC2ContainerService-ecs-demo-EcsSecurityGroup. If you don't have this, check you've chosen the correct VPC, as security groups are VPC specific. If you still don't have this, you can create a new security groups with the following rule:
Ports Protocol Source
80 tcp 0.0.0.0/0
Choose the security group, and continue to the next step: adding routing. For this initial setup, we're just adding a dummy healthcheck on /
. We'll add specific healthchecks for our service endpoints when we register them with the ALB.
Finally, skip the "Register targets" step, and continue to review. If your values look correct, click Create.
Note: If you created your own security group, and only added a rule for port 80, you'll need to add one more. Select your security group from the list > Inbound > Edit and add a rule to allow your ALB to access the port range for ECS (0-65535). The final rules should look like:
Type Ports Protocol Source
HTTP 80 tcp 0.0.0.0/0
All TCP 0-65535 tcp <id of this security group>
Before you can register a container to a service, it needs be a part of a Task Definition. Task Definitions define things like environment variables, the container image you wish to use, and the resources you want to allocate to the service (port, memory, CPU). To create a Task Definition, choose Task Definitions from the ECS console menu. Then, choose Create a Task Definition:
At this point, you'll have the opportunity to Create an Amazon EC2 Container Service Role in the IAM Console. Follow the link to create the role:
Once you've created the role, you can refresh the Role list in the Task Definition creation wizard. It should now appear in the dropdown. Select your role, and continue to adding a container definition.
A few things to note here:
-
We've specified a specific container image, including the
:latest
tag. Although it's not important for this demo, in a production environment where you were creating Task Definitions programmatically from a CI/CD pipeline, Task Definitions could include a specific SHA, or a more accurate tag. -
Under Port Mappings, we've specified a Container Port (3000), but left Host Port as 0. This was intentional, and is used to faciliate dynamic port allocation. This means that we don't need to map the Container Port to a specific Host Port in our Container Definition- instead, we can let the ALB allocate a port during task placement. To learn more about port allocation, check out the ECS documentation here.
Once you've specified your Port Mappings, scroll down and add a log driver. There are a few options here, but for this demo, choose awslogs:
For the web container, make sure the awslogs-stream-prefix is web.
Once you've added your log driver, save the Container Definition, and create the Task Definition.
Repeat the Task Definition creation process with the API container, taking care to use the api container image registry, and the correct port (8000) for the Container Port option. For the log driver, make sure the awslogs-stream-prefix is api.
Navigate back to the ECS console, and choose the cluster that you created during the first run wizard. This should be named ecs-demo. If you don't have a cluster named ecs-demo, create one with the Create Cluster option.
Next, you'll need to create your web service. From the cluster detail page, choose Services > Create.
Choose the web Task Definition you created in the previous section. For the purposes of this demo, we'll only start one copy of each task. In a production environment, you will always want more than one copy of each task running for reliability and availability.
You can keep the default AZ Balanced Spread for the Task Placement Policy. To learn more about the different Task Placement Policies, see the documentation, or this blog post.
Under Optional Configurations, choose Configure ELB:
Select the web container, choose Add to ELB.
This final step allows you to configure the container with the ALB. When we created our ALB, we only added a listener for HTTP:80. Select this from the dropdown as the value for Listener. For Target Group Name, enter a value that will make sense to you later, like ecs-demo-web. For Path Pattern, the value should be /web*
. This is the route that we specified in our Python application.
If the values look correct, click Save to add your Container.
Repeat this process for the API container and task definition.
You can see service level events from the ECS console. This includes deployment events. You can test that both of your services deployed, and registered properly with the ALB by looking at the service's Events tab:
We can also test from the ALB itself. To find the DNS A record for your ALB, navigate to the EC2 Console > Load Balancers > Select your Load Balancer. Under Description, you can find details about your ALB, including a section for DNS Name. You can enter this value in your browser, and append the endpoint of your service, to see your ALB and ECS Cluster in action:
You can see that the ALB routes traffic appropriately based on the paths we specified when we registered the containers: /web*/
requests go to our web service, and /api*/
requests go to our API service.
When we created our Container Definitions, we also added the awslogs driver, which sends logs to Cloudwatch. You can see more details logs for your services by going to the Cloudwatch console, and selecting first our log group:
And then choosing an individual stream:
Congratulations! You've deployed an ECS Cluster with two working endpoints.