Um exemplo de Continuous Deployment com Gitlab CI e Heroku

Um exemplo de Continuous Deployment com Gitlab CI e Heroku

O Gitlab além de ser um excelente gerenciador de repositórios Git também permite que sejam criados pipelines para os projetos. O Heroku por sua vez fornece uma plataforma excepcional para executar e escalar aplicações. Nesse post vamos mostrar um exemplo de como usar essas duas ferramentas para fazer Continuous Deployment de uma aplicação simples escrita em Node JS.

 

i've got a little black book with my poems in...

Vamos colocar isso logo pra rodar ...

Antes de mais nada vamos garantir que tudo está configurado corretamente para depois rodarmos o pipeline de CD. Vamos executar alguns passos e colocar uma aplicação de exemplo no ar.

A aplicação de exemplo é a Gitlab CI Example, feita em Node JS com React. Ela tem uma parte server com o endpoint /message que gera uma....adivinha....uma mensagem, e um cliente em React que consome esse serviço e apresenta a mensagem. Você pode consultar o README para obter mais detalhes.

Tem conta no Gitlab.com? Se não tiver crie uma for free.
Comece fazendo o fork do repositório e o clone em sua máquina, dessa forma você conseguirá seguir os passos descritos a seguir.

Configurando o Heroku

Para executar esse passo você deve ter uma conta no Heroku, caso ainda não tenha crie uma gratuitamente. Também faça a instalação do Heroku CLI conforme descrito na página.

Vamos user o Heroku CLI para criar um projeto. Entre no diretório que você clonou do Gitlab e execute o comando :

heroku login # informe as suas credenciais do Heroku

heroku create  

Pronto, sua aplicação foi criada e o Heroku CLI mostrará o nome dela no console. Algo como:

Creating app... done, ⬢ afternoon-harbor-69305  
https://afternoon-harbor-69305.herokuapp.com/ | https://git.heroku.com/afternoon-harbor-69305.git  

Esse afternoon-harbor-69305 foi gerado automaticamente pelo Heroku e é o nome da MINHA aplicação. No seu caso será outro nome, sei lá, tipo i-love-burzum-666. Além do nome da aplicação o CLI também mostra a URL que ela será publicada e o endereço do repositório Git usado para fazer deploy no Heroku. A ideia aqui é simples, se você fizer um push nesse repositório do Heroku ele vai fazer o deploy da aplicação! Guarde esse nome da aplicação que depois vamos usá-lo. Vamos chamá-lo por enquanto de APP_NAME.

Estamos bem por enquanto, sua aplicação está configurada no Heroku e se quiser fazer um deploy básico para testar basta fazer push do código para o repositório do Heroku com o comando:

git push heroku master # 🙏 🙏  
heroku open  

O heroku open é um atalho para abrir a URL da aplicação no browser. Você deverá ver algo assim oh:

Parabéns, sua aplicação está rodando no Heroku 🤘

Precisamos só fazer uma última coisinha! Para fazer deploy pelo Gitlab CI vamos precisar do API Token do Heroku. Execute o seguinte comando e guarde o resultado, esse é o token! Vamos chama-lo de AUTH_TOKEN.

heroku auth:token  

 

is there anybody out there?

Configurando o Gitlab CI

O Gitlab CI é o responsável por fazer o build da nossa aplicação e o deploy no Heroku. Isso tudo é feito com base na declaração do pipeline disponível no arquivo .gitlab-ci.yml (note que ele é oculto) que está na raiz do projeto. O arquivo disponível já foi configurado para usar variáveis de ambiente, portanto, basta que você defina essas variáveis e seja feliz.

Entre no Gitlab e vá para as configurações do projeto (aquele que você fez fork). Na página do projeto clique em Settings -> Pipelines e procure a opção Secret variables. Se quiser use essa URL: https://gitlab.com/<your-user>/gitlab-ci-example/settings/ci_cd. Adicione as seguintes variáveis:

  • APP_NAME: com o nome da aplicação no Heroku
  • AUTH_TOKEN: a API token do Heroku

Após adicionar você deverá ver algo como:

