Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

03 - GitHub Actions

#github-actions#oidc#runners#supply-chain#workflows
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione video: https://youtu.be/... no frontmatter da nota.

GitHub Actions

GitHub Actions é o sistema de integração e entrega contínua nativo do GitHub, no qual workflows escritos em YAML reagem a eventos do repositório, push, pull request, tag, agendamento ou despacho manual, e executam jobs em runners hospedados ou próprios. A proposta é transformar a automação em mais um artefato do repositório: versionada, revisada, reutilizável e distribuída junto com o código. O marketplace de actions amplia o modelo com milhares de componentes prontos, do checkout ao deploy em qualquer nuvem, e os recursos de cache, artefatos e matrix builds cobrem o repertório completo de um pipeline de produção. Em serviços na internet, a combinação de baixo atrito inicial, execução gerenciada e integração profunda com o fluxo de pull request fez da ferramenta o padrão de fato para times que já hospedam código no GitHub.

Conceito e modelo de execução

O modelo de execução tem quatro níveis hierárquicos: evento, workflow, job e step. O evento dispara um workflow definido em .github/workflows; o workflow agrupa jobs que rodam em paralelo por padrão, cada um em um runner isolado; o job contém steps, que são comandos ou actions reutilizáveis; e os steps compartilham o mesmo filesystem dentro do job. O resultado aparece no commit, no pull request e nas checks de proteção de branch, fechando o ciclo de validação no próprio fluxo de revisão.

  • Evento: gatilho que inicia o workflow; pode ser push, pull_request, schedule, workflow_dispatch ou dezenas de eventos específicos da plataforma.
  • Workflow: arquivo YAML versionado em .github/workflows, com gatilhos, permissões, jobs e steps declarados de forma explícita.
  • Job: unidade de execução em um runner isolado; jobs independentes rodam em paralelo, e needs estabelece dependências entre eles.
  • Action: pacote reutilizável consumido por uses, que encapsula um passo inteiro, como autenticar na nuvem, configurar um runtime ou publicar um artefato.

O isolamento por job é uma decisão de desenho relevante: cada job recebe um runner limpo, o que elimina contaminação entre execuções, ao custo de exigir mecanismos explícitos de cache e artifacts para compartilhar dados entre jobs.

Dois recursos complementares completam o modelo. Os grupos de concorrência permitem cancelar automaticamente a execução em curso de um branch quando um novo push chega, com a chave concurrency e a opção cancel-in-progress, o que economiza runners e evita pilha de resultados obsoletos. Os environments nomeados agrupam segredos, regras de aprovação e restrições por branch, funcionando como fronteira declarativa entre o deploy automatizado de homologação e o deploy de produção com revisão humana.

O problema que resolve

O GitHub Actions resolve o custo de montar e manter infraestrutura de CI para times que não querem operar servidores de automação. Antes das plataformas gerenciadas, adotar CI significava provisionar máquina, instalar agente, renovar credenciais, escalar capacidade e aplicar patches; com a ferramenta, o time descreve o pipeline em YAML e a plataforma cuida do resto. O segundo problema atacado é o da distribuição de automação: scripts de release comuns a vários projetos, que antes se copiavam manualmente, viram actions compostas versionadas, consumidas por referência e atualizadas de forma controlada. Em produção, esse modelo transformou a forma como times pequenos entregam, porque entrega contínua deixa de ser projeto de infraestrutura e passa a ser arquivo de configuração.

Anatomia de um workflow

O exemplo abaixo concentra os recursos que aparecem na prática diária: permissões mínimas, matriz de build, cache de dependências e autenticação em nuvem sem segredo estático:

name: ci-cd
on:
  push:
    branches: [main]          # valida o tronco a cada commit
  pull_request:               # valida antes de integrar
permissions:
  contents: read              # privilégio mínimo por padrão
  id-token: write             # permite emissão de token OIDC
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: true         # primeira falha cancela os irmãos
      matrix:
        node: [18, 20, 22]    # três versões em runners separados
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - uses: actions/cache@v4
        with:
          path: ~/.npm
          key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
      - run: npm ci
      - run: npm test -- --coverage
  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    permissions:
      id-token: write          # OIDC para autenticação na nuvem
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/deploy-ci
          aws-region: sa-east-1
      - run: ./deploy.sh prod ${{ github.sha }}

