La canalización de la lección anterior deja api-reservas en producción con un artefacto verificado, firmado y probado. Y sin embargo, en el momento en que Argo CD sincroniza el nuevo digest, ocurre algo que ninguna prueba previa puede evitar del todo: todos los usuarios pasan a la versión nueva. Si algo se escapó —una consulta que degrada bajo carga real, una condición de carrera que solo aparece con mil peticiones por segundo, una integración con la pasarela de pagos que falla con datos reales—, lo descubren todos a la vez.

Esta lección trata de eliminar ese salto. Veremos cuatro maneras de reducir el riesgo de publicar una versión: la actualización rodante que ya conocemos, el despliegue azul-verde, el canario y las banderas de funcionalidad. Y al final aplicaremos lo aprendido al caso concreto que tiene delante el equipo de Rutas Norte: publicar la nueva versión de api-reservas la semana antes del puente de mayo, cuando el tráfico se multiplica por diez durante tres días y un fallo cuesta ventas reales.

Contenido

  1. Por qué el RollingUpdate no siempre basta
  2. Azul-verde: dos versiones, un interruptor
  3. Migraciones de base de datos compatibles con ambas versiones
  4. Canario: liberar a un porcentaje de usuarios
  5. Argo Rollouts: automatizar el canario con análisis
  6. Flagger como alternativa
  7. Banderas de funcionalidad: separar el despliegue de la activación
  8. El plan de Rutas Norte para el puente de mayo
  9. Comparativa de las cuatro estrategias

  1. Por qué el RollingUpdate no siempre basta

En 02-04 vimos el RollingUpdate y con la configuración de 11-01 (maxSurge: 25%, maxUnavailable: 0) funciona bien: sin corte de servicio, sin pérdida de capacidad. Sus límites no están en la disponibilidad, sino en el control sobre el riesgo.

Limitación Consecuencia práctica en Rutas Norte
Mezcla versiones durante el despliegue Con 40 réplicas y un despliegue de 8 minutos, un usuario puede ver la versión antigua en una petición y la nueva en la siguiente. Si el contrato de la API cambió, la SPA se rompe a mitad de una compra
No permite validar antes de comprometerse Cuando notas el problema, la versión nueva ya atiende a una parte grande de los usuarios
La reversión no es instantánea rollout undo vuelve a recorrer 40 pods: otros 6-8 minutos con usuarios afectados
El criterio de avance es la sonda de preparación Un pod que responde 200 en /preparado pero devuelve 500 en el 30 % de las compras avanza igualmente
No hay ningún análisis automático Nadie mira Grafana durante el despliegue de un martes a las 11 de la mañana

El punto cuarto es el central. La sonda de preparación responde a "¿el proceso puede atender?", no a "¿esta versión funciona bien?". Son preguntas muy distintas, y solo la segunda importa para decidir si seguir adelante.

graph LR
  subgraph RU["RollingUpdate"]
    direction TB
    A1[100% v1] --> A2[75% v1 · 25% v2] --> A3[50/50] --> A4[100% v2]
    A5[Criterio de avance:<br/>readinessProbe] -.-> A3
  end
  subgraph BG["Azul-verde"]
    direction TB
    B1[100% azul] --> B2[verde desplegado<br/>sin tráfico real] --> B3[100% verde<br/>cambio instantáneo]
    B4[Criterio: validación<br/>manual o automática] -.-> B3
  end
  subgraph CN["Canario"]
    direction TB
    C1[100% v1] --> C2[95% v1 · 5% v2] --> C3[75/25] --> C4[100% v2]
    C5[Criterio: análisis de<br/>métricas reales] -.-> C3
  end

  1. Azul-verde: dos versiones, un interruptor

La idea es simple: dos entornos completos coexistiendo, uno recibiendo tráfico y otro no. El Service apunta a uno de los dos mediante una etiqueta, y publicar es cambiar esa etiqueta.

2.1. Los manifiestos

# Deployment AZUL: la versión que está sirviendo hoy.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-reservas-azul
  namespace: rutas-norte-pro
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: api-reservas
      rutasnorte.example/color: azul
  template:
    metadata:
      labels:
        app.kubernetes.io/name: api-reservas
        rutasnorte.example/color: azul     # ← la etiqueta que decide todo
        rutasnorte.example/version: "2.7.0"
    spec:
      # ... idéntico al Deployment de 11-01
      containers:
        - name: api
          image: registry.rutasnorte.example/rutasnorte/api-reservas@sha256:3f9c1b7e5a04c2d8f61b93ae7c05d2419e8f6ab3c4d5e6f708192a3b4c5d6e7f
---
# Deployment VERDE: la versión candidata.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-reservas-verde
  namespace: rutas-norte-pro
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: api-reservas
      rutasnorte.example/color: verde
  template:
    metadata:
      labels:
        app.kubernetes.io/name: api-reservas
        rutasnorte.example/color: verde
        rutasnorte.example/version: "2.8.0"
    spec:
      containers:
        - name: api
          image: registry.rutasnorte.example/rutasnorte/api-reservas@sha256:9a8b7c6d5e4f30291a8b7c6d5e4f30291a8b7c6d5e4f30291a8b7c6d5e4f3029
---
# Service PRODUCTIVO: el que usa el Ingress. Su selector es el interruptor.
apiVersion: v1
kind: Service
metadata:
  name: api-reservas
  namespace: rutas-norte-pro
spec:
  selector:
    app.kubernetes.io/name: api-reservas
    rutasnorte.example/color: azul       # ← cambiar aquí = publicar
  ports:
    - { name: http, port: 80, targetPort: http }
---
# Service de PREVISUALIZACIÓN: apunta siempre a verde, sin tráfico de usuarios.
apiVersion: v1
kind: Service
metadata:
  name: api-reservas-preview
  namespace: rutas-norte-pro
spec:
  selector:
    app.kubernetes.io/name: api-reservas
    rutasnorte.example/color: verde
  ports:
    - { name: http, port: 80, targetPort: http }
---
# Ingress de previsualización: URL interna para validar la candidata.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-reservas-preview
  namespace: rutas-norte-pro
  annotations:
    # Solo desde la red corporativa: no es una URL pública.
    nginx.ingress.kubernetes.io/whitelist-source-range: "203.0.113.0/24"
    cert-manager.io/cluster-issuer: letsencrypt-produccion
