2 pontos por GN⁺ 2025-03-21 | Ainda não há comentários. | Compartilhar no WhatsApp
  • Ao trazer de volta para o GitHub Actions uma CI complexa, merge queue, vários runners, build de Rust, imagens Docker e testes de integração se entrelaçaram, e o custo de depuração acabou ficando mais evidente do que o da configuração
  • Todas as mudanças que entram na main precisam passar em todos os testes, e erros pequenos como formatação, dependências não usadas e lint são corrigidos automaticamente
  • Para forçar tanto a validação antes quanto depois da merge queue, foi preciso deixar os nomes dos jobs idênticos nas duas etapas; caso contrário, os checks podem travar ou mudanças com falha podem ser mescladas
  • GITHUB_TOKEN, permissions do workflow, tokens customizados e exceções envolvendo fork e self-hosted runner se misturam, tornando o modelo de segurança difícil de entender e propenso a erros
  • Executar contêineres Docker, manter workflows em YAML e testar localmente de forma limitada desacelerou o desenvolvimento, mas o novo script de CI reduziu bastante o tempo de merge

Uma CI complexa de volta ao GitHub Actions

  • Nas últimas duas semanas, os scripts de CI foram reescritos novamente em GitHub Actions
  • Esta é a terceira reformulação da configuração de CI
    • Primeiro era GitHub Actions
    • Depois migrou para Earthly
    • Como o Earthly foi descontinuado, voltou para o GitHub Actions
  • Hoje a CI lida ao mesmo tempo com merge queue, vários runners, build de Rust, imagens Docker e testes de integração pesados
    • Os runners incluem self-hosted, blacksmith.sh e GitHub-hosted
    • Para mesclar um único PR, são consumidas cerca de 1 hora de CI em vários runners paralelos

Condições que a CI precisa atender

  • Toda mudança que entra na main precisa passar em todos os testes
  • Erros pequenos, como formatação, dependências não usadas e problemas de lint, não devem terminar em falha, e sim ser corrigidos automaticamente
  • Os artefatos testados na CI precisam ser os mesmos artefatos reais de release
  • Para uma boa experiência de desenvolvimento, a CI precisa terminar rápido
  • O GitHub Actions oferece suporte técnico a esses requisitos, mas o processo de configuração vem acompanhado de armadilhas ocultas, comportamentos inconsistentes e depuração difícil

Merge queue e status checks

  • A chave para manter a branch main limpa é a merge queue do GitHub
    • A merge queue faz rebase do PR sobre a main antes da execução da CI
  • A execução necessária da CI se divide em duas etapas
    • Antes de entrar na fila, a CI roda para corrigir automaticamente problemas pequenos
    • Dentro da fila, a CI roda de novo para validar o estado final da mesclagem
  • Para tornar obrigatórias as duas execuções no GitHub Actions, foi necessário definir o mesmo nome de job nas duas etapas
    • Assim, o GitHub trata as duas execuções como o mesmo check, e a mesclagem só é permitida se ambas tiverem sucesso
    • Essa solução só foi encontrada depois de horas de depuração e de uma resposta no Stack Overflow
  • Outras abordagens podem fazer com que o status check fique aguardando antes da entrada na fila e o job nem comece, ou permitir que um job que deveria falhar dentro da merge queue ainda assim resulte em mesclagem

O modelo de segurança difícil de entender do GitHub Actions

  • Depois do caso recente em que uma GitHub Action popular foi comprometida, surgiu a recomendação de “fixar dependências por hash”, mas os comentários mostraram que quase ninguém faz isso
  • O GitHub Actions tem um token padrão chamado GITHUB_TOKEN
    • Esse token é inicializado com permissões padrão
    • As permissões padrão podem ser definidas em Actions → General → Workflow Permissions nas configurações do repositório
  • Se as permissões padrão do GITHUB_TOKEN forem restritas, é preciso elevá-las para executar actions e comandos necessários; se forem permissivas, é possível remover algumas permissões no arquivo de workflow
  • Um padrão melhor seria começar com nenhuma permissão e deixar o usuário adicionar apenas o necessário
  • muitos tipos de permissão, e, para quem não é especialista em GitHub, é difícil entender o que exatamente cada uma protege

Exceções de token e permissão

  • Ao gerar releases do GitHub automaticamente com softprops/action-gh-release, é usado um token customizado chamado CI_RELEASE
- name: Release on GitHub
  if: env.version_exists == 'false'
  uses: softprops/action-gh-release@v2
  with:
    tag_name: v${{ env.CURRENT_VERSION }}
    generate_release_notes: true
    make_latest: true
    token: ${{ secrets.CI_RELEASE }}
  • Com o token padrão, a release em si até é concluída, mas workflows posteriores não são disparados
    • Como não há indicação separada disso, foi preciso encontrar uma issue de alguém com o mesmo problema para entender a causa
  • Também é possível elevar permissões dentro do YAML do workflow
    • A ideia de elevar permissões dentro do próprio código que se quer proteger soa estranha
  • Segundo a documentação do GitHub, a chave permissions pode adicionar ou remover permissões de leitura para repositórios forkados, mas normalmente não permite conceder permissões de escrita
    • A exceção é quando um administrador ativa a opção Send write tokens to workflows from pull requests nas configurações do GitHub Actions
  • Com tantas exceções e armadilhas, o modelo de segurança do GitHub Actions é poderoso, mas também amplia a superfície de ataque e a possibilidade de erro