Matrix builds

A matriz executa o mesmo conjunto de steps sob combinações diferentes, versões de runtime, sistemas operacionais ou flags de configuração, cada combinação em um runner independente. O caso de uso clássico é a biblioteca que precisa garantir compatibilidade com três versões de linguagem; o caso mais frequente em serviços é validar o build em duas arquiteturas de CPU antes de publicar a imagem. O fail-fast controlado é a diferença entre matriz profissional e desperdício: cancelar irmãos na primeira falha economiza minutos de execução, enquanto permitir que todos os ramos completem ajuda a mapear o alcance de uma quebra. Variáveis derivadas como include e exclude permitem tratar exceções sem duplicar o workflow, e é essa capacidade que mantém a matriz legível quando ela cresce.

A regra de dimensionamento prática é evitar matrizes explosivas, aquelas que multiplicam três runtimes por dois sistemas por quatro regiões e consomem a concorrência da organização inteira em um único push. Matriz existe para cobrir risco real de incompatibilidade; combinações sem decisão associada são custo permanente sem retorno, e o corte periódico da matriz é manutenção tão importante quanto sua criação.

Runners hospedados e self-hosted

Os runners hospedados pela plataforma dispensam qualquer infraestrutura e atendem a maioria dos cenários, com limites de tempo e concorrência por plano. Os runners self-hosted existem para os casos em que a nuvem da plataforma não alcança: redes privadas, hardware específico, cargas intensivas e requisitos de residência de dados. O avanço recente mais relevante nessa frente é o runner scale sets, mecanismo de auto scaling para runners self-hosted em Kubernetes, no qual um conjunto de escala observa a fila e provisiona pods de execução sob demanda, eliminando a antiga escolha entre runner permanente e fila infinita. A operação de self-hosted exige disciplina que a versão hospedada não cobra:

  • Versão mínima atualizada do runner: versões antigas perdem suporte e deixam de receber correções de segurança; a atualização precisa entrar na rotina de manutenção.
  • Runners efêmeros: cada execução em um runner recém-criado e destruído no fim, eliminando o estado residual que produz falha intermitente.
  • Isolamento por repositório: runner registrado para um repositório privado não deve atender a forks públicos, porque pull requests externos executam código não confiável.
  • Capacidade monitorada: fila e concorrência observadas com o mesmo rigor de qualquer serviço, porque runner saturado aparece como latência silenciosa no feedback do time.

Segurança: secrets, OIDC e supply chain

O modelo de segurança da ferramenta tem três camadas que precisam ser usadas juntas. As secrets por repositório, ambiente e organização armazenam credenciais sem expor valores nos logs. A federação OIDC substitui o segredo estático de longa duração por tokens temporários emitidos a cada execução, o que remove a classe inteira de incidentes com chaves vazadas de repositório. E a governança de supply chain cuida do que o workflow executa: actions de terceiros são dependências de código com poder de acesso a segredos e ao checkout. O desenho de autenticação em nuvem por OIDC tem a forma abaixo:

permissions:
  id-token: write          # autoriza emissão do token OIDC na execução
  contents: read
steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/deploy-ci
      aws-region: sa-east-1
      # a política de confiança do role aceita apenas o repositório
      # e o workflow específicos, limitando o raio de comprometimento

As práticas essenciais da governança de supply chain:

  • Pinning de versão: consumir actions por SHA de commit completo, e não por tag móvel, garantindo que a atualização seja decisão revisada, não evento remoto.
  • Permissão mínima no workflow: o bloco permissions restringe o token de execução ao estritamente necessário, job por job.
  • Revisão de dependências de terceiros: avaliar a procedência e a manutenção de cada action antes de adotar, tratando a escolha como revisão de biblioteca.
  • Ambientes com aprovação: deploys para produção em environments com revisores exigidos, ligando o gate humano ao mecanismo nativo da plataforma.

