feat: AddonClaim + InitContainer wait-for-secret en chart helm
Some checks failed
__APP_NAME__ — build & deploy / Calidad + build + push (push) Failing after 1m19s
__APP_NAME__ — build & deploy / Helm upgrade --install (push) Has been skipped

Cierra GOLDEN-PATH-ADDONCLAIM-MISSING (P0 hands-off pipeline).

Cambios:
- helm/APP_NAME/templates/addonclaim.yaml (NEW): hook pre-install/pre-upgrade
  weight -10, SIN delete-policy (sobrevive upgrades, evita disparar finalizer
  cleanup-logical). Gated por .Values.database.enabled (default true).
- helm/APP_NAME/templates/role-db-secret-reader.yaml (NEW): Role + RoleBinding
  weight -7 que permite al SA leer el Secret <release>-db-creds. Lo consume
  el InitContainer wait-for-secret del Job Alembic.
- helm/APP_NAME/templates/job-alembic-upgrade.yaml: gated por
  database.enabled, anade InitContainer wait-for-secret (48x5s=240s) que
  espera a que addon-provisioner publique el Secret tras reconciliar el
  AddonClaim. Absorbe la latencia del @kopf.timer (RECONCILE_INTERVAL 300s
  + initial_delay 60s) sin tocar el operator.
- helm/APP_NAME/values.yaml: bloque database completo (enabled, addonType,
  databaseName, user.permissions, deletionPolicy Retain). Opt-out con
  database.enabled=false + alembic.enabled=false.
- .gitea/workflows/APP_NAME-build.yaml: helm timeout 5m -> 7m (margen para
  reconcile addon-provisioner + StatefulSet ready + DDL Job + Alembic).
- claim-mariadb.yaml (DELETED): obsoleto - estaba en la raiz del template
  y nunca se renderizaba por helm. Reemplazado por addonclaim.yaml dentro
  del chart helm.
- README.md: documenta nueva tabla de hook weights, opt-out database, y
  trade-off del orphan AddonClaim post-uninstall (cleanup explicito).

Decision arquitectural: Opcion D modificada (Opus 4.6 consult).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
ilopez
2026-05-25 07:56:42 -05:00
parent 1f2eb2f908
commit 5a215ed293
7 changed files with 208 additions and 43 deletions

View File

@@ -1,8 +1,17 @@
{{- if .Values.alembic.enabled }}
{{- if and .Values.alembic.enabled .Values.database.enabled }}
# Job pre-upgrade que corre Alembic antes de rolar el Deployment.
# - backoffLimit: 0 si falla, NO reintenta; Helm aborta el upgrade.
# - hook-delete-policy hook-succeeded solo se borra al éxito; en fallo queda
# visible para `kubectl logs job/...` y diagnóstico.
# - backoffLimit: 0 -> si falla, NO reintenta; Helm aborta el upgrade.
# - hook-delete-policy hook-succeeded -> solo se borra al exito; en fallo queda
# visible para `kubectl logs job/...` y diagnostico.
#
# InitContainer `wait-for-secret`:
# - El AddonClaim (hook weight -10) ya fue aplicado, pero addon-provisioner es
# timer-based (RECONCILE_INTERVAL 300s, initial_delay 60s) y materializa el
# Secret `{{ .Values.database.secretName }}` de forma asincrona.
# - El InitContainer hace busy-wait hasta que el Secret existe — absorbe la
# latencia del reconcile dentro del pod, sin tocar el operator ni
# bajar RECONCILE_INTERVAL global.
# - Loop: 48 iteraciones x 5s = 240s — encaja dentro del helm timeout 7m.
apiVersion: batch/v1
kind: Job
metadata:
@@ -28,6 +37,42 @@ spec:
securityContext:
runAsNonRoot: true
runAsUser: 1001
initContainers:
- name: wait-for-secret
image: bitnami/kubectl:1.30
command:
- sh
- -c
- |
set -eu
# Espera hasta que addon-provisioner publique el Secret tras
# reconciliar el AddonClaim (hook weight -10). 48 x 5s = 240s.
SECRET="{{ .Values.database.secretName }}"
i=1
while [ "$i" -le 48 ]; do
if kubectl get secret "$SECRET" -n "$NAMESPACE" >/dev/null 2>&1; then
echo "Secret $SECRET encontrado en intento $i"
exit 0
fi
echo "[$i/48] Secret $SECRET aun no publicado, esperando 5s..."
sleep 5
i=$((i + 1))
done
echo "ERROR: Secret $SECRET no aparecio tras 240s — addon-provisioner stuck?"
exit 1
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
resources:
requests: {cpu: 10m, memory: 32Mi}
limits: {cpu: 100m, memory: 64Mi}
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
containers:
- name: alembic
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
@@ -37,7 +82,7 @@ spec:
name: {{ .Values.database.secretName }}
env:
# Si el Secret expone MIGRATION_DATABASE_URL, lo usa; si no, env.py
# cae a DATABASE_URL automáticamente (ver settings.migration_url).
# cae a DATABASE_URL automaticamente (ver settings.migration_url).
- name: DATABASE_URL
valueFrom:
secretKeyRef: