Implantando um aplicativo React na AWS com Gitlab CI
Gitlab é uma ferramenta web que fornece hospedagem de repositórios Git em conjunto com diversos outros serviços relacionados a DevOps como, por exemplo, pipelines de Integração e Implantação Contínua. Isso permite que desenvolvedores consigam automatizar o ciclo de vida de DevOps como um todo, desde o build (construção) até a implantação, tudo dentro do mesmo ambiente no qual o código fonte está hospedado.
Nesse artigo iremos focar na criação de um arquivo chamado “.gitlab-ci.yml”, que é responsável por implementar ambos processos de integração Contínua e Implantação Contínua para o nosso projeto. Ele cria um pipeline que consiste em uma ou mais etapas que são executadas sequencialmente, de forma que cada etapa pode conter uma ou mais tarefas (jobs/scripts) que podem ser executadas paralelamente.
Para poder executar “.gitlab-ci.yml”, esse arquivo deve ser colocado na raíz do repositório e deve-se instalar um Gitlab Runner, que é o responsável por operar o arquivo. Para mais detalhes sobre a instalação do Gitlab Runner, ver http://docs.gitlab.com/ee/ci.
Portanto, antes de iniciar a codificação do arquivo, é importante planejar todas as etapas e tarefas que serão necessárias para que se consiga atingir os resultados esperados com o maior custo-benefício possível.
Definindo etapas
O nosso código fonte é baseado em uma aplicação React para front-end. Isso significa que:
1) Ele gera arquivos estáticos para implantação.
2) Ele pode ser implantado em qualquer hospedeiro que possa hospedar arquivos estáticos. No nosso caso, escolhemos o Amazon S3, pois ele é a opção mais fácil e rápida para implantação de um projeto front-end dentro do ecossistema AWS.
Além disso, nós queremos ter 2 ambientes para nossa aplicação, sendo um para testes de novas funcionalidades e outro para os clientes propriamente.
Diante dessas condições, nós podemos dividir nosso arquivo “.gitlab-ci.yml” em duas etapas: build (construção) e deploy (implantação).
Etapa build
Nessa etapa, geramos os arquivos estáticos da nossa aplicação. Se não existir nenhum comando de build definido dentro do arquivo “package.json” na propriedade “scripts”, podem ser utilizados “npm run build”, “yarn build” ou um comando de acordo com o seu gerenciador de pacotes de preferência.
O comando de build irá gerar uma pasta chamada “/build”, que conterá todos os arquivos estáticos necessários. Precisaremos que essa pasta esteja disponível na etapa de deploy, visto que seus arquivos serão enviados para o bucket S3.
Segue o código referente a etapa de “build”:
.yarn_build:
image: node:10
script: |
yarn # Install all dependencies
yarn build:${APP_ENV} # Build command
artifacts:
paths:
- ./buildyarn_dev:
extends: .yarn_build
stage: build
before_script:
- export APP_ENV="dev"
only:
refs:
- develop
yarn_prod:
extends: .yarn_build
stage: build
before_script:
- export APP_ENV="prod"
only:
refs:
- master
No código acima, foram definidas 2 tarefas “yarn_dev” e “yarn_prod” que representam os ambientes que queremos para nossa aplicação. Ambas extendem de “yarn_build” já que elas contém a variável que define o ambiente para o qual o build será realizado. A possibilidade de se utilizar yarn para o comando de build se deve ao uso da imagem “node:10” (do Docker Hub), que já vem com o yarn pré-instalado. Para mais informações sobre imagens node disponíveis: https://hub.docker.com/_/node/.
Cada tarefa irá executar dependendo do branch que o commit tiver sido feito. Um commit feito em “develop” vai acionar a tarefa “yarn_dev” e um commit em “master” vai acionar “yarn_prod”. Commit em outros branches não vão acionar nenhum pipeline, pois queremos economizar o uso de recursos o máximo possível. A estrutura “only-refs” já deixa em aberto a possibilidade de múltiplos branches acionarem um mesmo pipeline caso isso venha a ser necessário.
Por fim, definimos a pasta “/build” como um artefato da nossa etapa de build. Dessa forma, ela estará acessível na etapa seguinte.
Etapa deploy
Nessa etapa, enviamos todos os arquivos estáticos gerados para dentro do nosso bucket S3. Dado que queremos 2 ambientes, foram criados 2 buckets, um para cada ambiente. Os nomes dos buckets são definidos como variáveis de ambiente dentro das configurações do repositório. Para definir essas variáveis, basta ir em “Settings” => “CI/CD” => seção Variables.
Ao definir variáveis nesse menu, elas se tornam acessíveis dentro do “.gitlab-ci.yml”. A vantagem é poder alterar os valores sem a necessidade de atualizar o arquivo em si.
Assim, foram definidos ambos os nomes dos buckets e as credenciais AWS requeridas para poder executar o comando “s3 sync” como pode ser visto abaixo:
.deploy_aws:
image: python:latest
when: manual
script: |
pip install awscli #Install awscli tools
aws s3 sync ./build/ s3://${S3_BUCKET}deploy_dev:
extends: .deploy_aws
stage: run
dependencies:
- yarn_dev
before_script:
- export S3_BUCKET=${S3_BUCKET_DEV}
only:
refs:
- developdeploy_prod:
extends: .deploy_aws
stage: run
dependencies:
- yarn_prod
before_script:
- export S3_BUCKET=${S3_BUCKET_PROD}
only:
refs:
- master
Utilizamos uma imagem python, dado que ela possui a implementação mais robusta do awscli. O awscli é instalado dentro da imagem por meio do gerenciador de pacotes “pip” e, posteriormente, o comando “s3 sync” pode ser executado para enviar os arquivos estáticos no respectivo bucket. Para esse processo ocorrer com sucesso, o bucket em questão já deve estar criado. Para mais detalhes em implantação Gitlab, ver https://about.gitlab.com/blog/2016/08/26/ci-deployment-and-environments/.
Ademais, para executar o comando “s3 sync”, os valores de AWS_ACCESS_KEY and AWS_SECRET_ACCESS_KEY devem estar definidos. Eles podem ser obtidos quando entrar em sua conta AWS e acessa o menu: “Security Credentials” => “Create access key”.
Após clicar no botão, uma caixa de diálogo aparecerá com ambos os valores. Quando essa caixa for fechada, não será mais possível acessar tais valores. Por isso, recomenda-se baixar as credenciais em um arquivo no formato .csv.
AWS: Hospedagem na web
Com a execução bem sucedida do “.gitlab-ci.yml”, todos os arquivos estáticos deverão estar dentro do bucket. Porém, isso não significa que eles já estão sendo servidos. Para habilitar essa funcionalidade, primeiro abra o bucket. Ele irá inicialmente abrir com a aba “Overview” selecionada, então selecione a aba “Properties” e algumas caixas irão aparecer. Escolha “Static website hosting” como mostrado abaixo.
Com a caixa aberta, selecione “Use this bucket to host a website”, preencha ambos os campos “Index document” e “Error document” com index.html e salve. Com isso feito, um endpoint será criado, mas ainda não estará acessível.
Para habilitar acesso público para o endpoint, vá para a aba “Permissions” e adicione a seguinte permissão de bucket:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::my-awesome-app/*"
]
}
]
}
Dentro do objeto “Resource”, insira o nome do seu bucket e clique no botão “Save”. De agora em diante, a aba “Permissions” irá mostrar um crachá Public.
Incrível! Agora temos nossa aplicação React hospedada e acessível de qualquer lugar!
Conclusão
Com todas as etapas e variáveis definidas, nós pudemos criar um simples, mas poderoso script que automatiza todo o processo de implantação da nossa aplicação. A partir de agora não precisamos mais nos preocupar em como proceder com a implatanção toda vez que precisarmos realizá-la.
O script é fácil de ser lido e compreendido, além de ser suficientemente flexível para escalar de acordo com as demandas que vierem a surgir para a aplicação e/ou novas estratégias que a equipe tiver.
Segue o arquivo “.gitlab-ci.yml” completo: