Estudo e Prática — CI/CD com GitLab
Introdução
Este documento apresenta um estudo prático sobre CI/CD (Integração Contínua e Entrega Contínua) utilizando o GitLab. Aqui você encontrará os principais conceitos, benefícios, estrutura dos pipelines e exemplos de configuração, com o objetivo de facilitar a compreensão e a aplicação dessas práticas no desenvolvimento de software.
1. Conceito de CI/CD
O que é CI/CD?
CI/CD (Continuous Integration / Continuous Delivery) é um conjunto de práticas que automatiza o processo de:
- Integração Contínua (CI): cada alteração de código é automaticamente testada e integrada ao projeto.
- Entrega Contínua (CD): o sistema é automaticamente preparado (e opcionalmente implantado) em ambientes de homologação ou produção.
Leitura recomendada:
Benefícios principais:
- Redução de erros humanos
- Feedback rápido para os desenvolvedores
- Implantação mais previsível e confiável
- Automação de tarefas repetitivas
Leitura recomendada:
2. Estrutura e Conceitos-Chave do GitLab CI/CD
Conceito | Descrição |
---|---|
Pipeline | Conjunto de tarefas automatizadas (build, test, deploy). |
Stage | Etapa do pipeline (ex: “test”, “build”, “deploy”). |
Job | Uma tarefa específica executada dentro de uma stage. |
Runner | Máquina (container, VM, etc.) que executa os jobs. |
Artifact | Arquivo gerado por um job e salvo para uso posterior. |
Cache | Mecanismo de reuso de dependências (ex: pacotes Python). |
Variable | Variáveis de ambiente que armazenam dados e senhas. |
Documentação Oficial:
Resumo dos passos acima
Pipeline
Um pipeline é o fluxo automatizado de execução das etapas do seu projeto.
Ele representa a sequência de tarefas que o GitLab executa sempre que há uma mudança no código (ex.: commit, merge request ou tag).
Exemplo de pipeline simples:
stages:
- test
- build
- deploy
Stage
As stages são as etapas principais do pipeline, agrupando jobs com um mesmo objetivo. Os jobs dentro de uma mesma stage rodam em paralelo, e o GitLab só passa para a próxima quando todos terminam.
stages:
- test
- build
- deploy
Job
Um job é uma tarefa individual dentro de uma stage. Define os comandos, variáveis e scripts a serem executados.
Exemplo:
test_job: # nome do job
stage: test # define a etapa
script: # comandos executados dentro do runner
- pytest # executa os testes
Runner
O Runner é o agente executor dos jobs. É ele que realmente executa os comandos definidos no .gitlab-ci.yml.
Tipo | Descrição |
---|---|
Shared Runner | Disponível para todos os projetos no GitLab.com |
Specific Runner | Instalado manualmente e usado por um projeto específico |
Group Runner | Compartilhado entre projetos de um mesmo grupo |
Artifact
Um artifact é um arquivo ou diretório gerado por um job e salvo pelo GitLab. Serve para armazenar logs, relatórios e resultados de execução.
Exemplo:
test:
stage: test
script:
- pytest --junitxml=report.xml
artifacts:
when: always
paths:
- report.xml
expire_in: 1 week
Cache
O cache é um mecanismo de reuso de dependências entre jobs ou pipelines. Ele armazena arquivos temporários (como bibliotecas instaladas) para acelerar execuções futuras.
cache:
paths:
- .cache/pip
Variable
As variables são valores configuráveis usados dentro dos jobs. Servem para armazenar senhas, tokens, chaves de API e URLs de serviços.
Exemplo:
variables:
DATABASE_URL: "postgres://user:pass@db:5432/app"
deploy:
stage: deploy
script:
- echo "Conectando ao banco $DATABASE_URL"
Como tudo se conecta
- O pipeline organiza as etapas principais (stages).
- Cada stage contém jobs individuais.
- Os runners executam esses jobs em ambientes isolados.
- Artifacts e cache guardam resultados e dependências.
- Variables controlam configurações e credenciais com segurança.
3. O Arquivo exemplo .gitlab-ci.yml
Esse arquivo controla todo o pipeline. Ele é criado na raiz do repositório e descreve as etapas (stages), tarefas (jobs), imagens e comandos. O arquivo abaixo é um exemplo completo de .yml, é controlador de todo o pipeline do sistema. Sendo cirado na raíz do repositório, o .yml descreve as etapas (stages), tarefas (jobs), imagens e comandos necessários para construir, testar e implantar a aplicação da EJ Plataform, com backend em Python (Flask) e frontend em React, além de fazer deploy em ambientes de homologação (staging) e produção. Esse arquivo exemplo está adaptado para a aplicação ao qual o projeto de GCES está vinculado. Para acessar o arquivo original, segue o link do arquivo criado exemplo_git_lab.yml
Estrutura Básica
stages:
- setup
- test
- build
- deploy_staging
- deploy_production
# Define um cache global para acelerar a instalação de dependências.
# A chave "default" garante que o cache seja compartilhado entre todas as branches.
cache:
key:
files:
- backend/requirements.txt
- frontend/package-lock.json
paths:
- .cache/pip
- frontend/node_modules/
policy: pull-push
# =================================================================
# STAGE: SETUP
# =================================================================
install_dependencies:
stage: setup
image: node:18-alpine # Imagem leve com Node.js e Python pré-instalados
script:
- echo "Instalando dependências do Backend..."
- pip install --cache-dir .cache/pip -r backend/requirements.txt
- echo "Instalando dependências do Frontend..."
- cd frontend && npm install
# Este job atualiza o cache, os demais jobs (policy: pull) apenas o consomem.
cache:
<<: *cache # Reutiliza a definição de cache global
policy: pull-push
# =================================================================
# STAGE: TEST
# =================================================================
backend_test:
stage: test
image: python:3.9-slim
needs: [install_dependencies] # Garante que as dependências já foram instaladas
script:
- echo "Executando testes do Backend com Pytest..."
- pip install --cache-dir .cache/pip -r backend/requirements.txt
- cd backend
- pytest --junitxml=report.xml # Gera um relatório de testes
artifacts:
when: always # Salva o artefato mesmo se o job falhar
paths:
- backend/report.xml
expire_in: 1 week # O relatório ficará disponível por uma semana
frontend_lint_test:
stage: test
image: node:18-alpine
needs: [install_dependencies]
script:
- echo "Executando linter e testes do Frontend..."
- cd frontend
- npm test
# Opcional: se o seu teste gera um relatório de cobertura, salve-o como artefato.
# artifacts:
# paths:
# - frontend/coverage/
# =================================================================
# STAGE: BUILD
# =================================================================
build_docker_images:
stage: build
image: docker:20.10.16
services:
- docker:20.10.16-dind # Docker-in-Docker para construir imagens
needs: [backend_test, frontend_lint_test] # Só constrói se os testes passarem
before_script:
# $CI_REGISTRY_USER, $CI_REGISTRY_PASSWORD e $CI_REGISTRY são variáveis pré-definidas pelo GitLab
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- echo "Construindo e enviando a imagem do Backend..."
# Usa a tag do commit para versionar a imagem
- docker build -t "$CI_REGISTRY_IMAGE/backend:$CI_COMMIT_SHORT_SHA" ./backend
- docker push "$CI_REGISTRY_IMAGE/backend:$CI_COMMIT_SHORT_SHA"
- echo "Construindo e enviando a imagem do Frontend..."
- docker build -t "$CI_REGISTRY_IMAGE/frontend:$CI_COMMIT_SHORT_SHA" ./frontend
- docker push "$CI_REGISTRY_IMAGE/frontend:$CI_COMMIT_SHORT_SHA"
rules:
# Este job só executa na branch 'main' ou 'develop'
- if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop"'
# =================================================================
# STAGE: DEPLOY
# =================================================================
deploy_to_staging:
stage: deploy_staging
image: alpine:latest # Imagem mínima, pois só usaremos SSH
needs: [build_docker_images]
before_script:
- 'which ssh-agent || ( apk add --update openssh )'
- eval $(ssh-agent -s)
# Adiciona a chave SSH (armazenada em uma variável de ambiente) ao agente
- echo "$STAGING_SSH_PRIVATE_KEY" | ssh-add -
- mkdir -p ~/.ssh
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
script:
- echo "Fazendo deploy no ambiente de Staging (Homologação)..."
- >
ssh $STAGING_SSH_USER@$STAGING_SERVER_IP "
cd /home/user/ej-platform &&
export BACKEND_IMAGE=$CI_REGISTRY_IMAGE/backend:$CI_COMMIT_SHORT_SHA &&
export FRONTEND_IMAGE=$CI_REGISTRY_IMAGE/frontend:$CI_COMMIT_SHORT_SHA &&
docker-compose pull &&
docker-compose up -d --force-recreate
"
environment:
name: staging
url: http://staging.ejplatform.org # URL do ambiente para acesso rápido no GitLab
rules:
# Este job só executa na branch 'develop'
- if: '$CI_COMMIT_BRANCH == "develop"'
deploy_to_production:
stage: deploy_production
image: alpine:latest
needs: [build_docker_images]
before_script:
- 'which ssh-agent || ( apk add --update openssh )'
- eval $(ssh-agent -s)
- echo "$PROD_SSH_PRIVATE_KEY" | ssh-add -
- mkdir -p ~/.ssh
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
script:
- echo "Fazendo deploy no ambiente de Produção..."
- >
ssh $PROD_SSH_USER@$PROD_SERVER_IP "
cd /home/user/ej-platform &&
export BACKEND_IMAGE=$CI_REGISTRY_IMAGE/backend:$CI_COMMIT_SHORT_SHA &&
export FRONTEND_IMAGE=$CI_REGISTRY_IMAGE/frontend:$CI_COMMIT_SHORT_SHA &&
docker-compose pull &&
docker-compose up -d --force-recreate
"
environment:
name: production
url: https://app.ejplatform.org
rules:
# Este job só executa na branch 'main' e PRECISA de aprovação manual para rodar.
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual # Ponto-chave para segurança em produção!