spec:
  ingressClassName: nginx
  tls:
    - hosts: ["api-preview.rutasnorte.example"]
      secretName: api-preview-tls
  rules:
    - host: api-preview.rutasnorte.example
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service: { name: api-reservas-preview, port: { name: http } }

Cada Deployment lleva su propio HPA, su PDB y su ServiceMonitor con la etiqueta de color, para poder observar ambas versiones por separado en Grafana.

2.2. El procedimiento completo

# --- 1. Desplegar la candidata en verde (sin tráfico de usuarios) ---
kubectl -n rutas-norte-pro set image deploy/api-reservas-verde \
  api=registry.rutasnorte.example/rutasnorte/api-reservas@sha256:9a8b7c6d...
kubectl -n rutas-norte-pro rollout status deploy/api-reservas-verde --timeout=10m
kubectl -n rutas-norte-pro scale deploy/api-reservas-verde --replicas=8

# --- 2. Validar sin tráfico real ---
# Pruebas de humo contra la URL de previsualización
npm run pruebas:humo -- --base-url https://api-preview.rutasnorte.example
# Prueba de carga suave para descartar regresiones evidentes de rendimiento
k6 run --vus 40 --duration 5m pruebas/carga-preview.js
# Comprobación manual del flujo de compra completo por parte de producto

# --- 3. Conmutar (esto es la publicación: dura milisegundos) ---
kubectl -n rutas-norte-pro patch svc api-reservas \
  -p '{"spec":{"selector":{"rutasnorte.example/color":"verde"}}}'

# --- 4. Vigilar ---
watch -n5 'curl -sG http://prometheus.rutas-norte.example/api/v1/query \
  --data-urlencode "query=sum(rate(api_reservas_peticiones_total{codigo=~\"5..\"}[2m]))
                          / sum(rate(api_reservas_peticiones_total[2m]))" | jq -r ".data.result[0].value[1]"'

# --- 5. Revertir, si hace falta (segundos) ---
kubectl -n rutas-norte-pro patch svc api-reservas \
  -p '{"spec":{"selector":{"rutasnorte.example/color":"azul"}}}'

El paso 5 es el argumento de venta del azul-verde: la reversión es un cambio de etiqueta. La versión anterior sigue viva, caliente, con sus pods listos y sus conexiones a base de datos abiertas. Se vuelve atrás en menos de cinco segundos, y no en los ocho minutos de un rollout undo.

2.3. Lo que cuesta

Coste Detalle en Rutas Norte
Recursos Durante la ventana de validación hay doble capacidad desplegada: 8 réplicas azules + 8 verdes. En el pico del puente de mayo serían 40 + 40, algo inasumible
Conexiones en curso Al conmutar, las conexiones abiertas contra pods azules siguen ahí hasta que se cierran. No es un corte, pero sí una transición de unos segundos
Estado compartido Ambas versiones hablan con la misma base de datos y la misma caché. Este es el problema serio, y va en el apartado siguiente
Complejidad de manifiestos Todo duplicado: Deployment, HPA, PDB, ServiceMonitor. Con Kustomize o Helm se maneja, pero es más superficie
Todo o nada Se conmuta al 100 %. Si el fallo solo se manifiesta con carga real, lo sufren todos los usuarios durante los segundos que tardas en revertir

La regla de Rutas Norte: azul-verde para cambios grandes y arriesgados fuera de temporada alta; nunca durante el puente de mayo, porque duplicar 40 réplicas no es viable.

  1. Migraciones de base de datos compatibles con ambas versiones

Aquí está el problema real del azul-verde, y también del canario y del RollingUpdate. Se pueden tener dos versiones de código a la vez. No se pueden tener dos versiones del esquema de la base de datos.

Si api-reservas 2.8.0 necesita que la columna asiento se llame plaza, y la migración se aplica al conmutar, la versión azul deja de funcionar en ese instante y la reversión se vuelve imposible.

La solución es el patrón expandir y contraer: toda migración se divide en pasos, cada uno compatible con la versión anterior y la siguiente.

graph LR
  V1[v2.7.0<br/>usa 'asiento'] --> E[Despliegue 1: EXPANDIR<br/>añadir columna 'plaza'<br/>+ disparador que sincroniza]
  E --> V2[Despliegue 2<br/>v2.8.0 escribe en ambas<br/>lee de 'plaza']
  V2 --> V3[Despliegue 3<br/>v2.9.0 solo usa 'plaza']
  V3 --> C[Despliegue 4: CONTRAER<br/>eliminar 'asiento'<br/>y el disparador]

3.1. El ejemplo completo

-- ===== Migración 1: EXPANDIR (compatible con 2.7.0) =====
-- Añadir, nunca renombrar ni borrar.
ALTER TABLE reservas ADD COLUMN plaza integer;

-- Copiar el histórico en lotes, sin bloquear la tabla.
UPDATE reservas SET plaza = asiento
 WHERE plaza IS NULL AND id IN (SELECT id FROM reservas WHERE plaza IS NULL LIMIT 5000);
-- (se repite hasta agotar; en producción, mediante un Job por lotes)

-- Disparador que mantiene ambas columnas sincronizadas mientras
-- conviven versiones que escriben en una o en otra.
CREATE OR REPLACE FUNCTION sincronizar_plaza() RETURNS trigger AS $$
BEGIN
  IF NEW.plaza IS NULL THEN NEW.plaza := NEW.asiento; END IF;
  IF NEW.asiento IS NULL THEN NEW.asiento := NEW.plaza; END IF;
  RETURN NEW;
END; $$ LANGUAGE plpgsql;

CREATE TRIGGER trg_sincronizar_plaza
  BEFORE INSERT OR UPDATE ON reservas
  FOR EACH ROW EXECUTE FUNCTION sincronizar_plaza();
-- ===== Migración 2: CONTRAER (solo cuando NADIE usa 'asiento') =====
-- Semanas después, con la versión antigua ya retirada del todo.
DROP TRIGGER trg_sincronizar_plaza ON reservas;
DROP FUNCTION sincronizar_plaza();
ALTER TABLE reservas DROP COLUMN asiento;
ALTER TABLE reservas ALTER COLUMN plaza SET NOT NULL;

3.2. Reglas prácticas

Cambio deseado Cómo hacerlo compatible
Renombrar columna Añadir la nueva, sincronizar con disparador, migrar código, borrar la antigua después
Borrar columna Dejar de usarla en el código, esperar dos publicaciones, borrarla
Añadir columna obligatoria Añadirla anulable con valor por defecto, rellenar, poner NOT NULL en un paso posterior
Cambiar el tipo de una columna Columna nueva con el tipo nuevo, doble escritura, migrar lecturas, borrar la antigua
Añadir índice CREATE INDEX CONCURRENTLY para no bloquear la tabla
Cambiar el significado de un valor Valor nuevo distinto; nunca reinterpretar uno existente

