Bem-vindos à aula 87 do nosso curso de DevOps! Hoje vamos mergulhar em um dos pilares da automação moderna: o conceito de Pipeline como Código. Em vez de configurar pipelines através de interfaces gráficas ou cliques, tratamos a definição do pipeline como um artefato de software: versionado, revisado, testado e evoluído como qualquer outro código. Essa prática traz rastreabilidade, colaboração e reprodutibilidade, fundamentais para equipes que buscam alta maturidade em integração e entrega contínuas.

Nesta aula, você aprenderá a importância de versionar pipelines, como reutilizar trechos de código, como usar templates para padronizar e como manter pipelines saudáveis ao longo do tempo. Vamos usar exemplos em YAML, a linguagem mais comum para definir pipelines (GitHub Actions, GitLab CI, Azure DevOps, etc.). Ao final, você terá uma visão completa para aplicar Pipeline como Código nos seus projetos.

Tudo versionado

O primeiro princípio do Pipeline como Código é que o pipeline deve ser versionado junto com o código da aplicação. Isso significa que o arquivo de definição do pipeline (por exemplo, .gitlab-ci.yml ou .github/workflows/ci.yml) reside no mesmo repositório e é versionado com Git. Assim, cada alteração no pipeline é rastreada, pode ser revisada via pull request e associada a uma mudança no código.

Versionar pipelines traz benefícios como: histórico completo de mudanças, possibilidade de reverter para versões anteriores, e auditoria clara de quem alterou o quê e por quê. Além disso, ao versionar junto com o código, garantimos que a versão do pipeline corresponde exatamente à versão do código, eliminando problemas de inconsistência entre o que está em produção e o que está no repositório.

Um exemplo simples de um pipeline versionado em GitLab CI:

# .gitlab-ci.yml
stages:
  - build
  - test
  - deploy

build-job:
  stage: build
  script:
    - echo "Compilando o projeto..."
    - make build

test-job:
  stage: test
  script:
    - echo "Executando testes..."
    - make test

deploy-job:
  stage: deploy
  script:
    - echo "Fazendo deploy..."
    - make deploy
  only:
    - main

Esse arquivo é versionado e qualquer alteração aparece no histórico do Git. Para revisar, um desenvolvedor cria um branch, altera o YAML e abre um merge request. Isso torna o pipeline tão revisável quanto o código da aplicação.

Reutilização

Reutilização é a chave para evitar duplicação e manter pipelines consistentes. Em vez de copiar e colar trechos de script entre diferentes pipelines, podemos extrair funções, comandos ou até mesmo jobs inteiros para serem reutilizados. Isso reduz erros, facilita a manutenção e garante que todos os projetos sigam as mesmas práticas.

Em GitLab CI, por exemplo, podemos usar extends para herdar configurações de um job base. No GitHub Actions, podemos usar composite actions ou reusable workflows. Vamos ver um exemplo de reutilização com extends no GitLab:

# .gitlab-ci.yml
.default-job:
  image: node:14
  before_script:
    - npm ci
  after_script:
    - echo "Limpeza..."

build:
  extends: .default-job
  script:
    - npm run build

test:
  extends: .default-job
  script:
    - npm test

Aqui, o job .default-job define uma imagem e scripts comuns. Os jobs build e test reutilizam essas configurações, evitando repetição. Além disso, podemos criar scripts em arquivos separados e referenciá-los, ou usar bibliotecas de scripts (como shell scripts) que são compartilhados entre projetos.

Templates

Templates vão além da reutilização simples: eles permitem criar modelos padronizados de pipelines que podem ser parametrizados. Com templates, você define uma estrutura genérica e os projetos apenas fornecem valores específicos. Isso é especialmente útil em organizações com muitos projetos que precisam seguir padrões de CI/CD.

No GitLab, podemos usar includes com template para importar arquivos YAML de um repositório central. Também podemos usar variáveis para parametrizar. No GitHub Actions, podemos criar reusable workflows que aceitam entradas. Exemplo de um template em GitLab:

# template.yml (em um repositório central)
.build-template:
  stage: build
  script:
    - echo "Building ${PROJECT_NAME}"
    - make build
  variables:
    PROJECT_NAME: "default"

# No projeto .gitlab-ci.yml
include:
  - project: 'my-org/ci-templates'
    ref: main
    file: 'template.yml'

build-app:
  extends: .build-template
  variables:
    PROJECT_NAME: "my-app"

Assim, o template define o processo de build genérico, e o projeto apenas especifica o nome. Mudanças no template são propagadas automaticamente para todos os projetos que o utilizam, garantindo consistência e centralizando a manutenção.

Manutenção

Manter pipelines é uma tarefa contínua. Assim como o código, pipelines precisam ser revisados, atualizados e limpos para evitar dívidas técnicas. Boas práticas de manutenção incluem: manter pipelines pequenos e focados, usar nomes descritivos, documentar decisões complexas, e revisar regularmente se os passos ainda são necessários.

