From 929a4cefee2d5b6b6b1ee9ed7d98661941e389b9 Mon Sep 17 00:00:00 2001 From: Irving Lopez Date: Sun, 14 Jun 2026 17:01:56 -0500 Subject: [PATCH] =?UTF-8?q?fix(ci):=20guard=20concurrency=20+=20recuperaci?= =?UTF-8?q?=C3=B3n=20de=20release=20pending-install=20en=20helm-deploy=20(?= =?UTF-8?q?TENANT-BUILD-NO-CONCURRENCY-GUARD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sin guard `concurrency:` múltiples dispatches del repo del tenant corrían helm upgrade en paralelo sobre el mismo release → 'another operation in progress' → release trabado en pending-install → primer deploy nunca completa (vivido E2E 2026-06-10, tenant 9e6babb0). Causa raíz del first-deploy flaky (runs 3418/3419). - concurrency workflow-level (group por repo, cancel-in-progress:false: encola, no cancela — cancelar a mitad de helm install es lo que CREA el pending-install) - step de recuperación: detecta pending-install/uninstalling→uninstall, pending-upgrade/pending-rollback→rollback, antes del helm upgrade. Fail-safe: solo toca estados pending-*, no toca deployed/failed/inexistente. Co-Authored-By: Claude Opus 4.8 (1M context) Además: añadido el wait de rancher-webhook (RANCHER-WEBHOOK-RACE-RECOVERY) que faltaba en rest-api — paridad con el template web-backend. --- .gitea/workflows/APP_NAME-build.yaml | 68 ++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/.gitea/workflows/APP_NAME-build.yaml b/.gitea/workflows/APP_NAME-build.yaml index d720f59..3c31004 100644 --- a/.gitea/workflows/APP_NAME-build.yaml +++ b/.gitea/workflows/APP_NAME-build.yaml @@ -16,6 +16,19 @@ on: - '.gitea/workflows/__APP_NAME__-build.yaml' workflow_dispatch: {} +# ── Serialización de deploys (TENANT-BUILD-NO-CONCURRENCY-GUARD) ────────────── +# Sin este guard, múltiples dispatches del mismo repo (scaffold `on:push` + +# tenant-manager fase 1/fase 2 + re-dispatch por rotación de kubeconfig + smoke) +# corren `helm upgrade --install` EN PARALELO sobre el MISMO release → Helm aborta +# con "another operation (install/upgrade/rollback) is in progress" y deja el +# release trabado en `pending-install` → el primer deploy del tenant nunca +# completa (vivido E2E 2026-06-10, tenant 9e6babb0; destrabado a mano borrando el +# secret del release). Los runs se ENCOLAN, NO se cancelan: cancelar un run a mitad +# de `helm install` es justo lo que CREA el estado `pending-install`. +concurrency: + group: deploy-${{ gitea.repository }} + cancel-in-progress: false + jobs: # ── Gate de credenciales de build (GOLDEN-PATH-BUILD-DEPLOY-ORDER-RACE) ─── # Los secrets HARBOR_ROBOT_USER/TOKEN los siembra tenant-manager en su fase 1 @@ -180,6 +193,61 @@ jobs: echo "$KUBECONFIG_B64" | base64 -d > "${HOME}/.kube/config" chmod 600 "${HOME}/.kube/config" + - name: Esperar rancher-webhook ready (RANCHER-WEBHOOK-RACE-RECOVERY) + # Race condition durante el bootstrap del cluster tenant: el + # `rancher-webhook` Deployment en cattle-system aún no tiene endpoints + # cuando el primer `helm upgrade --install` intenta crear el namespace + # `apps`, lo que dispara el webhook + # `rancher.cattle.io.namespaces.create-non-kubesystem` y aborta el + # release con `no endpoints available for service rancher-webhook`. + # Paridad con el template web-backend (este paso faltaba en rest-api). + run: | + # `kubectl wait` falla con NotFound si el Deployment aún no existe: + # durante el bootstrap del cluster tenant, `rancher-webhook` puede no + # haberse creado todavía cuando corre este step. Primero esperamos a + # que el objeto APAREZCA (poll), luego a que esté Available. + until kubectl get deployment rancher-webhook -n cattle-system >/dev/null 2>&1; do + echo "Esperando a que el Deployment rancher-webhook exista en cattle-system..." + sleep 5 + done + kubectl wait deployment rancher-webhook \ + -n cattle-system \ + --for=condition=Available \ + --timeout=600s + + - name: Recuperar release Helm atascado (TENANT-BUILD-NO-CONCURRENCY-GUARD) + # Red de seguridad ADEMÁS del guard `concurrency`: si un run previo quedó a + # mitad de una operación Helm (cancelado, runner reiniciado, o carrera + # histórica anterior a este fix), el release queda en un estado `pending-*` + # y el siguiente `helm upgrade --install` aborta. Lo detectamos y recuperamos + # antes de desplegar. + # Fail-safe: SOLO actúa sobre estados `pending-*`/`uninstalling`; un release + # `deployed`, `failed` o inexistente NO se toca. Si el parseo no encuentra + # STATUS (formato inesperado) cae al branch no-op → comportamiento idéntico + # al actual, nunca peor. + run: | + STATUS="$(helm status __APP_NAME__ -n apps 2>/dev/null | sed -n 's/^STATUS: //p')" + echo "Estado actual del release __APP_NAME__: ${STATUS:-}" + case "$STATUS" in + pending-install|uninstalling) + # Nunca hubo una revisión desplegada con éxito → no se puede rollback. + # `helm uninstall` limpia el secret del release trabado (equivale al + # destrabado manual del 2026-06-10) sin riesgo de datos: el primer deploy + # aún no levantó la app. + echo "Release en '${STATUS}' (sin revisión estable) → helm uninstall para limpiar." + helm uninstall __APP_NAME__ -n apps --wait --timeout 2m || true + ;; + pending-upgrade|pending-rollback) + # Hubo una revisión estable previa → rollback a la última desplegada + # (preserva la app viva; el upgrade siguiente reintenta la nueva imagen). + echo "Release en '${STATUS}' → helm rollback a la última revisión estable." + helm rollback __APP_NAME__ 0 -n apps --wait --timeout 2m || true + ;; + *) + echo "Release en estado estable o inexistente → sin recuperación necesaria." + ;; + esac + - name: Helm upgrade --install env: TENANT_ID: ${{ vars.TENANT_ID }}