Por: @jonasc Publicado em: 2021-07-21
Amazon ECS/ECR para publicação e orquestração de serviços em container docker
O Amazon Elastic Container Service (ECS) é um serviço da AWS usado para orquestração de contêineres docker na nuvem. O serviço de orquestração pode ser implementeado em 3 diferentes topologias: AWS Fargate (contêineres executados diretamente na infraestrutura gerenciada pela AWS), AWS EC2 (requer instanciar máquina AMI Linux ou Windows para executar o Docker na AWS) ou infraestrutura exterma on-premise. Além disso, o ECS permite fácil integração com outras ferramentas de alta disponibilidade, balanceamento e segurança da AWS.
O Amazon Elastic Container Registry (ECR) é um serviço para repositório/registro de contaêineres Docker. Ele pode ser uma alternativa para o Dockerhub ou o Portus (usado pela iTFLEX). Uma vantagem de usar o ECR é não se preocupar com a escalabilidade e disponibilidade dos recursos. O repositório é compatível com o AWS EKS, AWS ECS, AWS Lambda e também com outros ambientes de contêiner externos, como o docker puro no Linux, por exemplo.
Este laboratório irá focar na implementação básica, do zero, de um cluster de uma página Web Nginx na infraestrutura AWS ECR/ECS com instância EC2. Será apresentado todo procedimento manual para implementação, bem como a configuração do GitLab CI/CD para deployment automático.
Topologia
Necessário uma conta AWS, ambiente local para build de imagens do docker e GitLab para o CI / CD de deploy das imagens e publicação dos serviços.
Pacotes necessários
Máquina local:
- Docker - https://docs.docker.com/get-docker/
- AWS CLI version 2 - https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html
- Git
Amazon ECR - Configuração Manual
Para exemplificar, será feito push de uma imagem com base o nginx.
Dockerfile de exemplo:
FROM nginx
MAINTAINER iTFLEX Tecnologia <dev@itflex.com.br>
EXPOSE 80 443
COPY itflex.conf /etc/nginx/conf.d/itflex.conf
COPY server.key /etc/pki/itflex/server.key
COPY server.crt /etc/pki/itflex/server.crt
COPY itflex.conf /etc/nginx/conf.d/itflex.conf
COPY index.html /var/www/itflex/
Conf Nginx itflex.conf
de exemplo:
server {
listen 80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 default_server ssl http2;
server_name _;
ssl_certificate /etc/pki/itflex/server.crt;
ssl_certificate_key /etc/pki/itflex/server.key;
gzip on;
gzip_comp_level 3;
gzip_types text/plain text/css application/json text/javascript application/javascript;
root /var/www/itflex;
location / {
try_files $uri $uri/ /index.html;
}
}
Comando para gerar certificado auto-assinado:
openssl req -x509 -newkey rsa:4096 -nodes -keyout server.key -out server.crt -days 3650 -subj '/C=BR/ST=SC/L=Joinville/O=iTFLEX Tecnologia Ltda./OU=suporte/CN=itflex-fwm'
Para cada imagem de contêiner, é necessário criar um repositório privado em Amazon ECR > Create repository >

Para fazer build e push da imagem, a própria AWS instrui nos comandos. Basta acessar a tela de detalhes do repositório, em Amazon ECR > repositories > nome_repo_imagem e acessar as instruções em View push commands.

Os comandos de login, build e push para o repo nginxweb
ficaram da seguinte forma:
aws ecr get-login-password --region us-east-1 | sudo docker login --username AWS --password-stdin 757807667488.dkr.ecr.us-east-1.amazonaws.com
sudo docker build -t nginxweb .
sudo docker tag nginxweb:latest 757807667488.dkr.ecr.us-east-1.amazonaws.com/nginxweb:latest
sudo docker push 757807667488.dkr.ecr.us-east-1.amazonaws.com/nginxweb:latest
A imagem com a tag latest já está disponível para uso com docker.
Amazon ECS - Configuração Manual
Com a imagem 757807667488.dkr.ecr.us-east-1.amazonaws.com/nginxweb:latest
criada na seção anterior, o próximo passo é configuar o ECS.
Criação do cluster
O ECS cluster é um agrupamento regional de uma ou mais instâncias de contêiner. Neste laboratório, o cluster será ativado na região us-east-1
com apenas 1 instância.
O cluster deve ser criado em Amazon ECS > Clusters > Create Cluster> Na tela inicial do Wizard, deve ser selecionado a opção EC2 Linux + Networking, pois será instanciado uma VM Linux para a execução.
Instância configurada On-Demand, tipo t2.micro, número de 2 instâncias, AMI EC2 padrão, 30 GB de disco (padrão) e par de chaves criado para acesso SSH às instâncias (host docker).

