Manual — plat-eng-platform
Como adicionar um componente novo, editar config viva, e armadilhas já conhecidas.
Adicionar um componente de plataforma novo
- Criar a pasta do componente na raiz (ex.:
meu-componente/values.yaml). - Criar
apps/meu-componente.yaml— umaApplicationapontando pro Helm chart (upstream) +$values/meu-componente/values.yaml(este repo), ou pra própria pasta se for manifesto puro. - Escolher o
projectcerto (corepra runtime de plataforma,networkingpra CNI/ingress/TLS,app-deliveryé só proApplicationSet— não usar pra componente novo). - Escolher
sync-wave— depender do que o componente precisa que já exista. Convenção:-1AppProjects,1-2fundação de rede/storage/segredo,3runtime que depende de rede+storage,4-5o que depende de Crossplane instalado,6o que depende de tudo (tenants, app-delivery). - Abrir PR — merge dispara o ArgoCD sozinho, sem passo manual.
Editar config de um componente já vivo
Mudar values.yaml de qualquer componente e mergear é suficiente — o ArgoCD reconcilia sozinho. A
exceção conhecida é o argocd/values.yaml deste repo, que não é sincronizado por nenhuma
Application (parece GitOps, não é — quem instala/atualiza o ArgoCD é o seed.sh do
plat-eng-bootstrap, com um arquivo diferente, bootstrap/manifests/argocd-values.yaml, day-0
only). Pra mudar RBAC/config do ArgoCD de verdade:
helm repo add argo https://argoproj.github.io/argo-helm && helm repo update argo
helm upgrade argocd argo/argo-cd -n argocd --version <mesma da release, `helm list -n argocd`> \
--reuse-values -f <patch pequeno, só o delta>--reuse-values preserva o que já está vivo — nunca passar -f argocd/values.yaml inteiro, o
arquivo já diverge da release real.
Aplicações sem cliente — AppPlacement
Desde o Grupo F (ADR-0010/0011), as aplicações sem tenant (stack: platform — hoje
plat-eng-docs, devops-dashboard, scaffolder, scaffolder-api) não passam mais por
app-delivery/apps/<app>/{app-delivery,values}.yaml nem pelo ApplicationSet — os dois foram
removidos do repo (PR#161). Cada uma é um recurso AppPlacement (delivery.prophub.tech/v1alpha1,
cluster-scoped) no próprio cluster; a Composition app-placement o transforma no mesmo
Application do ArgoCD de sempre, apontando pros mesmos 4 charts genéricos — nada muda do
Application pra baixo.
spec tem dois donos, cada um com um jeito diferente de mudar:
spec.contract— projetado doapp.yamldo repositório da aplicação (forma, porta, health path, quais env vars vêm de Secret). Só muda reprojetando (scaffold register, o botão "Registrar"/onboarding da UI, ou a tool MCPregister_app) — nunca editando o recurso direto.spec.placement— autorado pela plataforma (namespace, imagem, ingress, scaling, resources). Registro só o escreve na criação; depois disso só muda por Promoção (troca sóimage.tag— botão "Promover" na UI,scaffold promote, ouPOST /api/ci/promotevia OIDC do CI) ou por edição direta (PATCH /api/placements/{name}, oukubectl patchna emergência).
Aplicar via kubectl pula a validação de render. scaffold register/promote conferem, antes
de escrever qualquer coisa, que o values resultante realmente renderiza no chart daquela forma
(values.schema.json do chart, mais restrito que o XRD — ex.: o XRD aceita env.fromSecret em
qualquer forma, mas prophub-static não tem bloco env e o chart recusa no render). Um kubectl apply/kubectl patch direto no AppPlacement só passa pela validação (mais frouxa) do XRD — o
recurso é aceito e o problema só aparece depois, como Application Degraded. Legítimo pra
emergência (rollback via spec.placement.image.tag, runbook ADR-0010/0011 D3) — evitar pra
qualquer outro campo.
Pausar uma aplicação
spec.placement.paused: true deixa a aplicação com ZERO pod sem apagar nada: Deployment, Service,
IngressRoute, certificado, ExternalSecret e namespace continuam declarados. Vale para as seis
formas — em api/web/static-web o número de réplicas vai a zero e o HPA deixa de ser
renderizado; em worker/feed o literal de réplica única vira zero; em job o CronJob nasce com
suspend: true.
scaffold edit <nome> --paused true --by <login> --reason "fora de uso"
scaffold edit <nome> --paused false --by <login> --reason "de volta ao ar"Também pela UI (caixa "Pausada" na edição da Colocação) e por PATCH /api/placements/{name} com
{"paused": true}.
O que saber antes:
- A aplicação pausada continua
Synced/Healthyno ArgoCD — um Deployment de zero réplica temAvailable=True. Para ver a pausa,kubectl get appplacementtem a colunaPausada. - Aplicação exposta pausada responde 503, não 404: o host e o certificado seguem de pé, o Service é que fica sem endpoint. Um 404 do curinga significa outra coisa — Colocação APAGADA.
scaling.minnão aceita zero, e isso é de propósito: HPA comminReplicas: 0exige o feature gateHPAScaleToZero, que o cluster não tem. Quem zera é o Deployment, com o HPA fora.- Réplica zero não fere o invariante I-10: ele proíbe MAIS DE UM consumidor do stream
order, não proíbe nenhum. - A pausa vence
replicas, e os dois convivem: numa Colocaçãoapi,paused: truecomreplicas: 2roda com zero pod e volta com dois ao despausar. O número declarado é o valor de RETORNO, não o de agora — por isso pausar não o zera.
Alternativa antiga, hoje desnecessária: apagar a Colocação. Isso leva Applications, workloads, HPA,
IngressRoute, Certificate, ExternalSecrets e — se a Colocação for a dona dos recursos compartilhados
— o namespace inteiro, e voltar depende de um backup fora do Git (foi o caso do octus-dev).
Detalhe completo (schema, Composition, saída de emergência): crossplane/xrd-app-placement.yaml,
crossplane/composition-app-placement.yaml, e tools/scaffolder/docs/manual/index.mdx.
Adicionar um building block Crossplane novo
Seguir o padrão dos 4 existentes: xrd-<nome>.yaml (apiextensions.crossplane.io/v2, scope: Cluster) + composition-<nome>.yaml (apiextensions.crossplane.io/v1, mode: Pipeline,
function-patch-and-transform + function-auto-ready), provisionando via provider-kubernetes
Object (manifesto real embutido), não via provider nativo OCI. argocd.argoproj.io/sync-wave: "0" na XRD, "1" na Composition. ci/validate.py roda o check xrd automaticamente em qualquer
PR que toque nesses arquivos — não precisa configurar nada a mais.
Armadilhas conhecidas (de CLAUDE.md, confirmadas em produção)
$values/muda de significado dependendo de onde a Application está — path relativo aorepoURLdaquela fonte específica, não ao repo raiz. Um$values/x/values.yamlcopiado sem ajustar já quebrou Application antes.helm lintsozinho não pega os erros que o schema pega — sempre rodar com-f <values-real>, nunca só o chart nu.kube-prometheus-stackderrubou oapplication-controllerdo ArgoCD por OOM com o limite default de 512Mi — precisa de pelo menos 1536Mi.- ArgoCD recusa prunar o último recurso de uma Application (
Skipping sync attempt: auto-sync will wipe out all resources) — guarda de segurança, não bug. Acontece ao remover o último tenant. Fix:argocd app sync <nome> --core --pruneexplícito. - CRD via
helm upgradenão atualiza — CRDs do Helm só instalam na primeira vez; upgrade de chart que muda CRD precisa dekubectl applymanual na CRD antes. - Cilium:
chainingMode: generic-vethsemchainingTargeté inválido em 1.16+. Usar sóchainingTarget: oci, semchainingMode. - Namespace E
ocir-pull-secretde apps de instância única são automáticos desde o PR#95 — os 4 chartsprophub-{static,web,worker,job}declaramtemplates/namespace.yaml(sync-wave-2) etemplates/imagepullsecret.yaml(ExternalSecret, sync-wave-1, lêsecret/data/platform/ ocir-pulldo Vault) quandonot .Values.tenant. Só tenants continuam ganhando o namespace via building blockTenantNamespace(Crossplane) — os dois caminhos nunca criam o mesmo namespace, pra não gerar dono duplo. Nenhumkubectl create namespace/cópia manual de Secret é necessário. Único pré-requisito, uma vez só:secret/platform/ocir-pulljá populado no Vault (comando emapp-delivery/README.md, seção "Namespace + pull de imagem") — não por app, por cluster. ocir-pull-secretnão é GitOps — cada namespace novo precisa dele copiado/anexado ao SAdefaultmanualmente, senãoErrImagePull.