- What is needed for an deployment
- Initial deployment
- How to deploy a new version?
- Expose the service to the outside world
- Things you should know
Deployment process on ECS consists of two steps. The first step is registering a Task definition that holds the information about what container you want to start and what the requirements are. For instance memory, cpu or port. The second step is creating or updating a Service definition which defines a Service which eventually uses the Task Definition to start the containers on ECS and keeps them running.
The Task definition documentation can be found here and the Service definition documentation can be found here
Most deployments will be automatically, this means we need an ECS deployment user. See the users module on how to create a proper user with proper rights. Be careful with iam:PassRole as described in the module, you could misuse it to give a task in ECS Admin rights.
To deploy an application to ECS for the first time we need to register a Task definition, you can see an example here:
{
"family": "nginx",
"containerDefinitions": [
{
"name": "nginx",
"image": "nginx:alpine",
"memory": 128,
"portMappings": [
{
"containerPort": 80,
"protocol": "tcp"
}
]
}
]
}
We are defining a Nginx docker container with 128 MB of memory and we are specifying that the container is listening on port 80. You can look at the task definition as a predefinition of the Docker run command without actually executing the run. For all possible Task definition parameters have a look at the documentation.
This is the AWS cli command to create the Task definition:
aws ecs register-task-definition --cli-input-json file:https://td-nginx.json
To actually run the container we need a Service, to create a service we need the Service definition like here:
{
"cluster": "test",
"serviceName": "nginx",
"taskDefinition": "@@TASKDEFINITION_ARN@@",
"loadBalancers": [
{
"targetGroupArn": "@@TARGET_GROUP_ARN@@",
"containerName": "nginx",
"containerPort": 80
}
],
"desiredCount": 1,
"role": "/ecs/test_ecs_lb_role",
"deploymentConfiguration": {
"maximumPercent": 100,
"minimumHealthyPercent": 0
}
}
While the Task definition is not aware of the environment the Service definition definitely is. That is why we are specifying the cluster name and the role. For service to know which Task definition to run we need to specify the taskDefinition arn. This can be found in AWS console under Task definitions or you will get it when Registering a Task definition.
We also need to provide a targetGroupArn, which is used to expose the service to the outside world
For all possible Service definition parameters have a look at the documentation.
This is the AWS cli command to create the Service definition:
aws ecs create-service --cli-input-json file:https://service-create-nginx.json
When we want to deploy a new version of a container we need to update the Task definition and register a new revision. This is done exactly as described in Register a Task definition
Because we already have a service we can not create a new one, we need to update it. That means we are telling the service to update our Task definition from revision X to revision Y. Therefore we just need to provide a small set of information to the service as it can be found here:
{
"cluster": "test",
"service": "nginx",
"taskDefinition": "@@TASKDEFINITION_ARN@@",
"desiredCount": 1,
"deploymentConfiguration": {
"maximumPercent": 100,
"minimumHealthyPercent": 0
}
}
This is the AWS cli command to update the Service definition:
aws ecs update-service --cli-input-json file:https://service-update-nginx.json
The goal is not to deploy an application but to make it accessible to the outside world or the internal services. This can be done by using the ALB (Application LoadBalancer) or the ELB (Elastic LoadBalancer). The difference is that the ELB has no knowledge of containers, it just looks at the health of the EC2 node and exposes a predefined port of that node.
ALB is 'container' aware, in the sense that the containers get registered to the ALB and that the ALB exposes containers to the outside world instead of the EC2 node. This also means that you can have multiple containers of the same type on one EC2 node.
For a full overview have a look at the documentation.
This deployment example is looking at the ALB because that makes sense in a Docker platform world. It is good to know that the ALB consists of a Listener and a target group, as seen in the illustration below. The full documentation can be found here.
The listener is the actual port that is exposed to the outside world. For the listener to route traffic to something it uses a context path like /api to target the target group. The target group allows containers or other resources to register them self so that they receive the traffic. Target group also checks the health of the containers and decides if they are healthy or not.
The Task definition is global on AWS. It means that when you create a Task definition with the name test you can not remove it. Even when you get rid of it in the UI the next time you create a Task definition with the name test it will have a revision number that is +1 of the previous version.
AWS has a concept of having an external or internal facing LB (LoadBalancer), as can be read here External facing means available on the internet and is probably the most used one. The internal one is not available on the internet but only inside your VPC.
The internal facing LB can be very interesting for connecting microservices without using any complicated service discovery.
Although it is posible to deploy manualy trough AWS console or executing the above commands by your self, it makes more sense to automate this process. It is posible to use one of the following scripts https://github.com/silinternational/ecs-deploy Or apply KISS and do something like this (deploy.sh can be found here:
To do an initial deployment:
CONTAINER_VERSION=nginx:alpine ./deploy.sh create
To do an update deployment:
CONTAINER_VERSION=nginx:alpine ./deploy.sh update
Almost all containers require some form of external values or secrets, like the database password or keys to another service. There are a lot of ways to do this, the simplest way when using ECS is by using AWS Parameter Store. Here is a blog post I wrote that describes different options and how to use AWS Parameter Store.
To allow a task to access the Parameter Store you need a role that you can assing to your task. The ecs roles module can create such a role.