A incerteza do self-hosted runner

  • A documentação do GitHub não recomenda o uso de self-hosted runner em repositórios públicos
    • Isso porque forks de repositórios públicos podem executar código perigoso na máquina do self-hosted runner por meio de PRs
  • O GitHub também tem uma configuração de self-hosted runner que exige aprovação para executar PRs de contribuidores externos
  • A documentação não dá uma resposta clara sobre se usar essa configuração junto com self-hosted runner é realmente seguro, e também não há consenso na internet
  • O nível de complexidade é alto demais para chegar a 100% de confiança

O choque entre Docker e GitHub Actions

  • O GitHub Actions pode executar jobs dentro de contêineres
    • A vantagem é poder empacotar previamente dependências em um dev container, sem precisar instalá-las toda vez
  • No uso real, problemas de permissão de arquivo apareceram repetidamente
    • O contêiner faz o build dos arquivos como um usuário, mas o GitHub runner pode rodar com outro uid/gid
    • Como resultado, pode não ser possível acessar arquivos dentro do contêiner, no workspace do GitHub ou em diretórios temporários do host
  • O diretório $HOME também pode ficar desalinhado
    • O dev container pode instalar ferramentas em /home/ubuntu
    • Dentro do GitHub Actions, o $HOME pode ser /github/home
    • Ferramentas que dependem de arquivos sob $HOME podem não encontrar o que precisam
  • Actions que interagem com o sistema host também podem quebrar
    • O cache do GitHub é limitado a 10GB, então foi usada a sticky disk action da blacksmith para montar um drive NVMe para cache
    • Isso não funcionava dentro do contêiner, e só foi resolvido depois de uma correção da blacksmith.sh
  • O próprio campo container também tem limitações
    • Não é possível sobrescrever o entrypoint
    • Também não dá para executar apenas alguns steps dentro do contêiner e o restante fora dele

Desenvolvimento e depuração de workflows em YAML

  • A lógica do GitHub Actions é escrita em YAML, e, quanto mais complexa fica, mais fácil é errar
  • A inspeção do linter de GitHub YAML no RustRover ajudou, mas ainda falta uma análise estática melhor
  • Localmente, é difícil testar de forma suficiente o comportamento real da CI
    • O act é uma ferramenta conhecida, mas só suporta uma pequena parte do que se queria fazer na CI
  • A melhor forma de depuração acabou sendo criar outro repositório idêntico e repetir git commit -a -m "wip" && git push test-ci branch até a CI se comportar como esperado

Separação de workflows e reutilização de artefatos

  • Para não rodar o pipeline inteiro de CI toda vez, cada workflow foi mantido pequeno
  • Ao final de cada workflow, os artefatos são enviados, e workflows posteriores os baixam para evitar reconstruir tudo do zero
  • Também é possível baixar artefatos de execuções anteriores para testar workflows de forma isolada
    • Porém, ao baixar de uma execução anterior, ainda é preciso fornecer um token à action download-artifact
    • Esse token pode ser o token padrão, mas por que ainda precisa ser informado explicitamente continua sendo uma dúvida sem resposta
  • O arquivo principal de workflow acaba virando uma cadeia que chama outros arquivos YAML
jobs:
  invoke-build-rust:
    name: Build Rust
    uses: ./.github/workflows/build-rust.yml
  invoke-build-java:
    name: Build Java
    uses: ./.github/workflows/build-java.yml
  invoke-tests-unit:
    name: Unit Tests
    needs: [invoke-build-rust, invoke-build-java]
    uses: ./.github/workflows/test-unit.yml
  invoke-tests-adapter:
    name: Adapter Tests
    needs: [invoke-build-rust]
    uses: ./.github/workflows/test-adapters.yml
    secrets: inherit
  invoke-build-docker:
    name: Build Docker
    needs: [invoke-build-rust, invoke-build-java]
    uses: ./.github/workflows/build-docker.yml
  invoke-tests-integration:
    name: Integration Tests
    needs: [invoke-build-docker]
    uses: ./.github/workflows/test-integration.yml
  invoke-tests-java:
    name: Java Tests
    needs: [invoke-build-java]
    uses: ./.github/workflows/test-java.yml
  • Alguns jobs precisam de secrets: inherit
    • Quando um workflow chama outro workflow, os secrets não são compartilhados por padrão
    • Esse era o motivo de a falha acontecer no pipeline completo de CI, mas não na execução de steps individuais

Merges mais rápidos, depuração ainda cara

  • O novo script de CI reduziu bastante o tempo de merge, e o resultado agradou
  • Ainda assim, foi preciso gastar tempo demais para chegar a esse ponto, e a depuração deveria ser bem mais fácil quando problemas aparecem

Ainda não há comentários.

Ainda não há comentários.