La regla que resume todo: el esquema debe ir siempre un paso por delante del código y nunca romper la versión anterior. Cada migración se aplica en su propio despliegue, antes que el código que la aprovecha. Es lo que en 11-03 obligaba a declarar en la petición de cambio de promoción si la migración era compatible hacia atrás.

  1. Canario: liberar a un porcentaje de usuarios

El canario resuelve lo que el azul-verde no: en lugar de conmutar al 100 %, se expone la versión nueva a una fracción pequeña de usuarios, se observan las métricas reales y se aumenta progresivamente.

4.1. La forma pobre: por número de réplicas

Sin nada más que un Service y dos Deployments, el reparto de tráfico es proporcional al número de pods.

# 19 réplicas estables + 1 canaria ≈ 5 % del tráfico
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-reservas-canario
  namespace: rutas-norte-pro
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: api-reservas
      rutasnorte.example/pista: canario
  template:
    metadata:
      labels:
        app.kubernetes.io/name: api-reservas    # el Service selecciona por esta
        rutasnorte.example/pista: canario

El Service productivo selecciona solo por app.kubernetes.io/name, así que envía tráfico a ambos Deployments repartido entre los 20 pods.

Funciona, y en un clúster sin controlador de Ingress capaz de repartir por peso es una opción razonable. Sus límites:

  • La granularidad depende del número de réplicas. Con 4 réplicas, el mínimo es el 20 %. Bajar al 5 % exigiría 19 réplicas estables.
  • El HPA interfiere. Si escala el Deployment estable, el porcentaje del canario cambia solo, sin que nadie lo decida.
  • No hay pegajosidad. Un usuario alterna entre versiones petición a petición, lo que en una compra de billetes es un problema.
  • No se puede segmentar. No se puede decir "solo los empleados de Rutas Norte" o "solo quien tenga esta cabecera".

4.2. La forma buena: pesos en ingress-nginx

ingress-nginx permite declarar un Ingress canario que comparte host con el principal y captura un porcentaje del tráfico.

# Ingress principal: sin cambios respecto a 11-01.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-reservas
  namespace: rutas-norte-pro
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-produccion
spec:
  ingressClassName: nginx
  tls:
    - hosts: ["api.rutasnorte.example"]
      secretName: api-rutasnorte-tls
  rules:
    - host: api.rutasnorte.example
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service: { name: api-reservas-estable, port: { name: http } }
---
# Ingress CANARIO: mismo host, backend distinto, con peso.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-reservas-canario
  namespace: rutas-norte-pro
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-produccion
    # Activa el modo canario para este Ingress.
    nginx.ingress.kubernetes.io/canary: "true"
    # Porcentaje del tráfico que va al canario.
    nginx.ingress.kubernetes.io/canary-weight: "5"
    # Cabecera de escape: fuerza el canario para pruebas internas,
    # independientemente del peso.
    nginx.ingress.kubernetes.io/canary-by-header: "X-Rutasnorte-Canario"
    nginx.ingress.kubernetes.io/canary-by-header-value: "siempre"
    # Cookie: una vez asignado, el usuario se queda en su versión.
    nginx.ingress.kubernetes.io/canary-by-cookie: "rutasnorte_canario"
spec:
  ingressClassName: nginx
  tls:
    - hosts: ["api.rutasnorte.example"]
      secretName: api-rutasnorte-tls
  rules:
    - host: api.rutasnorte.example          # ← MISMO host que el principal
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service: { name: api-reservas-canario, port: { name: http } }

El orden de precedencia de las anotaciones es importante y no siempre evidente:

  1. canary-by-header con el valor exacto → siempre al canario (o never → nunca).
  2. canary-by-cookie → según el valor de la cookie.
  3. canary-weight → decisión aleatoria por el porcentaje indicado.

La cabecera es lo que permite validar en producción sin exponer a nadie:

# Validación interna dirigida al canario, con 0 % de peso
curl -H 'X-Rutasnorte-Canario: siempre' https://api.rutasnorte.example/horarios?linea=BIL-SAN

Progresión manual del peso:

for PESO in 5 10 25 50 100; do
  kubectl -n rutas-norte-pro annotate ingress api-reservas-canario \
    nginx.ingress.kubernetes.io/canary-weight="$PESO" --overwrite
  echo "Peso al $PESO %. Observando 10 minutos..."
  sleep 600
  # aquí alguien mira Grafana y decide si sigue
done

Y este bucle, con su sleep 600 y su "aquí alguien mira Grafana", es exactamente lo que hay que automatizar.

  1. Argo Rollouts: automatizar el canario con análisis

Argo Rollouts sustituye el Deployment por un recurso Rollout que sabe progresar por pasos, consultar métricas y decidir por sí mismo si continuar o abortar.

sequenceDiagram
  participant G as Git (nuevo digest)
  participant R as Controlador Rollout
  participant N as ingress-nginx
  participant P as Prometheus
  G->>R: Cambia la imagen del Rollout
  R->>R: Crea ReplicaSet canario
  R->>N: setWeight 5 %
  R->>P: AnalysisRun (tasa de error, p95)
  P-->>R: 0,04 % errores · p95 210 ms → correcto
  R->>N: setWeight 25 %
  R->>P: AnalysisRun
  P-->>R: 0,9 % errores → FALLO
  R->>N: setWeight 0 % (aborta)
  R->>R: Escala el canario a cero, la estable sigue intacta

5.1. El recurso Rollout

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: api-reservas
  namespace: rutas-norte-pro
