Deployment do igrp-stack por GitOps — repositório de deployment, modelo de segredos por camadas e as armadilhas do sync.
Modelo de deployment em que se copia o Chart.yaml e um values.yaml para um repositório git de deployment, se editam os parâmetros do ambiente, e o ArgoCD sincroniza com o cluster. Os subcharts são resolvidos do registry OCI.
1. Pré-requisitos
Subcharts publicados no registry
Todos os subcharts igrp-* e o umbrella têm de existir em oci://registry.nosi.cv/igrp-charts. O CI do repositório do chart trata disso: job manual publish-all para bootstrap, mais publicação automática no merge.
Registry OCI registado no ArgoCD
Sem isto, o helm dependency build do repo-server falha ao puxar os subcharts.
argocd repo add registry.nosi.cv --type helm --enable-oci \
--name igrp-charts --username <user> --password <pass>Ou declarativo, como Secret no namespace do ArgoCD:
apiVersion: v1
kind: Secret
metadata:
name: igrp-charts-oci
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
stringData:
name: igrp-charts
url: registry.nosi.cv
type: helm
enableOCI: "true"
username: <user>
password: <pass>Cluster
Operador cloudnative-pg, StorageClass (Longhorn / Azure Disk) e nginx IngressController — ver a visão geral.
2. Repositório de deployment
Estrutura mínima por ambiente:
deployments/
└── igrp-stack/
├── Chart.yaml ← CÓPIA do Chart.yaml do igrp-stack (não editar deps)
├── values.yaml ← parâmetros do ambiente (domínios, réplicas, toggles…)
└── sealed-secrets/ ← só em produçãoO Chart.yaml copia-se tal e qual: as dependências apontam para o OCI com versão fixa, e assim os paths dos values ficam idênticos aos de toda a documentação (igrp-access-management.oauth…). Para o values.yaml, parte dos ficheiros do chart e ajusta os três domínios global.*.
A Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: igrp-stack
namespace: argocd
spec:
project: default
source:
repoURL: https://<servidor-git>/<grupo>/<repo-deployments>.git
targetRevision: main
path: deployments/igrp-stack
destination:
server: https://kubernetes.default.svc
namespace: igrp
syncPolicy:
syncOptions:
- CreateNamespace=true
- ServerSideApply=true # ConfigMap grande do realm do Keycloak
# automated: { prune: true, selfHeal: true } # opt-in
ignoreDifferences:
- group: postgresql.cnpg.io
kind: Cluster
jsonPointers: [ /status ]Comportamento dos Jobs no primeiro sync
O ArgoCD converte hooks post-install em PostSync, que só corre quando todos os recursos estão Healthy. Num sync inicial de raiz, os process backends não ficam Healthy sem os Secrets e BDs que estes Jobs criariam — pode ficar circular. O chart já mitiga isto com argocd.argoproj.io/hook: Sync e a coreografia de waves. No fluxo helm upgrade --install pelo CLI esta limitação não existe.
3. Segredos — modelo por camadas
O chart cria por default os Secrets a partir dos values (create: true). Para GitOps há toggles que desligam essa criação, e o chart passa a apenas referenciar Secrets pré-existentes.
| Secret | Toggle nos values | Chaves esperadas |
|---|---|---|
igrp-*-db-user (4, ou 6 com notificações) | igrp-pg-cluster.dbUsers.createSecrets: false | username/password, type kubernetes.io/basic-auth, label cnpg.io/reload: "true" |
igrp-access-management-secrets | igrp-access-management.secret.create: false | smtp-password |
igrp-access-management-oauth-client | igrp-access-management.oauthClient.create: false | client-id/client-secret |
igrp-access-management-oauth-keys | já é sempre existingSecret | public.pem/private.pem — via scripts/generate-oauth-keys.sh |
igrp-minio-creds | igrp-minio.credsSecret.create: false | MINIO_ROOT_USER/MINIO_ROOT_PASSWORD |
igrp-app-center-nextauth | igrp-app-center.nextauth.existingSecret | nextauth-secret — partilhado por TODOS os frontends Next.js |
| nextauth das process UIs | nenhum — apontam por default ao Secret do app-center | nextauth-secret |
igrp-keycloak-admin | igrp-keycloak.keycloak.auth.existingSecret | admin-password |
igrp-redis-auth | igrp-redis.authSecret.create: false (prod) ou password explícita (dev) | redis-password |
igrp-process-*-oauth-client (4) | já é sempre existingSecret, ou Job m2m.autoRegister | client-id/client-secret |
igrp-notification-{api,ui}-oauth-client (2) | idem | client-id/client-secret |
Sem authSecret explícito, a password do Redis roda a cada sync
O lookup + random do wrapper gera uma password nova em cada sincronização do ArgoCD. Define authSecret.create: false (produção) ou authSecret.password (dev).
O mapa dbUsers.users exige password mesmo com o módulo desligado
As entradas notification e notificationTemporal vêm declaradas no values.yaml. A validação validate-db-users exige password a cada entrada do mapa mesmo com o módulo de notificações desligado, e um null no values do ambiente não as remove. Em GitOps: dbUsers.createSecrets: false e pré-criar os dois Secrets, ou dar-lhes password no values do ambiente.
Excepção: o clientSecret do Keycloak não pode vir de um Secret
O igrp-keycloak.igrp.clientSecrets.accessManagement é renderizado para dentro do realm_config.json, que é um ConfigMap. Só é usado no modo de federação Keycloak. As opções são: aceitar este único valor no values, ou importar o realm com um valor temporário e definir o secret real na consola do Keycloak (kcadm) depois do primeiro arranque.
3.1 Dev e stage — segredos nos values
Manter os defaults (create: true) e preencher os valores no values.yaml do repo de deployment. Aceitável apenas porque o repo é privado e as credenciais são de ambientes de teste. Nunca com credenciais de produção.
3.2 Produção — Sealed Secrets
Ver Instalação de produção para o procedimento completo. Em resumo: instalar o controller, guardar o backup da chave privada fora do git, gerar os SealedSecrets com scripts/bootstrap-secrets.sh --seal, commitar a pasta cifrada, e ligar os toggles no values de produção.
O ArgoCD não mistura chart e manifests soltos no mesmo source
Se a pasta sealed-secrets/ estiver dentro do path de um Application com chart Helm, cria um segundo Application (ou um segundo source) para ela.
3.3 Sem ferramenta — bootstrap manual
O mesmo script sem --seal cria os Secrets directamente no cluster, uma vez por ambiente, antes do primeiro sync. Os values usam os mesmos toggles.
3.4 Futuro — External Secrets Operator
O contrato "Secrets pré-existentes com estes nomes e chaves" é exactamente o que o ESO cumpre. Quando se adoptar Azure Key Vault, o bootstrap ou os SealedSecrets são substituídos por recursos ExternalSecret que criam os mesmos Secrets — sem alterações ao chart nem aos values.
4. Checklist de primeiro deploy
publish-allcorrido no CI (registry populado);- registry OCI registado no ArgoCD;
- Secrets do ambiente criados + chaves RSA (
generate-oauth-keys.sh); Chart.yaml+values.yamlno repo de deployment;- (CNPG < 1.25)
igrp-pg-cluster.cluster.databases: []nos values, e criaraccess-manager-db/keycloakà mão depois do PostgreSQL subir — as BDs dosprocess-*são criadas pelo Jobdb-initem qualquer versão do CNPG; - criar o Application e sincronizar, acompanhando a ordem: pg-cluster → redis/minio/keycloak → access-management → app-center/gateway → process-*;
- registar os clientes OAuth dos
process-*(Jobm2m.autoRegisterou manualmente); rollout restartdo gateway se serviços novos não aparecerem nas rotas.
5. Armadilhas de uma instalação de raiz
Validado num cluster novo, apagando tudo e sincronizando só pelo ArgoCD. Correu — mas só depois destas quatro coisas.
5.1 O sync tem de ser disparado pelo caminho oficial
kubectl patch do .operation corre com skipHooks=true
Nenhum hook do chart é executado — nem db-init, nem oauth-register, nem catalog-seed, nem oauth-keys-init.
O sintoma não parece um problema de sync, parece um chart partido: o Temporal em CrashLoop porque as suas BDs não existem, e cinco apps em CreateContainerConfigError à espera dos Secrets *-oauth-client. E a instalação fica em ciclo — a operação espera que a wave 3 fique Healthy, e a wave 3 depende de hooks que não correram.
Dois agravantes:
- Se já houver uma operação em
Running, o controller retoma-a (Resuming in-progress operation) e ignora a nova. Para a substituir é preciso terminá-la primeiro e esperar que saia deRunning/Terminating. syncStrategy: {apply:{}}é apply-only e salta hooks por definição. Um merge-patch não remove uma estratégia já lá posta — fica{apply:{},hook:{}}, e o ArgoCD escolheapply.
Usa o botão SYNC da UI, ou argocd app sync. Diagnóstico rápido:
kubectl logs -n argocd sts/argocd-application-controller --since=5m \
| grep "application=<app>" | grep -o 'skipHooks=[a-z]*' | sort | uniq -c5.2 Um repo que redeclara o umbrella não herda o values.yaml dele
Se o repo de deployment tem um Chart.yaml próprio com os subcharts como dependências, em vez de depender do umbrella publicado, não há camada base de values — todo o wiring tem de ser reproduzido.
O que mais escapa: igrp-pg-cluster.cluster.cluster.roles e cluster.databases. O access-management e o Keycloak não têm Job db-init — as suas roles e BDs são declarativas (CNPG ≥ 1.25). Sem esse bloco o Keycloak entra em CrashLoop com password authentication failed for user "keycloak_user" e o Access Management fica preso no initContainer sas-schema-init.
Verificação: renderizar o chart do repo de deployment e comparar o conjunto de recursos com um render do umbrella publicado.
5.3 Renderizar sem --namespace envenena o RBAC
helm template <release> . deixa .Release.Namespace em default. Os RoleBinding dos hooks passam a apontar a ServiceAccounts em default/… e os recursos namespaced saem com metadata.namespace: default. Os manifestos parecem bem; os Jobs falham em runtime com Forbidden … cannot get resource. Ao comparar com o cluster, renderiza sempre com --namespace <ns>.
5.4 Chaves RSA — manual ou automático, mas conscientemente
No subchart e no values.prod.yaml, o Secret igrp-access-management-oauth-keys é criado por ops. Enquanto não existir, o pod do Access Management fica em FailedMount indefinidamente, porque o Deployment monta-o como volume.
Para GitOps de raiz sem passos manuais: oauth.keys.generate.enabled: true (já ligado no values de teste), que corre o Job oauth-keys-init na sync-wave -5. Gera só se o Secret não existir, por isso não roda chaves em upgrades. Em produção continua com o Secret de ops — a chave privada fica gerível fora do cluster e sobrevive a um helm uninstall.
5.5 Uma sync não basta para convergir
A instalação de raiz convergiu ao fim de três argocd app sync seguidos. Cada um avança as waves, mas termina em Failed porque Deployments de waves posteriores ainda não têm as dependências e batem no progress deadline. Com syncPolicy.automated + retry na Application, o ArgoCD faz esses ciclos sozinho e uma única acção chega.
6. Armadilhas de um upgrade
6.1 Republicar a mesma versão: são DUAS caches, não uma
A política do chart permite corrigir in place, sem bump de versão. Mas o rollout restart do repo-server — que era a receita documentada — não chega:
| Cache | O que guarda | Como se limpa |
|---|---|---|
| repo-server (disco do pod) | os .tgz dos subcharts OCI, por versão | kubectl rollout restart deploy/<release>-argocd-repo-server -n argocd |
| manifestos gerados (Redis) | o YAML renderizado, indexado pela revisão git do repo de deployment | hard refresh — a revisão não mudou, por isso a cache continua válida |
kubectl rollout restart deploy/<release>-argocd-repo-server -n argocd
kubectl rollout status deploy/<release>-argocd-repo-server -n argocd
kubectl exec -n argocd sts/<release>-argocd-application-controller -- \
sh -c 'argocd --core app get <app> --hard-refresh'
argocd app sync <app>Synced + Healthy pode estar a mentir
Com só o restart, o sync reportou Synced e Healthy enquanto aplicava o render antigo. E parecia meio aplicado, porque annotations postas à mão sobrevivem ao merge patch. Confirma sempre num campo que o chart novo mudou — nunca no estado Synced.
6.2 Correr o CLI argocd quando não está instalado no nó
Usa o binário que já vive no pod do application-controller. O do argocd-server não tem RBAC para o modo --core:
kubectl exec -n argocd sts/<release>-argocd-application-controller -- \
sh -c 'argocd --core app sync <app> --timeout 600'6.3 O ArgoCD quer podar PVCs que ficaram como backup
Um PVC criado por uma versão anterior do chart mantém o argocd.argoproj.io/tracking-id e aparece em requiresPruning: true — um sync com prune apaga os dados. Para o preservar, tira-o do tracking (não apaga nada):
kubectl label pvc <pvc> -n <ns> app.kubernetes.io/instance-
kubectl annotate pvc <pvc> -n <ns> argocd.argoproj.io/tracking-id-6.4 Recursos com campos imutáveis
O spec.selector de Deployments e StatefulSets não se altera. Quando um subchart muda de esqueleto, o sync falha com field is immutable e o recurso tem de ser apagado à mão antes. Despiste genérico, a correr antes de um upgrade que troque a origem de um subchart:
# comparar os selectors renderizados com os que estão vivos
helm template <rel> . -n <ns> | grep -A4 'matchLabels'
kubectl get deploy,sts -n <ns> -o custom-columns='NAME:.metadata.name,SEL:.spec.selector.matchLabels'Última atualização:
