DevOps Admin

Dominando GitHub Actions Avançado: Matrix Builds, Environments e OIDC em Projetos Reais Já leu

Matrix Builds: Executando Testes em Múltiplas Configurações A funcionalidade de Matrix Builds é um dos recursos mais poderosos do GitHub Actions. Ela permite que você execute o mesmo workflow em diferentes combinações de versões, sistemas operacionais ou ambientes sem precisar duplicar todo o código. Imagine que você precisa testar sua aplicação Node.js nas versões 16, 18 e 20, e também em Ubuntu, Windows e macOS. Sem matrix, você escreveria três conjuntos de jobs praticamente idênticos. Com matrix, você escreve uma única configuração e o GitHub Actions gera automaticamente todas as 9 combinações possíveis. O conceito funciona através de variáveis de matriz que definem um espaço multidimensional de execução. Cada dimensão representa um eixo de variação — versão do Node, sistema operacional, ou até configurações de banco de dados. O workflow é executado uma vez para cada combinação dessas variáveis, e você acessa os valores dentro do job usando a sintaxe . Isso economiza tempo de manutenção e garante que seu

Matrix Builds: Executando Testes em Múltiplas Configurações

A funcionalidade de Matrix Builds é um dos recursos mais poderosos do GitHub Actions. Ela permite que você execute o mesmo workflow em diferentes combinações de versões, sistemas operacionais ou ambientes sem precisar duplicar todo o código. Imagine que você precisa testar sua aplicação Node.js nas versões 16, 18 e 20, e também em Ubuntu, Windows e macOS. Sem matrix, você escreveria três conjuntos de jobs praticamente idênticos. Com matrix, você escreve uma única configuração e o GitHub Actions gera automaticamente todas as 9 combinações possíveis.

O conceito funciona através de variáveis de matriz que definem um espaço multidimensional de execução. Cada dimensão representa um eixo de variação — versão do Node, sistema operacional, ou até configurações de banco de dados. O workflow é executado uma vez para cada combinação dessas variáveis, e você acessa os valores dentro do job usando a sintaxe matrix.varname. Isso economiza tempo de manutenção e garante que seu código seja testado em cenários reais e diversificados.

name: Matrix Build Example
on: [push, pull_request]

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [16.x, 18.x, 20.x]

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Log configuration
        run: echo "Testing on ${{ matrix.os }} with Node ${{ matrix.node-version }}"

Neste exemplo, o job test será executado 9 vezes (3 sistemas operacionais × 3 versões do Node). Cada execução terá acesso aos valores específicos através de matrix.os e matrix.node-version. O GitHub Actions gerencia toda a orquestração; você apenas define as dimensões e referencia os valores.

Incluindo e Excluindo Configurações

Nem sempre você quer todas as combinações possíveis. Matrix permite refinar o comportamento com include para adicionar combinações específicas e exclude para remover aquelas que não fazem sentido. Por exemplo, talvez sua aplicação não funcione em Windows com Python 3.7, ou você queira adicionar um teste especial apenas para uma combinação rara.

jobs:
  build:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        python-version: ['3.8', '3.9', '3.10']
        exclude:
          # Python 3.8 não é testado em Windows
          - os: windows-latest
            python-version: '3.8'
        include:
          # Adiciona um teste especial apenas em Ubuntu com Python 3.10
          - os: ubuntu-latest
            python-version: '3.10'
              extra-flags: '--strict'

    steps:
      - uses: actions/checkout@v4

      - name: Setup Python ${{ matrix.python-version }}
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install dependencies
        run: pip install -r requirements.txt

      - name: Run tests
        run: pytest ${{ matrix.extra-flags }}

Neste caso, você começa com 6 combinações (2 SOs × 3 versões), remove 1 com exclude (ficando com 5), e adiciona 1 novamente com include que contém um campo extra (extra-flags). Essa flexibilidade permite otimizar seus testes sem perder cobertura.