spec:
  # replicas lo sigue gobernando el HPA, que ahora apunta al Rollout.
  revisionHistoryLimit: 5
  selector:
    matchLabels:
      app.kubernetes.io/name: api-reservas
  # 'template' es idéntico al del Deployment de 11-01: mismas sondas,
  # mismo securityContext, mismos recursos, mismo topologySpread.
  template:
    metadata:
      labels:
        app.kubernetes.io/name: api-reservas
    spec:
      serviceAccountName: api-reservas
      containers:
        - name: api
          image: registry.rutasnorte.example/rutasnorte/api-reservas@sha256:9a8b7c6d5e4f30291a8b7c6d5e4f30291a8b7c6d5e4f30291a8b7c6d5e4f3029
          # ... resto idéntico a 11-01

  strategy:
    canary:
      canaryService: api-reservas-canario     # Services que el controlador
      stableService: api-reservas-estable     # reetiqueta automáticamente
      trafficRouting:
        nginx:
          stableIngress: api-reservas          # crea y gestiona el Ingress canario
      # Análisis que se ejecuta en paralelo a TODA la progresión.
      analysis:
        templates:
          - templateName: analisis-api-reservas
        startingStep: 1        # empieza tras alcanzar el primer peso
        args:
          - name: servicio-canario
            value: api-reservas-canario
      steps:
        - setWeight: 5
        - pause: { duration: 10m }     # 10 min con el 5 % del tráfico
        - setWeight: 25
        - pause: { duration: 15m }
        - setWeight: 50
        - pause: { duration: 20m }
        - setWeight: 75
        - pause: { duration: 10m }
        # Sin duración: espera aprobación humana explícita antes del 100 %.
        - pause: {}
      # Ventanas de seguridad
      scaleDownDelaySeconds: 600   # la versión anterior sigue viva 10 min tras promocionar
      abortScaleDownDelaySeconds: 30
      maxSurge: "25%"
      maxUnavailable: 0

El último pause: {} sin duración es una decisión deliberada de Rutas Norte: el análisis automático puede llevar el canario hasta el 75 %, pero el salto final al 100 % lo confirma una persona. Es un equilibrio entre automatizar la vigilancia y conservar un punto de control humano.

5.2. El AnalysisTemplate contra Prometheus

Aquí se decide de verdad. Las consultas son PromQL sobre las métricas que api-reservas expone desde 07-03.

apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: analisis-api-reservas
  namespace: rutas-norte-pro
spec:
  args:
    - name: servicio-canario
  metrics:

    # --- 1. Tasa de error 5xx ---
    - name: tasa-error
      interval: 1m
      # Tolera 2 medidas malas aisladas; 3 consecutivas abortan.
      failureLimit: 2
      consecutiveErrorLimit: 3
      # No juzga durante los primeros 2 min: los pods se están calentando.
      initialDelay: 2m
      provider:
        prometheus:
          address: http://prometheus-operated.monitorizacion:9090
          query: |
            sum(rate(api_reservas_peticiones_total{
              service="{{args.servicio-canario}}", codigo=~"5.."
            }[2m]))
            /
            sum(rate(api_reservas_peticiones_total{
              service="{{args.servicio-canario}}"
            }[2m]))
      # Éxito si el resultado es < 0,5 %. NaN (sin tráfico) también se acepta:
      # sin peticiones no hay evidencia de fallo.
      successCondition: result[0] < 0.005 || isNaN(result[0])

    # --- 2. Percentil 95 de latencia ---
    - name: latencia-p95
      interval: 1m
      failureLimit: 2
      initialDelay: 2m
      provider:
        prometheus:
          address: http://prometheus-operated.monitorizacion:9090
          query: |
            histogram_quantile(0.95,
              sum by (le) (rate(api_reservas_duracion_segundos_bucket{
                service="{{args.servicio-canario}}"
              }[2m]))
            )
      successCondition: result[0] < 0.35 || isNaN(result[0])   # 350 ms

    # --- 3. Comparación relativa contra la versión estable ---
    # Un umbral absoluto puede pasar por alto una regresión si el sistema
    # ya iba lento. Esta métrica exige que el canario no sea más de un 20 %
    # peor que la estable.
    - name: latencia-relativa
      interval: 2m
      failureLimit: 1
      initialDelay: 5m
      provider:
        prometheus:
          address: http://prometheus-operated.monitorizacion:9090
          query: |
            (
              histogram_quantile(0.95, sum by (le) (rate(
                api_reservas_duracion_segundos_bucket{service="api-reservas-canario"}[3m])))
              /
              histogram_quantile(0.95, sum by (le) (rate(
                api_reservas_duracion_segundos_bucket{service="api-reservas-estable"}[3m])))
            )
      successCondition: result[0] < 1.2 || isNaN(result[0])

    # --- 4. Métrica de negocio: la que de verdad importa ---
    # Una versión puede tener 0 errores y latencia perfecta, y haber roto
    # el botón de "confirmar reserva". Esto lo detecta.
    - name: tasa-conversion
      interval: 5m
      failureLimit: 1
      initialDelay: 10m
      provider:
        prometheus:
          address: http://prometheus-operated.monitorizacion:9090
          query: |
            sum(rate(api_reservas_reservas_confirmadas_total{
              service="api-reservas-canario"}[5m]))
            /
            sum(rate(api_reservas_reservas_iniciadas_total{
              service="api-reservas-canario"}[5m]))
      successCondition: result[0] > 0.55 || isNaN(result[0])

La cuarta métrica es la más valiosa y la que más equipos olvidan. Un despliegue puede ser impecable en todos los indicadores técnicos y estar perdiendo la mitad de las ventas.

5.3. Promoción y abortado automáticos

Promoción automática: si todas las métricas cumplen su condición durante todos los pasos, el Rollout avanza solo por la escalera de pesos hasta el último pause: {}.

Abortado automático: en cuanto una métrica supera su failureLimit, el controlador pone el peso del canario a 0 inmediatamente, escala el ReplicaSet canario a cero y deja intacto el estable. No hay que revertir nada, porque la versión estable nunca dejó de servir la mayoría del tráfico.

kubectl argo rollouts get rollout api-reservas -n rutas-norte-pro --watch
Name:            api-reservas
Namespace:       rutas-norte-pro
Status:          ✖ Degraded
Message:         RolloutAborted: metric "tasa-error" assessed Failed
Strategy:        Canary
  Step:          2/9
  SetWeight:     0
  ActualWeight:  0
Images:          api-reservas@sha256:3f9c1b7e (stable)
                 api-reservas@sha256:9a8b7c6d (canary)
Replicas:
  Desired:       8
  Current:       8
  Updated:       0
  Ready:         8
  Available:     8

NAME                                     KIND         STATUS        AGE   INFO
⟳ api-reservas                           Rollout      ✖ Degraded    41d
├──# revision:18
│  └──⧉ api-reservas-6d9f7b4c8            ReplicaSet   • ScaledDown  14m   canary
│     └──⊞ analisis-api-reservas-18       AnalysisRun  ✖ Failed      12m
│        ├──📊 tasa-error                 Measurement  ✖ Failed            0.021
│        └──📊 latencia-p95               Measurement  ✔ Successful        0.198
└──# revision:17
   └──⧉ api-reservas-7d4b8c9f5            ReplicaSet   ✔ Healthy     14d   stable

