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:

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