Limitando Execuções Paralelas com max-parallel

Por padrão, o GitHub Actions executa todos os jobs da matriz em paralelo, dentro dos limites da sua conta. Se você precisa controlar isso — seja para economizar créditos ou evitar sobrecarregar um serviço externo — use a chave max-parallel.

strategy:
  max-parallel: 2
  matrix:
    os: [ubuntu-latest, windows-latest, macos-latest]
    node-version: [16.x, 18.x, 20.x]

Com max-parallel: 2, apenas 2 jobs rodão simultaneamente, e os demais esperam na fila. As 9 combinações serão executadas sequencialmente em lotes de 2, aumentando o tempo total mas controlando recursos.


Environments: Isolamento e Controle de Acesso

Environments (ambientes) no GitHub Actions permitem que você defina regras de proteção, segredos específicos e reviewers obrigatórios para diferentes fases do deployment. Pense em um ambiente como uma "porta de entrada" para ações críticas — produção, staging, homologação — onde você pode impor controles antes de permitir que o workflow prossiga. Um reviewer humano pode ser obrigado a aprovar um deploy para produção, ou segredos específicos podem ser acessados apenas quando certos ramos fazem deploy.

Cada ambiente é configurado no repositório (Settings → Environments) e referenciado no workflow através da chave environment no job. Isso oferece isolamento natural: segredos de produção não vazam para staging, e você tem controle granular sobre quem pode fazer o quê em cada fase.

name: Deploy Pipeline
on:
  push:
    branches: [main, develop]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build artifact
        run: npm run build
      - name: Upload artifact
        uses: actions/upload-artifact@v3
        with:
          name: dist
          path: dist/

  deploy-staging:
    needs: build
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: actions/download-artifact@v3
        with:
          name: dist

      - name: Deploy to staging
        env:
          DEPLOY_KEY: ${{ secrets.STAGING_DEPLOY_KEY }}
          API_URL: ${{ secrets.STAGING_API_URL }}
        run: |
          chmod 600 ~/.ssh/deploy_key
          ssh -i ~/.ssh/deploy_key deploy@staging.example.com "cd /app && ./deploy.sh"

  deploy-production:
    needs: build
    runs-on: ubuntu-latest
    environment: 
      name: production
      url: https://example.com
    steps:
      - uses: actions/download-artifact@v3
        with:
          name: dist

      - name: Deploy to production
        env:
          DEPLOY_KEY: ${{ secrets.PROD_DEPLOY_KEY }}
          API_URL: ${{ secrets.PROD_API_URL }}
        run: |
          chmod 600 ~/.ssh/deploy_key
          ssh -i ~/.ssh/deploy_key deploy@prod.example.com "cd /app && ./deploy.sh"

Neste exemplo, você tem dois ambientes: staging e production. Cada um pode ter seus próprios segredos (STAGING_DEPLOY_KEY vs PROD_DEPLOY_KEY). No painel do GitHub, você pode configurar que o ambiente production exija aprovação manual, ou que só funcione quando triggered pela branch main. O job deploy-production não começará até que essas condições sejam atendidas.

Proteções e Rules de Ambiente

As proteções são configuradas via interface do GitHub, mas têm impacto direto no workflow. As principais são:

  • Required reviewers: Uma ou mais pessoas devem aprovar antes do job executar.
  • Deployment branches: Apenas branches específicas (geralmente main ou tags) podem fazer deploy neste ambiente.
  • Secrets: Cada ambiente tem seu próprio conjunto de segredos, inacessíveis a outros ambientes.

Essas regras garantem que mesmo que alguém abra um PR malicioso, não conseguirá fazer deploy em produção sem aprovação. O workflow continua funcionando; apenas o acesso aos segredos e a permissão para prosseguir são controlados.

deploy-production:
  needs: build
  runs-on: ubuntu-latest
  environment:
    name: production
    url: https://example.com
  if: github.ref == 'refs/heads/main'
  steps:
    - name: Deploy
      run: echo "Deploying to production..."

