Durante um incidente, alguém faz sempre a mesma pergunta: que versão está realmente a correr? A resposta costuma começar com uma tag, como api:1.8.2, seguida de uma longa pausa enquanto as pessoas tentam perceber se essa tag foi reconstruída, de que commit veio e se a correção que entrou ontem está lá.
Não tem de ser uma pausa. Com algumas convenções no build, qualquer container em execução pode ser rastreado até ao commit, à execução do build e ao pull request que o produziram.
As tags descrevem a intenção, não a realidade
Uma tag é um ponteiro. Pode ser publicada outra vez, sobrescrita por um rebuild ou partilhada por várias imagens ao longo do tempo. Dois clusters a correr api:1.8.2 podem estar a correr código diferente. Um digest, o hash sha256: do manifest da imagem, identifica uma imagem exata e mais nenhuma.
O Kubernetes regista o digest resolvido mesmo quando o manifest usou uma tag, por isso consegue sempre ver o que foi realmente descarregado:
kubectl get pods -n prod \
-o custom-columns=POD:.metadata.name,IMAGE:.status.containerStatuses[0].imageID
O rasto, passo a passo
- PodLer o digest da imagem a partir do estado do container.
- ImagemLer as labels OCI ou a provenance associadas a esse digest.
- CommitA label de revisão indica o commit exato no repositório de código.
- BuildO commit leva à execução de CI que construiu e publicou a imagem.
- Pull requestO commit leva à alteração revista que o fez merge.
Cada elo é barato de acrescentar. O rasto só se quebra quando falta um deles.
O mesmo rasto funciona no sentido inverso, que é muitas vezes a pergunta mais útil. Quando uma correção faz merge, quer saber que ambientes já a estão a correr. A partir do commit, encontre o digest que o build produziu e depois procure nos clusters pods que corram esse digest. Se a resposta for nenhum, a correção ainda não foi entregue, digam as release notes o que disserem. As equipas que conseguem responder rapidamente nos dois sentidos tendem a ver as chamadas de incidente ficarem mais curtas e os rollbacks menos assustadores.
Ponha a origem na imagem
A especificação de imagens OCI define chaves de anotação standard exatamente para isto. Defina-as no momento do build:
docker build \
--label org.opencontainers.image.source=https://github.com/acme/api \
--label org.opencontainers.image.revision=$(git rev-parse HEAD) \
--label org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
-t ghcr.io/acme/api:1.8.2 .
No GitHub Actions, a docker/metadata-action gera estas labels por si. Lê-las de volta a partir de qualquer digest exige um comando:
crane config ghcr.io/acme/api@sha256:<digest> | jq '.config.Labels'
As labels são fáceis, mas qualquer pessoa que consiga fazer build de uma imagem pode defini-las. Trate-as como uma pista útil e use proveniência assinada quando a resposta tiver de resistir a escrutínio.
Torne o rasto fiável
A proveniência do build é uma declaração assinada pela plataforma de CI que diz que repositório, commit e workflow produziram um digest. Ao contrário de uma label, não pode ser escrita pelos próprios passos do build, por isso mantém-se válida mesmo que alguém tente fazer passar uma imagem construída noutro lado.
gh attestation verify oci://ghcr.io/acme/api@sha256:<digest> --repo acme/api
# from the commit, find the build run and the pull request
gh run list --repo acme/api --commit <commit-sha>
gh api repos/acme/api/commits/<commit-sha>/pulls --jq '.[].html_url'
O que quebra o rasto
- Imagens construídas em portáteis. Sem execução de CI, sem provenance, muitas vezes sem labels. Bloqueie pushes para registries de produção a partir de tudo o que não seja o CI.
- Retagging entre ambientes. Promover reconstruindo cria um novo digest. Promova antes o mesmo digest.
- Squash merges sem links. O commit na main tem de continuar a apontar para o seu pull request. A maioria das plataformas trata disto, mas verifique as suas definições de merge.
- Desvio da imagem base. O seu commit não mudou, mas a base sim. Fixe as imagens base por digest e deixe o bot de atualizações propor a alteração.
Verifique o seu em 10 minutos
- Escolha um serviço de produção e liste os digests das imagens em execução com o comando
kubectlacima. - Corra
crane confignum digest. Existe uma label de revisão? - Siga esse commit até uma execução de build e um pull request. Cronometre quanto tempo demora.
- Verifique se os seus manifests de deploy usam tags ou digests.
- Verifique se alguém fora da CI consegue fazer push para o registry de produção.
Se o rasto demorar mais do que alguns minutos, ou parar no segundo passo, sabe que elo acrescentar primeiro.
Onde isto fica na linha
Na torre (em desenvolvimento):