Skip to content

Incidente 2026-05-10 — ArgoCD apps degradados

Sessão de diagnóstico e correção dos apps com problemas no ArgoCD CPPS (vm-cpps-02, 192.168.0.51).


OJS — MariaDB em CrashLoop por senha regenerada

Duração: ~21 dias em CrashLoop antes da resolução.

Causa raiz

apps/ojs/templates/secret.yaml usava randAlphaNum para gerar as senhas do MariaDB:

# ANTES (errado)
data:
mariadb-root-password: {{ randAlphaNum 24 | b64enc | quote }}

Cada sync do ArgoCD renderizava uma senha nova. O banco de dados tinha a senha antiga. O probe de liveness tentava conectar com a senha nova → acesso negado → CrashLoop.

Diagnóstico

Terminal window
kubectl logs ojs-mariadb-0 -n ojs
# ERROR 1045 (28000): Access denied for user 'root'@'localhost' using password: YES

Resolução

Passo 1: reset da senha no banco via pod auxiliar (MariaDB 12.x — usar ALTER USER, não UPDATE mysql.user).

Terminal window
# Pod com acesso ao PVC data-ojs-mariadb-0, rodando como root (UID 0)
mariadbd --datadir=/bitnami/mariadb/data --socket=/tmp/mysql.sock \
--skip-grant-tables --skip-networking --user=root &
until test -S /tmp/mysql.sock; do sleep 1; done
mariadb --socket=/tmp/mysql.sock -u root -e "
FLUSH PRIVILEGES;
ALTER USER 'root'@'%' IDENTIFIED BY '<senha>';
CREATE USER IF NOT EXISTS 'root'@'localhost' IDENTIFIED BY '<senha>';
ALTER USER 'root'@'localhost' IDENTIFIED BY '<senha>';
FLUSH PRIVILEGES;
"

Atenção: MariaDB 12.x removeu a coluna Password de mysql.user. Comandos UPDATE mysql.user SET Password=... retornam ERROR 1348: Column 'Password' is not updatable. Use ALTER USER.

Passo 2: corrigir secret.yaml para preservar valores existentes via lookup:

# DEPOIS (correto)
{{- $secretName := printf "%s-secret" (include "ojs-app.fullname" .) }}
{{- $existing := lookup "v1" "Secret" .Release.Namespace $secretName }}
{{- $existingData := $existing.data | default dict }}
data:
mariadb-root-password: {{ index $existingData "mariadb-root-password" | default (randAlphaNum 24 | b64enc) | quote }}

O lookup retorna o secret existente no cluster. Se não existir (primeiro deploy), gera aleatório. Se já existir, reutiliza — sem drift em syncs subsequentes.

Lição

randAlphaNum em secret.yaml sem lookup quebra qualquer app stateful. Sempre usar o padrão lookup + default para segredos de banco de dados.


Rancher — pod em CreateContainerConfigError por 22h

Causa raiz

O ArgoCD sincronizou o chart do Rancher com uma nova variável de ambiente (IMPERATIVE_API_APP_SELECTOR), disparando um rollout. O novo pod referenciava bootstrap-secret com optional: false, mas esse secret havia sido deletado após o bootstrap inicial do Rancher.

Warning Failed 22h kubelet Error: secret "bootstrap-secret" not found

O pod antigo (rancher-7db5f6c845) continuava rodando — o Rancher funcionava normalmente, só o rollout estava travado.

Resolução

Recriar o secret com o bootstrapPassword do values.yaml:

Terminal window
kubectl create secret generic bootstrap-secret \
--from-literal=bootstrapPassword='<senha>' \
-n cattle-system

O novo pod subiu e o Rancher auto-atualizou seus sub-componentes (rancher-webhook, system-upgrade-controller).

Lição

O bootstrap-secret do Rancher é consumido no primeiro login mas o chart continua referenciando-o. Ele precisa existir permanentemente para que novos rollouts do pod funcionem.


tutor-openedx — LMS/CMS/workers em CrashLoop por 22 dias

Dois problemas independentes, ambos com 22 dias de duração.

Problema 1: ALLOWED_HOSTS rejeitando probe de liveness

