Deploy Spring Boot + Docker na AWS ECS com Github Actions e AWS ECR

1. Introdução
Neste tutorial iremos automatizar o deploy de uma aplicação Spring Boot com Docker na AWS.
Iremos utilizar o Github Actions para automatizar nossa tarefa de deploy.
A tarefa irá publicar uma imagem docker da nossa aplicação no Amazon Elastic Container Registry (ECR). Que será executada em um cluster no Amazon Elastic Container Service (ECS).
Etapas que iremos seguir:
- Criar um usuário no Identity and Access Management (IAM);
- Criar repositório no Elastic Container Registry (ECR);
- Criar um cluster, serviço e uma task definition no Elastic Container Service (ECS);
- Criar aplicação Spring boot;
- Criar arquivo Dockerfile;
- Configurar repositório Github
2. Pré-requisitos
Para seguir este tutorial, irei considerar que você possui conhecimento básico em:
- Git e Github;
- Docker;
Irei considerar que já possui cadastro na AWS:
3. Identity and Access Management (IAM)
Acesse sua conta na AWS. (link de acesso).
No AWS Management Console pesquise por IAM.
Ao acessar o IAM. No menu lateral localize e selecione o item Users.
Após selecione a opção Add User
:

Na próxima tela, insira um nome para o usuário no campo User name*
.
Em Access type*
selecione Programmatic access
. Está opção irá gerar uma id e secret key, que iremos configurar em nosso repositório no Github, para possibilitar o deploy com o Github Action.
Clique Next: Permissions
:

Agora iremos atribuir permissões para o usuário.
Em Set permissions
selecione o box Attach existing policies directly
, e selecione a permissão: AdministratorAccess

Clique em Next: Tags
.
Na próxima etapa podemos adicionar tags ao usuário para organização, descrição, etc... Podemos deixar em branco, e seguir.
Clique em Next: Review
no canto inferior direito de sua tela.
Nesta etapa revise se as informações estão corretas. E por fim clique em Create User
:

Se lembra que selecionamos a opção Programmatic access
, e eu disse que seria gerarado uma id e secret key.
Na próxima tela será apresentado para você estes dados. Copie o Access Key ID
e Secret access key
.
Iremos precisar delas para configurar em nosso repositório no GitHub.

4. Elastic Container Registry (ECR)
Por definição no próprio site da AWS, o ECR é:
Um registro de contêiner totalmente gerenciado que facilita o armazenamento, gerenciamento, compartilhamento e implantação de imagens e artefatos de contêiner em qualquer lugar.
Resumindo…será onde iremos disponibilizar a imagem Docker da nossa aplicação para posterior deploy.
Vá no AWS Management Console
.
Em Containers
selecione a opção Elastic Container Registry
:

Ao entrar na página do Elastic Container Registry
, localize no menu lateral a opção Repositories
, para que possamos criar nosso repositório.

Ao selecionar Repositories
, no canto direto da tela clique em Create Repository
:

Nas configurações do repositório selecione qual será a visibilidade do repositório, e defina um nome.
Para nosso exemplo criei um repositório privado.

As demais configurações podemos deixar o padrão, e ir até o final da página, e clicar em Create Repository
.

No topo da tela irá aparecer uma barra verde informando que nosso repositório foi criado com sucesso.
OBS: Na barra verde de confirmação, é dado a opção
View push commands
. Onde podemos ver os comandos necessários para realizar a publicação de algum artefato neste repositório que criamos. Mostra comandos tanto para macOS/Linux quanto para Windows. — Não iremos ver sobre isso neste artigo pois a ideia é que tudo isso seja automatizado com o Github Actions.
OBS: copie a URI do seu repositório, pois iremos utilizar mais a frente.
5. Elastic Container Service (ECS), Task Definition e Service
Agora precisamos ter onde executar a imagem da aplicação que será disponibilizada no repositório que criamos.
E para isso iremos configurar um cluster no ECS.
Definição dada pela própria AWS:
Um cluster do Amazon ECS é um agrupamento lógico de tarefas ou serviços. Se estiver a executar tarefas ou serviços que utilizam o EC2 tipo de inicialização,um cluster é também um agrupamento de instâncias de contentor. Se você estiver usando provedores de capacidade, um cluster também é um agrupamento lógico de provedores de capacidade. Na primeira vez que você usar o Amazon ECS, um cluster padrão será criado, mas você poderá criar vários clusters em uma conta para manter os recursos separados.
De forma simplificada, é um orquestrador de serviçios. Onde iremos “rodar” nossa aplicação.
O ECS é algo muito mais completo/complexo, do que o apresentado aqui. Caso queira saber mais, recomendo a leitura documentação oficial. Você pode encontrar aqui.
Agora vamos configurar nosso cluster:
PASSO 1:
No mesmo menu lateral onde criamos nosso repositório, iremos agora na opção Clusters
:

Na próxima página, clique em Create Cluster
:

Selecione o template EC2 Linux + Networking
e clique em Next: Step
:

Nas configurações do cluster, dê um nome para o cluster.
Na opção EC2 instance type
selecione a opção t2.micro
para utilizar um free tier.

