Pular para o conteúdo

Kubernetes

A imagem é construída por apps/runner/Dockerfile, com contexto na raiz do repositório. O entrypoint é apps/runner/docker-entrypoint.sh e o comando é:

node --import @rootpilot/otel/register apps/runner/dist/index.js

A imagem roda como root, e é por isso que o Compose monta ~/.aws em /root/.aws. Se o seu cluster exige runAsNonRoot, essa é uma adaptação que você precisa fazer, e ela não está feita aqui.

O runner é cliente de saída. Ele não escuta em porta nenhuma, não expõe /healthz e não tem endpoint de readiness.

Consequência direta: não configure httpGet em liveness ou readiness probes: todos vão falhar. Se você precisa de um sinal, ele vem dos logs de boot (boot: ready) e do Fleet do control-plane, que enxerga o runner conectado. A saúde por connector também é reportada no handshake, não por HTTP.

Requisito 2: graça de terminação maior que o drain

Seção intitulada “Requisito 2: graça de terminação maior que o drain”

O runner trata SIGTERM drenando: ele para de aceitar trabalho novo, termina o que está em voo e só então sai. O teto disso é RUNNER_DRAIN_TIMEOUT_MS, cujo default é 120 000 ms.

O default do Kubernetes para terminationGracePeriodSeconds é 30 s. Se você deixar assim, o SIGKILL chega no meio do drain.

terminationGracePeriodSeconds: 150 # > RUNNER_DRAIN_TIMEOUT_MS (120s), com folga

Um reclaim duro sem drain (SIGKILL, OOM, spot) perde o estado aprendido desde o último flush. O RUNNER_LEARNED_FLUSH_INTERVAL_MS (default 60 s) existe justamente para limitar essa perda, mas não a elimina.

O runner é cattle e não tem estado durável. O cache SQLite do learned-store é :memory: por default. Não crie PVC. Se você apontar RUNNER_LEARNED_STORE_PATH para um caminho em disco, saiba que está criando um cache, não uma fonte de verdade: a fonte de verdade é o control-plane.

Este é o ponto em que Kubernetes muda o desenho, e para melhor.

O runner lê o token OIDC projetado da ServiceAccount do Kubernetes e o envia como atestação. Nada secreto entra na imagem: o token é um JWT de vida curta, com audiência fixa, que o kubelet projeta.

O caminho default é /var/run/secrets/tokens/gke-oidc/token, configurável por RUNNER_GKE_TOKEN_PATH. Ou seja, o pod precisa de um volume projected com serviceAccountToken montado exatamente ali.

Se o volume não estiver montado, o erro é explícito e diz isso:

RUNNER_ATTESTATION_MODE=gke-oidc: failed to read projected KSA token at
/var/run/secrets/tokens/gke-oidc/token (is the Workload Identity token volume mounted?)

O aws-sts assina um GetCallerIdentity com a cadeia de credenciais default do SDK, que no EKS é o IRSA. Isso significa que a ServiceAccount anotada com o role IAM já basta, sem token de bootstrap e sem humano no navegador.

Ele exige AWS_REGION explicitamente. Sem ela o boot falha com RUNNER_ATTESTATION_MODE=aws-sts requires AWS_REGION. A região não é adivinhada de propósito, porque o control-plane só aceita região da própria allowlist e um palpite viraria um attestation_invalid que se lê como problema de credencial.

Prefira o secret manager que você já opera a Secrets do Kubernetes: RUNNER_SECRET_STORE_MODE=aws (AWS Secrets Manager) ou vault (HashiCorp Vault KV v2). Os dois resolvem por workload identity, sem chave estática. Veja Secret stores.

Para as credenciais de leitura da AWS, RUNNER_AWS_CRED_MODE=chain faz o SDK resolver por IRSA direto, sem passar chave nenhuma pelo cluster.

Só saída, nada de entrada. Se você usa NetworkPolicy, a lista de destinos está em Pré-requisitos. Lembre que a allowlist declarada nos manifests dos connectors é documentação, não enforcement: para os connectors nativos, quem aplica egress control é o cluster.

Sendo explícito sobre o gap, já que a pergunta vai aparecer: um chart de verdade precisaria de decisões que este repositório ainda não tomou: runAsNonRoot e a mudança de /root/.aws, valores de resources (não há benchmark versionado), e a forma canônica de expor a escolha de atestação por provedor. Enquanto isso não existir, escrever os manifests do lado de quem instala é o caminho honesto.