Cache e artifacts

Cache e artifacts respondem a necessidades diferentes e costumam ser confundidos. O cache guarda dados intermediários para acelerar execuções futuras, dependências de pacotes e builds incrementais, com chave derivada de hash de lockfile e validade automática. O artifact guarda saída que precisa sobreviver à execução, relatório de cobertura, log de testes, imagem empacotada, para download posterior ou consumo por outro job via upload-artifact e download-artifact. Em pipelines com jobs isolados, os artifacts são a ponte natural entre build e deploy, e o hábito saudável é definir retenção explícita, porque armazenamento acumulado sem política vira custo silencioso.

A distinção tem consequência de segurança que merece destaque: cache pode ser lido por execuções de outros branches do mesmo repositório, e artifacts seguem as permissões do repositório. Publicar segredos ou dados de cliente em qualquer um dos dois mecanismos transforma o repositório em cofre indevido, acessível por qualquer pessoa com permissão de leitura da ação. A regra prática é simples, cache e artifact transportam dados derivados do código, nunca credenciais, e a revisão do workflow deve questionar qualquer passo que publique conteúdo além de relatórios e binários.

Comparações: GitHub Actions versus GitLab CI e Jenkins

Aspecto GitHub Actions GitLab CI Jenkins
Configuração Workflow em YAML por evento .gitlab-ci.yml por projeto Jenkinsfile em Groovy
Execução gerenciada Runners hospedados por padrão Runners compartilhados no SaaS Requer servidor próprio
Reuso de automação Actions compostas e workflows reutilizáveis Includes e templates hierárquicos Shared libraries em Groovy
Autenticação em nuvem OIDC nativo sem segredo estático Variáveis CI/CD com máscara Credenciais do controller
Escala self-hosted Runner scale sets em Kubernetes Runners próprios ou Kubernetes Agents estáticos ou efêmeros
Risco de supply chain Marketplace amplo exige pinning Templates oficiais mais fechados Plugins com ciclo de segurança próprio

A leitura comparativa objetiva é direta: o GitHub Actions ganha quando o código já vive no GitHub e o time quer entrega sem infraestrutura; o GitLab CI mantém vantagem em ambientes e review apps nativos; o Jenkins permanece soberano onde o runtime precisa ser controlado ao detalhe.

Erros comuns e boas práticas

Os erros abaixo aparecem com regularidade e têm correção conhecida:

  • Consumo de actions por tag móvel: uma action atualizada a montante pode introduzir comportamento hostil ou quebrado a montante; pinning por SHA e atualização programada resolvem.
  • Segredos estáticos onde caberia OIDC: credenciais de nuvem gravadas em secrets expiram, vazam e geram rotação manual; federação OIDC elimina a classe de problema.
  • Workflows com permissions amplas: token de execução com acesso de escrita ao repositório inteiro amplia o estrago de qualquer comprometimento; permissão mínima por job é o padrão.
  • Runner self-hosted exposto a pull requests públicos: execução de código não confiável em runner com acesso a rede interna é vetor clássico de comprometimento; isolamento por repositório e runners efêmeros são obrigatórios.
  • Fila ignorada até doer: concorrência insuficiente aparece como lentidão crescente no feedback; monitorar duração e fila evita a degradação silenciosa do hábito de integrar.
  • Execução sem limite de tempo: workflow sem timeout depende de limites da plataforma e pode consumir minutos de cobrança em loop de teste travado; timeout por job é configuração de uma linha.

Entre as boas práticas que sustentam a ferramenta em produção, três se destacam. Tratar o workflow como código de produção, com revisão obrigatória de mudanças e proteção de branch sobre os arquivos de pipeline. Centralizar a automação comum em actions compostas da organização, atualizadas por dependabot com revisão humana, para que segurança e padrão se propaguem sem cópia manual. E revisar o inventário de workflows a cada trimestre, removendo gatilhos redundantes e execuções duplicadas, porque pipelines que ninguém mais entende são a forma mais barata de desperdício recorrente.