Pular para conteúdo

Pendências

TLDR: bypass de CI pra desfazer depois, débito técnico real (não gambiarra descartável), rotação de senha planejada, a rotina de operação contínua ainda por criar, e o trabalho maior da organização (arquivar repositório, consolidar serviço, padronizar GitOps) ainda não retomado. RabbitMQ foi desligado em 2026-08-24. Cada item tem uma condição clara de quando resolver, não fica em aberto pra sempre sem critério.

Esta página existe pelo mesmo motivo de Estado fora do git: decisão deliberada de adiar algo é diferente de esquecer que ela existe. Toda vez que um bypass, gate desligado, ou solução temporária entrar no repositório, uma linha entra aqui antes de considerar o trabalho terminado.

Bypasses de CI a desfazer

Ordem deliberada, decisão do usuário em 2026-08-21: corrigir os bypasses de CI desta seção só depois de todo o resto do backlog deste documento estar resolvido, não antes e não em paralelo. Motivo prático: promover um check de continue-on-error: true pra bloqueante quebra merge de qualquer PR (inclusive da sessão paralela que também trabalha neste repositório) até o baseline estar limpo, então mexer aqui cedo demais é fonte de atrito sem necessidade. Melhor deixar por último, quando o resto já estiver estável.

  • misconfig (trivy) em security.yml está continue-on-error: true desde 2026-08-21. Motivo: a primeira varredura real achou securityContext ausente em postgres, mariadb, minio, adminer (namespace dados), rabbitmq e redis-server, achado real, mas cada imagem tem UID/GID próprio, corrigir errado quebra o container. Condição pra reverter pra bloqueante: cada Deployment listado abaixo, em "Débito técnico: securityContext", corrigido e validado contra o serviço real.
  • Todos os checks de codigo-e-infra em quality.yml (yamllint, ansible-lint, jscpd, kubeconform, shellcheck, actionlint, zizmor, cspell, ast-grep, kubescape) são continue-on-error: true desde que o job foi criado, nunca foram bloqueantes. Condição pra promover cada um: revisar o baseline de achados daquele check especificamente, zerar o que for real, só então tirar o continue-on-error (ver Qualidade de código e infraestrutura).
  • argocd/apps/operators/cert-manager não existe mais, exclusões correspondentes removidas: resolvido em 2026-08-21, efeito colateral da migração do cert-manager pro chart oficial (ver auditoria de chart oficial abaixo). cert-manager tirado de skip-dirs (trivy, security.yml), --exclude-namespaces (kubescape, quality.yml; o chart oficial já define securityContext por padrão, resolvendo também o débito de securityContext ausente pra esse componente específico), --ignore (jscpd), ignore (.yamllint), ignorePaths (.cspell.config.yaml), ignores da regra no-comments-yaml do ast-grep (bloco inteiro removido, já que não sobrou nenhuma exceção; cnpg nunca precisou dessa exclusão específica). argocd/apps/operators/cnpg continua excluído dos mesmos checks (exceto kubescape, que ver abaixo), permanente por natureza (RBAC amplo e conteúdo exigido pelo próprio operador vendorizado, ver Foundation) até esse operador também migrar pro chart oficial. find -not -path na geração manual do kube-diagrams (não é CI, ver Topologia declarada do cluster) ainda precisa de conferência/ajuste separado (não coberto por este item).

Licença do repositório

  • Este repositório não tinha LICENSE: resolvido em 2026-08-21, criado LICENSE (MIT, mesmo texto do management-service, Copyright (c) 2024-present LADESA e Colaboradores) e linkado no README.md.

Segurança do próprio Argo CD

Achados da auditoria de 2026-08-21 (ver Aprender: Argo CD). O detalhe dos que seguem em aberto fica no inventário privado apontado por Estado fora do git.

  • TLS real na UI/API do Argo CD: resolvido. Estava em HTTP puro sem redirect (confirmado ao vivo com curl, HTTP 200 completo na porta 80) e, na 443, caía no certificado autoassinado padrão do Traefik. Corrigido com server.ingress.tls: true + annotation cert-manager.io/cluster-issuer: ladesa-ro-issuer-production; certificado real do Let's Encrypt confirmado (argocd-server-tls, válido até 2026-11-19).
  • resources (requests/limits) em todos os componentes do Argo CD: resolvido, mas revelou um achado maior, registrado no inventário privado. Também desligados dex/notifications (rodavam sem nenhuma configuração real) e zerado applicationSet.replicas (nenhum ApplicationSet existe no cluster).
  • TLS real em infisical.ladesa.com.br, portainer.ladesa.com.br e adminer.ladesa.com.br: resolvido em 2026-08-21, mesmo padrão do Argo CD (tls: + cert-manager.io/cluster-issuer: ladesa-ro-issuer-production), certificados reais do Let's Encrypt confirmados ao vivo pros três. O Ingress do adminer não existia no git (criado fora de banda pela UI do Portainer, io.portainer.kubernetes.ingress.owner: unknown), trazido pro git agora em argocd/foundation/dados/adminer.yaml. Pegadinha real descoberta no processo: portainer/infisical têm seu Ingress gerado por chart Helm com values embutido direto no manifest da Application (argocd/applications/), e esse manifest só é aplicado no cluster quando a Application root é sincronizada (ver "O padrão app-of-apps"); sincronizar foundation-portainer/foundation-infisical direto, sem sincronizar root primeiro, aplicou os recursos usando o spec.source.helm.values antigo, ainda em cache no objeto Application, produzindo um "sucesso" que não mudou nada. Só percebido conferindo o Ingress ao vivo depois, não confiando no argocd app diff --core (que também não renderirizou o chart Helm corretamente nesse modo, mais um motivo pra sempre verificar o recurso real, não só a saída do comando).
  • TLS real em sso.ladesa.com.br: resolvido em 2026-08-21, na mesma mudança que migrou o provedor de identidade pro Argo CD. Servia em HTTP puro, e o 443 caía no certificado autoassinado padrão do Traefik. Agora serve certificado da Let's Encrypt emitido pelo ladesa-ro-issuer-production, o mesmo emissor dos outros nove certificados do cluster, declarado em gitops/apps/sso/values-production.yaml do repositório satélite. O HTTP continua respondendo, ver o item sobre o issuer do OIDC no inventário privado.
  • Achados desta revisão que seguem em aberto: descritos no inventário privado apontado por Estado fora do git, e não aqui. Quem for trabalhar neles consulta lá. Conforme cada um for corrigido, a descrição completa volta pra esta página, porque problema resolvido pode ser documentado por inteiro sem servir de mapa pra ninguém.

Revisão de segurança: portas, Ingress e Service expostos

Levantamento completo feito em 2026-08-21, a pedido explícito do usuário ("revisão de segurança de tudo o que está liberado: portas no host, ingress, services"). Metodologia: nada assumido, cada achado testado ao vivo (nc/curl de fora do node, não só lendo configuração).

  • Achados desta revisão que seguem em aberto: descritos no inventário privado apontado por Estado fora do git, e não aqui. Quem for trabalhar neles consulta lá. Conforme cada um for corrigido, a descrição completa volta pra esta página, porque problema resolvido pode ser documentado por inteiro sem servir de mapa pra ninguém.
  • NodePorts e LoadBalancer do cluster, achado positivo: todos os Service desse tipo foram testados ao vivo de fora do node e estão bloqueados de verdade, só 22, 80 e 443 respondem. Corrige uma suposição antiga em Foundation, que atribuía o bloqueio a um NAT que não existe. Sem ação necessária, achado só de correção de registro. O detalhe fica no inventário privado apontado por Estado fora do git.

Débito técnico: securityContext ausente

Acompanha o item acima. Um item por serviço, porque a correção de cada um é independente (UID diferente, risco de quebrar não é o mesmo):

  • argocd/foundation/dados/postgres.yaml: resolvido pela migração pro CloudNativePG em 2026-08-21 (o operador roda com usuário fixo não-root por padrão), arquivo removido do repositório, ver Foundation.
  • argocd/foundation/dados/mariadb.yaml: resolvido em 2026-08-21, migrado pro mariadb-operator, arquivo removido do repositório, ver Foundation.
  • argocd/foundation/dados/minio.yaml: resolvido em 2026-08-21. A imagem Silo não define nenhum usuário não-root no Dockerfile (roda como uid=0 por padrão, sem convenção própria, igual ao MinIO upstream), então foi escolhido uid=1000/gid=1000 arbitrário. Backup do hostPath feito antes (tar czf, ~19KB, só .minio.sys vazio, confirmado sem bucket real), chown -R 1000:1000 /root/dados/vols/miniodata/ no node (autorizado explicitamente pelo usuário, ação bloqueada pelo classificador de modo automático), depois securityContext adicionado ao Deployment. Validado: id ao vivo no pod confirma uid=1000, servidor Silo iniciou sem erro de permissão em /data.
  • argocd/foundation/dados/adminer.yaml: resolvido em 2026-08-21. id ao vivo confirmou que a imagem já roda como uid=100(adminer) gid=101(adminer) por padrão, sem hostPath nem estado nenhum, então runAsNonRoot: true/runAsUser: 100/runAsGroup: 101 só formaliza o que já acontecia; validado com rollout limpo (1/1 Running, sem restart).
  • argocd/foundation/rabbitmq/deployment.yaml: resolvido em 2026-08-21 pela migração pro RabbitMQ Cluster Operator, que já roda com runAsNonRoot: true, runAsUser: 999, readOnlyRootFilesystem: true, capabilities dropadas por padrão. O deployment.yaml legado foi removido em 2026-08-24 junto do desligamento completo do RabbitMQ (ver Desligar o RabbitMQ), fechando este item de verdade.
  • argocd/foundation/redis/deployment.yaml: resolvido em 2026-08-21. id ao vivo confirmou que a imagem Bitnami já roda como uid=1001/gid=0 por padrão (convenção Bitnami de UID arbitrário compatível com OpenShift), então runAsNonRoot: true/runAsUser: 1001/fsGroup: 0 só formaliza o que já acontecia. Validado depois do rollout: DBSIZE (1856 chaves) e as 23 conexões BullMQ do Infisical intactas, nenhum dado perdido no restart.

Rotação de senha planejada

Ver Estado fora do git, rotação preventiva pro procedimento de cada item. Itens específicos que valem rotação assim que a condição bater, não só "algum dia":

  • As 4 roles do Postgres (ladesa, infisical, api-dev, sso-production): depois que a migração pro CloudNativePG estiver concluída e confirmada (o processo de import descriptografa a senha de cada role em memória volátil no node, mesmo nunca gravando em disco ou git, defesa em profundidade justifica rotacionar depois, não só confiar que não vazou).
  • Senha do Ansible Vault: sem condição de gatilho específica ainda, cadência a definir pela equipe (ver rotação preventiva).
  • As duas deploy keys (infrastructure, infrastructure-vault): mesma cadência a definir, ou imediatamente se alguém que teve acesso ao node sair da equipe (ver ciclo de vida de acesso).

Rotina de operação contínua a criar