Aqui você combina a proteção de ambiente do GitHub com uma condição no workflow (if: github.ref == 'refs/heads/main'), criando camadas de proteção. Mesmo que alguém tente fazer deploy de outra branch, o workflow não prosseguirá.


OIDC: Autenticação sem Segredos Armazenados

O OpenID Connect (OIDC) é uma mudança fundamental na forma como GitHub Actions autentica com provedores externos (AWS, Azure, Google Cloud, etc.). Tradicionalmente, você armazenava uma chave de acesso ou token como um segredo no repositório. Isso funciona, mas cria um ponto de falha: se o segredo vazar, qualquer pessoa consegue acessar sua infraestrutura. OIDC inverte isso: GitHub prova sua identidade dinamicamente, sem armazenar credenciais permanentes.

O fluxo funciona assim: seu workflow solicita um token OIDC assinado do GitHub. Você configura o provedor de nuvem (AWS, etc.) para confiar em GitHub como um provedor de identidade. Quando o token chega, o provedor verifica a assinatura e confirma que é realmente GitHub executando seu workflow. Se tudo está correto, a nuvem emite credenciais temporárias de curta duração exclusivas para aquela execução. Após o workflow terminar, essas credenciais expiram automaticamente.

name: Deploy with OIDC to AWS
on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials with OIDC
        uses: aws-actions/configure-aws-credentials@v2
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-role
          aws-region: us-east-1

      - name: Upload to S3
        run: |
          aws s3 cp dist/ s3://my-bucket/app/ --recursive

      - name: Invalidate CloudFront
        run: |
          aws cloudfront create-invalidation \
            --distribution-id E1234ABCD \
            --paths "/*"

Esse workflow não contém nenhuma chave secreta. A ação aws-actions/configure-aws-credentials@v2 faz o trabalho pesado: solicita o token OIDC do GitHub, troca por credenciais AWS temporárias, e as injeta como variáveis de ambiente. O resto do workflow usa AWS CLI normalmente. Quando termina, as credenciais expiram.

Configurando OIDC no AWS IAM

Para que isso funcione, você precisa configurar uma relação de confiança no AWS. Crie uma role no IAM que permita que tokens OIDC do GitHub a assumam. Aqui está o JSON de confiança:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:seu-usuario/seu-repo:ref:refs/heads/main"
        }
      }
    }
  ]
}

A condição StringLike restringe quais workflows podem assumir essa role. Neste caso, apenas a branch main do seu repositório. Você pode ser tão restritivo quanto necessário: por repositório, branch, ou até por ambiente.

Benefícios e Boas Práticas

O OIDC oferece vários benefícios sobre segredos tradicionais:

  • Sem vazamento permanente: Credenciais expiram automaticamente. Se um atacante conseguir capturá-las, só funcionam por minutos.
  • Auditoria melhorada: Cada ação é ligada a um workflow específico, branch e commit. Você sabe exatamente qual execução fez o quê.
  • Escalabilidade: Você não precisa gerenciar múltiplos segredos para múltiplos ambientes. A confiança é configurada uma vez no provedor.
  • Conformidade: Muitos padrões de conformidade (SOC 2, ISO 27001) preferem OIDC a credenciais estáticas.
name: Multi-Cloud Deploy with OIDC
on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy-aws:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v2
        with:
          role-to-assume: arn:aws:iam::111111111111:role/github-actions
          aws-region: us-east-1
      - run: aws s3 cp dist/ s3://bucket-aws/ --recursive

  deploy-gcp:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: google-github-actions/auth@v1
        with:
          workload_identity_provider: projects/123456789/locations/global/workloadIdentityPools/github/providers/github
          service_account: github-actions@project-id.iam.gserviceaccount.com
      - uses: google-github-actions/setup-gcloud@v1
      - run: gsutil -m cp -r dist/ gs://bucket-gcp/

  deploy-azure:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: azure/login@v1
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      - run: az storage blob upload-batch -s dist/ -d app --account-name myaccount