Esa salida es diagnóstico completo por sí sola: se abortó en el paso 2, por tasa-error al 2,1 % frente al umbral del 0,5 %, con la latencia perfectamente bien. El siguiente sitio donde mirar son los logs de los pods canarios antes de que desaparezcan (scaleDownDelaySeconds los mantiene un rato justo para eso).

Comandos de control manual:

kubectl argo rollouts promote api-reservas -n rutas-norte-pro        # avanzar un paso
kubectl argo rollouts promote api-reservas -n rutas-norte-pro --full # saltar al 100 %
kubectl argo rollouts abort api-reservas -n rutas-norte-pro          # abortar ya
kubectl argo rollouts undo api-reservas -n rutas-norte-pro --to-revision=17

5.4. La interfaz de seguimiento

kubectl argo rollouts dashboard -n rutas-norte-pro

Levanta una interfaz web en localhost:3100 que muestra la escalera de pasos, el peso actual, el resultado de cada medida y los botones de promocionar y abortar. En Rutas Norte se proyecta en una pantalla durante los despliegues de la temporada alta: no es imprescindible, pero convierte una operación opaca en algo que todo el equipo entiende de un vistazo.

5.5. Convivencia con Argo CD

Un Rollout es un CRD, así que Argo CD lo sincroniza como cualquier otro recurso. Dos ajustes necesarios:

# En la Application de Argo CD
spec:
  ignoreDifferences:
    - group: argoproj.io
      kind: Rollout
      jsonPointers:
        - /spec/replicas          # lo gobierna el HPA
  syncPolicy:
    automated:
      selfHeal: true
      # El controlador de Rollouts modifica los Services durante la
      # progresión; sin esto, selfHeal lucharía contra él.
    syncOptions:
      - RespectIgnoreDifferences=true

Y el HPA debe apuntar al Rollout, no a un Deployment:

spec:
  scaleTargetRef:
    apiVersion: argoproj.io/v1alpha1
    kind: Rollout
    name: api-reservas

  1. Flagger como alternativa

Flagger resuelve el mismo problema con una filosofía distinta: en lugar de sustituir el Deployment, lo envuelve. Se mantiene el Deployment de siempre y se añade un recurso Canary que lo gestiona.

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: api-reservas
  namespace: rutas-norte-pro
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment          # ← el Deployment original, sin tocar
    name: api-reservas
  autoscalerRef:
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    name: api-reservas
  provider: nginx
  service:
    port: 80
    targetPort: 8080
  analysis:
    interval: 1m
    threshold: 5              # medidas fallidas antes de abortar
    maxWeight: 50
    stepWeight: 10
    metrics:
      - name: request-success-rate
        thresholdRange: { min: 99.5 }
        interval: 2m
      - name: request-duration
        thresholdRange: { max: 350 }
        interval: 2m
    webhooks:
      - name: pruebas-de-carga
        type: rollout
        url: http://flagger-loadtester.plataforma/
        metadata:
          cmd: "hey -z 2m -q 10 -c 2 http://api-reservas-canary.rutas-norte-pro/horarios"
Aspecto Argo Rollouts Flagger
Recurso de carga Sustituye el Deployment por Rollout Conserva el Deployment y lo envuelve
Migrar desde Deployment Cambiar kind y ajustar HPA y Argo CD Añadir un Canary, sin tocar lo existente
Interfaz gráfica Sí, propia y muy buena No propia; se usa Grafana
CLI dedicada kubectl argo rollouts, muy completa kubectl describe canary
Estrategias Canario, azul-verde, con o sin malla Canario, azul-verde, A/B, espejo de tráfico
Métricas AnalysisTemplate totalmente libre Métricas predefinidas + personalizadas
Generación de carga Externa Integrada (loadtester)
Encaja mejor con Ecosistema Argo (Argo CD, Workflows) Ecosistema Flux
Control manual fino Muy bueno (promote, abort, pasos) Más automático, menos intervención

No hay una respuesta universal. Rutas Norte eligió Argo Rollouts por coherencia con Argo CD (10-05), por la CLI y la interfaz, y por la libertad total del AnalysisTemplate, que hacía falta para la métrica de conversión. Un equipo que use Flux elegiría Flagger con la misma lógica.

  1. Banderas de funcionalidad: separar el despliegue de la activación

Todo lo anterior controla qué versión del código corre. Las banderas de funcionalidad controlan algo distinto: qué comportamiento tiene el código que ya está corriendo.

// api-reservas: la funcionalidad viaja desplegada pero apagada.
app.post('/reservas', async (req, res) => {
  const usarNuevoMotor = await banderas.activa('motor-precios-v2', {
    usuarioId: req.usuario.id,
    porcentaje: 5,                       // despliegue progresivo por usuario
    listaBlanca: ['empleados-rutasnorte']
  });

  const precio = usarNuevoMotor
    ? await motorPreciosV2.calcular(req.body)
    : await motorPreciosV1.calcular(req.body);

  // Métrica etiquetada por bandera: permite comparar ambos caminos
  // en Grafana sin desplegar nada distinto.
  metricas.precioCalculado.inc({ motor: usarNuevoMotor ? 'v2' : 'v1' });
  ...
});
Aspecto Canario Bandera de funcionalidad
Unidad de control La versión completa del artefacto Una funcionalidad concreta
Granularidad del público Porcentaje de peticiones, por cabecera o cookie Por usuario, plan, región, o cualquier atributo
Velocidad de activación Minutos (progresión de pesos) Segundos (cambio de configuración)
Velocidad de desactivación Segundos (abortar) Segundos, y sin tocar el clúster
Quién puede accionarla Plataforma o desarrollo Producto, soporte, negocio
Coste Doble capacidad temporal Complejidad en el código
Deuda que genera Ninguna Ramas muertas si no se limpian

Cuándo la bandera es mejor que el canario:

  • La funcionalidad es de negocio, no técnica: quien decide activarla es producto, no ingeniería.
  • Hay que activar para un segmento concreto (los clientes empresa, una región, los empleados internos), no para un porcentaje aleatorio.
  • El cambio debe poder apagarse en segundos por alguien de soporte a las tres de la mañana, sin desplegar.
  • Se quiere hacer una prueba A/B y medir conversión durante semanas.
  • El cambio es grande y se quiere integrar en main por partes sin publicar nada.