Hoje só existe documentação de bootstrap (rodar uma vez, do zero até o cluster de pé, ver Bootstrap mínimo na VM). Falta a contraparte de manutenção contínua: uma seção própria, separada do bootstrap, cobrindo o que se repete depois que o cluster já está rodando.

  • Rotação de senha recorrente, cobrindo os dois universos de segredo deste repositório, não só um: os de bootstrap (Ansible Vault, ver acima) e os gerenciados pelo Infisical (mariadb-env, minio-env, rabbitmq-config, os próprios segredos internos do Infisical). Definir cadência, quem executa, e o procedimento exato pra cada categoria (os dois não se resolvem do mesmo jeito: um é ansible-vault + scripts/create-from-cluster, o outro é a UI/API do Infisical).
  • Acompanhamento de saúde do cluster (o que checar, com que frequência, o que fazer se algo estiver fora do esperado) ainda não tem escopo definido, precisa ser desenhado antes de virar documentação.
  • Revisão dos bypasses e afrouxamentos de CI registrados nesta página (ver Bypass e afrouxamento de CI): reler a seção "Bypasses de CI a desfazer" acima e achar o que já tem condição de reversão satisfeita, não deixar acumular.
  • Revisão de versão de cada ferramenta do stack de CI (ver Versões desatualizadas das ferramentas de bootstrap pro equivalente do lado Ansible/Helm/k3s): as ferramentas hermitizadas em .config/hermit/hermit-packages/*.hcl e as actions pinadas por hash nos workflows, conferindo se algum release novo já passou da janela de cooldown de 7 dias.
  • Revisão de licença de cada ferramenta do stack de CI (ver Licenças das ferramentas de CI): licença pode mudar numa atualização de versão, não só na adoção inicial, o caso real do gitleaks-action v2 (MIT pra licença própria) é o motivo desta linha existir.
  • Inventário do node, percorrendo três superfícies item por item: pacotes instalados, units habilitadas no systemd, e sockets em escuta. Pra cada item, as mesmas três perguntas: quem é o dono, ainda serve, e a exposição de rede dele é compatível com quem alcança aquela interface. O desfecho é uma das três saídas de sempre, declarar, restringir, ou remover. Apoiado por scripts/inventariar-node.sh, previsto na próxima leva, que torna a passada repetível em vez de depender de quem lembra dos comandos. Achado com implicação de segurança vai pra quem opera o node, não pra esta página.
  • Decidir onde essa seção mora: docs/operacao/ ganha uma página nova (ex.: manutencao.md), ou vira parte de Checklist com uma segunda tabela, separada da de bootstrap.

Modernização de Applications do argocd/foundation

Levantamento feito em 2026-08-21, pesquisando estado atual de cada tecnologia antes de propor qualquer mudança (mesmo cuidado da migração do Postgres pro CloudNativePG, que já está concluída, ver Foundation). Cada item precisa do mesmo processo cuidadoso (pesquisa, plano, cluster descartável, dead man switch) antes de ir pra produção, nenhum aqui é "só trocar o YAML".

  • argocd/foundation/dados/minio.yaml: resolvido em 2026-08-21, imagem trocada pro Silo (fork comunitário mantido pelo projeto Pigsty, drop-in, mesmo CLI/env vars/formato de dado). Investigação achou que este MinIO nunca teve uso real (.minio.sys só, zero bucket, nenhum consumidor no cluster referenciando as credenciais), então a troca foi direta, sem migração de dado. De quebra, resolveu os 9652 restarts históricos do pod antigo (erro crônico "drive /data offline", não reproduzido no Silo).
  • argocd/foundation/dados/mariadb.yaml: resolvido em 2026-08-21, migrado pro mariadb-operator (Helm, mesmo padrão de chart externo do Infisical). Sem import de dado (diferente do Postgres): investigação achou zero database de aplicação, só schemas de sistema, senha do root preservada via rootPasswordSecretKeyRef. Ver Foundation.
  • argocd/foundation/rabbitmq/deployment.yaml: resolvido em 2026-08-21. Achado urgente que motivou a migração: a imagem docker.io/bitnami/rabbitmq:3.12 não existe mais (docker pull retorna not found, confirmado ao vivo), o repositório docker.io/bitnami foi descontinuado e apagado em 2025. Mitigado de imediato trocando pra docker.io/bitnamilegacy/rabbitmq:3.12 (pinada por digest), depois migrado por completo pro RabbitMQ Cluster Operator oficial (argocd/foundation/rabbitmq-operator/, release manifest vendorizado v2.22.4, mesmo padrão do cert-manager/CNPG, sem Helm). RabbitmqCluster novo (argocd/foundation/ladesa-ro-production-rabbitmq-cluster/) com import nativo de definitions (rabbitmqctl export_definitions, senhas preservadas como password_hash, nunca em texto puro, segredo cifrado em infrastructure-vault). Achado não documentado em lugar nenhum: o startup probe padrão do operador (/api/health/checks/reached-target-cluster-size) só existe a partir do RabbitMQ 4.2.4, causando 404 permanente em qualquer versão anterior (inclusive 3.13); resolvido com a annotation rabbitmq.com/legacy-startup-probe: "true", documentada oficialmente pra esse cenário. Cutover de produção feito trocando o selector de rabbitmq-amqp/rabbitmq-web pro novo pod (kubectl patch, sem Argo CD, mesma cautela do Postgres/MariaDB) e reconectando os consumidores reais (managements-service, timetable-generator-dev) com rabbitmqctl close_all_connections no broker antigo, verificado ao vivo (rabbitmqctl list_connections) antes e depois. Application do cluster promovida pra automated. Legado removido em 2026-08-24: o Deployment/PVC/Service antigos (argocd/apps/messaging/rabbitmq/) foram apagados junto do desligamento completo do RabbitMQ, depois de confirmar zero conexões e zero consumidores ao vivo (ver Desligar o RabbitMQ).
  • argocd/foundation/cert-manager/cert-manager-v1.16.2.yaml: resolvido em 2026-08-21, atualizado pra v1.21.1 (release estável mais recente na data), ver Foundation. Sobrou lixo inofensivo: a v1.21 removeu o Role/RoleBinding cert-manager-tokenrequest (RBAC de um recurso que este cluster nunca usou, nenhum Issuer/ClusterIssuer referencia serviceAccountRef), Argo CD sinalizou como requires pruning mas o argocd app sync --prune foi bloqueado pelo classificador de modo automático desta sessão. Os dois objetos continuam no cluster, sem efeito nenhum (não concedem nada que algo use); remover é seguro quando alguém rodar argocd app sync foundation-cert-manager --prune manualmente.
  • argocd/foundation/redis/deployment.yaml: avaliado em 2026-08-21, decisão: manter single instance, sem operador. Investigação ao vivo (CLIENT LIST no broker, nomes de fila decodificados de base64) achou que este Redis não é só cache: é o backend de fila do BullMQ usado internamente pelo Infisical (23 conexões ativas, filas como secret-rotation, dynamic-secret-revocation, audit-log, ca-crl-rotation, secret-full-repo-scan, entre outras, todas do mesmo cliente). Isso mudaria a decisão se não houvesse persistência, mas appendonly yes (AOF) já está ativo e a PVC (redis-pvc, 5Gi, local-path) já sobrevive a um restart de pod sem perda (1856 chaves confirmadas ao vivo), então o requisito real de durabilidade já está atendido pela configuração atual, HA/Sentinel/Cluster não é necessário pra esse risco específico. Achado à parte, sem urgência: a imagem é bitnami/redis sem tag (latest implícito, ainda puxável e atualizado há poucos dias, diferente do caso do RabbitMQ), o que é reprodutibilidade ruim (atualização silenciosa a cada restart de pod) mas não indisponibilidade iminente; pinar numa versão+digest específicos fica como item de robustez, não como bloqueio.
  • argocd/foundation/dados/adminer.yaml: ferramenta de debug simples, não precisava de operador. securityContext já resolvido (ver acima), Ingress com TLS real trazido pro git também (ver "Segurança do próprio Argo CD" acima).

Débito técnico: organização de pastas e arquivos em argocd/

Levantado em 2026-08-21, a pedido explícito do usuário ("analise outros débitos técnicos e registre, seja criativo e meticuloso"). Nada aqui quebra nada hoje, é dívida de legibilidade/manutenção, mas com o repositório crescendo (14 Application já em argocd/applications/) fica mais caro de corrigir quanto mais tempo passar.

  • Repetição do prefixo foundation- em todo arquivo de argocd/apps/: resolvido em 2026-08-21. argocd/apps/ e argocd/foundation/ reorganizados por categoria (operators/, data/, messaging/, platform/), prefixo removido, nome do arquivo virou só o nome do componente. metadata.name de toda Application não mudou (só o arquivo/pasta), condição explícita do usuário pra não recriar nenhum recurso, ver Aprender: Argo CD sobre por que isso importa.
  • Separação por categoria, inexistente antes: resolvido junto do item acima, mesma reorganização, mesmas categorias da referência do Abrl (operators/, data/, messaging/, platform/, adaptadas ao que este repositório realmente tem).
  • Inconsistência de nomenclatura entre o Application e a pasta que ele aponta: resolvido em 2026-08-21. dados-mariadb/dados-postgres-cluster agora aninhados em argocd/apps/data/, junto do dados (MinIO/Adminer). O nome de arquivo do RabbitMQ de produção encurtou pra messaging/rabbitmq-cluster.yaml (o metadata.name da Application, foundation-ladesa-ro-production-rabbitmq-cluster, continua igual, só o arquivo).
  • Avaliação de Helm chart local, pedida explicitamente pelo usuário, comparando com o padrão do Abrl: decisão seletiva, não wholesale (este cluster é single-environment, sem o ganho de reaproveitar template com valores diferentes que justifica o Abrl embrulhar quase tudo em chart). Adotado chart Helm local (Chart.yaml+templates/+values.yaml) só pros dois CRs com dado real (argocd/apps/data/dados-postgres-cluster/, argocd/apps/messaging/rabbitmq-cluster/), templatizando só os campos que fazem sentido variar (imagem, storage, resources/replicas), mantendo bootstrap/roles/Database/Service estáticos. Os Deployments simples (dados, rabbitmq legado, redis) continuam manifesto cru: não há hoje ganho real de parametrização pra um Deployment com imagem e réplica fixas, embrulhar em chart só pra seguir padrão seria ritual sem função. Verificado antes de aplicar: helm template de cada chart comparado documento a documento (não byte a byte, a saída do helm template reordena por kind e inclui comentário # Source: que nunca chega no cluster) contra o manifesto original, idêntico nos dois casos; depois do sync, spec ao vivo do Cluster/RabbitmqCluster confirmado byte-idêntico ao backup pré-mudança. Nenhum recurso recriado (AGE inalterado em ambos).
  • Nomenclatura dos dois diretórios de topo não batia com o Abrl: resolvido em 2026-08-21. argocd/apps/ (14 arquivos de Application) renomeado pra argocd/applications/, e argocd/foundation/ (conteúdo/charts) renomeado pra argocd/apps/, batendo exatamente com gitops/applications/+gitops/apps/ do Abrl (git mv, histórico preservado). argocd/root/application.yaml (spec.source.path) e as 7 Application cujo source.path apontava pra argocd/foundation/... atualizadas na mesma leva. Sequência de 3 commits (suspender automated, aplicar rename, restaurar automated, mesma disciplina já usada nas migrações de chart) pra nenhuma Application com selfHeal: true reagir a uma mudança de source.path no meio do processo. Achado novo, mesma fronteira do project.yaml: argocd/root/application.yaml também não é sincronizado pelo próprio root (é o path da própria Application root, não pode se autoatualizar via sync dela mesma), exigindo kubectl apply -f argocd/root/application.yaml direto no node depois do pull, lição detalhada em Aprender: Argo CD. Confirmado depois: as 14 Application com automated restaurado exatamente com o prune de antes da suspensão (11 com prune: true, e dados-mariadb/cnpg/cert-manager com prune: false, mitigação do Namespace não declarado pelo chart, ver abaixo), root continua o único gate Manual, zero pod reiniciado, zero recurso recriado.
  • Corrigir a causa raiz do Namespace não declarado (cert-manager e CNPG), em vez de só mitigar com prune: false: resolvido em 2026-08-21, mesmo padrão do dados-mariadb: wrapper chart local (argocd/apps/operators/cert-manager/ e argocd/apps/operators/cnpg/, Chart.yaml com o chart oficial como dependencies:, templates/namespace.yaml declarando o Namespace explicitamente). helm template do wrapper comparado documento a documento contra o chart direto nos dois casos, diferença única e esperada: o Namespace a mais. Sequência de 3 commits por operador (suspender automated, trocar source pro path local, restaurar automated, agora com prune: true), cert-manager primeiro (menor stakes), CNPG depois com o diff conferido três vezes antes de sincronizar, dado o incidente anterior. Nos dois casos o diff posterior ao sync veio totalmente vazio (sem exceção do Namespace, diferente de antes), confirmando a causa raiz resolvida, não só mitigada. Verificado depois: uid/creationTimestamp do Namespace e de todo recurso inalterados nos dois (zero recriação), Cluster postgres de produção continuou Cluster in healthy state o tempo todo, pod sem restart. automated.prune dos dois virou true (não precisa mais de false). dados-mariadb continua com prune: false por um motivo diferente e não relacionado (mitigação de escopo mais grosso pra um CR de produção, não o caso do Namespace não declarado, ver acima na auditoria de chart oficial), fora do escopo desta correção.
  • Auditoria completa de chart oficial pra cada tecnologia do repositório, pedido explícito do usuário ("que o chart interno tente usar o chart oficial, confiável, mantido, respeitável, maduro, atualizado, de cada dependência... e o stakater/application como fallback pra caso não tenha"), levantamento em 2026-08-21 componente por componente (helm search repo/helm show values contra o repositório oficial de cada projeto, não suposição):
  • cert-manager (operador): resolvido em 2026-08-21, migrado pro chart oficial jetstack/cert-manager (v1.21.1, mesma versão do manifesto vendorizado anterior). Plano próprio com dead man switch, executado com o mesmo rigor do item de maior risco desta lista: helm template comparado documento a documento contra o manifesto antigo (só diferença: labels helm.sh/chart/app.kubernetes.io/managed-by aditivas, e o Namespace que o chart não renderiza, protegido por construção já que cert-manager continua sem automated/prune); backup completo de Deployments/Services/RBAC/webhooks/CRDs antes de tocar em qualquer coisa; AppProject (argocd/root/project.yaml) precisou de https://charts.jetstack.io adicionado em sourceRepos (aplicado direto via kubectl apply, já que argocd/root/*.yaml não é gerenciado pelo sync do próprio root, é do bootstrap do Ansible); dead man switch (systemd-run --on-active=10min, reaplicando o manifesto original limpo) armado antes do sync, só cancelado depois de provar o webhook funcionando de verdade, não só os pods de pé, criando um Certificate de teste descartável contra o ladesa-ro-issuer-production real e confirmando a cadeia completa (CertificateRequest aprovado, Order, Challenge criados) antes de limpar. Verificado depois: uid/creationTimestamp idênticos nos 3 Deployments e nas 6 CRDs (zero recriação), todo Certificate real do cluster continua Ready: True com idade inalterada, os 4 hosts públicos (argocd/infisical/portainer/adminer) respondendo 200 sobre TLS. syncPolicy promovido pra automated: {prune: false, selfHeal: true} em 2026-08-21 (revisitado depois do CNPG, que ensinou na prática o motivo de prune: false aqui: o chart não renderiza Namespace, então prune: true apagaria o namespace inteiro no próximo ciclo, mesmo incidente do CNPG documentado em Aprender: Argo CD). Confirmado depois: namespace cert-manager intacto, 3 pods sem restart.
  • CloudNativePG (operador): resolvido em 2026-08-21, migrado pro chart oficial cnpg-op/cloudnative-pg (0.29.0, appVersion 1.30.0, mesma versão já rodando). Diferente do cert-manager: o chart usa convenção de nome diferente do manifesto vendorizado (Deployment/ServiceAccount/ClusterRole/ClusterRoleBinding viram cnpg-controller-manager/cnpg-manager, ambos renomeados pra cnpg, sem jeito de preservar via values, confirmado no _helpers.tpl do chart), então a migração envolveu rename de verdade desses 4 recursos (CRDs, webhooks e Service mantiveram nome). Investigado antes de prosseguir, a pedido explícito do usuário ("investigar se isso pode causar perda de dados"): confirmado ao vivo que o Cluster de produção não tem ownerReferences, e que o Pod/PVC reais são possuídos pelo Cluster CR, não pelo Deployment/ServiceAccount do operador, zero cadeia de cascata entre os dois. values corrigido pra restaurar resources (chart não define nenhum por padrão, crítico dado o node sem folga de memória) e imagePullPolicy: Always (chart usa IfNotPresent por padrão). Perdidas 6 ClusterRole granulares de RBAC agregável (editor/viewer por tipo de CRD: database/publication/subscription) que o manifesto antigo tinha e o chart não replica. Confirmado que nenhuma RoleBinding as usa hoje, aceito como redução de escopo sem impacto real.
    • Incidente real durante o sync com --prune (necessário pra completar o rename): o Namespace cnpg-system também apareceu como "só ao vivo, não no alvo" (mesmo caso do Namespace do cert-manager, o chart não o renderiza), e por estar na mesma leva de prune do rename, foi prunado junto, cascateando a exclusão de tudo que tinha acabado de ser criado no namespace (o operador ficou fora do ar por ~1 minuto). O Cluster de produção (namespace dados, fora do cnpg-system) nunca foi afetado, confirmado ao vivo durante o incidente (Cluster in healthy state o tempo todo). Recuperado com um segundo argocd app sync sem --prune, que recriou o Namespace (via CreateNamespace=true) e todos os recursos do operador de uma vez. Lição nova, registrada em Aprender: Argo CD: --prune explícito numa sincronização de rename afeta qualquer recurso "só ao vivo", não só os que a pessoa tinha em mente, inclusive o Namespace, se o chart não o declarar. automated.prune restaurado como false (não true) no final, mesma mitigação do dados-mariadb, justamente pra essa situação nunca se repetir automaticamente.
  • CloudNativePG, Cluster CR: avaliado a fundo em 2026-08-21, decisão explícita: manter o chart local atual, não migrar. Tem chart oficial (cnpg/cluster, mesmo repositório do operador), e cobre boa parte do que o Cluster de produção precisa (recovery.method: import tipo monolith produz exatamente o mesmo bootstrap.initdb.import+externalClusters que o chart local já declara na mão, confirmado renderizando o próprio fixture de teste do chart oficial; cluster.roles reproduz managed.roles com passwordSecret por role igual; fullnameOverride força o nome do Cluster pra postgres, batendo com o que já existe). Achado que motivou não migrar: o template databases.yaml do chart oficial nomeia toda Database CR como {{ fullname }}-{{ nome }} (ex.: postgres-api-dev-v1), sem nenhum campo de override disponível nos values, então adotar o chart renomearia as 5 Database CR de produção (api-dev-v1, api-dev-v2, infisical, sso-production, management-service-v3) sem alternativa, e o chart embute um Service gerenciado só pelo mecanismo nativo do operador (spec.managed.services, não um manifesto que o Argo CD aplica direto), o que faria o Argo CD parar de rastrear o Service db-postgres que ele rastreia hoje, uma mudança de responsabilidade adicional. databaseReclaimPolicy: retain protege o banco real do rename (o CNPG casa Database por spec.name, não por identidade do objeto Kubernetes), mas o custo real (rename de 5 recursos de produção, handoff de posse do Service) não trouxe ganho proporcional: o chart local hand-rolled já funciona, já tem ignoreDifferences cobrindo os campos que o CNPG default sozinho (ver "Auditoria de syncPolicy.automated" acima), e não é mais difícil de manter que o wrapper que a migração exigiria de qualquer jeito. Decisão do usuário, com a pesquisa completa registrada aqui pra não precisar refazer se a pergunta voltar.
  • mariadb-operator (operador): já usa chart oficial (mariadb-operator/mariadb-operator + mariadb-operator-crds, https://helm.mariadb.com/mariadb-operator), nada a mudar.
  • mariadb-operator, MariaDB CR: resolvido em 2026-08-21, migrado pro chart oficial mariadb-operator/mariadb-cluster (26.6.0, mesmo repositório do operador). O chart não expõe hook de annotation customizada nem gera o Service db-mariadb que as aplicações já usam. Resolvido com wrapper chart local (Chart.yaml com o chart de terceiro como dependencies:, templates/service.yaml próprio pro Service), não multi-source (tentativa inicial, abandonada em favor do wrapper, ver Aprender: Argo CD), e automated.prune: false no lugar do antigo Prune=false por-recurso (mitigação de escopo mais grosso). Verificado helm template idêntico campo a campo ao CR antigo antes do commit, e spec ao vivo sem recriação depois do sync (MariaDB CR manteve creationTimestamp, Service manteve os 312 dias de idade). Decisão explícita: o wrapper chart não foi generalizado pra outro chart de terceiro do repositório (mariadb-operator, mariadb-operator-crds, portainer, infisical, secrets-operator continuam com referência direta). Pesquisa confirmou que umbrella/wrapper chart só compensa quando há recurso extra acoplado, esse é o único caso aqui.
  • RabbitMQ Cluster Operator (operador): sem chart oficial (só Bitnami/comunidade, nenhum no próprio repositório rabbitmq/cluster-operator). Manifesto vendorizado continua sendo a escolha certa.
  • RabbitMQ Cluster Operator, RabbitmqCluster CR: sem chart oficial. Chart local escrito nesta sessão continua sendo a escolha certa, não é dívida.
  • Infisical (secrets-operator, app infisical-standalone) e Portainer: já usam chart oficial de cada projeto (repositórios próprios deles), nada a mudar.
  • MinIO/Silo: resolvido em 2026-08-21, mas não como a nota original desta linha assumia. Pesquisa mais funda (pedida explicitamente antes de mexer em qualquer coisa, ver Metodologia de mudança) revelou que não existe helm/silo/ standalone no repositório do Pigsty como um chart simples: o caminho realmente oficial e maduro é o MinIO Operator + Tenant chart (da própria MinIO Inc., com tenant.image sobrescrito pra pgsty/silo), arquitetura pesada (CRD Tenant, Operator próprio, RBAC cluster-wide) desproporcional ao uso atual (zero bucket real). Existe também um silo-operator específico do Pigsty, mas é projeto comunitário ainda em avaliação pra nem entrar no README oficial, não confiável/maduro o bastante. Decisão, com o usuário: migrar pro fallback genérico stakater/application (argocd/applications/data/minio.yaml, foundation-minio, 9.3.1), a mesma política de fallback já definida pra quando não existe chart oficial do tamanho certo. helm template comparado documento a documento contra o manifesto antigo antes do sync: idêntico, exceto a troca deliberada de volume hostPath (/root/dados/vols/miniodata, agora órfão no disco do node, sem dado real, não limpo automaticamente) por uma PersistentVolumeClaim gerenciada pelo chart (local-path, 1Gi). Sequência de 3 commits (suspender automated de foundation-dados, prune do Deployment/Service antigos com autorização explícita do usuário pro --prune, criar foundation-minio e sincronizar, por fim restaurar automated nos dois, agora foundation-minio também com prune: true, sem motivo pra ficar mais restrito). Verificado depois: pod Running, PVC Bound, S3 API e console respondendo 200 de verdade (curl contra os dois NodePorts), diff vazio.
  • Adminer: ferramenta de debug de um container só, não existe (nem faz sentido existir) um "chart oficial" pra ela como tecnologia. Se algum dia virar chart, seria via o fallback genérico abaixo, não um chart específico do Adminer.
  • Redis: resolvido em 2026-08-21, migrado pro fallback stakater/application (argocd/apps/platform/redis/, foundation-redis, 9.3.1), sem chart oficial de primeira linha pro caso de uso simples deste cluster (Bitnami descontinuado como chart, e os charts "Redis Stack" da Redis Inc. são pra outro produto). Achado colateral, não específico deste chart: docker.io/bitnami/redis sofreu a mesma mudança de política já vista no RabbitMQ (ver "Modernização de Applications" acima), só a tag latest continua disponível de graça, versão pinada exige assinatura paga da Bitnami (bitnami/redis:8.10.1 não resolve, só sha256-.../latest/latest-metadata aparecem no índice de tags). Mitigado pinando por digest (sha256:a97a9e3e..., o mesmo que já estava rodando, Redis 8.10.1, confirmado via redis-cli INFO server antes e depois), sem trocar de versão, só travando contra drift futuro do :latest. Dado real envolvido, diferente do MinIO: DBSIZE mostrou 1858 chaves antes da migração, então a estratégia foi manter os mesmos nomes de recurso (redis-server pro Deployment/Service, redis-pvc pro PVC, releaseName redis-server) de propósito, pra virar update in-place em vez de delete+recreate, helm template comparado documento a documento confirmando isso antes de qualquer sync. Incidente real, recuperado na hora: mesmo com os nomes preservados, o Deployment falhou repetidamente (spec.selector: field is immutable) porque o Service/Deployment originais usavam app: redis-server como seletor e o chart oficial usa app.kubernetes.io/name+application.stakater.com/workload-class fixo no _helpers.tpl, sem campo de override em values. spec.selector de um Deployment não pode ser alterado por update, só recriando o objeto. Corrigido apagando só o Deployment (não o Service, não o PVC, autorização explícita do usuário) e deixando o Argo CD recriar do zero com o seletor novo, montando o mesmo redis-pvc por nome. Verificado depois: PVC com a idade original preservada (604 dias, prova de que nunca foi recriado), DBSIZE 1859 chaves (drift normal de tráfego real, não perda de dado), redis_version:8.10.1 inalterado, PING respondendo PONG de verdade pelo hostname do Service que outros serviços usam. Lição nova: spec.selector imutável de Deployment é um risco que helm template sozinho não avisa (o diff mostra a mudança de campo, mas não que ela vai ser rejeitada pela API), só descoberto na prática ao tentar sincronizar; vale conferir se um chart novo muda o selector de um Deployment que já existe antes de assumir que "nomes preservados" é suficiente pra evitar recriação.
  • Fallback pra quando não existe chart oficial nem justifica chart próprio: stakater/application (https://stakater.github.io/stakater-charts, versão 9.3.1, ativamente mantido), o mesmo chart genérico já usado em produção pelos repositórios satélite (management-service, authentication-service, docs) pra Deployment/Service/Ingress simples. Candidatos: redis (Deployment simples hoje) e rabbitmq legado (mas este último está sendo desligado, não vale investir). Ainda não avaliado se stakater/application cobre tudo que os manifestos crus atuais declaram (securityContext, PVC, NodePort) sem perder nada.
  • Prioridade sugerida, do mais seguro pro mais arriscado: 1) MariaDB CR e MinIO/Silo (zero bucket real), feitos; 2) stakater/application pro Redis, depois de confirmar que cobre securityContext/PVC sem regressão, ainda não feito, único item real que resta neste backlog; 3) CNPG Cluster, avaliado a fundo em 2026-08-21, decisão explícita de não migrar (ver acima); 4) cert-manager e CloudNativePG (operador), feitos com plano próprio e dead man switch.
  • argocd/root/project-satellites.yaml (AppProject ladesa-satellites) existe e é aplicado no cluster, mas nenhuma Application em argocd/applications/ o referencia: resolvido em 2026-08-21, com um comentário. Confirmado de novo que não é bug (grep -rl "ladesa-satellites" argocd/applications/ continua sem achar nada, esperado). argocd-bootstrap.md agora linka explicitamente o porquê do recurso órfão pro item "Padronizar o deployment com Argo CD" abaixo, em vez de só mencionar de passagem que "nenhum app-*.yaml existe ainda". Nenhuma mudança de manifesto, só a doc de arquitetura passou a explicar de propósito, não por lacuna.
  • argocd/apps/foundation-secrets-operator.yaml implantava no namespace default, e releaseName tinha sufixo de timestamp Unix: os dois resolvidos juntos em 2026-08-21, mesma reinstalação (mudar namespace exige recriar o release de qualquer jeito, então corrigir o nome junto não é trabalho extra). releaseName: secrets-operator-1735177689 (1735177689 = 2024-12-25, artefato de como foi criado originalmente) virou releaseName: secrets-operator, destination.namespace virou secrets-operator (mesmo padrão de todo outro operador deste repositório: cnpg-system, mariadb-system, rabbitmq-system, cert-manager). Achado durante a correção: mesmo com nome limpo, o chart trunca o prefixo dos recursos em 15 caracteres ({{ .Release.Name | trunc 15 }} no _helpers.tpl, hardcoded), então secrets-operator (16 caracteres) ainda vira secrets-operato nos nomes de recurso, um caractere cortado, não é mais culpa do timestamp, é limite do próprio chart, aceito como está (nenhum nome mais curto seria tão descritivo). helm template comparado documento a documento contra o release antigo antes do sync: única diferença real foi label app.kubernetes.io/instance e o campo namespace dentro de RoleBinding/ClusterRoleBinding (aponta pro ServiceAccount no namespace novo). ClusterRole/ClusterRoleBinding/as duas CRDs (nome fixo, não depende do release) só receberam update in-place, sem prune; só Deployment/Service/ServiceAccount/Role/RoleBinding (recursos namespaced) precisaram ser recriados no namespace novo, com prune explícito dos antigos em default, autorizado explicitamente pelo usuário. Verificado depois: pod novo 2/2 Running, log do operador mostrando reconciliação ativa de verdade pra todo InfisicalSecret do cluster (dados, default, ladesa-ro-development, ladesa-ro-production), não só um teste isolado, já que este operador é consumido cluster-wide. Namespace default confirmado limpo depois (só o Service kubernetes embutido do próprio cluster).
  • Duas convenções diferentes de instalação de operador coexistindo sem documentação explícita da escolha: mariadb-operator usa duas Application separadas (foundation-mariadb-operator-crds + foundation-mariadb-operator, padrão do chart Helm oficial, que separa CRD de controller), enquanto cert-manager, cnpg e rabbitmq-operator usam manifesto vendorizado único (CRD + controller no mesmo arquivo, kubectl apply, sem Helm). A escolha entre os dois padrões já está implicitamente documentada em Foundation espalhada por parágrafo próprio de cada operador, mas nunca resumida num único lugar como "regra geral de como instalar operador novo neste repositório".

Recursos vivos que o git não declara, levantados em 2026-08-21

Varredura feita no fim da migração do management-service, cruzando todo Deployment, StatefulSet, DaemonSet, Ingress, Service, CronJob e PersistentVolumeClaim do cluster contra a anotação argocd.argoproj.io/tracking-id. O que aparece aqui é o que sobrou fora do Argo CD depois de três repositórios migrados.

  • rabbitmq-ingress, no namespace ladesa-ro-production, existia só no cluster: resolvido em 2026-08-24, removido junto do resto do RabbitMQ (ver Desligar o RabbitMQ). Nunca esteve em argocd/, então não havia o que declarar, só apagar (kubectl delete ingress rabbitmq-ingress -n ladesa-ro-production).
  • Seis Secret de release Helm órfãos, resquício de instalação imperativa que o Argo CD já substituiu. Três vieram das migrações de 2026-08-21 (ladesa-ro-api, ladesa-ro-waha e ladesa-ro-docs, em ladesa-ro-development) e três são anteriores (portainer, infisical e secrets-operator-1735177689, este no namespace default). O Argo CD renderiza chart com helm template e não cria Secret de release, então nenhum deles tem dono: são histórico de um helm install que ninguém mais consulta, ocupando espaço no etcd e confundindo quem rodar helm list. O do ladesa-ro-sso já foi removido na migração do piloto, e serve de referência do que fazer com os outros. Não são urgentes nem perigosos, mas apagar um Secret de release é ação destrutiva e fica pra decisão explícita. Ficam de fora desta lista, por serem legítimos, o release do argocd (instalado pelo role de bootstrap) e os do traefik/traefik-crd (geridos pelo próprio k3s via HelmChart).
  • A instalação do próprio Argo CD não é gerida pelo Argo CD: os quatro Deployment, o StatefulSet do application-controller e o Ingress do argocd-server não carregam anotação de rastreio. É deliberado, o role argocd_bootstrap é quem instala, e auto-gestão traria o risco clássico de o Argo CD se derrubar num sync ruim e não ter como se levantar. Registrado aqui só pra que a ausência apareça como decisão e não como lacuna.

Não são achado: o StatefulSet mariadb do namespace dados e o rabbitmq-server de ladesa-ro-production nascem de CR de operador (MariaDB e RabbitmqCluster), e esses CR estão rastreados, então a cadeia de posse fecha. Os dois PersistentVolumeClaim do management-service estão fora de propósito, motivo documentado em Repositórios satélite.

Auditoria de syncPolicy.automated, 2026-08-21

Levantado a pedido do usuário ("o que mais não está automatizado ou idempotente, exigindo intervenção manual, e por que não está mapeado em pendências"). Das 14 Application, 7 estavam Manual sem razão documentada por trás da escolha (herdada, não decisão deliberada por app). Auditoria rodada com argocd app diff --core --hard-refresh duas vezes por app (pra pegar campo que muda a cada render, não só drift real) e, pras que usam chart remoto (histórico de diff pouco confiável nesse cenário, ver "O padrão app-of-apps"), conferência direta via kubectl get contra o valor esperado, independente da ferramenta.

  • dados, portainer, redis, secrets-operator: resolvido em 2026-08-21, promovidas pra automated: {prune: true, selfHeal: true}. Diff vazio nas duas rodadas, sem recurso órfão ao vivo (kubectl get all conferido por namespace), sem recriação depois do sync (idade de todo Deployment inalterada).
  • infisical tinha um bloqueio técnico real pra virar automated: resolvido em 2026-08-21. O chart infisical-standalone (templates/infisical.yaml, confirmado no fonte do chart) calcula os campos metadata.annotations.updatedAt e spec.template.metadata.annotations.updatedAt do Deployment com {{ now | date ... }}, timestamp do momento do render, deliberado no chart pra forçar rollout a cada helm upgrade. Cada argocd app diff produzia um valor "desejado" novo, nunca igual ao anterior (confirmado rodando duas vezes seguidas). Corrigido com ignoreDifferences nos dois jsonPointers (argocd/applications/platform/infisical.yaml). Verificado depois do deploy: diff estável nas duas rodadas seguintes (resíduo cosmético annotations: {}, mas Sync Status: Synced, não conta mais como drift). automated: {prune: true, selfHeal: true} ligado depois, confirmado Auto-Prune/Synced/Healthy, sem recriar o pod estável.
  • Achado à parte, no meio dessa verificação: o Deployment do Infisical estava com um rollout travado havia 2h13 (ReplicaSet novo Pending por Insufficient memory no node), causado por 3 syncs manuais nesta Application feitos por terceiro (argocd app history mostra 3 entradas hoje, 12:01/12:03/13:08 UTC; nenhum deles rodado nesta sessão, que só usou argocd app diff). Não derrubou o serviço (o pod antigo continuou Running/servindo, RollingUpdate nunca remove a versão antiga até a nova ficar pronta), mas nunca teria se resolvido sozinho, dado que o node não tem memória sobrando (mesmo achado já registrado em "Node sem folga de memória"). Corrigido com kubectl rollout undo --to-revision=<estável> (revisão-alvo confirmada antes via kubectl rollout history, não um undo cego de um passo só: o primeiro undo sem --to-revision voltou pra outra revisão também travada). Confirmado depois: 1/1 Running, rollout status successfully rolled out, site respondendo 200.
  • cert-manager-tokenrequest (Role+RoleBinding) órfão ao vivo em cert-manager: resolvido em 2026-08-21, removido do cluster (kubectl delete, backup salvo em /root/cert-manager-tokenrequest-backup.yaml no node). Investigação completa antes de remover: release notes oficiais do cert-manager v1.21.0 confirmam que esse RBAC foi removido oficialmente (adicionado no v1.16, motivado por um caso de uso de Route53 removido da documentação em 2024, só afeta quem usa o padrão não documentado serviceAccountRef.name apontando pro SA do controller). Confirmado que o ClusterIssuer deste cluster usa http01/Traefik, não Route53/Vault, não depende desse padrão. Confirmado também no próprio histórico deste repositório (git show 1b2b04b^:.../cert-manager-v1.16.2.yaml) que o manifesto vendorizado anterior criava esse RBAC e o bump pra v1.21.1 (mesma sessão) parou de declará-lo. Não é recurso de origem desconhecida, é sobra esperada do upgrade. argocd app diff no foundation-cert-manager veio vazio depois da remoção, cert-manager continuou 1/1 Running nos 3 pods, todos os Certificate do cluster seguiram Ready: True.
  • secrets-operator (pod default/secrets-operato-controller-manager-*) tinha histórico de milhares de restart: resolvido em 2026-08-21. Causa confirmada nos logs: o operador (baseado em controller-runtime) derrubava o próprio processo de propósito quando perdia a lease de liderança (dial tcp 10.43.0.1:443: connection refused, falha de leader election), padrão comum desses operadores em blip da API do k3s (reinício de node, upgrade, churn de CRD), não bug do Infisical. Investigado mais a fundo: controllerManager.replicas deste chart é fixo em 1 (helm show values infisical/secrets-operator), então não existe cenário de HA real que justifique leader election, a flag só existia pra proteger uma disputa entre réplicas que nunca acontece aqui. Corrigido sobrescrevendo controllerManager.manager.args no values da Application (argocd/applications/operators/secrets-operator.yaml) sem --leader-elect, removendo a classe de crash por completo (não é mitigação de timeout, é eliminação da causa). Risco aceito e documentado: se replicas for aumentado no futuro sem restaurar --leader-elect, duas réplicas processariam os mesmos recursos sem coordenação. Não é o caso hoje, e mudar replicas exigiria edição consciente do mesmo values. Verificado ao vivo: rollout limpo (pod novo 2/2 Running, 0 restarts), args confirmados sem a flag, logs mostrando reconciliação normal de todo InfisicalSecret, argocd app diff vazio.
  • rabbitmq (Deployment legado, ocioso) e cert-manager promovidos pra automated: resolvido em 2026-08-21. rabbitmq com prune: true (diff limpo em duas rodadas, zero risco real). cert-manager com prune: false (mesma mitigação do dados-mariadb/CNPG, chart não declara Namespace). Das 15 Application (14 + root), só root continua Manual, decisão permanente, não pendência (ver acima, "único gate humano antes de qualquer edição em argocd/applications/ virar mudança real").

Trabalho inicial da organização, ainda não retomado

Registrado em 2026-08-21: o objetivo original desta sessão, antes de focar em CI/qualidade do infrastructure, era um conjunto de mudanças maiores, que tocam mais de um repositório da organização. Investigação inicial (só leitura, nenhuma mudança feita ainda) registrada abaixo item por item:

  • Arquivar repositórios não usados da organização ladesa-ro no GitHub. Três saíram em 2026-08-21 junto da aposentadoria do gerador de horário (arquivado-timetable-generator, arquivado-messages, arquivado-protobufs), depois de conferir quem os referenciava. Falta levantar o resto (arquivar é reversível, mas ainda merece uma lista explícita, não um chute). Atenção a um efeito colateral do rename: o docs linka o antigo ladesa-ro/messages em três páginas publicadas e o management-service o referencia num script, e os dois seguem funcionando só por redirecionamento do GitHub, exatamente o estado que este documento já critica no item do arquivado-esteira-ci-cd.
  • Migrar a geração de horário e os arquivos .proto pro management-service: em andamento, não commitado. O management-service está na branch feat/timetable-generator-proto, com src/infrastructure.timetable-generator/proto/ inteiro não rastreado pelo git. O contrato .proto do serviço timetable-generator-v1 veio do repositório protobufs, e proto/.commands/generate builda um container fixado por versão (protoc + ts-proto) que gera TypeScript em proto/generated/ e C# em proto/generated-csharp/. A aposentadoria do serviço em 2026-08-21 (abaixo) resolveu a pendência do destino do C#, mas não tornando o generated-csharp descartável, e sim mudando o que ele é: em vez de um passo de sincronização com outro repositório, ele passa a ser parte do próprio monorepo, o lado C# do protocolo que o worker vai usar. O desenho alvo, definido pelo usuário, é monólito modular: a API conversa com um worker também em TypeScript, e esse worker executa o algoritmo C# como processo filho, mandando ServiceGenerateRequest serializado pelo stdin e lendo ServiceGenerateResponse do stdout. O .proto deixa de ser contrato entre serviços em rede e vira contrato entre processos na mesma máquina, o que ele já suporta sem mudança nenhuma: o arquivo não declara bloco service, só mensagens, e a delimitação fica por conta do EOF, uma mensagem por invocação. Continua valendo a pendência de declarar @bufbuild/protobuf como dependência real, já que hoje só o código gerado o importa em runtime, e some-se a ela a equivalente do outro lado, Google.Protobuf no projeto C# que compilar as classes geradas. Some-se a isso o que a varredura de 2026-08-21 achou: nada em src/ importa proto/generated/ nem o messages/schemas.ts, e o alvo nx codegen:timetable-generator:fresh aponta pra messages/commands/generate quando a pasta real é messages/.commands/, ou seja, está quebrado desde que foi escrito e ninguém percebeu porque ninguém o roda.

  • Aposentar o gerador de horário como serviço à parte: feito em 2026-08-21. Os três Deployment (ladesa-ro-timetable-api, ladesa-ro-timetable-generator, ladesa-ro-timetable-worker), os dois Service e o InfisicalSecret que produzia o config foram removidos do cluster, e os repositórios timetable-generator, messages e protobufs foram renomeados com o prefixo da convenção e arquivados. A decisão se apoiou em evidência colhida antes de apagar: as três filas de horário estavam zeradas, sem DLQ acumulada, nenhum Ingress público apontava pros serviços, e os logs dos três pods não mostravam nenhum processamento real, só health check. O achado que fechou a questão é que o ciclo já estava quebrado de qualquer jeito: o message-broker-subscribe.service.ts do management-service está inteiramente comentado, então a resposta que o gerador publicava em dev.timetable_generate.response nunca era lida por ninguém. Backup dos manifestos foi tirado antes da remoção, e o config não precisou de cópia porque o Infisical é a fonte. Sobra o usuário timetable-generator-dev no RabbitMQ, agora sem uso, deixado de propósito para não mexer no broker no mesmo passo.

  • Desligar o RabbitMQ: resolvido em 2026-08-24. A condição registrada em 2026-08-21, a folha de ponto deixar de depender do broker, foi atendida: o management-service migrou toda a mensageria (geração de horário e notificação de folha de ponto por WhatsApp, incluindo a fila folha_ponto.notificacao.whatsapp) para BullMQ sobre o próprio PostgreSQL da aplicação, sem broker externo. Confirmado ao vivo antes de remover: rabbitmqctl list_connections zerado (o pod antigo da API, rodando código anterior à migração por causa do achado em GitOps com tag mutável, ainda tinha 2 conexões abertas, resolvido reiniciando o deployment) e rabbitmqctl list_queues com zero mensagens/consumidores em todas as filas, inclusive as de horário e de folha de ponto. Nenhum pod do cluster, em nenhum namespace, referenciava RabbitMQ em variável de ambiente além do próprio broker. Removidos do git e do cluster: as três Application (foundation-rabbitmq-operator, foundation-rabbitmq, foundation-ladesa-ro-production-rabbitmq-cluster), os manifests em argocd/apps/messaging/rabbitmq/, argocd/apps/messaging/rabbitmq-cluster/ e argocd/apps/operators/rabbitmq-operator/, o whitelist rabbitmq.com/RabbitmqCluster em argocd/root/project.yaml, a referência ao segredo de bootstrap em ansible/inventory/host_vars/ldsa.yml, e o rabbitmq-ingress órfão (ver acima).

  • A criação de folha de ponto quebra se o broker estiver fora, e deixa estado inconsistente, achado em 2026-08-21 ao investigar se o RabbitMQ podia ser desligado. Em folha-ponto-create.command.handler.ts o await messageBrokerService.publishFolhaPontoCreated(payload) acontece depois de a folha já estar gravada no banco e antes do retorno, sem try. Como getBroker() lança ServiceUnavailableError quando não há conexão, uma indisponibilidade do broker faz o endpoint responder erro para um registro que já foi persistido: quem chamou entende que falhou, o banco entende que deu certo. Não é hipótese: o próprio broker caiu por dois minutos em 2026-08-21 às 23:29, e a API sobreviveu justamente porque a conexão é reestabelecida em laço com backoff, mas qualquer folha criada naquela janela teria caído nesse caminho. A publicação é notificação, não parte da transação, então o tratamento adequado é não deixar a falha dela derrubar a resposta. Some-se que a fila folha_ponto.notificacao.whatsapp está com zero consumidores, porque o serviço de assinatura do management-service está inteiramente comentado, ou seja, hoje a mensagem é publicada e nunca lida.

  • Padronizar o deployment com Argo CD nos repositórios restantes da organização, substituindo o push imperativo do chart application (Stakater) por uma pasta gitops. Confirmado em 2026-08-21, não é só o management-service: ladesa-ro/authentication-service (SSO) e ladesa-ro/docs também usam o mesmo padrão imperativo (helm upgrade -i --repo https://stakater.github.io/stakater-charts application, disparado direto por .github/workflows/build-deploy*.yml, sem GitOps, sem syncPolicy, sem histórico de reconciliação), achado ao investigar por que dois serviços da organização não tinham TLS (ver "Segurança do próprio Argo CD" acima). O authentication-service é o caso mais extremo, o values de produção nem fica num arquivo do repositório, é uma variável de ambiente do GitHub (DEPLOY_HELM_VALUES), editável só pela UI, sem PR nem histórico de revisão nenhum. Esse item e o Débito técnico: organização de pastas e arquivos em argocd/ acima andam juntos: a convenção gitops/applications/<categoria>/ + gitops/apps/<categoria>/<app>/ é a mesma pensada pros dois, e o próprio infrastructure.git já tem agora um precedente vivo de chart Helm local funcionando (argocd/apps/data/dados-postgres-cluster/, argocd/apps/messaging/rabbitmq-cluster/, resolvido em 2026-08-21), referência concreta pra quando esses repositórios satélite adotarem o mesmo padrão. argocd/apps//argocd/foundation/ deste repositório renomeados pra argocd/applications//argocd/apps/ também em 2026-08-21, batendo com o nome exato usado aqui (gitops/applications/+gitops/apps/). A convenção de referência já validada em produção noutro contexto, a seguir aqui: Piloto concluído em 2026-08-21 com o authentication-service, seguindo o padrão de três camadas documentado em Repositórios satélite e inspirado numa implementação de referência que já rodava noutro projeto. Depois do piloto migraram o docs e o management-service (este com duas Application, ladesa-ro-api e ladesa-ro-waha, porque são dois releases Helm distintos), ambos com diff vazio na adoção. web fechou o backlog em 2026-08-22, o único que exigiu apagar e recriar em vez de update in-place: usava manifesto cru com spec.selector na forma {app: ladesa-ro-web}, o chart do Stakater rotula por app.kubernetes.io/name, e spec.selector de Deployment é imutável (mesmo achado já registrado na migração do Redis, ver Aprender: Argo CD). Deployment e Ingress antigos apagados de propósito antes do primeiro sync, cutover confirmado com o site real respondendo. Os quatro repositórios de time (authentication-service, docs, management-service, web) estão sob Argo CD agora, o runner self-hosted dev-deploy perdeu o último consumidor e foi desligado (ver logo abaixo). O timetable-generator saiu da conta em 2026-08-21 por ter sido aposentado, não migrado.
  • Uma pasta gitops/ (não argocd/), com duas subpastas: gitops/applications/, só manifests Application/AppProject do Argo CD, organizados por categoria (ex.: business/, monitoring/, operators/, security/, system/, mais projects/ pros AppProject); e gitops/apps/, os charts Helm e values de verdade que cada Application aponta, no mesmo esquema de categoria.
  • Um app-of-apps por cluster de destino, não uma lista mantida à mão: uma única Application raiz com source.path: gitops/applications e directory: {recurse: true}, deixando o próprio Argo CD descobrir todo Application/AppProject aninhado, ao contrário do argocd/applications/ atual deste repositório (uma Application por arquivo, listada manualmente).
  • Pra um repositório de serviço único (o caso do management-service): gitops/apps/<serviço>/ vira um chart Helm próprio, local, que declara o chart application do Stakater como dependência (uma instância aliased por processo, quando o serviço tem mais de um, por exemplo API mais worker mais job de migração), acrescentando só os templates que o chart genérico não cobre (HTTPRoute, secret do Infisical). gitops/envs/<ambiente>/applications/<serviço>.yaml é a Application de fato, com values-<ambiente>.yaml e syncPolicy.automated: {prune: true, selfHeal: true}, substituindo o helm upgrade -i disparado por CI por reconciliação pull-based de verdade.
  • Falta decidir, quando este item for retomado, o que exatamente muda na estrutura atual deste repositório (argocd/applications/, argocd/apps/, argocd/root/) pra adotar esse mesmo esquema de categoria, e se o infrastructure também ganha uma pasta gitops/ própria ou se a mudança de nome fica só nos repositórios de serviço individual.

GitOps com tag mutável não reinicia o pod sozinho, achado real em 2026-08-22

docs e management-service já adotaram o padrão gitops/ descrito em Trabalho inicial da organização, ainda não retomado acima, cada um com sua própria Application reconciliada automaticamente (syncPolicy.automated: {prune: true, selfHeal: true}). Achado ao verificar uma migração de conteúdo no ladesa-ro/docs: depois do merge, o workflow [DEV] Build & Push completou com sucesso, publicando uma imagem nova em ghcr.io/ladesa-ro/docs/docs:development, mas o site em docs.ladesa.com.br continuou servindo o conteúdo antigo (confirmado com cache-busting, não é cache de CDN/navegador).

Causa: gitops/apps/docs/values-development.yaml usa pullPolicy: Always com uma tag mutável (tag: development). pullPolicy: Always só força um novo pull quando o pod é (re)criado, não reinicia sozinho um pod que já está rodando só porque o conteúdo da imagem no registry mudou. O Argo CD não detecta isso como diff nenhum (a string da tag continua development, igual antes e depois), então selfHeal não dispara. O mesmo padrão (pullPolicy: Always + tag mutável development, sem mecanismo de restart) existe em gitops/apps/api/values-development.yaml do management-service, mesma classe de risco, não confirmado ao vivo ainda.

  • Confirmar se management-service tem o mesmo problema: confirmado em 2026-08-24, ao acompanhar o deploy da migração de mensageria. O pod ladesa-ro-api continuava rodando há 2 dias e 22 horas depois do merge e da imagem :development nova já publicada, só a imagem do timetable-worker (Deployment novo, criado do zero, sem pod anterior pra comparar) atualizou sozinha. kubectl rollout restart deployment/ladesa-ro-api -n ladesa-ro-development manual foi necessário pra pod novo pegar o código migrado.
  • Decidir o mecanismo de correção: resolvido em 2026-08-28. Levantamento completo das opções (tag/digest imutável via CI, Argo CD Image Updater, Kargo, Argo Rollouts) e a decisão tomada, digest sha256 computado pelo build e promovido via PR automático (GITHUB_TOKEN, sem PAT dedicado), estão registrados em Rollout de imagens, incluindo a seção de hardening futuro (quando reavaliar Image Updater e Kargo).
  • Implementado em docs e management-service: .github/workflows/build-push.dev.yml dos dois repositórios ganhou um step que resolve o digest da imagem publicada e abre (ou atualiza, se já existir) um PR trocando tag: development por digest: sha256:... no values-development.yaml correspondente, branch fixa por satélite, sempre reaproveitando o mesmo PR em vez de acumular um por push. web e authentication-service ainda não migraram: mesmo padrão a replicar, o authentication-service já tem um promote.yml parcialmente pronto pra esse exato mecanismo (nunca confirmado se funciona), vale reaproveitar antes de escrever do zero.
  • Enquanto web/authentication-service não migram, o restart depois de um build novo continua manual nesses dois: kubectl rollout restart deployment/<nome> -n <namespace> (ou equivalente via argocd app actions run <app> restart).

Versões desatualizadas das ferramentas de bootstrap

Levantado em 2026-08-21, contra o que está fixado em ansible/inventory/host_vars/ldsa.yml. Ordem deliberada: upgrade de versão só depois de todo o resto ao redor já modernizado e validado (os itens acima), nunca como primeiro passo, e sempre com o mesmo processo de contingência já usado na migração do Postgres (cluster/ambiente descartável, dead man switch, backup confirmado antes de aplicar em produção).

  • k3s: resolvido em 2026-08-21, atualizado de v1.33.3+k3s1 pra v1.36.3+k3s1 (salto direto, changelog das três linhas 1.34/1.35/1.36 conferido, nenhuma mudança de ruptura afeta este cluster: o único aviso real, renomeação do provider kubernetesIngressNginx pra kubernetesIngressNGINX no Traefik, só importa pra quem customiza esse HelmChartConfig, que não é o nosso caso, só usamos ingressClassName: traefik). Backup do datastore sqlite feito antes (systemctl stop k3s, cp -a /var/lib/rancher/k3s/server/db, systemctl start k3s, ~529MB, em /root/k3s-upgrade-backup/) por não existir sqlite3 CLI no node pra um backup online. Achado colateral, não é bug do k3s: o reinstall regenerou /etc/rancher/k3s/k3s.yaml do zero, o que apagou um namespace: argocd que estava setado no contexto do kubectl (usado pelo argocd ... --core), causando configmap "argocd-cm" not found até kubectl config set-context --current --namespace=argocd ser refeito.
  • Helm: resolvido em 2026-08-21, atualizado de v3.18.6 pra v4.2.4. Reavaliado o risco depois de checar o uso real: só roda um helm upgrade --install argocd ... --version ... --values -, sem --atomic/--force/post-renderer/plugin (as únicas coisas que quebram de v3 pra v4), e os outros charts do cluster (Infisical, mariadb-operator) são renderizados pelo Argo CD internamente, não pelo binário helm do node. Formato de release no cluster não mudou, helm list -n argocd seguiu enxergando a release criada em v3 sem migração nenhuma. Suporte de segurança do v3 acaba em 2026-11-11, motivo real pra não adiar demais.
  • Argo CD (chart argo/argo-cd): resolvido em 2026-08-21, atualizado de 10.3.3 pra 10.4.0. Risco confirmado baixo antes de aplicar: mesma versão do app (v3.5.1), diff do chart só adiciona uma opção de VPA recommender opcional que não usamos. Rollout limpo, todos os pods do argocd saudáveis depois.
  • secrets-operator (Infisical): resolvido em 2026-08-28, atualizado de 0.8.0 pra 0.11.8. O sidecar kubeRbacProxy foi removido entre as versões, metrics passou a bind direto com RBAC embutido no manager; override manual de args (apontava pro modelo antigo) removido, chart usa o default novo.
  • Postgres engine (CNPG): resolvido em 2026-08-28, atualizado de 18.0 pra 18.6 (6 patches). instances: 1 neste cluster, sem réplica: o restart do pod primário causou breve indisponibilidade real do banco (Keycloak perdeu conexão por ~1 minuto durante a janela), esperado e sem dado perdido, cluster confirmado Ready logo depois.
  • Portainer: resolvido em 2026-08-28, chart 1.0.59 pra 245.0.0, imagem EE 2.21.5 pra 2.45.0. Schema de image/service/ingress idêntico entre as versões, só features novas opcionais adicionadas.
  • Infisical platform (infisical-standalone): resolvido em 2026-08-28, imagem v0.99.0-postgres pra v0.164.1 (~65 minors). Achado ao vivo: o pod novo ficou preso em Init:Error, causa era um initContainer órfão (migration-init, imagem k8s-wait-for) deixado por um helm install manual anterior ao Argo CD assumir o release, nunca limpo porque o Server-Side Apply do Argo CD só reivindica campos que ele mesmo define, nunca remove campo de outro field manager. Corrigido removendo o campo direto (kubectl patch ... --type=json, remove em spec.template.spec.initContainers). Login e InfisicalSecret confirmados sincronizando depois.

Trocar de CNI, considerar depois

Registrado em 2026-08-21, ligado a um achado em aberto da "Revisão de segurança" acima, descrito no inventário privado. O mecanismo, que é genérico e vale pra qualquer cluster k3s, está em Aprender: firewalld: o firewalld não consegue restringir o tráfego que chega no Traefik via hostPort (chain FORWARD, dominada pelo kube-router, sem solução oficial do k3s pra isso). Uma opção que pode resolver de vez, ainda não avaliada a fundo: adotar Cilium (ou CNI equivalente com enforcement próprio) no lugar do flannel + kube-router atuais. Cilium substitui o kube-proxy/kube-router por um dataplane eBPF próprio e tem features dedicadas exatamente pra esse cenário (CiliumClusterwideNetworkPolicy com host firewall, toEntities: host, controle de tráfego que entra via NodePort/LoadBalancer antes mesmo de virar Service), então não compete com nada por ordem de chain iptables do jeito que o firewalld compete hoje.

Nada disso foi pesquisado a fundo ainda (versão compatível com k3s v1.36.3+k3s1, esforço de migração do flannel existente, se dá pra trocar sem downtime, se resolve mesmo o caso específico de hostPort do svclb-traefik ou só de NodePort/LoadBalancer normal). Trocar de CNI num cluster de produção de nó único é uma mudança de alto risco (rede inteira do cluster depende disso), mesmo cuidado de sempre antes de tocar (pesquisa dedicada, cluster/ambiente descartável se possível, dead man switch). Não é pra fazer sem esse processo completo.

Importação do estado do node pro Ansible, primeira leva

Registrado em 2026-08-21. É a contraparte de "Mapear operação de rotina/manutenção pro Ansible" abaixo, e as duas não são a mesma coisa: aquela é sobre transformar procedimento em código, esta é sobre trazer pro git o estado que já existe no node desde antes deste repositório. Esta pode andar antes porque não depende do backlog de argocd/ estar resolvido, o estado do sistema operacional não muda quando a organização de pastas do Argo CD muda.

Os roles pacotes-base e sistema são a primeira leva. O que ficou deliberadamente em aberto:

  • pacotes_base_falhar_em_drift está false desde 2026-08-21. Motivo: gate novo nasce informativo, mesma regra dos checks de CI. Diferente dos bypasses de CI, aqui o baseline já nasceu limpo, os 28 pacotes instalados à mão no node estão todos classificados. Condição pra ligar: a remoção listada no item abaixo executada, porque enquanto pacotes_base_a_remover tiver conteúdo o gate ligado passaria a exigir que a lista continue existindo pra sempre.
  • Pacotes classificados como remover, ainda não removidos: podman, podman-compose, libicu-dev, libbpfcc, libbpfcc-dev, linux-headers-6.12.48+deb13-cloud-amd64, make, liblttng-ust1, spectre-meltdown-checker, bpfcc-tools e zsh. Nenhum tem consumidor hoje (apt-cache rdepends --installed vazio nos pacotes de desenvolvimento, e podman ps -a vazio sem nenhum quadlet em /etc/containers/systemd/). Junto deles saem a interface dummy0 e a linha de registry.ladesa.com.br em /etc/hosts, que são resquício do mesmo stack podman compose desmontado. Condição: executar o how-to de limpeza supervisionada, nunca pelo ansible-pull, que roda sem ninguém olhando.
  • Journal do node ainda ocupa mais de 4G, e o teto declarado por sistema nasceu acima do uso atual de propósito, decisão do usuário em 2026-08-21. Motivo: SystemMaxUse abaixo do uso corrente faz o journald apagar histórico sozinho na próxima rotação, e este journal alcança 2025-09-10, mais de 11 meses e 17 boots. Baixar o teto agendaria uma exclusão silenciosa em vez de deixar a escolha com quem opera. Pela mesma razão o role não declara MaxRetentionSec. Condição pra fechar: decidir quanto histórico de log vale a pena guardar, baixar sistema_journald_teto pro alvo, e rodar uma vez com sistema_reduzir_journal_agora ligado, com alguém acompanhando. Hoje são cerca de 3G recuperáveis num disco em 79%.
  • Performance Co-Pilot fica exatamente como está, decisão do usuário em 2026-08-21, registrada em Ambiente do node. O fato que justifica revisitar, a escrita contínua de métrica em /var/log/pcp, está anotado lá. Condição: quando observabilidade virar uma decisão consciente deste cluster, adotar ou remover, em vez de seguir de pé por herança.
  • protect-kernel-defaults ainda não está ligado no k3s. O role sistema declara os quatro parâmetros de kernel que o guia CIS do k3s exige como pré-requisito, mas a flag em si fica pra depois de propósito: com ela ligada, o kubelet se recusa a subir se os parâmetros não estiverem valendo. Condição: confirmar no node que os quatro valores estão aplicados de verdade, e só então ligar a flag, com o ritual de mudança de risco.
  • Conta apalrd continua existindo com sudo sem senha e authorized_keys vazio, herdada da imagem base. Condição: role contas, da próxima leva.
  • A rotina de manutenção do node parou de rodar e ninguém notou: resolvido em 2026-08-21. O runner auto-hospedado que executava os workflows do repositório cluster-maintenance estava inativo desde 2026-08-12, com o registro apagado pelo próprio GitHub, e os workflows falhavam desde 2026-01-19, sete meses sem alarme nenhum. Substituído pelo role manutencao, que traz a coleta de lixo pra um timer do systemd no próprio node e declara a política do unattended-upgrades como template deste repositório. O topgrade foi aposentado, não portado, porque um atualizador que atualiza tudo o que encontra conflita com o contrato de fixar versão por checksum. Resultado da primeira execução: imagens em cache de 96 pra 34, ReplicaSet zerados apagados, e o disco do node de 81% pra 36%, de 19G livres pra 61G. O repositório foi renomeado pra arquivado-cluster-maintenance e arquivado, seguindo a convenção da organização, e o runner maintenance foi removido do node junto dos 2G dele.
  • O runner dev-deploy rodava no node como root, com kubeconfig de cluster-admin: resolvido em 2026-08-22. web foi o último repositório a migrar pro Argo CD (mesmo padrão dos outros três satélites: gitops/apps/web/ com stakater/application como dependência, gitops/envs/development/applications/web.yaml, satellite-web declarado neste repositório). Como web nunca teve Helm nenhum (era kubectl apply direto em três manifestos crus), o wrapper chart foi escrito do zero, não adaptado de um values.yml já existente. Cutover ao vivo: Deployment/Ingress antigos apagados de propósito antes do primeiro sync (mesmo aprendizado do seletor imutável do Deployment, achado na migração do Redis, aplicado preventivamente desta vez, e o Ingress antigo tinha nome diferente do novo, então ficaria órfão coexistindo com o novo se não fosse removido). Confirmado depois: pod Running, diff vazio, https://dev.ladesa.com.br respondendo de verdade (x-powered-by: Nuxt). Zero consumidor do runner confirmado na organização inteira antes de desligar (gh api search/code). Serviço parado e desinstalado com o svc.sh oficial do próprio runner, diretório apagado (/root/github-devops/actions-runner/devops/, ~188MB de RAM liberados, node sem folga). Registro do lado do GitHub (Settings, Actions, Runners) removido pelo usuário, exige admin:org que a sessão não tinha. Achado à parte, não é deste item: o build de imagem do web está quebrado desde maio (pnpm run -w build:all, falha em todos os runs antes desta migração), não impede o deploy (usa a tag já publicada), mas precisa de atenção separada de quem mantém o código da aplicação.
  • Dois repositórios foram renomeados com o prefixo arquivado- mas nunca chegaram a ser arquivados: resolvido em 2026-08-22. arquivado-esteira-ci-cd tinha dependência viva real (ver item abaixo, authentication-service ainda usava duas actions reusáveis de lá pro job de build de imagem, achado só confirmado buscando esteira-ci-cd no código da organização inteira, não só pelo nome do repositório sozinho). As duas actions (prepare-images-builder, image-build-push) foram inlinadas direto no workflow do authentication-service, com os mesmos hashes de commit já usados no web pras mesmas actions de terceiro, testado em CI real antes de considerar resolvido. arquivado-dev-kit já não tinha consumidor nenhum. Os dois arquivados de verdade agora (archived: true confirmado via API).
  • Bug latente achado pelo gate de drift zero na adoção do SSO, em 2026-08-21, e conferido nos demais satélites depois. O manifesto do Helm declarava o backend do Ingress apontando pra uma porta de Service que não existe, porque usava o nome do targetPort (a porta do container) em vez do nome da porta do Service. O cluster tinha a forma correta, por número, divergindo do que o Helm achava que tinha aplicado, e sincronizar sem conferir teria derrubado o roteamento. Conferido no docs, management-service e, em 2026-08-22, no web (migração pro Argo CD, ver acima): os três limpos, cada um nomeia a porta do Service diferente do targetPort do container (web usa web-service/web-port), que é justamente o que evita a confusão. Os quatro satélites cobertos, nenhum caso real do bug fora do SSO original.
  • Registro órfão de runner na organização do GitHub: resolvido em 2026-08-22. A remoção do runner do node não apagava o registro do lado do GitHub sozinha, e isso exigia permissão de administrador da organização que a sessão não tinha; o usuário removeu direto pela interface em Settings, Actions, Runners.
  • O authentication-service dependia de um repositório já renomeado pra arquivamento: resolvido por completo em 2026-08-22 (começou em 2026-08-21, ver histórico). A migração pro Argo CD já tinha removido o job de CD, que invocava deploy-k8s-stakater-application do arquivado-esteira-ci-cd. O job de build, que ainda usava duas actions daquele repositório (prepare-images-builder, image-build-push), foi corrigido inlinando as duas direto no workflow, mesmos hashes de commit já usados no web pras mesmas actions de terceiro (docker/setup-buildx-action, docker/login-action, docker/build-push-action). Testado em CI real antes de considerar resolvido. Dependência zerada, repositório arquivado (ver acima).
  • /etc/ssh/sshd_config foi editado à mão e não pertence a nenhum pacote apt, enquanto /etc/ssh/sshd_config.d/ está vazio. Como o arquivo principal já tem a linha de Include, a configuração deste repositório pode assumir por drop-in sem tocar no que está lá. Condição pra fechar: role ssh_servidor, com validação por sshd -t antes de escrever e o ritual de mudança de risco na execução, que é o mesmo caso do firewalld. Fecha junto um dos achados em aberto registrados no inventário privado.
  • /etc/apt/apt.conf.d/50unattended-upgrades e 20auto-upgrades foram editados à mão, nenhum dos dois pertence a pacote apt. Hoje só as origens do Debian e do Debian-Security estão declaradas, e não há política de reinício automático definida. Condição pra fechar: os dois viram template do role manutencao, com a decisão de reinício explícita.
  • Sobras de operação manual no /root: a unit rabbitmq-cutover-revert.service continua em estado failed, resquício de um dead man switch que disparou e falhou, e seis diretórios *-backup acumulam sem regra de retenção. Condição pra fechar: o how-to de limpeza supervisionada, junto dos pacotes classificados como remover, e uma regra escrita de por quanto tempo backup manual fica no node.
  • Estado deixado pelo cloud-init, que foi removido do node. O pacote está em rc, sem binário e com o serviço not-found, mas /etc/netplan/50-cloud-init.yaml, /etc/hosts e /etc/cloud/** continuam lá, sem dono nenhum. Como não há mais nada pra disputar esses arquivos, eles deixam de ser ambiente e passam a ser candidatos legítimos a declaração, ver Ambiente do node. Condição pra fechar: decidir se o Ansible assume a configuração de rede do node, e se assumir, remover os resquícios de /etc/cloud/ junto, porque o aviso no topo do /etc/hosts sobre manage_etc_hosts hoje engana quem for editar.
  • Configuração de sistema que segue no padrão de fábrica, sem declaração: fuso horário, locale, e os arquivos do systemd-timesyncd e do systemd-resolved, todos em stub vazio ou default do Debian. Nenhum está errado, e é justamente por isso que ninguém percebe que não estão declarados: uma reprovisão do zero pode cair em valores diferentes sem que nada acuse. Condição pra fechar: decidir quais desses este projeto quer fixar, e declarar no role sistema.
  • Node sem swap nenhum. Não é erro, é escolha comum em node de Kubernetes, mas aqui ela se soma ao que o inventário privado registra sobre memória. Condição pra fechar: decidir explicitamente entre seguir sem swap, o que torna esgotamento de memória abrupto em vez de lento, ou configurar zram, e declarar a decisão em qualquer um dos dois casos.
  • Conffile órfão de pacote já removido: cloud-init, sysstat e quatorze versões antigas de linux-image estão em estado rc. Junto ficou /etc/cron.d/sysstat, que dispara a cada dez minutos apontando pra binário que não existe mais. É inofensivo, porque a linha se protege com command -v e o journal não registra erro nenhum em sete dias, mas é entulho que confunde qualquer inventário futuro. Condição pra fechar: apt purge dos pacotes em rc, junto do how-to de limpeza supervisionada.
  • Conta ifro será importada como está, com sudo sem senha, decisão do usuário em 2026-08-21. Registrado por ser um privilégio declarado sem correção, não um descuido. Condição: nenhuma automática, revisitar junto de qualquer revisão de acesso ao node.

Débito editorial: log de incidente dentro de Aprender

Registrado em 2026-08-21. aprender/index.md declara que a seção ensina conceitos "de forma geral, sem depender de nenhuma decisão específica de nenhum projeto", e Diátaxis reforça que fato específico de um cluster real pertence a Arquitetura, não a Explicação. Na prática aprender/argocd.md tem seis seções datadas de 2026-08-21 que são log de incidente deste cluster: o syncPolicy padronizado, a organização por categoria, o wrapper chart, a migração do cert-manager, o incidente do --prune com o CNPG, o rename de topo e a correção de causa raiz do Namespace.

  • Mover o log de incidente de aprender/argocd.md pra Arquitetura, deixando em Aprender só a lição genérica. Não é urgente e não é exposição, são lições de engenharia legítimas, só estão no lugar errado segundo a regra que o próprio repositório declara. Condição pra fechar: decidir se viram uma página nova em arquitetura/ ou se cada uma vai pro documento de arquitetura correspondente, e então mover preservando os links de entrada. O mesmo padrão já foi aplicado, em menor escala, ao tirar o achado de segurança dessas páginas.

Ferramentas e práticas candidatas, levantadas dos guias

Registrado em 2026-08-21, a outra metade do mesmo pedido que originou a seção de importação acima. Levantamento feito cruzando as páginas de Aprender com guias externos: o guia de hardening CIS do k3s, a documentação de segurança do GitHub Actions e do Argo CD, os princípios do OpenGitOps e o ferramental da OpenSSF.

Cada linha é candidato, não decisão tomada. Toda ferramenta nova passa antes pelos portões que este repositório já se impõe: licença conferida, versão fixada no hermit, release com no mínimo 7 dias, verbete no dicionário do cspell, e zero comentário nos arquivos de configuração. E todo check novo nasce não bloqueante, pela mesma regra que rege a seção de bypasses acima.

  • main não tem proteção de branch nenhuma. gh api repos/ladesa-ro/infrastructure/branches/main/protection devolve 404 e rulesets devolve lista vazia, num repositório cuja tese inteira é infraestrutura revisada em pull request. Pesa mais do que parece por causa do alcance que uma permissão de commit aqui carrega, registrado no inventário privado apontado por Estado fora do git. Condição pra fechar em duas etapas: exigir revisão de CODEOWNERS agora, e exigir os checks de status só depois que eles deixarem de ser continue-on-error, porque exigir um check que nunca falha não significa nada.
  • Renovate ao lado do Dependabot. O Dependabot não alcança três coisas que este repositório fixa à mão: as versões de k3s, Helm e do chart do Argo CD em ansible/inventory/host_vars/, as oito versões dos .hcl do hermit, e o targetRevision dos charts remotos em argocd/applications/. O manager nativo de Argo CD do Renovate casa por spec.source.chart sem exigir comentário no YAML, o que importa aqui porque comentário em YAML é proibido por regra do ast-grep. Limitação a registrar antes de adotar: o Renovate não recalcula o sha256 dos binários fixados, então isso precisa de um passo companheiro.
  • Trivy com mais escopo. Já está instalado em security.yml, rodando só scanners: misconfig. Ligar vuln e secret, e gerar SBOM em CycloneDX, é mudança de flag numa ferramenta que já passou por todos os portões. Fecha de uma vez os itens de Supply chain e SBOM e de Vulnerability scanning, que hoje têm adoção zero apesar de terem página própria.
  • step-security/harden-runner nos jobs que rodam em ubuntu-latest, em modo de auditoria primeiro e com lista de egresso permitido depois. É a primeira adoção concreta de defesa de cadeia de suprimentos neste repositório.
  • OpenSSF Scorecard. Gratuito pra repositório público, mede o que este repositório já faz bem (action fixada por hash, CODEOWNERS, atualização automática de dependência) e nomeia o que falta, começando pela proteção de branch da primeira linha desta seção.
  • ansible-lint no perfil production. Hoje roda no perfil padrão. Os roles de importação do node dobraram a superfície de Ansible do repositório, então vale apertar, sempre nascendo não bloqueante.
  • Job de argocd app diff em pull request. Transforma o gate de drift zero num check por PR em vez de um ritual de bootstrap. Exige acesso de leitura ao cluster a partir do CI, então precisa de uma ServiceAccount só de leitura, e não do kubeconfig de administração.
  • ValidatingAdmissionPolicy em vez de Kyverno ou Gatekeeper. Policy as code argumenta que admission controller é a forma forte da garantia, porque impede aplicar direto no cluster pulando a revisão. A escolha por ValidatingAdmissionPolicy é deliberada: é nativa do Kubernetes, não exige operador e não custa nenhum pod, o que importa porque este node não tem folga de memória. Aplicaria securityContext obrigatório e imagem fixada por digest no momento da admissão, atacando a raiz do bypass do misconfig registrado no topo deste documento.
  • ScheduledBackup do CNPG pro MinIO que já roda no cluster. Foundation registra que, se o disco do node morresse, o dado seria perdido sem recuperação possível. Os dois componentes já existem e já são gerenciados por GitOps. Junto vai um how-to de teste de restauração, porque Backup e disaster recovery aponta o backup nunca testado como o ponto cego real, não a ausência de backup.
  • Nenhum check de CI valida âncora de link. O mkdocs build --strict valida que o arquivo alvo existe, não que o fragmento depois do # existe, e o lychee roda hoje sem essa verificação ativa. Isso deixou passar um link morto de verdade: renomear um cabeçalho em aprender/firewalld.md quebrou a referência no TLDR de aprender/cloudflare.md, e todos os gates seguiram verdes. Achado em 2026-08-21, corrigido na hora, mas a lacuna que permitiu continua. Condição pra fechar: ligar a verificação de fragmento no lychee, que já está no quality.yml e já passou por todos os portões, nascendo não bloqueante como os demais.
  • Estágios seguintes do hardening CIS do k3s, depois do protect-kernel-defaults já registrado na seção de importação: secrets-encryption, que exige reinício e recifrar os segredos que já existem no cluster, e o bloco de kube-apiserver-arg com log de auditoria, que precisa de arquivos de política no disco e de espaço pro log, e por isso depende de resolver a ocupação de disco antes.

Mapear operação de rotina/manutenção pro Ansible, considerar por último

Registrado em 2026-08-21, pedido explícito do usuário, deliberadamente o último item da lista (depende do resto do backlog estar resolvido primeiro, senão mapeia procedimento que ainda vai mudar). Hoje o Ansible deste repositório (ansible/roles/: k3s, firewalld, vault_repo, argocd_bootstrap, self_pull_timer) cobre só o bootstrap (rodar uma vez, do zero até o cluster de pé, ver Bootstrap mínimo na VM). A contraparte de operação contínua não existe como código nenhum, é uma sequência de comando kubectl/ssh/argocd executada manualmente e documentada em prosa (quando documentada), não repetível de forma idempotente. Isso já está parcialmente reconhecido em "Rotina de operação contínua a criar" acima, mas aquela seção é sobre decidir onde a documentação mora; este item é sobre o que dessa operação vale a pena virar Ansible de verdade, não só prosa.

Candidatos reais, levantados só de olhar pro que já foi feito manualmente nesta sessão (lista de partida, não exaustiva, o próprio item pede uma análise antes de decidir o que entra):

  • Rotação de senha (Ansible Vault, deploy keys, roles do Postgres, segredos do Infisical): hoje cada rotação é uma sequência de comando ad-hoc no node (openssl, ansible-vault encrypt, kubectl pra reconciliar). Virar um role/playbook parametrizado (ansible-playbook rotacionar-segredo.yml -e alvo=postgres-role-x) reduziria o risco de esquecer um passo no meio de uma rotação sob pressão.
  • Backup de datastore antes de upgrade (o padrão usado pro sqlite do k3s nesta sessão: systemctl stop, cp -a, systemctl start): hoje é comando manual toda vez que alguém for atualizar o k3s. Virar uma task/role reaproveitável evita repetir o mesmo raciocínio (e o mesmo risco de esquecer um passo) na próxima atualização.
  • Sync do root/Application do Argo CD depois de um git pull: hoje é ssh + git pull --ff-only + argocd app sync root --core digitado à mão a cada mudança, com a pegadinha real já documentada (sincronizar root antes da Application filha, ver Aprender: Argo CD) dependendo de quem está operando lembrar na hora.
  • Sweep de saúde pós-mudança (kubectl get pods -A sem nada fora de Running/Completed, argocd app list --core tudo Synced/Healthy): repetido manualmente depois de cada sync nesta sessão inteira, candidato natural a virar um único comando/task, talvez até um health-check reaproveitado pelo item "Acompanhamento de saúde" já registrado acima.
  • Renovação/verificação do dead man switch (systemd-run --on-active=...): o padrão já é consistente entre os usos desta sessão (firewalld, RabbitMQ), mas cada vez foi escrito na mão no terminal; um role/template parametrizado (comando-de-revert, janela-de-tempo) reduziria a chance de errar a sintaxe do systemd-run numa hora de pressão real.

Por que só ao final: mapear procedimento operacional pro Ansible antes do resto do backlog estar resolvido significa documentar/automatizar um estado que ainda vai mudar (ex.: a reorganização de argocd/ muda o comando de sync; a auditoria de chart oficial muda o que existe pra fazer backup antes de tocar). Fazer isso por último evita reescrever o mesmo role duas vezes.

Mecanismo de rollout de imagens trocado de digest-via-PR pra Argo CD Image Updater, 2026-08-28

A correção registrada em "GitOps com tag mutável" foi implementada na mesma manhã como digest imutável + PR automático (GITHUB_TOKEN), e substituída na mesma tarde. Motivo: em docs e management-service, todo push em development, ambiente sem razão nenhuma pra exigir aprovação humana pra ver o próprio código rodando, passou a esperar alguém clicar em "Approve", por causa do ruleset de main que já existia antes desta correção, não introduzido por ela.

Mecanismo final: Argo CD Image Updater instalado como componente de foundation, aplicando a atualização direto na Application (write-back-method: argocd, sem commit, sem PR), com webhook do GHCR configurado pra reduzir o delay do polling padrão a segundos. Detalhe completo, incluindo a análise de segurança do endpoint exposto, em Rollout de imagens.

  • docs e management-service migrados: anotações na Application, ImageUpdater CR único cobrindo namePattern: "ladesa-ro-*". Confirmado ao vivo em 2026-08-28: application.deployment.image.digest aplicado via parâmetro Helm nos 3 satélites (docs, api, timetable-worker).
  • web migrado: mesmas anotações, PR web#790.
  • authentication-service ainda não migrado: mesmas anotações a replicar, nenhuma peça de infraestrutura nova necessária (o ImageUpdater CR já cobre qualquer satélite ladesa-ro-* novo).
  • Webhook de organização no GitHub ainda não criado: token gh local não tinha o escopo admin:org_hook; pedido de elevação (gh auth refresh -s admin:org_hook) expirou sem aprovação a tempo. Falta: aprovar o fluxo de dispositivo e rodar a criação do webhook (evento package, URL https://image-updater.ladesa.com.br/webhook?type=ghcr.io). Até lá, o Image Updater funciona só por polling (padrão do controller, tipicamente ~2min).
  • Webhook de organização criado: id 671516711, evento package, active: true.
  • Projeto foundation-image-updater-w0cw criado no Infisical, chave webhook.ghcr-secret registrada em prod.

Webhook do GitHub pro argocd-server, reduzindo o delay de root, 2026-08-28

Diferente do webhook do GHCR (que acorda o Image Updater), este é pro próprio argocd-server: reduz o delay do ciclo de polling padrão do Argo CD pra detectar mudanças em main do infrastructure (o que afeta principalmente o root, hoje sem automated.selfHeal reativo a webhook, só a polling).

  • root ganhou syncPolicy.automated: argocd/root/application.yaml, PR #7, mergeado e aplicado no cluster pelo self-pull-timer. root confirmado Synced/Healthy com automated.selfHeal ativo.
  • Webhook de organização no GitHub criado: evento push, URL https://argocd.ladesa.com.br/api/webhook.
  • Secret do webhook (webhook.github.secret em argocd-secret) aplicado no cluster: manifest cifrado em infrastructure-vault (secrets/k8s/argocd/webhook-github-secret.yaml), o self-pull-timer já reconciliou sozinho, chave confirmada presente em argocd-secret.

Image Updater estava instalado mas 100% inerte desde o PR #5, achado ao vivo em 2026-08-28

Achado ao investigar por que o Image Updater não estava atualizando docs/api/timetable-worker apesar das anotações e do PR #6 (fix de watch.namespaces) já mergeados: a Application foundation-image-updater estava presa em OutOfSync/Failed desde a instalação, erro resource argocd-image-updater.argoproj.io:ImageUpdater is not permitted in project ladesa. Causa: argocd/root/project.yaml nunca ganhou ImageUpdater no namespaceResourceWhitelist quando o componente foi instalado. O ImageUpdater CR nunca sincronizou, e o pod, apesar de Running, nunca aplicou o watch.namespaces=argocd do PR #6, log mostrando "Watching controller namespace only" desde o dia da instalação.

  • Corrigido: PR #10, namespaceResourceWhitelist liberado, aplicado direto no cluster (kubectl apply) pra desbloquear na hora. Application confirmada Synced, pod novo já loga "Watching specific namespaces" namespaces=argocd.
  • Anomalia resolvida, causa era outra: o diagnóstico anterior (controller "não listando" as 3 apps) estava errado, era o nível de log info escondendo as linhas de debug e mostrando só o resumo final, que por coincidência também dizia "3". Subindo pra log.level: debug de verdade (via values.yaml, PR #10) apareceu a causa real: "Image '...' seems not to be live in this application, skipping". Isso é um bug conhecido do próprio Argo CD (Application.status.summary.images vazio pra toda Application do cluster, não só as 3), argoproj/argo-cd#27861. Mitigado com a anotação <alias>.force-update: "true" em docs/api/timetable-worker/web (PRs docs#15, management-service#1101, web#790). Faltava ainda a tag explícita (:development) na anotação image-list, sem ela o update-strategy: digest não tem versão pra ancorar a resolução do digest, corrigido nos mesmos PRs. Detalhe completo em Rollout de imagens. log.level revertido pra info depois do diagnóstico (PR #10 já mergeada com o valor de debug; revertido em PR separada). Confirmado ao vivo funcionando de ponta a ponta pros 3 satélites (application.deployment.image.digest aplicado via parâmetro Helm).
  • AppProject ladesa perdeu o whitelist do ImageUpdater de novo, sem causa raiz confirmada: depois de corrigido pelo PR #10 e aplicado com sucesso pelo self-pull-timer (confirmado no log do ansible-pull às 15:31:46, diff aplicado corretamente), o campo sumiu de novo da AppProject viva por volta de 15:57, travando o foundation-image-updater de novo com o mesmo erro is not permitted in project ladesa. Investigado: sem segunda definição do AppProject em outro arquivo, sem redefinição via configs.projects no Helm values do Argo CD, sem outra execução do ansible-pull nesse intervalo. O histórico de managedFields que poderia mostrar quem escreveu por último foi apagado sem querer pelo próprio kubectl apply (client-side) usado pra corrigir na hora, tanto o meu quanto o do Ansible usam client-side apply, que sempre colapsa pra um único manager kubectl-client-side-apply. Corrigido de novo manualmente. Se acontecer de novo, capturar kubectl get appproject ladesa -o yaml (incluindo managedFields) antes de corrigir, pra não perder o rastro de novo.