Aqui você faz deploy para AWS, GCP e Azure no mesmo workflow, cada um usando OIDC (ou sua variante equivalente). Nenhuma chave secreta permanente está armazenada. Cada provedor valida o token OIDC e emite credenciais temporárias.


Combinando Matrix, Environments e OIDC: Estratégia Completa

Os três recursos funcionam muito bem juntos. Você pode usar matrix para testar múltiplas configurações, depois fazer deploy automático apenas para as que passarem, usando environments com OIDC para autenticação segura. Aqui está um exemplo realista de um pipeline CI/CD completo:

name: Complete CI/CD Pipeline
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

permissions:
  id-token: write
  contents: read

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        node-version: [18.x, 20.x]

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      - name: Cache dependencies
        uses: actions/cache@v3
        with:
          path: node_modules
          key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}

      - name: Install dependencies
        run: npm ci

      - name: Lint
        run: npm run lint

      - name: Run tests
        run: npm test

      - name: Build
        run: npm run build

  security-scan:
    runs-on: ubuntu-latest
    if: github.event_name == 'push'
    steps:
      - uses: actions/checkout@v4
      - name: Run Snyk scan
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

  deploy-staging:
    needs: [test, security-scan]
    if: github.ref == 'refs/heads/develop'
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20.x

      - name: Install and build
        run: |
          npm ci
          npm run build

      - name: Configure AWS with OIDC
        uses: aws-actions/configure-aws-credentials@v2
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-staging
          aws-region: us-east-1

      - name: Deploy to staging
        run: |
          aws s3 sync dist/ s3://staging-bucket/
          aws cloudfront create-invalidation --distribution-id E123 --paths "/*"

  deploy-production:
    needs: [test, security-scan]
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://example.com
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20.x

      - name: Install and build
        run: |
          npm ci
          npm run build

      - name: Configure AWS with OIDC
        uses: aws-actions/configure-aws-credentials@v2
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-production
          aws-region: us-east-1

      - name: Deploy to production
        run: |
          aws s3 sync dist/ s3://prod-bucket/ --delete
          aws cloudfront create-invalidation --distribution-id E456 --paths "/*"

      - name: Create deployment record
        run: |
          aws dynamodb put-item \
            --table-name deployments \
            --item "timestamp={S=$(date -u +%s)},repo={S=seu-repo},commit={S=$GITHUB_SHA}"

Este pipeline:
1. Testa em múltiplas configurações (matriz com 2 SOs × 2 versões Node = 4 jobs em paralelo)
2. Escaneia segurança apenas em merges (não em PRs)
3. Faz deploy em staging automaticamente quando código chega em develop, usando environment com OIDC
4. Faz deploy em production apenas após aprová-lo (environment com reviewers obrigatórios), quando merged para main
5. Registra cada deployment para auditoria

Tudo sem uma única chave secreta permanente armazenada no repositório.


Conclusão

Dominando esses três pilares — Matrix Builds, Environments e OIDC — você constrói pipelines robustos, escaláveis e seguros. Matrix elimina duplicação e garante cobertura em múltiplas plataformas. Environments isolam diferentes fases de entrega e impõem controle de acesso humano quando crítico. OIDC substitui segredos permanentes por credenciais temporárias verificadas criptograficamente, reduzindo drasticamente o risco de exposição.

Na prática, isso significa que você consegue testar seu código em Windows, macOS e Linux, com múltiplas versões de runtime, tudo automaticamente. Seu staging recebe updates contínuos, mas produção só avança com aprovação. E em nenhum momento há chaves de API ou senhas armazenadas como texto no repositório — apenas tokens de curta duração gerados sob demanda. Isso é infraestrutura moderna.


Referências


Artigos relacionados