Cuándo el canario es mejor:

  • El cambio es transversal: nueva versión de una biblioteca, cambio de la imagen base, refactorización interna, actualización del runtime. No hay un "si" que poner.
  • El riesgo es de rendimiento o de consumo de recursos, no de comportamiento funcional.
  • No quieres código muerto conviviendo en producción.

Se combinan muy bien: canario para publicar el artefacto con seguridad, banderas para activar la funcionalidad después, con el público que decida producto.

Una advertencia sobre la deuda: cada bandera duplica un camino de código y por tanto duplica lo que hay que probar. La regla de Rutas Norte es que toda bandera nace con fecha de caducidad anotada en el código, y hay una revisión trimestral que elimina las que ya cumplieron su función.

  1. El plan de Rutas Norte para el puente de mayo

Situación real: api-reservas 2.8.0 incluye el nuevo motor de precios dinámicos, que es la funcionalidad con la que la empresa espera aumentar ingresos durante la temporada alta. Hay que publicarla la semana anterior al puente de mayo, el peor momento posible para equivocarse.

8.1. Restricciones

Restricción Implicación
El puente empieza el viernes 1 de mayo Congelación de cambios desde el miércoles 29 a las 18:00
El tráfico se multiplica por diez durante tres días No se puede duplicar capacidad: azul-verde queda descartado
El motor de precios afecta a la conversión Hace falta una métrica de negocio, no solo técnica
Hay migración de esquema (tabla reglas_precio) Debe ser expandir y contraer, y compatible con 2.7.0
Una reversión debe ser inmediata Descartado cualquier cambio irreversible

8.2. El calendario

Momento Acción Criterio para continuar
Lunes 20, 10:00 Despliegue 1: migración de expansión (tablas nuevas, sin uso) Migración aplicada, api-reservas 2.7.0 sin cambios en sus métricas
Lunes 20, 16:00 Despliegue 2: Rollout de 2.8.0 con el motor v2 apagado por bandera Análisis del canario correcto en todos los pasos
Martes 21 2.8.0 al 100 %, motor v2 apagado 24 h sin alertas ni regresión de latencia
Miércoles 22, 10:00 Bandera activada para empleados (lista blanca) Validación funcional de producto: precios correctos
Miércoles 22, 16:00 Bandera al 1 % de usuarios reales Conversión del segmento ≥ 55 %, sin quejas en soporte
Jueves 23 Bandera al 10 % Conversión y ticket medio comparables o mejores; sin errores 5xx atribuibles
Viernes 24 Bandera al 50 % Igual criterio, con volumen mayor
Lunes 27, 10:00 Bandera al 100 % Decisión conjunta de producto y plataforma
Miércoles 29, 18:00 Congelación: ningún cambio hasta el martes 5 de mayo

Lo importante de este plan es que separa dos riesgos distintos: el riesgo técnico de la versión nueva (canario, lunes y martes) y el riesgo de negocio del motor de precios (bandera, de miércoles a lunes). Mezclarlos habría hecho imposible saber cuál de los dos causaba un problema.

8.3. Criterios de éxito y umbrales de abortado

# AnalysisTemplate específico para esta publicación, con umbrales más
# estrictos de lo habitual por la cercanía de la temporada alta.
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: analisis-puente-mayo
  namespace: rutas-norte-pro
spec:
  metrics:
    - name: tasa-error
      interval: 30s
      failureLimit: 1            # tolerancia mínima: una medida mala aborta
      initialDelay: 2m
      provider:
        prometheus:
          address: http://prometheus-operated.monitorizacion:9090
          query: |
            sum(rate(api_reservas_peticiones_total{service="api-reservas-canario",codigo=~"5.."}[2m]))
            / sum(rate(api_reservas_peticiones_total{service="api-reservas-canario"}[2m]))
      successCondition: result[0] < 0.003 || isNaN(result[0])      # 0,3 %

    - name: latencia-p95
      interval: 30s
      failureLimit: 1
      initialDelay: 2m
      provider:
        prometheus:
          address: http://prometheus-operated.monitorizacion:9090
          query: |
            histogram_quantile(0.95, sum by (le) (rate(
              api_reservas_duracion_segundos_bucket{service="api-reservas-canario"}[2m])))
      successCondition: result[0] < 0.30 || isNaN(result[0])        # 300 ms

    - name: errores-pasarela-pagos
      interval: 1m
      failureLimit: 1
      initialDelay: 5m
      provider:
        prometheus:
          address: http://prometheus-operated.monitorizacion:9090
          query: |
            sum(rate(api_reservas_pagos_fallidos_total{service="api-reservas-canario"}[5m]))
            / sum(rate(api_reservas_pagos_intentos_total{service="api-reservas-canario"}[5m]))
      successCondition: result[0] < 0.02 || isNaN(result[0])        # 2 %

    - name: conversion
      interval: 5m
      failureLimit: 1
      initialDelay: 15m
      provider:
        prometheus:
          address: http://prometheus-operated.monitorizacion:9090
          query: |
            sum(rate(api_reservas_reservas_confirmadas_total{service="api-reservas-canario"}[10m]))
            / sum(rate(api_reservas_reservas_iniciadas_total{service="api-reservas-canario"}[10m]))
      successCondition: result[0] > 0.55 || isNaN(result[0])
Criterio de éxito Umbral Umbral de abortado
Errores 5xx < 0,1 % ≥ 0,3 % en una medida
Latencia p95 < 250 ms ≥ 300 ms en una medida
Fallos de pasarela de pagos < 1 % ≥ 2 %
Conversión de reserva ≥ 60 % < 55 %
Reinicios de pod canario 0 ≥ 1
Alertas nuevas en Alertmanager 0 Cualquiera de severidad alta

8.4. El plan de reversión

Tres niveles, del más rápido al más lento:

  1. Bandera apagada (5 segundos, sin desplegar): desactiva el motor de precios v2. Cubre el 80 % de los riesgos de esta publicación. Puede hacerlo soporte sin llamar a nadie.
  2. Abortar el Rollout (10 segundos): kubectl argo rollouts abort api-reservas. Devuelve todo el tráfico a la versión estable, que nunca dejó de servir.
  3. Revertir en Git (4 minutos): git revert del commit de promoción en overlays/pro y sincronización de Argo CD. Es lo que deja el sistema coherente y lo que se hace siempre después del nivel 1 o 2.

