Introdução ao GitLab CI/CD: Automação de Ponta a Ponta
GitLab CI/CD é um sistema integrado de integração contínua e entrega contínua que permite automatizar testes, builds e deploys diretamente a partir do seu repositório. Diferentemente de ferramentas externas, o GitLab CI/CD funciona nativamente dentro da plataforma, eliminando necessidade de integrações complexas. Cada push para o repositório dispara automaticamente uma sequência de jobs configurados em um arquivo .gitlab-ci.yml, permitindo que você valide código, execute testes e implante aplicações sem intervenção manual.
A beleza do GitLab CI/CD reside na simplicidade e poder combinados. Você não precisa provisionar servidores separados ou gerenciar credenciais complexas — tudo é orquestrado através de uma configuração declarativa. Quando implementado corretamente, elimina erros humanos, acelera o ciclo de desenvolvimento e fornece visibilidade completa sobre o status de cada etapa do pipeline.
Entendendo Pipelines: Estrutura e Execução
O que é um Pipeline?
Um pipeline é uma série de estágios que são executados sequencialmente ou em paralelo. Cada estágio contém um ou mais jobs que realizam tarefas específicas. A execução só prossegue para o próximo estágio se todos os jobs do estágio anterior forem bem-sucedidos (comportamento padrão).
Estrutura Básica do .gitlab-ci.yml
O arquivo de configuração define toda a lógica do pipeline. Você especifica variáveis globais, estágios, imagens Docker, e os jobs que serão executados. GitLab processa este arquivo YAML e cria os jobs automaticamente.
# Definição global
image: python:3.11
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
stages:
- test
- build
- deploy
# Job no estágio "test"
unit_tests:
stage: test
script:
- pip install -r requirements.txt
- pytest tests/ -v --cov=src
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
coverage: '/TOTAL.*\s+(\d+%)$/'
# Job no estágio "build"
build_artifact:
stage: build
script:
- pip install -r requirements.txt
- python -m PyInstaller --onefile src/main.py
artifacts:
paths:
- dist/main
expire_in: 30 days
dependencies:
- unit_tests
# Job no estágio "deploy"
deploy_production:
stage: deploy
script:
- echo "Deploying to production..."
- ./deploy.sh
environment:
name: production
url: https://app.exemplo.com
only:
- main
Neste exemplo, o estágio test executa testes unitários com cobertura. O estágio build depende do sucesso do test e gera um artefato. Finalmente, deploy_production só executa em merges para a branch main. O atributo dependencies garante que apenas os artefatos necessários sejam baixados.
Conditions e Triggers Avançados
Você pode controlar quando jobs executam usando condições. only restringe a branches ou tags; except faz o inverso. Para lógica mais complexa, use rules:
deploy_staging:
stage: deploy
script:
- echo "Deploying to staging..."
rules:
# Executa em merge requests
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
# Executa em pushes para branches que começam com "feature/"
- if: '$CI_COMMIT_BRANCH =~ /^feature\/.*/'
# Caso nenhuma condição seja atendida, não executa
environment:
name: staging
url: https://staging.exemplo.com
GitLab Runners: Executores do Pipeline
O que é um Runner?
Um Runner é um agente que executa os jobs definidos no pipeline. GitLab fornece runners compartilhados, mas em ambientes corporativos você frequentemente provisiona seus próprios runners para ter controle total sobre recursos, segurança e dependências.
Instalação e Configuração de um Runner
Um runner pode ser instalado em Linux, macOS ou Windows. Vamos usar Linux como exemplo. O processo envolve instalar o binário, registrá-lo com sua instância GitLab e deixá-lo rodando como serviço.
# 1. Baixar e instalar o runner (Ubuntu/Debian)
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
sudo apt-get install gitlab-runner
# 2. Registrar o runner
sudo gitlab-runner register \
--url https://gitlab.seu-dominio.com/ \
--registration-token <seu-token> \
--executor docker \
--docker-image alpine:latest \
--description "Docker runner principal" \
--docker-volumes /var/run/docker.sock:/var/run/docker.sock
# 3. Iniciar o runner
sudo gitlab-runner start
Após o registro, o runner aparecerá na interface do GitLab e estará pronto para executar jobs. Cada job é isolado em um container Docker (se você escolher o executor docker), garantindo ambiente limpo e reproduzível.
Executores Disponíveis
GitLab oferece vários executores. O docker é padrão em CI/CD moderno, mas shell executa diretamente na máquina host (útil para builds nativos), kubernetes integra com clusters K8s, e docker-machine escala automaticamente com máquinas virtuais.
# Exemplo usando executor shell
build_native:
stage: build
tags:
- shell
- macos
script:
- xcodebuild -scheme MyApp -configuration Release
# Exemplo usando docker
build_containerized:
stage: build
image: node:18
tags:
- docker
script:
- npm install
- npm run build
O atributo tags conecta o job a runners específicos. Se você tem múltiplos runners registrados com tags diferentes, o GitLab escolhe o runner apropriado baseado nas tags do job.
Caching e Artefatos
Caching acelera pipelines reutilizando dependências entre execuções. Artefatos armazenam outputs que precisam passar entre estágios ou serem disponibilizados para download.
stages:
- dependencies
- build
- test
# Job que popula o cache
install_deps:
stage: dependencies
image: node:18
script:
- npm install
cache:
key: npm-cache-${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
# Job que usa o cache
build_app:
stage: build
image: node:18
script:
- npm run build
cache:
key: npm-cache-${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
artifacts:
paths:
- dist/
expire_in: 1 week
# Job que usa o artefato do build
test_app:
stage: test
image: node:18
script:
- npm run test
cache:
key: npm-cache-${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
dependencies:
- build_app
A chave do cache inclui a branch (CI_COMMIT_REF_SLUG) para isolar caches por branch. O build_app job gera um artefato na pasta dist/, que test_app pode acessar via dependencies.
Integração com Kubernetes
Arquitetura: GitLab e Kubernetes
Integrar GitLab CI/CD com Kubernetes permite deploys automáticos e escaláveis. A abordagem moderna usa o executor kubernetes para runners, executando jobs diretamente em pods do cluster. Para isso, você registra um runner que se conecta ao seu cluster K8s via kubeconfig.
Registrando um Runner em Kubernetes
# 1. Criar namespace para runners
kubectl create namespace gitlab-runners
# 2. Criar service account
kubectl create serviceaccount gitlab-runner -n gitlab-runners
# 3. Dar permissões
kubectl create clusterrolebinding gitlab-runner \
--clusterrole=cluster-admin \
--serviceaccount=gitlab-runners:gitlab-runner
# 4. Obter token
RUNNER_TOKEN=$(kubectl create token gitlab-runner -n gitlab-runners --duration=87600h)
# 5. Registrar runner
gitlab-runner register \
--url https://gitlab.seu-dominio.com/ \
--registration-token <seu-token> \
--executor kubernetes \
--kubernetes-host https://seu-k8s-api:6443 \
--kubernetes-cert-file /path/to/ca.crt \
--kubernetes-key-file /path/to/key.pem \
--kubernetes-cert-file /path/to/cert.pem \
--kubernetes-namespace gitlab-runners \
--description "K8s runner"
Agora, quando um job é disparado, GitLab cria um pod ephemeral no cluster, executa o job, e o destrói após conclusão. Isso é extremamente eficiente para CI/CD em larga escala.
Deploy em Kubernetes via GitLab CI/CD
stages:
- build
- deploy
variables:
REGISTRY: registry.seu-dominio.com
IMAGE_NAME: minha-app
IMAGE_TAG: ${CI_COMMIT_SHA}
build_image:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker login -u $REGISTRY_USER -p $REGISTRY_PASSWORD $REGISTRY
- docker build -t $REGISTRY/$IMAGE_NAME:$IMAGE_TAG .
- docker push $REGISTRY/$IMAGE_NAME:$IMAGE_TAG
deploy_k8s:
stage: deploy
image: bitnami/kubectl:latest
script:
# Configurar kubectl
- kubectl config use-context <seu-cluster-context>
# Criar namespace se não existir
- kubectl create namespace production --dry-run=client -o yaml | kubectl apply -f -
# Fazer deploy usando kubectl apply
- kubectl set image deployment/minha-app minha-app=$REGISTRY/$IMAGE_NAME:$IMAGE_TAG -n production
# Aguardar rollout
- kubectl rollout status deployment/minha-app -n production
environment:
name: production
url: https://app.seu-dominio.com
kubernetes:
namespace: production
only:
- main
O job build_image constrói a imagem Docker e a envia para um registry. O job deploy_k8s então atualiza o deployment do Kubernetes com a nova imagem. O atributo environment.kubernetes.namespace integra a informação do K8s diretamente no GitLab, fornecendo visualização sobre pods e resources.
Exemplo Avançado: GitOps com ArgoCD
Para uma abordagem GitOps verdadeira, integre GitLab CI/CD com ArgoCD. GitLab dispara builds e atualiza manifests Git; ArgoCD sincroniza automaticamente o cluster com os manifests.
stages:
- build
- update-manifest
build_and_push:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker build -t $REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA .
- docker push $REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA
update_manifest:
stage: update-manifest
image: alpine:latest
script:
# Clonar repositório de manifests
- apk add --no-cache git
- git clone https://oauth2:${DEPLOY_TOKEN}@gitlab.seu-dominio.com/seu-usuario/k8s-manifests.git
- cd k8s-manifests
# Atualizar tag da imagem no manifest
- sed -i "s|image: .*|image: $REGISTRY/$IMAGE_NAME:$CI_COMMIT_SHA|" deployment.yaml
# Commit e push
- git config user.email "ci@seu-dominio.com"
- git config user.name "CI Pipeline"
- git add deployment.yaml
- git commit -m "Update image to $CI_COMMIT_SHA"
- git push origin main
only:
- main
Quando o manifesto é atualizado, ArgoCD detecta a mudança e sincroniza automaticamente com o cluster. Isso desacopla a execução do pipeline (push de imagem) da aplicação (deploy no cluster), oferecendo mais segurança e auditoria.
Boas Práticas e Troubleshooting
Segurança: Variáveis e Secrets
Nunca commit credenciais no repositório. Use variáveis do GitLab com escopo limitado.
# Em Settings > CI/CD > Variables, crie:
# REGISTRY_USER (Protected, Masked)
# REGISTRY_PASSWORD (Protected, Masked, não exibir em logs)
# KUBE_CONFIG (Protected, Masked, conteúdo completo do kubeconfig)
deploy:
stage: deploy
script:
# A variável é injetada no ambiente e mascarda em logs
- echo "Usando registry user: ${REGISTRY_USER:0:5}***"
- kubectl config view --raw > /tmp/config
Marque variáveis como Protected (executam apenas em branches protegidas) e Masked (ocultam valor em logs). Para grandes secrets como kubeconfig, use arquivos ao invés de variáveis de texto simples.
Debugging: Logs e Failed Pipelines
Quando um job falha, o GitLab mostra logs completos. Use variáveis de debug para aumentar verbosidade:
debug_job:
stage: test
script:
- set -x # Bash: mostra cada comando antes de executar
- echo "CI_PROJECT_DIR: $CI_PROJECT_DIR"
- echo "CI_COMMIT_SHA: $CI_COMMIT_SHA"
- pwd
- ls -la
artifacts:
paths:
- debug-logs/
when: always # Coleta artefatos mesmo se job falha
O comando set -x em bash mostra cada linha antes de executar. O atributo when: always garante que artefatos sejam coletados mesmo em caso de falha, essencial para debugging.
Paralelização e Otimização
Reduce tempo total do pipeline executando jobs em paralelo:
stages:
- test
- build
# Estes três jobs rodam em paralelo no estágio "test"
unit_tests:
stage: test
script:
- pytest tests/unit/ -v
integration_tests:
stage: test
script:
- pytest tests/integration/ -v
lint:
stage: test
script:
- flake8 src/
- black --check src/
# Só inicia após todos os jobs do estágio "test"
build:
stage: build
script:
- python setup.py sdist bdist_wheel
Os três jobs do estágio test executam simultaneamente, reduzindo o tempo total de pipeline. Para jobs muito rápidos (como lint), execute-os em paralelo com testes para maximizar eficiência.
Conclusão
Dominando GitLab CI/CD você adquire três competências críticas: primeira, automação completa de pipelines eliminando deploy manual e reduzindo erros, usando .gitlab-ci.yml como fonte única da verdade; segunda, provisionar e gerenciar runners adequados ao seu contexto, seja docker, shell ou kubernetes, entendendo tradeoffs entre simplicidade e poder; terceira, integrar completamente com Kubernetes para deploys escaláveis e reproducíveis, aproveitando GitOps para auditoria e reversão automática.