Prophub Cloud Foundation

Manual — plat-eng-platform

Como adicionar um componente novo, editar config viva, e armadilhas já conhecidas.

Adicionar um componente de plataforma novo

  1. Criar a pasta do componente na raiz (ex.: meu-componente/values.yaml).
  2. Criar apps/meu-componente.yaml — uma Application apontando pro Helm chart (upstream) + $values/meu-componente/values.yaml (este repo), ou pra própria pasta se for manifesto puro.
  3. Escolher o project certo (core pra runtime de plataforma, networking pra CNI/ingress/TLS, app-delivery é só pro ApplicationSet — não usar pra componente novo).
  4. Escolher sync-wave — depender do que o componente precisa que já exista. Convenção: -1 AppProjects, 1-2 fundação de rede/storage/segredo, 3 runtime que depende de rede+storage, 4-5 o que depende de Crossplane instalado, 6 o que depende de tudo (tenants, app-delivery).
  5. 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 do app.yaml do 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 MCP register_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, ou POST /api/ci/promote via OIDC do CI) ou por edição direta (PATCH /api/placements/{name}, ou kubectl patch na 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/Healthy no ArgoCD — um Deployment de zero réplica tem Available=True. Para ver a pausa, kubectl get appplacement tem a coluna Pausada.
  • 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.min não aceita zero, e isso é de propósito: HPA com minReplicas: 0 exige o feature gate HPAScaleToZero, 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ção api, paused: true com replicas: 2 roda 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 ao repoURL daquela fonte específica, não ao repo raiz. Um $values/x/values.yaml copiado sem ajustar já quebrou Application antes.
  • helm lint sozinho não pega os erros que o schema pega — sempre rodar com -f <values-real>, nunca só o chart nu.
  • kube-prometheus-stack derrubou o application-controller do 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 --prune explícito.
  • CRD via helm upgrade não atualiza — CRDs do Helm só instalam na primeira vez; upgrade de chart que muda CRD precisa de kubectl apply manual na CRD antes.
  • Cilium: chainingMode: generic-veth sem chainingTarget é inválido em 1.16+. Usar só chainingTarget: oci, sem chainingMode.
  • Namespace E ocir-pull-secret de apps de instância única são automáticos desde o PR#95 — os 4 charts prophub-{static,web,worker,job} declaram templates/namespace.yaml (sync-wave -2) e templates/imagepullsecret.yaml (ExternalSecret, sync-wave -1, lê secret/data/platform/ ocir-pull do Vault) quando not .Values.tenant. Só tenants continuam ganhando o namespace via building block TenantNamespace (Crossplane) — os dois caminhos nunca criam o mesmo namespace, pra não gerar dono duplo. Nenhum kubectl create namespace/cópia manual de Secret é necessário. Único pré-requisito, uma vez só: secret/platform/ocir-pull já populado no Vault (comando em app-delivery/README.md, seção "Namespace + pull de imagem") — não por app, por cluster.
  • ocir-pull-secret não é GitOps — cada namespace novo precisa dele copiado/anexado ao SA default manualmente, senão ErrImagePull.

On this page