Mais abaixo, em Container instance IAM Role
, deixe selecionado a opção Create new role
.
Como a própria descrição do item nos informa, nosso container ECS fará chamadas para a Amazon ECS API
em nosso nome. Ao deixarmos selecionada a opção Create new role
, durante a criação do cluster, será criada uma role com a permissão necessária para está comunicação.

Vá até o final da página e clique em Create
.
A inicialização do cluster pode levar alguns minutos…na tela seguinte podemos acompanhar o progresso da criação do cluster:

Quando o cluster estiver pronto podemos clicar em View cluster
.
Confirme que o status do cluster seja: ACTIVE:

PASSO 2:
Agora iremos criar uma task definition.
Task Definition, é necessária para executar contêineres do Docker no nosso cluster Amazon ECS. Alguns dos parâmetros que podemos especificar são:
- A imagem do Docker a ser usada com cada contêiner em sua tarefa;
- A CPU e a memória a serem usadas com cada tarefa;
- O tipo de inicialização a ser usado, que determina a infraestrutura na qual as tarefas são hospedadas;
- O modo de rede do Docker a ser usado para os contêineres na tarefa;
- A configuração de registro em log a ser usada para suas tarefas;
- Se a tarefa deverá continuar sendo executada se o contêiner for concluído ou falhar;
- O comando que o contêiner deve executar quando iniciado;
- Eventuais volumes de dados que devem ser usados com os contêineres na tarefa;
- A função do IAM que suas tarefas devem usar
Você pode ler mais na documentação oficial da AWS sobre Task Definition.
Ainda na página da Amazon ECS. No menu lateral clique na opção Task Definition
e depois em Create new Task Definition
:

Selecione a compatibilidade EC2
— pois foi o template que utilizamos para nosso cluster:

Na próxima tela, dê um nome para a sua Task Definition.
Na opção Task Role
, deixe selecionado NONE.
No item mais abaixo Task Execution IAM role
, deixaremos selecionada a opção Create new role
.
Assim será criado uma role com o nome ecsTaskExecutionRole
que irá possuir as permissões necessárias.

No campoNetwork Mode
selecione a opção Bridge
para que possamos ter um host e uma porta de contêiner diferentes.

Vá até o item Container Definitions
e clique em Add container
:

Dê um nome para o seu container.
No campo image
coloque a URI do repositório que criamos no AWS ECR (tópico 4. Elastic Container Registry), no final da URI deixe com :latest
para que sempre seja feito o deploy da imagem mais recente do seu repositório.
Em Memory Limits(Mib)*
devemos informar o limite de memória para o nosso container. Temos duas opções — sendo que podemos configurar as duas:
- Soft limit -> a ECS irá reservar a quantidade de memória espeficiada para nosso container, e caso precise de mais irá solicitar ou até o Hard Limit(caso este seja configurado) ou até o limite da nossa máquina;
- Hard Limit -> a ECS terá este valor como o limite máxima para nosso container. E caso o container tente exceder este limite ele será “derrubado”
Um ponto importe aqui, é que o limite de memória que temos aqui, é até o máximo do cluster configurado. No nosso caso, como configuramos uma instância t2.micro o nosso máximo será de 1GiB. Você pode ler mais sobre as especificações de cada tipo de instancia aqui.
Para nosso exemplo podemos configurar apenas o Soft limit
com 983 MiB.
No campo Ports mappings
configurei tanto o Host port
quanto Container port
com o valor 8091. Aqui você pode colocar a porta de sua preferência.
OBS: o
Container port
deverá ser a porta que você configurar para sua spring boot app rodar.

As demais configurações não iremos alterar.
Clicar no botão Add
no canto inferior direito da sua tela.
Você voltará para a tela de criação da Task Definition.
Clique em Create
no fim da página:

Você será redirecionado para a tela abaixo. Deixe aberto na aba JSON
, como abaixo. Iremos utilizar este JSON
em nosso projeto Sring Boot.

PASSO 3:
Agora vamos criar um serviço no nosso cluster.
E para isso precisamos voltar no nosso cluster, ir na aba Services
e clicar em Create
:

Em Launch type
selecione a opção EC2
.
Em Task Definition
, selecione a task que criamos no passo anterior. Em cluster selecione o cluster que criamos anteriormente. Dê um nome para o serviço no campo Service name
. Em Number of tasks
insira “1”.
As demais opções manteremos o padrão.

Vá até o fim da página, e clique em Next step
até chegar no Step 4: Review
. Confira as informações, e clique em Create Service
.
6. Spring boot app / Docker
Para simplificar, podemos criar nossa aplicação pelo spring initializr.
Configurações da aplicação:
- Maven Project
- Java 8
- Spring boot 2.4.1
- Packaging Jar

