
Compartilhar:
Yonatan já participou de alguns projetos incríveis tanto no meio acadêmico quanto no setor privado — desde C/C++, passando por Matlab, até PHP e JavaScript. Ex-CTO da Webiks e arquiteto de software na WalkMe. Atualmente, ele é arquiteto de software na Vonage e instrutor na Egghead.
Configurando CI/CD com o GitHub Actions
Tempo de leitura: 8 minutos
A integração contínua e a implantação contínua são elementos indispensáveis para organizações que desejam crescer e entregar software de alta qualidade com grande agilidade. Este artigo orienta você passo a passo pelo processo de uso do GitHub Actions para criar fluxos de CI/CD.
O que é CI/CD?
CI/CD é um processo que conecta o desenvolvimento à implantação por meio de um processo de integração automatizado. A ideia é permitir que os desenvolvedores enviem alterações no código como a única etapa necessária para implantar essas alterações.
O pipeline de CI permite que o trabalho em equipe ocorra sem contratempos, automatizando padrões e garantindo a qualidade do software. Ferramentas como linters e testes automatizados fornecem feedback com base no qual é possível integrar as alterações ao ramo principal. Essas alterações integradas acabam sendo enviadas aos usuários finais (produção).
O pipeline de CD recebe o código testado e aprovado. Ele confirma que todos os artefatos necessários estão sendo implantados no local correto. Alguns exemplos: implantar uma aplicação web em um servidor, publicar uma biblioteca no repositório de um gerenciador de pacotes ou publicar um aplicativo móvel na loja de aplicativos.
A automação desses processos garante duas coisas importantes: o processo ocorre rapidamente e é muito menos propenso a erros.
Quer saber como as equipes aceleram seus processos de desenvolvimento, integração e implantação? Pronto para criar um novo pipeline de CI/CD? Vamos lá!
Um fluxo básico de CI/CD
Envie uma alteração para um branch de recurso.
Crie uma solicitação de pull para essa alteração
O CI é acionado e executa o seguinte:
Lint, Teste, Compilação
Assim que a integração contínua (CI) for concluída, ela marca o PR como válido e dá início ao processo de revisão de código
Quando o PR for aprovado, o código será incorporado ao branch master
Após a fusão, o processo de CD é iniciado:
Lint, Teste, Atualização de versão, Implantação
Vamos criar um CI/CD para uma aplicação simples, que você pode clonar aqui.
Como adicionar um fluxo de trabalho do GitHub ao seu projeto
Crie uma pasta .github/workflows. É nessa pasta que vamos adicionar nossas ações do GitHub. As ações são definidas em arquivos YAML, em uma estrutura bastante simples que consiste em três partes:
nome: o nome do fluxo de trabalho, no nosso caso Test and Build
sobre: o gatilho que inicia o fluxo de trabalho quando as condições são atendidas. No nosso caso, queremos que o fluxo de trabalho seja executado quando uma solicitação de pull for criada e enviada. Veremos exemplos de outros gatilhos mais adiante neste tutorial.
tarefas: os comandos propriamente ditos que são executados no fluxo. Podem ser várias tarefas executadas em paralelo ou tarefas que dependem umas das outras. Neste exemplo, há uma tarefa chamada build-testque é executado apenas no sistema operacional ubuntu-latest.
name: Test and Build
on:
pull_request:
branches:
- main
jobs:
build-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup NodeJS 14
uses: actions/setup-node@v1
with:
node-version: 14
- name: Install yarn
run: npm install -g yarn
- name: Install dependencies
run: yarn install
- name: Test
run: yarn testA primeira etapa faz o check-out do repositório, utilizando uma ação pré-definida actions/checkout@v2 do marketplace de ações. Passamos a fetch-depth: 0 variável usando o propriedade , que indica ao processo de checkout que deve buscar o repositório com todo o seu histórico. As etapas a seguir são para instalações e execução de comandos bash.
Criação de um fluxo de trabalho de integração contínua
Acionar o fluxo com um Pull Request
Ligue a máquina
Instale o Node.js no computador
Faça o checkout do repositório
Instalar dependências
Teste, Verificação de erros, Compilação
Mostrar os resultados na página de RP
Introdução
Se você ainda não fez o fork e clonou o repositório, faça isso agora e crie um branch chamado “add-ci”.
Crie um arquivo
.github/workflows/ci.ymle copie o conteúdo esse arquivo para o novo arquivo.Confirme as alterações e envie-as para o novo branch. Abra uma solicitação de pull do novo branch para o branch principal.
Se você der uma olhada na solicitação de pull no GitHub, deverá ver os testes sendo executados:

Você vai perceber que o GitHub já sabe o nome do fluxo de trabalho, o nome da tarefa e o gatilho. Quando estiver concluído, deve ficar assim:

