- Introdução à Escalabilidade em GitHub Actions
- A Necessidade de Workflows Centralizados: O Princípio DRY em CI/CD
- Implementando Workflows Reutilizáveis com `workflow_call`
- Estrutura de um Workflow Reutilizável (Callee)
- Invocando um Workflow Reutilizável (Caller)
- Workflows Reutilizáveis vs. Actions Compostas
- Expandindo a Cobertura com Matrizes de Teste (`strategy.matrix`)
- Sintaxe Básica da Matriz
- Configurações Avançadas de Matriz
- Adicionando ou Removendo Configurações Específicas
- Controlando a Execução com `fail-fast` e `max-parallel`
- Sinergia Total: Combinando Matrizes e Workflows Reutilizáveis
Introdução à Escalabilidade em GitHub Actions
github actions transformou a automação de ci/cd, permitindo que desenvolvedores integrem testes, builds e deployments diretamente em seus repositórios. No entanto, à medida que uma organização cresce, a simples cópia de arquivos de workflow entre repositórios se torna um pesadelo de manutenção. A falta de padronização leva a inconsistências, vulnerabilidades e um esforço duplicado para atualizar processos. Para resolver esses desafios de escala, o GitHub Actions oferece mecanismos avançados como Workflows Reutilizáveis e Matrizes de Teste. Este artigo explora em profundidade como utilizar essas ferramentas para criar pipelines de CI/CD robustos, manuteníveis e escaláveis.
A Necessidade de Workflows Centralizados: O Princípio DRY em CI/CD
O princípio Don’t Repeat Yourself (DRY) é fundamental no desenvolvimento de software, e sua aplicação em pipelines de CI/CD é igualmente crítica. Quando dezenas ou centenas de repositórios utilizam lógicas de build e teste semelhantes, a duplicação de código nos arquivos YAML de workflow acarreta diversos problemas:
- Manutenção Elevada: Uma simples atualização, como a mudança da versão de uma ferramenta de build ou a adição de um novo passo de segurança, precisa ser replicada manualmente em todos os repositórios.
- Inconsistência: É fácil que repositórios diferentes acabem com versões ligeiramente distintas do mesmo processo, dificultando o debug e a padronização.
- Vulnerabilidades: Uma falha de segurança descoberta em uma Action ou passo de um workflow pode permanecer sem correção em repositórios esquecidos ou menos ativos.
- Onboarding Lento: Novos projetos precisam recriar a lógica de CI/CD do zero, ou copiar e adaptar de um projeto existente, arriscando introduzir erros.
A solução para esse cenário é a centralização. Ao invés de cada repositório definir seu próprio workflow, eles devem invocar um workflow central, padronizado e mantido em um único local.
Implementando Workflows Reutilizáveis com `workflow_call`
O mecanismo principal para a reutilização de workflows no GitHub Actions é o gatilho workflow_call. Ele permite que um workflow (o “chamador” ou “caller”) execute outro workflow (o “chamado” ou “callee”) localizado em outro repositório ou no mesmo.
Estrutura de um Workflow Reutilizável (Callee)
O workflow reutilizável é um arquivo YAML comum, mas com uma seção on específica. Ele define as entradas (inputs) que espera receber e os segredos (secrets) que pode acessar, garantindo um contrato claro com quem o invoca.
Considere um workflow reutilizável para build e teste de uma aplicação Node.js, salvo em my-org/ci-templates/.github/workflows/reusable-node-ci.yml:
# reusable-node-ci.yml
name: Reusable Node.js CI
on:
workflow_call:
inputs:
node-version:
description: 'A versão do Node.js para usar'
required: true
type: string
app-directory:
description: 'O diretório da aplicação para executar os comandos'
required: false
type: string
default: '.'
secrets:
NPM_TOKEN:
description: 'Token para acessar registros privados do npm'
required: true
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: 'npm'
cache-dependency-path: '${{ inputs.app-directory }}/package-lock.json'
- name: Install Dependencies
run: npm ci
working-directory: ${{ inputs.app-directory }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Run Linter
run: npm run lint
working-directory: ${{ inputs.app-directory }}
- name: Run Tests
run: npm test
working-directory: ${{ inputs.app-directory }}
Neste exemplo, o gatilho on: workflow_call especifica que este workflow pode ser chamado por outros. Ele declara uma entrada obrigatória (node-version), uma opcional (app-directory) e um segredo obrigatório (NPM_TOKEN). Os valores recebidos são acessados através dos contextos inputs e secrets.
Invocando um Workflow Reutilizável (Caller)
O workflow chamador, localizado no repositório da aplicação, utiliza a sintaxe uses dentro de uma definição de job para invocar o workflow reutilizável. É crucial especificar a referência (branch, tag ou commit SHA) para garantir estabilidade e versionamento.
# .github/workflows/main-ci.yml
name: Application CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
call-reusable-workflow:
uses: my-org/ci-templates/.github/workflows/[email protected]
with:
node-version: '20'
secrets:
NPM_TOKEN: ${{ secrets.NPM_REGISTRY_TOKEN }}
Neste caso, o job call-reusable-workflow não possui steps. Em vez disso, ele aponta para o workflow reutilizável na tag v1.2.0. A seção with passa os valores para os inputs definidos, e a seção secrets mapeia um segredo do repositório atual (NPM_REGISTRY_TOKEN) para o segredo esperado pelo callee (NPM_TOKEN). O uso de secrets: inherit também é uma opção para passar todos os segredos do chamador.
Workflows Reutilizáveis vs. Actions Compostas
É importante distinguir Workflows Reutilizáveis de Actions Compostas (Composite Actions). Embora ambos promovam a reutilização, eles operam em níveis diferentes:
- Actions Compostas: São usadas para agrupar uma sequência de passos (steps) em uma única Action. Elas são executadas dentro do contexto de um único job e são ideais para encapsular tarefas repetitivas, como configurar uma ferramenta, fazer login em um registro e publicar um artefato.
- Workflows Reutilizáveis: São usados para reutilizar jobs inteiros ou até mesmo workflows completos. Cada chamada a um workflow reutilizável cria uma execução de workflow separada e visível na UI do GitHub. São ideais para orquestrar processos de CI/CD completos e padronizados.
Use Actions Compostas para reutilizar blocos de lógica *dentro* de um job. Use Workflows Reutilizáveis para reutilizar a estrutura e orquestração *de jobs* inteiros.
Expandindo a Cobertura com Matrizes de Teste (`strategy.matrix`)
Centralizar a lógica é o primeiro passo. O segundo é garantir uma cobertura de teste abrangente sem duplicar código. É aqui que as matrizes de teste se destacam. A diretiva strategy: matrix permite que você execute o mesmo job múltiplas vezes com diferentes configurações.
Sintaxe Básica da Matriz
A matriz é definida como um conjunto de chaves e arrays de valores. O GitHub Actions cria um job para cada combinação possível (produto cartesiano) das variáveis da matriz.
Exemplo de teste em múltiplas versões do Node.js e sistemas operacionais:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [18, 20]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
Este workflow irá gerar quatro jobs em paralelo:
- OS:
ubuntu-latest, Node:18 - OS:
ubuntu-latest, Node:20 - OS:
windows-latest, Node:18 - OS:
windows-latest, Node:20
Dentro dos passos, você acessa os valores atuais da matriz usando o contexto matrix, como em ${{ matrix.os }}.
Configurações Avançadas de Matriz
Para cenários mais complexos, a estratégia de matriz oferece opções de configuração adicionais que fornecem controle granular sobre as execuções.
Adicionando ou Removendo Configurações Específicas
Às vezes, o produto cartesiano completo não é desejado. Você pode usar include para adicionar combinações específicas ou exclude para remover combinações indesejadas.
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [18, 20]
experimental: [false]
include:
# Adiciona uma execução experimental apenas para Ubuntu com Node 21
- os: ubuntu-latest
node-version: 21
experimental: true
exclude:
# Exclui a combinação de Windows com Node 18
- os: windows-latest
node-version: 18
Neste exemplo, adicionamos uma execução para uma versão de Node experimental que só faz sentido no Ubuntu e removemos uma combinação legada que não é mais suportada no Windows.
Controlando a Execução com `fail-fast` e `max-parallel`
fail-fast: false: Por padrão, se um job da matriz falhar, o GitHub Actions cancela todos os outros jobs em andamento e futuros daquela matriz. Definirfail-fast: falsegarante que todos os jobs da matriz sejam executados até o fim, independentemente de falhas individuais. Isso é útil para obter um panorama completo de quais configurações estão falhando.max-parallel: <number>: Limita o número de jobs da matriz que podem ser executados simultaneamente. Isso é útil para gerenciar o consumo de runners ou para evitar sobrecarregar ambientes de teste externos.
Sinergia Total: Combinando Matrizes e Workflows Reutilizáveis
A verdadeira escalabilidade é alcançada quando combinamos essas duas funcionalidades. O padrão ideal é ter um workflow chamador (caller) que define a matriz de configurações e, para cada combinação, invoca um workflow reutilizável que contém a lógica de CI/CD.
Isso separa as responsabilidades de forma limpa:
- Workflow Reutilizável (Callee): Contém o “como” (a lógica de build, teste, análise). É genérico e parametrizado.
- Workflow Chamador (Caller): Contém o “o quê” (as configurações específicas, como versões, plataformas, ambientes).
Exemplo de um workflow chamador que utiliza uma matriz para invocar um workflow reutilizável:
# .github/workflows/main-ci.yml
name: Application CI
on: [push, pull_request]
jobs:
build-test-matrix:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [18, 20]
# Invoca o workflow reutilizável para cada combinação da matriz
uses: my-org/ci-templates/.github/workflows/[email protected]
with:
# Passa os valores da matriz como inputs para o callee
runner-os: ${{ matrix.os }} # Supondo que o callee tenha um input 'runner-os'
node-version: ${{ matrix.node-version }}
secrets: inherit
Este padrão é extremamente poderoso. A lógica de CI é mantida em um único lugar (reusable-node-ci.yml), mas cada repositório tem a flexibilidade de definir contra quais plataformas e versões seu código deve ser testado, simplesmente ajustando sua matriz local. Se um novo sistema operacional precisar ser suportado, basta adicionar um item ao array os na matriz, sem tocar na complexa lógica de build.
Sou um profissional na área de Tecnologia da informação, especializado em monitoramento de ambientes, Sysadmin e na cultura DevOps. Possuo certificações de Segurança, AWS e Zabbix.