Causa raiz: o Kubernetes envia o probe HTTP com Host: <pod-ip>:8000. O Django verifica o header Host contra ALLOWED_HOSTS = ["lms.colabh.org", "lms"]. IPs de pod não estão na lista → retorna 400 → uWSGI recebe SIGTERM → pod reinicia → loop.

Resolução: adicionar httpHeaders nos probes para forçar o header correto:

# k8s/deployments.yml — deployment lms
readinessProbe:
httpGet:
path: /heartbeat
port: 8000
httpHeaders:
- name: Host
value: lms # já está em ALLOWED_HOSTS
livenessProbe:
httpGet:
path: /heartbeat
port: 8000
httpHeaders:
- name: Host
value: lms

Aplicar o mesmo para o deployment cms com value: cms.

Efeito cascata: o Caddy usa a saúde do LMS como probe próprio (GET / Host: lms.colabh.org). Com o LMS em CrashLoop, o Caddy também ficava em CrashLoop, bloqueando o sync do ArgoCD (que aguardava o Caddy ficar saudável antes de aplicar os demais recursos).

Problema 2: workers Celery em OOMKill

Causa raiz: concorrência padrão do Celery = número de CPUs do node (12). Cada worker Django consome ~307 MB RSS. 12 × 307 MB = 3.7 GB > limite de 1 Gi. O kernel matava os processos com SIGKILL (OOMKill via cgroup), mas o K8s reportava exitCode: 137 com reason: Error — não OOMKilled — porque o processo morto era um worker filho, não o processo principal.

Confirmação via dmesg:

oom-kill:constraint=CONSTRAINT_MEMCG ... task=celery ... anon-rss:307716kB
Memory cgroup out of memory: Killed process ... (celery)

Resolução: limitar concorrência explicitamente:

# k8s/deployments.yml — lms-worker e cms-worker
args:
- "--concurrency=2"

2 workers × 307 MB = ~614 MB, dentro do limite de 1 Gi.

Lição

  • Probes HTTP no K8s não enviam Host por padrão. Em Django com ALLOWED_HOSTS configurado, sempre especificar httpHeaders.
  • exitCode: 137 + reason: Error (não OOMKilled) pode ser OOMKill em nível de cgroup. Confirmar com dmesg | grep oom-kill.

Airflow — OutOfSync perpétuo por Helm hooks

Causa raiz

O chart oficial do Airflow cria airflow-fernet-key e airflow-broker-url como Helm pre-install hooks (helm.sh/hook: pre-install). O ArgoCD não inclui hooks no “desired state” regular — por isso esses secrets aparecem como requiresPruning: true.

A cada sync:

  1. ArgoCD prune os apaga (são requiresPruning)
  2. O hook pré-sync os recria (possivelmente com valores novos)
  3. Os Deployments têm annotations checksum/fernet-key e checksum/result-backend-secret que mudam junto com os secrets
  4. ArgoCD detecta divergência nas annotations → OutOfSync → loop infinito

Resolução

Adicionar ignoreDifferences para as annotations de checksum nos Deployments em apps/argocd/airflow.yaml:

ignoreDifferences:
- group: apps
kind: Deployment
jqPathExpressions:
- .spec.template.metadata.annotations["checksum/airflow-config"]
- .spec.template.metadata.annotations["checksum/extra-configmaps"]
- .spec.template.metadata.annotations["checksum/extra-secrets"]
- .spec.template.metadata.annotations["checksum/jwt-secret"]
- .spec.template.metadata.annotations["checksum/metadata-secret"]
- .spec.template.metadata.annotations["checksum/pgbouncer-config-secret"]
- .spec.template.metadata.annotations["checksum/result-backend-secret"]

Requer RespectIgnoreDifferences=true em syncOptions (já estava configurado).

Lição

Charts Helm que usam hooks para criar secrets causam drift perpétuo no ArgoCD. O padrão de ignoreDifferences nas annotations de checksum é a solução oficial recomendada pelo Apache Airflow para deploy via ArgoCD.


Estado final

Após as correções, todos os 17 apps no ArgoCD CPPS: Synced + Healthy.