La migración de expansión no se revierte: las tablas nuevas quedan ahí, sin usar, sin molestar. Su contracción está programada para junio, pasada la temporada alta.

8.5. Qué pasó realmente

En el segundo paso del canario (peso 25 %), el análisis abortó por latencia-p95 en 340 ms. La causa: el motor de precios v2 consultaba reglas_precio sin índice sobre (linea, franja_horaria). Se detectó en once minutos, afectó al 25 % del tráfico durante menos de dos minutos y no generó ninguna incidencia de cara al cliente. Se añadió el índice con CREATE INDEX CONCURRENTLY, se repitió el canario esa misma tarde y pasó limpio.

Ese es exactamente el valor de todo lo anterior: un problema que con RollingUpdate habría sido un incidente de severidad 2 en pleno puente de mayo se resolvió como una tarea de una tarde.

  1. Comparativa de las cuatro estrategias

Criterio Rodante Azul-verde Canario Banderas
Riesgo de exposición Alto: llega al 100 % sin validar Medio: todo o nada al conmutar Bajo: porcentaje pequeño primero Muy bajo: segmento elegido
Coste en recursos Bajo (+25 % temporal) Alto (×2 durante la ventana) Medio (+10-25 %) Ninguno
Complejidad de implantación Nula, viene de serie Baja: dos Deployments y un selector Media-alta: controlador y análisis Media: servicio de banderas y disciplina
Velocidad de reversión Lenta: minutos Rápida: segundos Inmediata: abortar Inmediata: apagar
Validación con tráfico real antes de comprometerse No Solo sintética Sí, segmentada
Automatización de la decisión No Difícil Sí, con métricas Manual, o por experimento
Convivencia de versiones Sí, descontrolada No (salto limpio) Sí, controlada Sí, en el mismo proceso
Exigencia sobre migraciones Compatibles Compatibles Compatibles Compatibles
Deuda que deja Ninguna Manifiestos duplicados Configuración de análisis Código muerto si no se limpia
Cuándo usarla Cambios pequeños y rutinarios Cambios grandes fuera de temporada alta Publicaciones de riesgo con métricas fiables Funcionalidades de negocio y pruebas A/B

Elección práctica en Rutas Norte:

  • Rodante para tienda-web y para cambios de configuración: bajo riesgo, no compensa la complejidad.
  • Canario con Argo Rollouts para toda publicación de api-reservas: es el componente crítico y tiene métricas de calidad.
  • Azul-verde para migraciones de infraestructura grandes (como el cambio de versión mayor de PostgreSQL de 11-02), y solo fuera de temporada alta.
  • Banderas para toda funcionalidad visible para el usuario, siempre, sobre cualquiera de las anteriores.

Errores Comunes y Consejos

  • Hacer azul-verde con una migración de esquema no compatible. La reversión, que era el argumento entero de la estrategia, deja de ser posible en el instante en que se aplica la migración. Expandir y contraer, siempre.
  • Umbrales de análisis absolutos y nada más. Si el sistema ya va lento, un p95 de 340 ms puede pasar el umbral de 350 ms siendo una regresión del 70 %. Añade siempre una métrica relativa contra la versión estable.
  • Olvidar isNaN(result[0]) en las condiciones. Con peso al 5 % y poco tráfico, una consulta puede devolver NaN y el análisis fallar sin que haya ningún problema real.
  • Canario sin métrica de negocio. Todos los indicadores técnicos perfectos y la conversión por los suelos es un escenario real y frecuente. La métrica de negocio es la que justifica el canario.
  • Analizar desde el primer segundo. Los pods recién arrancados tienen la caché fría y el JIT sin calentar. Sin initialDelay, se aborta por falsos positivos sistemáticamente.
  • Pesos por número de réplicas con HPA activo. El autoescalado cambia el porcentaje sin que nadie lo decida. Si hay HPA, usa reparto por peso en el Ingress.
  • Banderas eternas. Cada bandera sin caducidad duplica caminos de código para siempre. Fecha de caducidad al crearla y revisión trimestral.
  • Consejo: ensaya el canario con una versión idéntica a la actual. Desplegar el mismo digest como canario valida toda la maquinaria sin ningún riesgo, y descubre umbrales mal calibrados antes de que importen.
  • Consejo: deja la versión anterior viva un rato tras promocionar (scaleDownDelaySeconds). Los problemas que aparecen a los cinco minutos son más frecuentes que los de los cinco primeros segundos.

Ejercicios

Ejercicio 1: elegir estrategia y justificarla

Para cada uno de estos cambios en Rutas Norte, elige la estrategia más adecuada y justifícala en dos o tres frases:

  1. Actualizar la imagen base de api-reservas de Node 22.4 a 22.9 por un parche de seguridad, sin cambios funcionales.
  2. Cambiar el diseño de la página de selección de asiento de tienda-web, sobre la que producto quiere medir conversión durante tres semanas.
  3. Sustituir el motor de búsqueda de rutas por uno nuevo, con el mismo contrato de API pero un algoritmo interno completamente distinto.
  4. Corregir una errata en un texto de la SPA.

Ejercicio 2: detectar el fallo en un AnalysisTemplate

metrics:
  - name: tasa-error
    interval: 10s
    failureLimit: 0
    provider:
      prometheus:
        address: http://prometheus-operated.monitorizacion:9090
        query: |
          sum(rate(api_reservas_peticiones_total{codigo=~"5.."}[30s]))
          / sum(rate(api_reservas_peticiones_total[30s]))
    successCondition: result[0] < 0.001

Este análisis aborta prácticamente todos los canarios, incluso los correctos. Identifica cuatro problemas y propón el manifiesto corregido.

Ejercicio 3: diseñar la publicación completa

api-reservas va a incorporar la posibilidad de cancelar una reserva desde la web. Requiere: una columna nueva estado en la tabla reservas (valores activa y cancelada), un endpoint nuevo DELETE /reservas/{id}, un cambio en tienda-web para mostrar el botón, y notificación por correo mediante worker-notificaciones. La publicación es en octubre, fuera de temporada alta. Diseña la secuencia completa: cuántos despliegues, qué estrategia en cada uno, en qué orden, y qué permite revertir en cada punto.

Soluciones