Você consegue identificar algum problema nesse processo?
Adicionando regras de mesclagem
A primeira questão é que o botão “Mesclar solicitação de pull” já estava disponível durante o processo de CI, quando queremos que a fusão só esteja disponível se todos os fluxos de trabalho necessários tiverem sido concluídos.
Podemos corrigir isso nas configurações do repositório, acessando: Configurações > Ramos > Adicionar regra

Aqui, vamos selecionar Exigir que as verificações de status sejam aprovadas antes da fusão e marcaremos todas as opções abaixo dela. Você verá todos os fluxos de trabalho necessários para habilitar a fusão — no nosso caso, temos apenas build-test.

Para Padrão do nome da agência insira main e crie a regra. E se você voltar à página de pull requests, verá que nenhum pull request pode ser mesclado antes que os testes sejam aprovados, a menos, é claro, que você tenha privilégios de administrador.

Integração após a revisão de código
Antes de prosseguirmos, também precisamos decidir como lidar com a aprovação de Pull Requests. Existem três abordagens principais:
Permitir que qualquer pessoa faça a integração assim que os testes forem aprovados
Fazer a integração automaticamente assim que os testes forem aprovados
Exigir uma revisão de código para que a fusão seja realizada
Vamos optar pela primeira opção, que é a mais comum, e que podemos alterar editando a regra do branch que acabamos de criar. No topo da lista, marque “Exigir revisões de pull request antes da fusão” e clique em “Salvar”.
É isso aí sobre integração contínua! Agora, todos os novos Pull Requests serão testados e revisados antes de serem incorporados ao branch principal! O próximo passo é a implantação.
Criação de um fluxo de trabalho de implantação contínua
A implantação pode envolver desde o envio de arquivos estáticos para o GitHub, passando pela publicação de pacotes npm, até a implantação de toda uma malha de microsserviços. Este é o processo que seguiremos para nossa aplicação assim que o código for mesclado ao repositório principal:
Instalar dependências
Teste
Atualizar versão
Construir
Marque o lançamento
Publicar no npm
Implantar o aplicativo de demonstração
Vamos examinar as diferentes partes do arquivo YAML que controla as ações:
O Gatilho
Essas são as condições em que nosso código de implantação será executado. Ele será acionado quando uma solicitação de pull for fechada e mesclada.
name: Test, Build and Deploy
on:
pull_request:
types: [closed]
jobs:
build-test-release:
if: github.event.action == 'closed' && github.event.pull_request.merged == true
runs-on: ubuntu-latest
Permissões do Git Actions
Na etapa de finalização da compra, vamos adicionar uma linha a mais que não estava no arquivo YAML anterior:
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
token: ${{ secrets.CI_REPOSITORY_ACCESS_TOKEN }}Precisamos definir explicitamente o token para que possamos realizar ações para as quais o token padrão, secrets.GITHUB_TOKENnão tem permissão para realizar. No nosso caso, queremos enviar uma alteração para o branch “main” sem revisão de código; por isso, precisamos de um token com privilégios de administrador. Definimos esse token em Configurações -> Segredos.
Atualizar as versões dos pacotes
Essa etapa varia de projeto para projeto. A ideia é atualizar a versão do pacote que você pretende publicar no repositório de pacotes. Faremos isso analisando todas as bibliotecas que sofreram alterações neste branch e atualizando a versão NPM delas:
- name: Raise version of affected libraries
run: |
LATEST_TAG=$(git tag -l "v*" --sort=-version:refname | head -n 1)
LIBS=$(yarn nx affected:libs --base=$LATEST_TAG --head=HEAD --plain | awk 'NR > 2 && $1 != "Done" { print $1 }')
for LIBRARY in $LIBS
do
cd ./libs/$LIBRARY
npm version minor --no-git-tag-version --no-push
echo "Bumping $LIBRARY"
cd ..
cd ..
done
npm version minor --no-git-tag-version --no-push
Observe que estamos usando o NX, uma estrutura de gerenciamento de monorepo, que ajuda a tornar as compilações e o desenvolvimento muito mais rápidos. Utilizamos o affected recurso, que nos informa quais bibliotecas foram “afetadas” pelo PR.
Construir
O processo de compilação também utiliza o affected recurso também. Ele compila as bibliotecas que sofreram alterações em relação ao ramo principal com as predefinições de produção:
- name: Build components
run: yarn nx affected:build --prod --with-deps --base=main Marque o lançamento
A marcação de liberação é feita em duas etapas:
O primeiro, get-npm-version, utiliza uma ação do marketplace que extrai a versão do pacote principal. A segunda etapa confirma as alterações feitas na Raise version etapa mencionada acima, cria uma tag com a versão do novo `package.json` e envia a alteração.
Lembra-se do CI_REPOSITORY_ACCESS_TOKEN segredo? É aqui que ele entra em ação. Como impedimos a fusão com o branch master sem a revisão de um pull request, precisamos de um token de administrador para essa parte, o que nos permitirá contornar a regra e enviar as alterações automaticamente.
- name: get-npm-version
id: package-version
uses: martinbeentjes/npm-get-version-action@master
- name: Tag the release
run: |
git fetch
git config user.email "unicorn.ci@yonatankra.com"
git config user.name "Unicorn CI"
git add --all
git commit -m "update versions to ${{ steps.package-version.outputs.current-version }}"
git push
- name: Tag release
run: |
git tag -a v${{ steps.package-version.outputs.current-version }} -m "tag release v${{ steps.package-version.outputs.current-version }}"
git push --follow-tags Implantar o aplicativo de demonstração
Por fim, compilamos nosso aplicativo e o implantamos:
- name: Build Demo
run: yarn build:deploy
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@3.7.1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages # The branch the action should deploy to.
FOLDER: dist/apps/unicorn-hunt # The folder the action should deploy.
CLEAN: true # Automatically remove deleted files from the deploy branchVocê pode encontrar o código YAML completo aqui
Otimização do fluxo de CI/CD
Como usar o cache no GitHub Actions
Se você acompanhou os passos e criou o fluxo de CI/CD, deve ter percebido que ele está demorando um pouco para ser executado. Uma solução simples para isso é usar o cache e reduzir o tempo de instalação das dependências, o que também podemos fazer com o GitHub Actions.
O armazenamento em cache funciona verificando se há um acerto no cache e, caso seja encontrado, pulando a etapa de instalação das dependências. Veja como fica o código:
- name: Install yarn
run: npm install -g yarn
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
- name: Cache yarn dependencies
uses: actions/cache@v2
id: yarn-cache
with:
path: |
${{ steps.yarn-cache-dir-path.outputs.dir }}
**\node_modules
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
if: steps.yarn-cache.outputs.cache-hit != 'true'
run: yarn installA primeira etapa instala o yarn globalmente. O segundo passo obtém o caminho da pasta de cache do Yarn. Em seguida, a etapa de armazenamento em cache propriamente dita utiliza a ação de cache, passa os caminhos para o cache (node_modules e a pasta de cache do Yarn) e atribui uma chave de cache ao arquivo yarn.lock por meio de um hash. Dessa forma, se instalarmos uma nova dependência ou atualizarmos uma dependência, isso causaria uma falha de cache. A etapa final define uma condição na install-dependencies etapa usando o variável cache-hit definida pela ação de cache.
Modularização do processo
Então, o CI/CD está em execução. Ele está usando o cache. Mencionamos anteriormente que também gostaríamos de publicar nossas duas bibliotecas na libs pasta no npm. Tecnicamente, poderíamos adicionar mais uma etapa ao nosso processo de CD, mas isso poderia torná-lo complicado e difícil de manter.
Em vez disso, dividimos o processo em fluxos de trabalho separados, que são acionados pelo processo principal do CD. O CD, então, lançaria uma nova versão, o que acionaria os outros processos, resultando na implantação e na publicação no NPM.
Além de facilitar a manutenção, isso também permite um melhor tratamento de erros. Afinal, e se a atualização da versão e a publicação forem bem-sucedidas, mas a implantação falhar? Isso nos permite simplesmente executar a implantação novamente e depurar até que ela seja bem-sucedida.
O código completo da solução pode ser encontrado aqui.
Resumo
Neste artigo, criamos um processo de CI/CD usando o GitHub Actions.
Começamos adicionando o processo de CI que executa os testes e avisa aos revisores de código que a solicitação de pull está pronta para revisão. Também aprendemos como bloquear a fusão em um branch caso os testes e a revisão não sejam aprovados. Analisamos um código de CI simplificado, mas você pode adicionar mais comandos do Bash à fase de testes ou até mesmo incluir etapas adicionais, como linting ou o `prettier`.
Em seguida, criamos um processo de CD que atualiza as versões e implanta a demonstração. Aprendemos como podemos armazenar em cache a instalação das dependências para economizar tempo e como modularizar o processo de CD para obter mais controle e facilidade de manutenção.
Há muito mais que pode ser feito com o GitHub Actions. Esperamos que este tutorial tenha ajudado você a dar os primeiros passos na criação de um processo de CI/CD que seja útil e significativo para sua equipe.
Compartilhar:
Yonatan já participou de alguns projetos incríveis tanto no meio acadêmico quanto no setor privado — desde C/C++, passando por Matlab, até PHP e JavaScript. Ex-CTO da Webiks e arquiteto de software na WalkMe. Atualmente, ele é arquiteto de software na Vonage e instrutor na Egghead.