Dependência para o projeto:
- Spring Web —Para aplicações web, RESTful, utilizando Spring MVC. Utiliza o Apache Tomcat como servidor de aplicação embedded(embutido)
Após criar o projeto…na pasta raiz, crie um arquivo chamado Dockerfile
e coloque as seguintes configurações:
Alguns pontos importantes sobre as informações que precisamos ter no Dockerfile:
- Linha 4: o valor deste argumento deve ser a região que o seu cluster ECS foi criado. Você pode conferir nos detalhes do seu cluster, no campo Cluster ARN. Caso esteja com dúvida volte ao item 5. Elastic Container Service (ECS) e Task Definition deste post, no finalzinho do passo 1;
- Linhas 8 e 9: É a chave de segurança que foi gerada para o nosso usuário que criamos nos item 3 deste post;
- As demais linhas não iremos abordar neste artigo. Caso tenha interesse pode acessar a documentação oficial do Docker aqui.
Agora na raiz do projeto crie um arquivo chamado task-definition.json
.
Lembra que pedi para deixar aberto a tela quando finalizamos a criação da Task Definition…essa aqui:

Copie o conteúdo da aba JSON, e cole no arquivo task-definition.json
do seu projeto.
No arquivo de propriedades da nossa aplicação, precisamos adicionar a propriedade server port
. Informe a porta que nossa aplicação irá executar:

7. Configurar credenciais no GitHub
No repositório do Github do projeto que irá utilizar para executar o pipeline com o Github Actions, vá na em settings. No item Secrets
(lateral esquerda da tela). No lado direito da tela, clique no botão New repository secret
, e insira as credenciais que foram geradas na criação do nosso usuário:

Na secret AWS_ACCESS_KEY_ID a access key id gerada na criação do usuário, e na secret AWS_SECRET_ACCESS_KEY inserir a Secret access key.
8. Configurar GitHub Actions
Ainda no seu repositório do GitHub…na aba Actions
:

Encontre a seção Deploy your code with these popular services
, e na opção Deploy to Amazon ECS
, clique em Set up this workflow
:

Você será redirecionado para um editor, com um arquivo criado a partir de um template:

O arquivo criado será criado no seguinte caminho no seu projeto: <nome-do-seu-projeto>/.github/workflows/aws.yml
.
Neste arquivo aws.yml
nós teremos os passos (steps), do trabalho(job) que o Github Actions irá executar quando um gatilho/evento — que iremos determinar — for acionado.
Existem alguns gatilhos/eventos, que acionam o workflow do Github Actions. Você pode ver a lista completa de eventos na documentação do Github Actions aqui.
Para o nosso tutorial, o gatilho/evento que acionará o Github Actions, será quando for gerado uma nova release da aplicação no Github.
Para isso altere no inicio do arquivo aws.yml
a propriedade on
, para que fique desta maneira:
on:
release:
types: [published]
Além da alteração do tipo de gatilho/evento, iremos inserir mais um step no arquivo aws.yml, para que seja realizado o build do nosso projeto — coloque este como o primeiro step do seu workflow:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Maven
run: mvn -B package --file pom.xml
No step de configuração de credenciais a propriedade aws-region
, por padrão vem us-east-2. Caso a sua conta na AWS seja em outra região, coloque a região correta no arquivo.
No step que realiza o build da nossa imagem, precisaremos alterar, para adicionar os argumentos que nosso Dockerfile aguarda.
No comando docker build, precisamos passar os argumentos de id e secret key, que deixamos parametrizavéis no Dockerfile.
Nosso step build-image, ficará assim:
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: <NOME DO SEU REPOSITÓRIO>
IMAGE_TAG: ${{ github.sha }}
run: |
# Build a docker container and
# push it to ECR so that it can
# be deployed to ECS.
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG --build-arg ACCESS_ARG=${{ secrets.AWS_ACCESS_KEY_ID }} --build-arg SECRET_ARG=${{ secrets.AWS_SECRET_ACCESS_KEY }} .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
Lembrando que o secrets.AWS_ACCESS_KEY_ID e secrets.AWS_SECRET_ACCESS_KEY são as chaves que criamos no nosso repositório do Github.
Nos últimos dois steps, confirme que no campo task-definition
, está com o mesmo nome do arquivo que criamos na raiz do projeto.
No campo container-name
, coloque o nome que definiu para seu container — caso não se lembre, pode conferir no arquivo task-definition.json
na propriedade: containerDefinitions > name.
Atribua no campo service
, o nome do seu serviço que criamos no item 5. Elastic Container Service (ECS), Task Definition e Service.
E no campo cluster
coloque o nome que foi definido para o cluster em que será feito o deploy.

Por fim, clique em Start commit
.

- Arquivo
aws.yml
de exemplo:
9. Hora da verdade 😎…
Passo 1: Gerar Release
No repositório do seu projetono Github, na aba Code
no lado direito da tela temos o item Releases
. Clique em Create a new release
:

Você será redirecionado para a tela abaixo.
Adicione uma tag para esta release e selecione a branch a partir da qual a release deve ser gerada. E clique em Publish release
:

Podemos acompanhar, e verificar se tudo correu bem na aba Actions
:

Criei um endpoint na aplicação, para testar que à aplicação está de pé e respondendo:

Parabéns por chegar até aqui.
Você criou uma aplicação Spring Boot, fez ela rodar com Docker na AWS. Com o processo de deploy automatizado, utilizando Github Actions.
Obrigado pela leitura! Qualquer sujestão…fique à vontade.