Solución 1.

  1. Canario. No hay funcionalidad que activar ni condicional que escribir, así que la bandera no aplica. El riesgo es técnico (rendimiento, compatibilidad de dependencias nativas) y precisamente eso es lo que el análisis de métricas detecta. Azul-verde sería innecesariamente caro.
  2. Bandera de funcionalidad, complementada con el despliegue rodante habitual de tienda-web. Producto necesita controlar el segmento y medir conversión durante semanas: eso es un experimento A/B, no una publicación. El canario dura minutos, no tres semanas.
  3. Canario con análisis reforzado, incluyendo métricas de negocio (calidad de los resultados de búsqueda medida por clics en la primera opción) y latencia relativa contra la estable. Como el contrato de API no cambia, no hay que tocar la SPA ni el esquema; el riesgo es de comportamiento y rendimiento, exactamente lo que el canario valida. Complementariamente, una bandera permitiría volver al motor antiguo sin desplegar.
  4. Rodante. Riesgo prácticamente nulo. Montar un canario para una errata es sobreingeniería que además retrasa la corrección.

Solución 2. Problemas:

# Problema Efecto
1 Sin initialDelay Juzga los primeros segundos, con los pods calentando y la caché fría: falsos positivos sistemáticos
2 failureLimit: 0 Una única medida mala, aunque sea un pico transitorio de un segundo, aborta
3 Sin filtrar por service="...-canario" Mide el error de toda la aplicación, estable incluida: si la estable tiene errores, el canario nunca puede pasar
4 Ventana [30s] con interval: 10s Ventana demasiado corta: muy poca muestra, mucho ruido, y medidas solapadas
5 Sin isNaN Con el 5 % del tráfico puede no haber peticiones en la ventana y devolver NaN, que falla la condición

Corrección:

metrics:
  - name: tasa-error
    interval: 1m
    failureLimit: 2
    consecutiveErrorLimit: 3
    initialDelay: 2m
    provider:
      prometheus:
        address: http://prometheus-operated.monitorizacion:9090
        query: |
          sum(rate(api_reservas_peticiones_total{service="api-reservas-canario",codigo=~"5.."}[2m]))
          / sum(rate(api_reservas_peticiones_total{service="api-reservas-canario"}[2m]))
    successCondition: result[0] < 0.005 || isNaN(result[0])

Solución 3. Cinco despliegues:

  1. Migración de expansión. ALTER TABLE reservas ADD COLUMN estado text DEFAULT 'activa' (anulable, con valor por defecto). Ninguna versión de código la usa aún. Reversión: innecesaria, la columna es inocua para 2.x actual.
  2. api-reservas con el endpoint nuevo, protegido por bandera apagada. Canario con Argo Rollouts. El endpoint DELETE /reservas/{id} existe pero devuelve 404 con la bandera apagada. Reversión: abortar el canario, o apagar la bandera (ya está apagada).
  3. worker-notificaciones con la plantilla de correo de cancelación. Rodante: es un consumidor de cola, sin tráfico de usuarios. Debe desplegarse antes de que se pueda cancelar, para que no haya cancelaciones sin notificación. Reversión: rollout undo, sin impacto.
  4. tienda-web con el botón, condicionado por la misma bandera. Rodante. El botón no aparece mientras la bandera esté apagada. Reversión: rodante inversa, o simplemente dejar el botón oculto.
  5. Activación progresiva de la bandera: empleados → 5 % → 25 % → 100 %, vigilando la tasa de cancelación (una tasa anormalmente alta indicaría que el botón está mal ubicado o que se cancela por error) y los errores del endpoint. Reversión: apagar la bandera, cinco segundos, sin desplegar nada.
  6. Contracción, en noviembre: ALTER COLUMN estado SET NOT NULL una vez confirmado que todas las filas tienen valor y que ninguna versión antigua sigue viva.

El orden clave es que la capacidad (endpoint y worker) se despliega antes que la interfaz (botón), y que ambas quedan inertes hasta que la bandera se activa. Así, en todo momento anterior al paso 5 el sistema es funcionalmente idéntico al actual, y cualquier despliegue se puede revertir sin consecuencias.

Conclusión

Hemos visto cuatro maneras de reducir el riesgo de publicar. El RollingUpdate es cómodo y suficiente para lo rutinario, pero avanza guiándose por la sonda de preparación, que solo sabe si el proceso responde, no si la versión funciona bien. El azul-verde compra una reversión instantánea a cambio de duplicar la capacidad y de conmutar todo de golpe. El canario expone la versión nueva a una fracción pequeña de usuarios y permite decidir con evidencia real; con Argo Rollouts esa decisión se automatiza consultando Prometheus, con promoción y abortado automáticos, y con Flagger se consigue lo mismo con otra filosofía. Las banderas de funcionalidad operan en otra dimensión: separan el despliegue del código de la activación del comportamiento, y ponen el interruptor en manos de producto y de soporte.

Y por debajo de todas ellas hay una condición que no se puede saltar: las migraciones de base de datos deben ser compatibles con la versión anterior y la siguiente. Sin expandir y contraer, ninguna de las cuatro estrategias puede revertir de verdad.

El caso del puente de mayo resume la lección: separando el riesgo técnico (canario) del riesgo de negocio (bandera), un problema de rendimiento causado por un índice ausente se detectó en once minutos, afectó a una fracción del tráfico durante menos de dos, y se resolvió como una tarea de una tarde en lugar de como un incidente en el fin de semana de más ventas del año.

Hasta aquí hemos trabajado siempre sobre un clúster. En la siguiente lección, Gestión Multi-Clúster, veremos qué cambia cuando la plataforma deja de caber en uno solo: por qué las empresas acaban con varios, cómo se trabaja a diario sin aplicar nunca en el equivocado, cómo Argo CD despliega en toda una flota, y el problema que no tiene solución fácil cuando hay que recuperarse de un desastre, que es el dato.

Curso de Kubernetes

Módulo 1: Introducción a Kubernetes

Módulo 2: Componentes Principales de Kubernetes

Módulo 3: Gestión de Configuración y Secretos

Módulo 4: Redes en Kubernetes

Módulo 5: Almacenamiento en Kubernetes

Módulo 6: Conceptos Avanzados de Kubernetes

Módulo 7: Monitoreo y Registro

Módulo 8: Seguridad en Kubernetes

Módulo 9: Escalado y Rendimiento

Módulo 10: Ecosistema y Herramientas de Kubernetes

Módulo 11: Estudios de Caso y Aplicaciones del Mundo Real

Módulo 12: Preparación para la Certificación de Kubernetes

© Copyright 2026. Todos los derechos reservados