Deve ser selecionado uma VPC, subnet e security group, conforme planejamento da infra na cloud e liberações necessárias para acesso à aplicação. Também é necessário permitir a criação da ecsInstanceRole, para as permissões do ECS container agent. Criar com demais opções default.

Caso selecione a criação de duas instâncias e selecione duas subnets, será provisionado uma instância em cada zona de disponibilidade selecionada.
Criação da Task Definition
A Task Definition, como o nome supõe, define um template de execução para a task. Ela especifica quantos cotêineres são executados pela task, recursos atrelados, imagens, volumes, links, portas mapeadas, etc.
A criação é feita em Amazon ECS > Task Definition > Create new Task Definition> Na tela inicial do Wizard, deve ser selecionado a opção EC2.
Define-se nome, Network Mode como Bridge, task memmory de 256 MB e task CPU como 1 vCPU (opções que definem limite de uso dos recursos).

Também é criado um volume, do tipo docker, com driver local. Este formato usa uma pasta local no armazenamento da máquina física. O scope tipo shared mantém o volume após a task ser destruída (persiste os dados).

Ainda dentro da configuração da Task Definition, é necessário criar um Container Definition. Ele especifica nome, imagem de contêiner, limite de memória e mapeamento de portas.

Na seção de Storage and Logging da definição de container, é mapeado o volume criado anteriormente para o diretório /var/www/itflex/
.

As demais opções podem utilizar o valor default.
OBS:
- É recomendado que para cada serviço seja definido uma Taks definition, pois permite escalar cada serviço independentemente;
- Links entre contêineres e dependências só são configurados dentro de uma mesma task definition;
- Os links de contêineres são configurações do docker, por este motivo rodam em um mesmo host sempre;
- Para serviços em diferentes tasks, que podem inclusive estar em diferentes hosts, deve ser usado outros mecanismos para comunicação entre os contêineres (roteamento da Amazon).
Criação do serviço
O serviço é criado dentro do cluster, em Amazon ECS > Clusters > nome_cluster_criado > Services > Create > A configuração define o launch type EC2, seleciona a task definition, seleciona o cluster, define um nome para o serviço e configura o Service type como replica com um número de 4 tasks criadas e distribuídas entre as 2 instâncias do cluster. Demais valores configurados default.