Além disso, é importante monitorar a saúde do pipeline: tempo de execução, taxa de falhas, e uso de recursos. Ferramentas como GitLab e GitHub oferecem métricas que ajudam a identificar gargalos. Uma manutenção eficaz também envolve a atualização de dependências (como imagens Docker) e a remoção de etapas obsoletas.

Um exemplo de pipeline bem mantido pode incluir comentários explicativos e uso de âncoras YAML para reduzir duplicação:

# .gitlab-ci.yml
image: docker:latest

.services: &services
  - docker:dind

before_script:
  - docker info

build:
  stage: build
  services: *services
  script:
    - docker build -t myapp .

test:
  stage: test
  services: *services
  script:
    - docker run myapp test

deploy:
  stage: deploy
  services: *services
  script:
    - docker push myapp
  only:
    - main

Nesse exemplo, usamos âncoras (&services) e referências (*services) para evitar repetir a configuração de serviços. Isso torna o pipeline mais fácil de manter.

Boas Práticas e Observações Finais

Adotar Pipeline como Código é mais do que uma mudança técnica; é uma mudança cultural. Algumas boas práticas: sempre versionar, revisar via pull request, testar o pipeline em um ambiente de staging antes de aplicar em produção, e manter a simplicidade. Lembre-se de que pipelines são código e devem ser tratados com o mesmo rigor de qualidade.

Outra dica: use ferramentas de lint para YAML (como yamllint) e validação específica da plataforma (por exemplo, gitlab-ci-lint). Isso ajuda a evitar erros de sintaxe antes de executar. Também é recomendável usar segredos e variáveis de ambiente para não expor informações sensíveis no arquivo versionado.

Referências

Exercícios

  1. Explique por que é importante versionar o pipeline junto com o código da aplicação. Dê um exemplo de problema que pode ocorrer se isso não for feito.
  2. ✓ Resposta: Versionar o pipeline junto com o código garante que a configuração de CI/CD corresponda exatamente à versão do código em cada commit. Isso permite rastrear quando e por que o pipeline mudou, facilita revisões e permite reverter para versões anteriores se necessário. Um problema comum é quando o pipeline não está versionado: alterações no código podem quebrar o pipeline sem que se saiba qual mudança causou, e a configuração pode divergir entre ambientes, causando falhas imprevisíveis.
  3. Descreva duas maneiras de reutilizar código em pipelines de CI/CD (por exemplo, no GitLab ou GitHub Actions).
  4. ✓ Resposta: Duas maneiras: (1) Uso de extends no GitLab CI, que permite que um job herde a configuração de um job base, evitando repetição de scripts e configurações comuns. (2) Uso de reusable workflows no GitHub Actions, que permitem definir um workflow completo em um arquivo separado e chamá-lo a partir de outros workflows, passando parâmetros e segredos.
  5. Crie um template YAML para um pipeline de build e teste usando GitLab CI, com variáveis para nome do projeto e imagem Docker. Mostre como um projeto usaria esse template.
  6. ✓ Resposta: Template (em um repositório central, por exemplo, ci-templates/ci-template.yml):
    # ci-template.yml
    .build-test-template:
      image: ${IMAGE}
      variables:
        IMAGE: "node:14"
      script:
        - echo "Building ${PROJECT_NAME}"
        - npm install
        - npm test
    
    Uso no projeto:
    # .gitlab-ci.yml
    include:
      - project: 'my-org/ci-templates'
        ref: main
        file: 'ci-template.yml'
    
    build-test:
      extends: .build-test-template
      variables:
        PROJECT_NAME: "my-app"
        IMAGE: "node:16"
    
  7. Quais são três boas práticas de manutenção de pipelines? Dê um exemplo concreto de cada uma.
  8. ✓ Resposta: (1) Revisar regularmente o pipeline: agendar uma revisão mensal para identificar etapas obsoletas ou ineficientes. Exemplo: remover um job de deploy manual que agora é automático. (2) Manter dependências atualizadas: usar imagens Docker com tags fixas e atualizá-las periodicamente. Exemplo: mudar de node:14 para node:18 após testes. (3) Documentar decisões complexas: adicionar comentários no YAML explicando por que certas configurações existem. Exemplo: comentar que um timeout de 1 hora é necessário para testes de integração longos.
  9. Escreva um pequeno pipeline em GitHub Actions que reutilize um workflow reutilizável para rodar testes. O workflow reutilizável deve aceitar uma entrada para o comando de teste.
  10. ✓ Resposta: Workflow reutilizável (em .github/workflows/test-reusable.yml):
    name: Reusable Test Workflow
    on:
      workflow_call:
        inputs:
          test-command:
            required: true
            type: string
        secrets:
          token:
            required: true
    
    jobs:
      test:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
          - uses: actions/setup-node@v3
            with:
              node-version: 14
          - run: |
              ${{ inputs.test-command }}
            env:
              GITHUB_TOKEN: ${{ secrets.token }}
    
    Workflow principal (em .github/workflows/ci.yml):
    name: CI
    on: [push]
    
    jobs:
      call-test:
        uses: ./.github/workflows/test-reusable.yml
        with:
          test-command: "npm test"
        secrets:
          token: ${{ secrets.GITHUB_TOKEN }}