Perfeito! Variáveis adicionadas, basta executar o pipeline. Volte para a página principal do projeto, clique no menu Pipelines (esse link https://gitlab.com/<my-user>/gitlab-ci-example/pipelines). Você deverá ver uma tela com o botão Run Pipelines:

Observe a tabelinha com os Pipelines executados deverá aparecer vazia para você!

Vai lá, clique em Run Pipeline. Na tela seguinte você deverá informar o branch para o qual será executado o pipeline. Preencha com master e clique em Create Pipeline. Pronto, agora é só acompanhar a execução pela tela seguinte.

Aguarde o pipeline terminar de executar e você terá sua aplicação quentinha rodando no Heroku!

Viu, nem foi tão difícil assim!! Toda vez que você fizer um push nesse repositório será executado o pipeline. Doido!!

 

that'll keep you going through the show...

O que aconteceu? Magia negra?

O CD funcionou, isso é bom, mas vamos entender como tudo aconteceu.

Como citamos anteriormente, o arquivo .gitlab-ci.yml define o pipeline que deverá ser executado para o projeto. Assim, toda vez que fizermos um push no repositório e o .gitlab-ci.yml estiver presente o Gitlab executará o pipeline. O pipeline, por sua vez, é composto de jobs. Cada job define os comandos que serão efetivamente executados. Nesse fluxo ainda existe um componente muito importante que é o Runner. O Runner é quem efetivamente vai executar os comandos dos jobs. Existem diversos tipos de Runners e eles podem ser configurados conforme a necessidade de cada aplicação ou empresa. No caso do Gitlab.com são definidos alguns Runners compartilhados e executados em containers Docker. Caso queira, você pode criar os seus próprios Runners e rodá-los em seu ambiente, mas isso é assunto para outro post.

Mas vamos pensar, se o Runner é responsável por executar os comandos dos jobs, como ele consegue resolver todos os comandos possíveis, todas as possibilidades de todos os projetos? Bem, ele não consegue! Temos 2 alternativas: usar imagens Docker ou mandar instalar pacotes do SO nos nossos jobs. Claro que usar Docker é melhor e mais fácil, mas essas são as opções. No nosso exemplo usaremos uma imagem Docker com Node JS para que possamos executar o npm.

Independente do CI que for usado, é necessário saber a sequência de passos que devem ser executados até chegar no deploy. Podemos definir várias sequências diferentes para executar esse processo. Definimos uma simples que faz o que precisamos, conforme descrito abaixo:

1. Fazer pull do projeto
2. Instalar as dependências
3. Fazer o build (isso envolve minifier, uglyfier, whateverifier, ...)
4. Executar testes
5. Gerar pacote
6. Fazer deploy

Observe que se qualquer um desses passos falhar o processo todo falha, ou em outras palavras, o pipeline inteiro falha!

Como estamos usando o Gitlab CI e o Heroku, alguns desses passos já serão feitos automaticamente. Por exemplo, o Gitlab CI já fez o pull do repositório e a geração do pacote também não é necessária para fazer o deploy no Heroku. No final teremos os seguintes passos:

2. Instalar as dependências
3. Fazer o build
4. Executar testes
6. Fazer deploy

Supondo que definimos scripts de build e test no package.json, então podemos traduzir esses passos nos seguintes comandos:

2. npm install
3. npm run build
4. npm run test
6. ...aqui embolou o meio campo...

Ok, o passo 6 ainda está sinistro, mas vamos colocar os demais no .gitlab-ci.yml:

webpack:  
  stage: build
  script:
    - npm run build

mocka:  
  stage: test
  script: 
    - npm run test

Poucas linhas e muitos conceitos nesse arquivo. Vamos lá!

Um pipeline é composto de jobs e cada job define um script com os comando que serão executados. No exemplo foram criados os jobs webpack e mocka representando a execução do Webpack para criar o Javascript final do cliente e o Mocka para rodar os testes. Os nomes dos jobs é você quem escolhe, mas via de regra eles deveriam ser significativos para o time no contexto do projeto.

Em cada job também é declarado um stage que o Gitlab CI usa para definir a ordem de execução dos jobs. Por padrão são executados os jobs em stages na seguinte ordem: build -> test -> deploy. Os estágios e a ordem de execução podem ser definidos conforme a necessidade. Caso haja mais de um job em um mesmo estágio eles serão executados em paralelo.

Os jobs são executados independentes uns dos outros. Assim, você deve pensar em cada job como uma unidade completa de execução. Por exemplo, antes de executar o npm run build devemos executar o npm install para instalar as dependências; o mesmo acontece para o npm run test; e assim por diante. Dessa forma, precisaríamos colocar o npm install como a primeira instrução de cada job. Felizmente o gitlab-ci.yml nos permite declarar um script que será executado antes de cada job e resolve esse nosso problema.

before_script:  
  - npm install

Estão lembrados que comentamos lá atrás que os Runners são responsáveis por executar os comandos dos jobs? Pois bem, os nossos scripts precisam que o npm esteja instalado, caso contrário não será possível executar os jobs do pipeline. A forma mais simples de resolver essa dependência é utilizando containers Docker. Adicione a seguinte linha no início do .gitlab-ci.yml:

image: node:8.1.2  

Ao encontrar essa declaração o Gitlab CI vai criar um container e executar os comandos do script dentro dela. Como a imagem já tem instalados Node JS e npm, então vai funcionar e continuamos bem! 😎

Agora só sobrou aquela parte maligna do deploy no Heroku pra resolvermos. Mas para a nossa alegria já resolveram isso pra gente! Vamos usar a ferramenta dpl desenvolvida pelo Travis CI que é um cliente capaz de fazer deploy de aplicações em diversos Clouds disponíveis, entre eles o Heroku. Vamos usar aquelas variáveis APP_NAME e AUTH_TOKEN que definimos no pipeline do projeto lá no começo do post. Usando tudo junto com o dpl teremos isso aqui:

dpl --provider=heroku --app=$APP_NAME --api-key=$AUTH_TOKEN  

Observe o bash way of life que o Gitlab CI usa para substituir as variáveis - no fundo é tudo bash, sh mesmo... Mas ainda temos um problema! Se lembram que estamos executando os comandos em um container Docker baseado em uma imagem de Node JS? É 100% de change de não ter um dpl da vida instalado nessa imagem! Mas tudo bem, porque podemos instalar e usar o comando normalmente. Como a imagem é baseada no Debian então podemos usar o apt facilmente.

No final o job de deploy fica assim:

heroku_deploy:  
  stage: deploy
  script:
    - apt update -qq
    - apt install -qq -y ruby
    - gem install -q dpl
    - dpl --provider=heroku --app=$APP_NAME --api-key=$AUTH_TOKEN

Essa é a versão final do gitlab-ci.yml incluindo o passo de deploy:

image: node:8.1.2

before_script:  
  - npm install

webpack:  
  stage: build
  script:
    - npm run build

mocka:  
  stage: test
  script: 
    - npm run test

heroku_deploy:  
  stage: deploy
  script:
    - apt update -qq
    - apt install -qq -y ruby
    - gem install -q dpl
    - dpl --provider=heroku --app=$APP_NAME --api-key=$AUTH_TOKEN

No próximo push que você fizer para o repositório será executado o pipeline automagicamente e caso finalize com sucesso sua aplicação estará disponível no Heroku!

 

...would you like to learn to fly?

Alguns toques finais

Se você acompanhar a execução do pipeline você verá que cada job perde um bom tempo executando o npm install. Para reduzir esse tempo podemos fazer um cache do diretório node_modules para ser reutilizado entre os jobs. Para fazer isso basta usar a declaração abaixo:

cache:  
  paths:
    - node_modules/

Outro ponto muito importante é que o pipeline está sendo executado inteiro sempre que é feito push em qualquer branch! Na grande maioria das vezes não vamos querer fazer deploy de todos os branches, especialmente quando você resolveu fazer push daquele seu branch teste-aleatorio-sem-sentido 💩! Assim, podemos definir, por exemplo, que o job de deploy seja executado somente quando for feito push para o branch master. Para isso basta usar a declaração only no job de deploy:

heroku_deploy:  
  [...]
  only:
    - master

Fizemos uma massaroca sem tamanho nesse gitlab-ci.yml, então se quiser você pode encontrar a versão final dele aqui.

Conclusão

O Gitlab CI é uma ferramenta muito útil para auxiliar no processo de CI/CD, porém, assim como em qualquer ferramenta, requer um bom estudo das suas funcionalidades e limitações. Nesse post somente demos uma pincelada em algumas funcionalidades e vale a pena investir um pouco mais de tempo na leitura da documentação.

Se você tem realmente a intenção de usar o Gitlab para fazer CI/CD, recomendo entender bem como funcionam os Runners, como é a instalação e a configuração deles. Os Runners compartilhados no Gitlab.com não são recomendados para um ambiente produtivo de CI/CD, mas claro, você pode e deve usá-los para experimentações como fizemos aqui. Inclusive você deveria pensar também se não é melhor manter a sua própria instalação do Gitlab!

Por enquanto é isso galera! Bons builds!!

but it was only fantasy...