O modo réplica distribui o número de tasks definidas entre as instâncias. O modo daemon cria uma task fixa em cada instância do cluster.
Neste laboratório básico não foi definido load balancer e não foi ativado o recurso de auto-scaling.
Amazon ECR - GitLab CI/CD
Para o CI/CD do GitLab, foi programado build das imagens para quando executado em branches de desenvolvimento e build + deploy na branch master.
Foi escolhido utilizar o container oficial da Amazon para executar o aws-cli.
Bloco com os comandos essenciais:
fwm:publish:deploy-nginx:
<<: *deploy-fwm
image:
name: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/amazon/aws-cli
entrypoint: [""]
before_script:
- amazon-linux-extras install docker
script:
- aws ecr get-login-password | docker login --username AWS --password-stdin $FWM_AWS_DEV_URI
- cd fwm/src/docker/nginx/
- docker pull $DOCKER_IMAGE || true
- docker build --cache-from $DOCKER_IMAGE -t tmp .
- docker push $DOCKER_IMAGE
variables:
DOCKER_IMAGE: ${FWM_AWS_DEV_URI}/nginx
A variável FWM_AWS_DEV_URI está definida nas configurações do GitLab.
OBS: Não foi apresentado a estrutura completa do CI. O bloco acima deve ser inserido no fluxo do pipeline de entrega do produto.
Amazon ECS - GitLab CI/CD
Para o deploy do serviço via GitLab CI/CD, também foi utilizado o aws-cli e definido utilizar as configurações de templates JSON.
Bloco com os comandos essenciais:
fwm:staging:deploy-cluster:
<<: *deploy-fwm-ecs-cluster
image:
name: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/amazon/aws-cli
entrypoint: [""]
script:
- aws ecs register-task-definition --cli-input-json file://fwm/src/aws/task-definition-template.json --region us-east-1
#- aws ecs create-service --cli-input-json file://fwm/src/aws/ecs-create-service-template.json --region us-east-1
- aws ecs update-service --cli-input-json file://fwm/src/aws/ecs-update-service-template.json --region us-east-1
variables:
DOCKER_IMAGE: ${FWM_AWS_DEV_URI}/nginx
TASK_DEFINITION_NAME: itflex-nginx
CLUSTER_NAME: itflex-nginx
SERVICE_NAME: itflex-nginx
Sempre que uma nova versão de imagem for construída, é necessário atualizar a task definition e atualizar o serviço para subir os contêineres na nova versão.
Arquivo JSON de configuração mínima para a task definition:
{
"family": "nginx-web",
"networkMode": "bridge",
"containerDefinitions": [
{
"name": "nginx-web",
"image": "757807667488.dkr.ecr.us-east-1.amazonaws.com/nginx:latest",
"cpu": 0,
"memoryReservation": 256,
"portMappings": [
{
"containerPort": 80,
"hostPort": 80,
"protocol": "tcp"
},
{
"containerPort": 443,
"hostPort": 443,
"protocol": "tcp"
}
],
"essential": true,
"mountPoints": [
{
"sourceVolume": "web",
"containerPath": "/var/www/itflex/",
"readOnly": true
}
]
}
],
"volumes": [
{
"name": "web",
"dockerVolumeConfiguration": {
"scope": "shared",
"autoprovision": true,
"driver": "local"
}
}
],
"requiresCompatibilities": [
"EC2"
],
"cpu": "1024",
"memory": "256"
}
Esta estrutura básica, como todos os campos possíveis, pode ser gerada através do comando aws ecs register-task-definition --generate-cli-skeleton
.
Arquivo JSON de configuração mínima para a criação do serviço:
{
"cluster": "itflex-nginx",
"serviceName": "nginx-web-access",
"taskDefinition": "nginx-web",
"launchType": "EC2",
"deploymentConfiguration": {
"deploymentCircuitBreaker": {
"enable": false,
"rollback": false
},
"maximumPercent": 100,
"minimumHealthyPercent": 0
},
"schedulingStrategy": "DAEMON",
"enableECSManagedTags": true,
"enableExecuteCommand": false
}
Esta estrutura básica, como todos os campos possíveis, pode ser gerada através do comando aws ecs create-service --generate-cli-skeleton
.
Arquivo JSON de configuração mínima para update do serviço:
{
"cluster": "itflex-nginx",
"service": "nginx-web-access",
"taskDefinition": "nginx-web",
"deploymentConfiguration": {
"deploymentCircuitBreaker": {
"enable": false,
"rollback": false
},
"maximumPercent": 100,
"minimumHealthyPercent": 0
},
"forceNewDeployment": true,
"enableExecuteCommand": false
}
Esta estrutura básica, como todos os campos possíveis, pode ser gerada através do comando aws ecs update-service --generate-cli-skeleton
.
Dica: O JSON parece complexo e com muitos parâmetros. A dica é se basear nos parâmetros obrigatórios que foram preenchidos via interface web. Os demais podem ser mantidos default ou apagados. A ferramenta sempre avisa os parâmetros inválidos e os que não podem ser vazio. Tentativa e erro até chegar na configuração mínima para a execução do serviço.
Amazon ECS - GitLab CI/CD para os dados de volumes
Como foi mapeado um volume para os contêineres, foi adicionado no CI/CD a transferência dos dados para o volume do docker. A configuração foi realizada de maneira simples, via scp para a pasta no servidor.
Bloco de configuração:
fwm:staging:deploy-volume-html:
<<: *deploy-fwm-volume-data
image: docker.itflex.com.br/ci/deploy:centos8
script:
- scp -o "LogLevel=error" -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking=no" fwm/src/docker/nginx/index.html ec2-user@$ECS_CLUSTER_HOST:/tmp/
- ssh -tt -o "LogLevel=error" -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking=no" ec2-user@$ECS_CLUSTER_HOST "sudo cp /tmp/index.html /var/lib/docker/volumes/html/_data/"
variables:
ECS_CLUSTER_HOST: ec2-3-91-223-158.compute-1.amazonaws.com
No exemplo acima foi copiado via scp para o diretório do volume html. Este volume foi criado com scope shared, ou seja, permanece os dados após remover a task.
Testes das funcionalidades
O teste para esta aplicação é simples: Acessar o IP público das instâncias e visualizar a página web.
OBS: O security group deve estar liberando a entrada para as portas da aplicação.
Resultados
Pontos positivos:
- Facilidade para manter imagens do docker
- Alta disponibilidade e escalabilidade dos recursos
- Otimização de recursos (vários contêineres na mesma VM na Amazon)
Desafios/dificuldades:
- Domínio e conhecimento das ferramentas escolhidas
- Conhecimento dos recursos avançados da AWS:
- Orquestração dos serviços
- dependência entre contêineres
- comunicação de rede
- storage
- Topologia e segurança: Desenho das VPCs, subnets e security groups
- Load balance: Ainda não explorado/estudado
- Debug e análise com cloudwatch e outros recursos: Ainda não explorado/estudado
- Integração com pipeline de entrega do GitLab CI/CD
Links relacionados
- https://aws.amazon.com/pt/ecr/
- https://aws.amazon.com/pt/ecs/
- https://medium.com/devops-with-valentine/gitlab-ci-build-push-docker-image-to-aws-ecr-elastic-container-registry-b63b91a58728
- https://medium.com/swlh/deploying-your-app-with-ecs-gitlab-ci-cd-e211e6f103e1
- https://faun.pub/deploy-to-aws-ecs-using-gitlab-ci-7beb40dbf9
- https://docs.aws.amazon.com/pt_br/AmazonECS/latest/developerguide/instance-connect.html
- https://docs.docker.com/cloud/ecs-integration/
- https://ec2spotworkshops.com/ecs-spot-capacity-providers/module-1/service.html
- https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html
- https://stackoverflow.com/questions/55857004/delete-key-value-pair-with-jq-when-key-is-a-known-string
- https://stackoverflow.com/questions/34834519/how-do-i-select-multiple-fields-in-jq
- https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ECS_AWSCLI_EC2.html
- https://docs.aws.amazon.com/cli/latest/reference/ecs/register-task-definition.html
- https://docs.aws.amazon.com/pt_br/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-taskdefinition-containerdefinitions-mountpoints.html
- https://ec2spotworkshops.com/ecs-spot-capacity-providers/module-1/service.html
- https://docs.aws.amazon.com/cli/latest/reference/ecs/create-service.html
- https://docs.aws.amazon.com/pt_br/AmazonECS/latest/developerguide/create-service-discovery.html
- https://docs.aws.amazon.com/AmazonECS/latest/developerguide/application_architecture.html
- https://www.linkedin.com/pulse/using-amazon-api-gateway-microservices-deployed-ecs-hui-zhou/
- https://aws.amazon.com/pt/blogs/compute/using-amazon-api-gateway-with-microservices-deployed-on-amazon-ecs/
- https://www.docker.com/blog/containerized-python-development-part-1/
- https://realpython.com/python-versions-docker/
- https://blog.realkinetic.com/building-minimal-docker-containers-for-python-applications-37d0272c52f3
- https://www.proud2becloud.com/deploy-a-docker-compose-application-inside-an-aws-environment-new-with-ecs-cli/