Hasta ahora, en todo el curso, ha habido un clúster. Los namespaces rutas-norte-dev, rutas-norte-pre y rutas-norte-pro convivían en él, separados por cuotas, políticas de red y RBAC. Es una arquitectura perfectamente defendible y es donde empieza casi todo el mundo.

Pero llega un momento en que deja de serlo. Puede ser una actualización del plano de control que afecta a los tres entornos a la vez; puede ser el auditor preguntando por qué el clúster que ejecuta rutas-norte-dev tiene acceso a la red donde viven los datos personales de producción; puede ser que la empresa abra operaciones en otra región y la latencia desde Portugal a eu-west-1 empiece a notarse en la conversión. En Rutas Norte fue lo primero: una actualización de EKS que salió mal en pre dejó también pro inaccesible durante diecinueve minutos.

Esta lección trata de qué cambia cuando la plataforma deja de caber en un clúster: por qué se llega ahí, qué topologías existen, cómo se trabaja a diario sin aplicar en el clúster equivocado, cómo Argo CD gobierna una flota, cómo se conectan clústeres entre sí, y el problema que no tiene solución fácil cuando hay que recuperarse de un desastre, que es el dato.

Advertencia de cumplimiento normativo. La distribución de clústeres entre regiones afecta directamente a la residencia de los datos personales de los clientes de Rutas Norte. Replicar postgres-reservas a otra región, almacenar copias de seguridad fuera de la región principal, o permitir que un clúster de una región consulte datos alojados en otra, son decisiones con implicaciones legales que deben ser revisadas y aprobadas por el responsable de cumplimiento normativo antes de implantarse. Los ejemplos de esta lección son ilustrativos.

Contenido

  1. Por qué una empresa acaba con varios clústeres
  2. Topologías habituales y cuál elegir
  3. El trabajo diario con varios contextos
  4. Despliegue multi-clúster con Argo CD
  5. Configuración y políticas comunes: el clúster como producto
  6. Conectividad entre clústeres
  7. Recuperación ante desastres
  8. El plan de Rutas Norte
  9. El coste, la complejidad y cuándo no hacerlo

  1. Por qué una empresa acaba con varios clústeres

No es una decisión que se tome una mañana. Se llega por acumulación de razones, y conviene reconocerlas para saber cuál te está empujando.

1.1. Aislamiento entre producción y el resto

Los namespaces separan cargas, pero no separan el plano de control. Comparten servidor de API, etcd, planificador, CNI, controlador de Ingress y complementos. Todo lo que sea "de clúster" es compartido: CRDs, ClusterRole, webhooks de admisión, versiones de operadores.

En la práctica, esto significa que un desarrollador que despliega un CRD mal formado en dev, o un operador que consume memoria sin control, puede degradar producción. En Rutas Norte fue un webhook de validación mal configurado en dev el que, al no responder, bloqueó la creación de pods en todo el clúster durante ocho minutos, producción incluida.

1.2. Radio de impacto

Con un clúster, la pregunta "¿qué pasa si el clúster falla?" tiene una respuesta única y muy mala: todo cae. El plano de control gestionado de EKS es muy fiable, pero no es el único punto de fallo: un NetworkPolicy demasiado amplia, un agotamiento de IPs de la VPC, una versión de CNI defectuosa o un error humano con kubectl delete afectan a todo lo que hay dentro.

1.3. Regiones y latencia

Rutas Norte opera principalmente en el norte peninsular, pero está estudiando expandirse al sur de Francia. Una petición desde Toulouse a eu-west-1 (Irlanda) suma unos 35-45 ms de ida y vuelta solo por la red. Sobre una compra de billete con seis llamadas encadenadas, eso son casi 300 ms de latencia añadida que el usuario percibe.

1.4. Requisitos normativos de residencia del dato

Este es el motivo que no admite discusión técnica: si la normativa aplicable, un contrato con un cliente corporativo o una decisión del responsable de cumplimiento exige que los datos personales de determinados clientes no salgan de un territorio, hace falta infraestructura en ese territorio. No hay configuración de namespace que resuelva eso.

1.5. Versiones distintas

Kubernetes publica una versión menor cada cuatro meses aproximadamente, y el soporte de cada una dura alrededor de catorce. Actualizar es obligatorio, y probar la actualización en el mismo clúster que produce es imposible por definición. Con varios clústeres se puede llevar dev a la versión nueva, dejar que rompa lo que tenga que romper, y actualizar producción semanas después con la lección aprendida.

1.6. El clúster también se actualiza

Relacionado, pero distinto: hasta las actualizaciones que salen bien tienen ventanas de riesgo. Los nodos se reemplazan, los complementos se reinician, el CNI se actualiza. Con un solo clúster, cada actualización es un evento de producción. Con dos, es un evento de producción una vez cada dos actualizaciones, porque la primera se ha ensayado.

1.7. Y lo que cuesta

Coste Detalle
Plano de control En EKS, unos 73 €/mes por clúster; multiplicado por la flota
Complementos duplicados Prometheus, Grafana, ingress-nginx, cert-manager, Kyverno, Argo CD en cada clúster
Capacidad ociosa Cada clúster necesita margen propio; no se comparte
Complejidad operativa Cada procedimiento se ejecuta N veces, o hay que automatizarlo para N
Deriva entre clústeres El riesgo real: dos clústeres que deberían ser iguales dejan de serlo
Carga cognitiva "¿En qué clúster estoy?" es una pregunta con consecuencias

La deriva merece énfasis: el problema de tener varios clústeres no es tenerlos, es mantenerlos iguales. Un parche aplicado a mano en uno y no en otro genera una diferencia que nadie recuerda hasta que causa un incidente meses después. Todo lo del apartado 5 existe para combatir eso.

  1. Topologías habituales y cuál elegir

Topología Descripción Ventajas Inconvenientes Cuándo
Por entorno Un clúster para dev+pre, otro para pro Aísla el plano de control de producción; permite ensayar actualizaciones Duplica complementos; no resuelve regiones Casi siempre el primer paso. Es lo más rentable
Por región Un clúster por región geográfica Latencia baja; residencia del dato; tolerancia a fallo regional Replicación de datos entre regiones (el problema difícil) Presencia en varias geografías o requisito normativo
Por unidad de negocio Un clúster por equipo o producto Autonomía total; costes claramente atribuidos Proliferación; complementos multiplicados; mucha deriva Organizaciones grandes con equipos de plataforma por unidad
Por criticidad Uno para lo crítico, otro para lo demás Protege lo importante sin duplicar todo Frontera difusa: ¿dónde va lo intermedio? Cuando hay una diferencia clara entre lo crítico y lo accesorio
Clúster de gestión Uno que solo aloja las herramientas (Argo CD, observabilidad central) y gobierna los demás Punto único de despliegue y visibilidad; no compite con las cargas Punto único de fallo del gobierno; sobredimensionado para flotas pequeñas A partir de tres o cuatro clústeres

2.1. Recomendación

Empieza por uno. Un solo clúster con namespaces bien separados, cuotas, políticas de red y RBAC resuelve el 80 % de los casos y cuesta una fracción.

El primer desdoblamiento debe ser producción frente al resto. Es el que aporta más por unidad de complejidad: aísla el plano de control de lo crítico, permite ensayar actualizaciones y satisface a cualquier auditoría razonable.

El segundo, si llega, viene impuesto por la geografía o la normativa, no por preferencias de arquitectura.

El clúster de gestión, a partir del tercero. Con dos clústeres, Argo CD puede vivir en el de producción gestionando ambos. Con cuatro, hace falta un sitio neutral.

graph TB
  subgraph fase1["Fase 1 · hoy"]
    C1[Clúster único<br/>ns dev / pre / pro]
  end
  subgraph fase2["Fase 2 · Rutas Norte ahora"]
    C2A[rutasnorte-pro<br/>eu-west-1]
    C2B[rutasnorte-nopro<br/>dev + pre · eu-west-1]
    ACD2[Argo CD en pro] -.gestiona.-> C2A
    ACD2 -.gestiona.-> C2B
  end
  subgraph fase3["Fase 3 · si llega la expansión"]
    MGT[Clúster de gestión<br/>Argo CD + observabilidad]
    C3A[pro eu-west-1]
    C3B[pro eu-west-3]
    C3C[nopro]
    MGT --> C3A
    MGT --> C3B
    MGT --> C3C
  end
  fase1 --> fase2 --> fase3

  1. El trabajo diario con varios contextos

Aquí es donde se producen los accidentes. Un kubectl delete deploy ejecutado creyendo estar en dev cuando se está en pro es un incidente real y frecuente.

3.1. El kubeconfig con varios clústeres

# ~/.kube/config (fragmento)
apiVersion: v1
kind: Config
current-context: rutasnorte-nopro
clusters:
  - name: rutasnorte-pro
    cluster:
      server: https://ABC123.gr7.eu-west-1.eks.amazonaws.com
      certificate-authority-data: LS0tLS1CRUdJTiBDRVJU...
  - name: rutasnorte-nopro
    cluster:
      server: https://DEF456.gr7.eu-west-1.eks.amazonaws.com
      certificate-authority-data: LS0tLS1CRUdJTiBDRVJU...
users:
  - name: joan-pro
    exec:
      apiVersion: client.authentication.k8s.io/v1
      command: aws
      args: [eks, get-token, --cluster-name, rutasnorte-pro, --role-arn,
             "arn:aws:iam::111122223333:role/rutasnorte-pro-lectura"]
  - name: joan-nopro
    exec:
      apiVersion: client.authentication.k8s.io/v1
      command: aws
      args: [eks, get-token, --cluster-name, rutasnorte-nopro]
contexts:
  # El nombre del contexto es la primera línea de defensa: que dé miedo.
  - name: PRODUCCION-rutasnorte
    context: { cluster: rutasnorte-pro, user: joan-pro, namespace: rutas-norte-pro }
  - name: rutasnorte-nopro
    context: { cluster: rutasnorte-nopro, user: joan-nopro, namespace: rutas-norte-dev }
kubectl config get-contexts
kubectl config use-context rutasnorte-nopro
kubectl config current-context
CURRENT   NAME                     CLUSTER              NAMESPACE
          PRODUCCION-rutasnorte    rutasnorte-pro       rutas-norte-pro
*         rutasnorte-nopro         rutasnorte-nopro     rutas-norte-dev

3.2. kubectx y kubens

kubectx                      # lista contextos
kubectx rutasnorte-nopro     # cambia de contexto
kubectx -                    # vuelve al anterior
kubens rutas-norte-pre       # cambia de namespace sin tocar el contexto

Son dos utilidades pequeñas que ahorran mucho tiempo. Instalarlas es lo primero que hace cualquiera que trabaje con más de un clúster.

3.3. Las medidas para no equivocarse de clúster

Cinco capas, de la más blanda a la más dura. Las tres primeras son avisos; las dos últimas son barreras reales.

Capa 1: el prompt muestra el contexto. Es imprescindible. Sin esto, todo lo demás es opcional.

# ~/.bashrc — contexto y namespace en el prompt, en rojo si es producción
contexto_k8s() {
  local ctx ns
  ctx=$(kubectl config current-context 2>/dev/null) || return
  ns=$(kubectl config view --minify -o jsonpath='{..namespace}' 2>/dev/null)
  if [[ "$ctx" == *PRODUCCION* ]]; then
    printf '\001\e[41;97m\002 ⚠ %s:%s \001\e[0m\002' "$ctx" "${ns:-default}"
  else
    printf '\001\e[36m\002(%s:%s)\001\e[0m\002' "$ctx" "${ns:-default}"
  fi
}
PS1='$(contexto_k8s) \w \$ '

Fondo rojo cuando estás en producción. Parece trivial y es la medida que más accidentes evita.

Capa 2: contexto por sesión, no global. El problema de use-context es que cambia el estado de todas las terminales abiertas. Una solución mejor es aislar el kubeconfig por terminal:

# Alias que abre una sesión con su propio kubeconfig
alias k-pro='export KUBECONFIG=~/.kube/pro.yaml && echo "⚠ SESIÓN DE PRODUCCIÓN"'
alias k-dev='export KUBECONFIG=~/.kube/nopro.yaml'

Así, la terminal de producción es la de producción y punto: no hay forma de que un use-context de otra ventana la cambie.

Capa 3: confirmación explícita para verbos destructivos.

# Envoltorio de kubectl: pide confirmación en producción para lo peligroso
kubectl() {
  local ctx; ctx=$(command kubectl config current-context 2>/dev/null)
  if [[ "$ctx" == *PRODUCCION* && "$1" =~ ^(delete|drain|cordon|scale|patch|replace|edit)$ ]]; then
    echo "⚠  Vas a ejecutar '$1' en $ctx"
    read -r -p "Escribe el nombre del clúster para confirmar: " r
    [[ "$r" == "$ctx" ]] || { echo "Cancelado."; return 1; }
  fi
  command kubectl "$@"
}

Capa 4: contextos de solo lectura por defecto. Esta es la primera barrera real, porque no depende de la disciplina de nadie. El usuario joan-pro del kubeconfig asume el rol rutasnorte-pro-lectura, mapeado a un ClusterRole de solo lectura:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: plataforma-lectura-pro
subjects:
  - kind: Group
    name: rutasnorte:plataforma
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: view          # ClusterRole integrado: get, list, watch. Nada más.
  apiGroup: rbac.authorization.k8s.io

Escribir en producción exige asumir explícitamente otro rol, con una sesión temporal y un motivo registrado. Es una fricción deliberada de treinta segundos que convierte cada escritura en un acto consciente.

Capa 5: no escribir nunca a mano en producción. El objetivo último. Con GitOps (10-05), el único que aplica cambios en pro es Argo CD. Las personas leen, diagnostican y proponen cambios en Git. La escritura manual queda reservada para incidentes, con el procedimiento de asunción de rol y su registro de auditoría.

Medida Coste de implantación Accidentes que evita
Prompt con contexto 5 minutos La mayoría de los despistes
Kubeconfig por sesión 10 minutos Cambios de contexto cruzados entre terminales
Confirmación en verbos destructivos 15 minutos Los delete por inercia
Contextos de solo lectura 1 hora + RBAC Todos los accidentes de escritura casual
Solo GitOps escribe en pro El proyecto entero Todo, y además da trazabilidad

  1. Despliegue multi-clúster con Argo CD

Argo CD gestiona clústeres remotos desde una única instalación. Cada clúster se registra como un destino y las Application apuntan al que corresponda.

4.1. Registrar clústeres

# Desde el clúster donde vive Argo CD
argocd cluster add rutasnorte-nopro --name nopro \
  --label entorno=nopro --label region=eu-west-1

argocd cluster add PRODUCCION-rutasnorte --name pro \
  --label entorno=pro --label region=eu-west-1 --label critico=true

argocd cluster list
SERVER                                          NAME    VERSION  STATUS      LABELS
https://DEF456.gr7.eu-west-1.eks.amazonaws.com  nopro   1.30     Successful  entorno=nopro,region=eu-west-1
https://ABC123.gr7.eu-west-1.eks.amazonaws.com  pro     1.30     Successful  entorno=pro,region=eu-west-1,critico=true
https://kubernetes.default.svc                  in-cluster 1.30  Successful

Las etiquetas son la parte importante: son lo que permite escribir "despliega esto en todos los clústeres de producción" sin enumerarlos.

argocd cluster add crea una ServiceAccount en el clúster destino con los permisos necesarios y guarda sus credenciales como un Secret en el clúster de Argo CD. Ese Secret es una credencial de alto valor: quien lo lea puede desplegar en producción. Debe estar protegido por RBAC estricto y, preferiblemente, cifrado en reposo con una clave gestionada externamente.

4.2. El generador de clústeres de ApplicationSet

Un ApplicationSet genera Application automáticamente a partir de una plantilla y un generador. El generador de clústeres crea una por cada clúster que cumpla un selector.

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: plataforma-base
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
    - clusters:
        # Todos los clústeres registrados con esta etiqueta.
        selector:
          matchLabels:
            rutasnorte.example/gestionado: "true"
  template:
    metadata:
      # El nombre incluye el clúster: una Application por clúster.
      name: 'base-{{.name}}'
    spec:
      project: plataforma
      source:
        repoURL: https://github.com/rutasnorte/manifiestos
        targetRevision: main
        path: 'plataforma/base'
        kustomize:
          # Cada clúster tiene su parche con sus particularidades.
          components:
            - '../componentes/{{index .metadata.labels "entorno"}}'
      destination:
        server: '{{.server}}'
        namespace: plataforma
      syncPolicy:
        automated: { prune: true, selfHeal: true }
        syncOptions: [CreateNamespace=true]

Esto despliega los complementos comunes (Kyverno, cert-manager, ingress-nginx, node-exporter) en todos los clústeres etiquetados, con las diferencias por entorno resueltas por Kustomize.

Para las aplicaciones de negocio, el generador de matriz combina clústeres con aplicaciones:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: rutas-norte-aplicaciones
  namespace: argocd
spec:
  goTemplate: true
  generators:
    - matrix:
        generators:
          # Eje 1: clústeres de producción
          - clusters:
              selector:
                matchLabels: { entorno: pro }
          # Eje 2: los componentes, leídos de los directorios del repositorio
          - git:
              repoURL: https://github.com/rutasnorte/manifiestos
              revision: main
              directories:
                - path: 'overlays/pro/*'
  template:
    metadata:
      name: '{{.path.basename}}-{{.name}}'
      annotations:
        # Ordena el despliegue: la base de datos antes que la API.
        argocd.argoproj.io/sync-wave: '{{if eq .path.basename "postgres-reservas"}}-1{{else}}0{{end}}'
    spec:
      project: rutas-norte
      source:
        repoURL: https://github.com/rutasnorte/manifiestos
        targetRevision: main
        path: '{{.path.path}}'
      destination:
        server: '{{.server}}'
        namespace: 'rutas-norte-pro'
      syncPolicy:
        automated: { prune: true, selfHeal: true }

Con dos clústeres de producción y seis componentes, esto genera doce Application sin escribir doce ficheros. Añadir una región es registrar un clúster con la etiqueta correcta; añadir un componente es crear un directorio.

4.3. Cómo se combina con Kustomize

La estructura del repositorio soporta dos dimensiones: entorno y clúster.

manifiestos/
├── base/
│   ├── api-reservas/
│   ├── tienda-web/
│   └── postgres-reservas/
├── componentes/                    # variaciones reutilizables
│   ├── alta-disponibilidad/        # más réplicas, PDB estricto
│   ├── recursos-reducidos/         # para dev
│   └── replica-lectura/            # solo para clústeres secundarios
└── overlays/
    ├── dev/
    ├── pre/
    ├── pro-eu-west-1/              # región principal
    │   ├── kustomization.yaml
    │   └── parche-region.yaml
    └── pro-eu-west-3/              # región secundaria
        ├── kustomization.yaml
        └── parche-region.yaml
# overlays/pro-eu-west-3/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: rutas-norte-pro
resources:
  - ../../base/api-reservas
  - ../../base/tienda-web
components:
  - ../../componentes/alta-disponibilidad
  - ../../componentes/replica-lectura     # aquí PostgreSQL es réplica, no primario
patches:
  - path: parche-region.yaml
    target: { kind: Deployment }
images:
  # El MISMO digest que en eu-west-1: la promoción actualiza ambos overlays.
  - name: api-reservas
    newName: registry.rutasnorte.example/rutasnorte/api-reservas
    digest: sha256:9a8b7c6d5e4f30291a8b7c6d5e4f30291a8b7c6d5e4f30291a8b7c6d5e4f3029

Regla importante: lo que es igual entre clústeres vive en base; lo que difiere, y solo eso, en el overlay. Cada línea de un overlay es una diferencia que alguien tendrá que entender dentro de seis meses. Si un overlay crece mucho, casi siempre significa que la base está mal factorizada.

  1. Configuración y políticas comunes: el clúster como producto

Con varios clústeres, la pregunta deja de ser "¿cómo despliego mi aplicación?" y pasa a ser "¿cómo garantizo que los N clústeres cumplen las mismas reglas?".

La idea de clúster como producto consiste en que el equipo de plataforma no entrega "acceso a un clúster", sino un producto con contrato: un clúster recién creado ya viene con observabilidad, políticas de seguridad, RBAC, cuotas, controlador de Ingress, gestión de certificados y copias configuradas. Nadie tiene que montar nada, y por tanto nadie lo monta distinto.

5.1. Qué forma parte del producto

Categoría Componentes Desplegado por
Seguridad Kyverno con las políticas corporativas, Pod Security Standards, Falco ApplicationSet plataforma-base
Identidad y acceso ClusterRole y bindings por grupo del proveedor de identidad ApplicationSet plataforma-rbac
Redes ingress-nginx, cert-manager con los emisores, NetworkPolicy deny-all por defecto ApplicationSet plataforma-base
Observabilidad kube-prometheus-stack, Fluent Bit hacia el destino central, reglas de alerta ApplicationSet plataforma-observabilidad
Costes OpenCost, etiquetado obligatorio de cargas ApplicationSet plataforma-base
Copias Velero con su destino y su calendario ApplicationSet plataforma-base
Autoescalado Karpenter con las clases de nodo aprobadas Terraform (es infraestructura, no carga)

5.2. Las políticas que garantizan la homogeneidad

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: exigir-etiquetas-corporativas
  annotations:
    policies.kyverno.io/description: >-
      Toda carga debe declarar equipo y componente. Sin esto, el reparto
      de costes de 11-06 y la respuesta a incidentes son imposibles.
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: etiquetas-obligatorias
      match:
        any:
          - resources:
              kinds: [Deployment, StatefulSet, DaemonSet, CronJob, Rollout]
      exclude:
        any:
          - resources:
              namespaces: [kube-system, plataforma, argocd, monitorizacion]
      validate:
        message: "Faltan las etiquetas rutasnorte.example/equipo y app.kubernetes.io/part-of"
        pattern:
          metadata:
            labels:
              rutasnorte.example/equipo: "?*"
              app.kubernetes.io/part-of: "?*"

5.3. Detectar la deriva

Aunque todo se despliegue por GitOps, la deriva aparece: versiones de complementos instalados por Terraform, parches de emergencia, diferencias en la versión del propio Kubernetes. Conviene un informe periódico.

#!/usr/bin/env bash
# comparar-flota.sh — ejecutado semanalmente por un CronJob
for CTX in rutasnorte-nopro PRODUCCION-rutasnorte; do
  echo "=== $CTX ==="
  kubectl --context "$CTX" version -o json | jq -r '.serverVersion.gitVersion'
  kubectl --context "$CTX" get clusterpolicies -o name | sort
  helm --kube-context "$CTX" list -A -o json | jq -r '.[] | "\(.name)\t\(.chart)"' | sort
done

Y la comprobación que importa de verdad, hecha desde Argo CD:

# Ninguna Application debe estar OutOfSync sin motivo declarado
argocd app list -o json | jq -r '.[] | select(.status.sync.status != "Synced")
  | "\(.metadata.name)\t\(.spec.destination.name)\t\(.status.sync.status)"'

  1. Conectividad entre clústeres

Dos clústeres son dos redes de pods distintas. Un pod de eu-west-3 no puede resolver api-reservas.rutas-norte-pro.svc.cluster.local del clúster de eu-west-1, ni alcanzar sus IPs. Hay tres niveles de solución, con complejidad muy distinta.

6.1. Nivel 1: DNS global y balanceo entre regiones

Lo más simple y lo que resuelve la mayoría de los casos. El tráfico de usuarios se dirige a la región adecuada mediante DNS, y cada región es autosuficiente.

www.rutasnorte.example
  ├── política de latencia
  ├── eu-west-1 → balanceador del clúster pro-1  [comprobación de salud: /salud]
  └── eu-west-3 → balanceador del clúster pro-3  [comprobación de salud: /salud]
  • Política de latencia: cada usuario va a la región más rápida para él.
  • Política de conmutación: si la comprobación de salud de una región falla, el DNS deja de resolverla.
  • Límite importante: el DNS tiene TTL. Con TTL de 60 segundos, la conmutación tarda entre uno y varios minutos, porque hay resolutores que ignoran los TTL cortos. No hay conmutación instantánea por DNS.

6.2. Nivel 2: conectividad de red entre clústeres

Cuando un servicio de un clúster necesita llamar a otro de otro clúster (por ejemplo, la réplica de PostgreSQL siguiendo al primario), hace falta conectividad de red real: emparejamiento de VPC o pasarela de tránsito, rangos CIDR que no se solapen, y reglas de cortafuegos explícitas.

El requisito de los rangos merece atención: si los dos clústeres usan 10.244.0.0/16 para pods, el enrutamiento entre ellos es imposible. Hay que planificar el direccionamiento antes de crear el segundo clúster, y es un error que se paga con una reconstrucción.

6.3. Nivel 3: malla de servicio multi-clúster

Istio, Linkerd o Cilium Cluster Mesh permiten que un Service de un clúster sea alcanzable desde otro de forma transparente, con mTLS, reparto de carga entre regiones, conmutación por localidad y observabilidad unificada.

Qué resuelven de verdad:

  • Descubrimiento transparente: api-reservas.rutas-norte-pro.svc funciona desde cualquier clúster de la malla.
  • Conmutación por localidad: se prefiere la instancia local y se desborda a la remota solo si la local no está sana.
  • mTLS entre clústeres sin tocar la aplicación.
  • Una sola vista de la topología del tráfico.

Qué cuestan:

  • Un sidecar por pod (o eBPF, según la implementación), con su consumo de CPU y memoria.
  • Un plano de control más que operar y actualizar.
  • Una capa entera de nuevos modos de fallo, con una curva de aprendizaje considerable.
  • Depuración notablemente más difícil: el tráfico ya no va donde parece.

El criterio honesto: una malla multi-clúster se justifica cuando hay muchos servicios llamándose entre regiones. Si la comunicación entre clústeres se reduce a la replicación de la base de datos y a un par de llamadas, el emparejamiento de VPC y unos Services de tipo ExternalName resuelven lo mismo por una fracción del coste.

Rutas Norte no usa malla de servicio. Con dos clústeres, seis componentes y una única conversación entre regiones (la replicación de PostgreSQL), no está justificada. Se revisará si aparece una tercera región.

  1. Recuperación ante desastres

Aquí es donde el multi-clúster deja de ser una cuestión de arquitectura y pasa a ser una cuestión de supervivencia del negocio.

7.1. Activo-pasivo frente a activo-activo

Aspecto Activo-pasivo Activo-activo
Tráfico normal Todo a la región principal Repartido entre regiones
Región secundaria Infraestructura mínima, datos replicándose Capacidad completa sirviendo
Coste adicional 20-40 % 100 % o más
Tiempo de recuperación Minutos a horas Segundos (el DNS deja de enviar a la caída)
Complejidad del dato Replicación en una dirección Escrituras en dos sitios: el problema difícil
Se prueba Con simulacros programados Continuamente, por construcción
Riesgo de sorpresa Alto: el camino frío puede no funcionar Bajo: el camino está siempre caliente

El argumento a favor del activo-activo no es el tiempo de recuperación, es que el camino de recuperación se ejerce todos los días. Un secundario pasivo que nunca ha servido tráfico real es una hipótesis, igual que la copia que nunca se restauró de 11-02.

El argumento en contra es doble: cuesta el doble, y sobre todo obliga a resolver las escrituras concurrentes en dos regiones, que es un problema de diseño de aplicación, no de infraestructura.

7.2. RTO y RPO

Los mismos conceptos de 11-02, ahora a escala de región:

Escenario RTO objetivo RPO objetivo Cómo se consigue
Caída de un pod segundos 0 Réplicas y sondas
Caída de un nodo 1-2 min 0 Reprogramación y topologySpread
Caída de una zona 2-5 min 0 Reparto multi-zona dentro del clúster
Caída del clúster 30 min < 5 min Segundo clúster + replicación
Caída de la región 2 h < 15 min Clúster en otra región + copias replicadas

7.3. El problema que no tiene solución fácil: el dato

Los manifiestos se replican con git push. La aplicación arranca en cualquier sitio en minutos. El dato es otra cosa.

Replicación asíncrona de PostgreSQL entre regiones. Es lo que hace Rutas Norte: una réplica en eu-west-3 que sigue al primario de eu-west-1 por replicación en flujo. El retraso típico entre Irlanda y París es de 25-40 ms de red, lo que en la práctica significa un retraso de replicación de menos de un segundo con carga normal, y de unos segundos durante el puente de mayo.

La consecuencia: si la región principal desaparece de golpe, se pierden las transacciones confirmadas que aún no habían llegado a la réplica. Con RPO de 15 minutos hay margen de sobra, pero conviene decirlo con claridad: activo-pasivo con replicación asíncrona no es RPO cero. Puede haber reservas confirmadas al cliente que no existan tras la conmutación, y el negocio tiene que saber qué hacer con eso.

Replicación síncrona daría RPO cero, pero cada confirmación de transacción esperaría la ida y vuelta a la otra región: 35 ms añadidos a cada escritura. En el puente de mayo, eso es inasumible.

graph LR
  subgraph W1["eu-west-1 · principal"]
    P[(postgres-reservas<br/>PRIMARIO)]
    API1[api-reservas<br/>8-40 réplicas]
    API1 --> P
  end
  subgraph W3["eu-west-3 · secundaria"]
    R[(postgres-reservas<br/>RÉPLICA en pie)]
    API3[api-reservas<br/>2 réplicas mínimas]
    API3 -.solo lectura.-> R
  end
  S3[(Copias + WAL<br/>replicadas a ambas regiones)]
  P ==>|replicación asíncrona<br/>retraso menor de 1 s| R
  P --> S3
  DNS[DNS global<br/>latencia + salud] --> API1
  DNS -.si eu-west-1 falla.-> API3

Y el problema aún peor: la vuelta. Cuando la región principal se recupera, su base de datos tiene un estado divergente del de la que ha estado sirviendo. No se puede simplemente volver: hay que reconstruir la antigua principal como réplica de la nueva y luego conmutar ordenadamente. Ese procedimiento tiene que estar escrito antes de necesitarlo.

  1. El plan de Rutas Norte

8.1. Estado actual: dos clústeres, misma región

Clúster Región Contiene Nodos
rutasnorte-nopro eu-west-1 rutas-norte-dev, rutas-norte-pre, entornos efímeros de 11-03 3-8 (Karpenter, mayoría interrumpibles)
rutasnorte-pro eu-west-1 rutas-norte-pro, Argo CD, observabilidad 6-30 (Karpenter, base bajo demanda + interrumpibles para lo tolerante)

La separación resolvió el problema que la motivó: las actualizaciones de EKS se ensayan en nopro, y un webhook mal configurado por desarrollo ya no puede afectar a la venta de billetes.

8.2. El plan aprobado: segunda región

Tras el análisis de riesgo, la dirección aprobó una región secundaria en eu-west-3 (París) en modo activo-pasivo, con estas decisiones:

Decisión Valor Razón
Modo Activo-pasivo El activo-activo exigiría resolver escrituras multi-región en api-reservas, un proyecto de meses
Capacidad en reposo 2 réplicas de api-reservas y tienda-web, siempre encendidas Un secundario a cero es un secundario que no se sabe si funciona
Base de datos Réplica de CloudNativePG siguiendo al primario RPO real de segundos
Copias Replicadas a ambas regiones, con inmutabilidad Un desastre no puede llevarse región y copias a la vez
Manifiestos Mismo repositorio, overlay pro-eu-west-3 Una sola fuente de verdad
Argo CD Instancia en pro de eu-west-1 y una segunda instancia inactiva en eu-west-3 Si cae la región principal, cae también el Argo CD que la gobierna
DNS Registros con comprobación de salud, TTL 60 s Conmutación en 1-3 minutos
Coste adicional estimado ~34 % sobre la factura actual Aprobado por dirección

El punto de Argo CD merece atención. Es un error clásico: poner la herramienta que despliega dentro del clúster que puede desaparecer. Rutas Norte lo resuelve con una segunda instancia en eu-west-3 apuntando al mismo repositorio, con syncPolicy.automated desactivada en condiciones normales; se activa como parte del procedimiento de conmutación.

8.3. Procedimiento de conmutación

# ===== PROCEDIMIENTO DE CONMUTACIÓN A eu-west-3 =====
# Ejecutar SOLO con la decisión del mando de incidencia (11-06).
# Tiempo objetivo total: 30 minutos.

# --- Paso 1 (2 min). Confirmar que es un desastre regional, no una avería local.
# Comprobar el estado de la región en el panel del proveedor y desde una red externa.

# --- Paso 2 (3 min). Congelar escrituras a la región caída si aún es alcanzable,
# para evitar el escenario de doble primario.
kubectl --context PRODUCCION-rutasnorte scale deploy/api-reservas --replicas=0 || true

# --- Paso 3 (5 min). Promocionar la réplica de PostgreSQL en eu-west-3.
kubectl --context rutasnorte-pro-w3 -n rutas-norte-pro \
  cnpg promote postgres-reservas postgres-reservas-1
kubectl --context rutasnorte-pro-w3 -n rutas-norte-pro get cluster postgres-reservas
# Anotar el último LSN aplicado: define la pérdida real de datos.

# --- Paso 4 (5 min). Activar Argo CD secundario y escalar la aplicación.
kubectl --context rutasnorte-pro-w3 -n argocd patch application api-reservas-w3 \
  --type merge -p '{"spec":{"syncPolicy":{"automated":{"selfHeal":true,"prune":true}}}}'
kubectl --context rutasnorte-pro-w3 -n rutas-norte-pro scale deploy/api-reservas --replicas=12
kubectl --context rutasnorte-pro-w3 -n rutas-norte-pro rollout status deploy/api-reservas

# --- Paso 5 (3 min). Verificación por capas de 11-01 contra la URL interna.
curl -sf https://api-w3.rutasnorte.example/preparado
npm run pruebas:humo -- --base-url https://api-w3.rutasnorte.example

# --- Paso 6 (2 min). Conmutar el DNS.
aws route53 change-resource-record-sets --hosted-zone-id Z123 \
  --change-batch file://conmutar-a-w3.json

# --- Paso 7 (10 min). Vigilar y comunicar.
# Panel de Grafana de la región secundaria. Comunicación al negocio: qué
# reservas pueden haberse perdido (según el LSN del paso 3).

8.4. El simulacro anual

Un procedimiento escrito y nunca ejecutado es una redacción, no un plan. Rutas Norte hace un simulacro anual completo en octubre, fuera de temporada alta:

Fase Contenido
Preparación (2 semanas antes) Revisar el procedimiento, avisar al negocio, definir criterios de éxito y de abortado
Ejecución Conmutación real a eu-west-3 con tráfico de producción, en horario de baja demanda
Permanencia 4 horas sirviendo desde la región secundaria
Retorno Reconstruir eu-west-1 como réplica y conmutar de vuelta ordenadamente
Análisis posterior Tiempos reales de cada paso, qué falló, acciones de mejora con responsable y fecha

Resultados del simulacro de octubre de 2025, el primero: RTO real de 71 minutos frente al objetivo de 30. Los hallazgos fueron reveladores y ninguno se habría descubierto sin ejecutarlo:

  • El certificado TLS de eu-west-3 había caducado: cert-manager no podía completar el reto ACME porque el DNS no apuntaba a esa región. Se corrigió usando el reto DNS-01 en lugar de HTTP-01.
  • Los ExternalSecrets de eu-west-3 apuntaban a un almacén que no estaba replicado a esa región.
  • Nadie recordaba dónde estaba escrito el procedimiento de conmutación del DNS.
  • La réplica de PostgreSQL llevaba once días sin replicar por una regla de cortafuegos añadida en un cambio no relacionado. Nadie lo sabía porque no había alerta sobre el retraso de replicación entre regiones.

Ese último hallazgo, por sí solo, justificó el simulacro entero: en un desastre real se habrían perdido once días de reservas.

  1. El coste, la complejidad y cuándo no hacerlo

9.1. Lo que realmente cuesta

Concepto Coste anual estimado en Rutas Norte
Plano de control del segundo clúster ~880 €
Plano de control del tercero (eu-west-3) ~880 €
Capacidad mínima en la región secundaria ~9.400 €
Réplica de PostgreSQL entre regiones ~4.200 €
Tráfico entre regiones (replicación) ~1.800 €
Complementos duplicados (cómputo) ~3.100 €
Total infraestructura ~20.260 €
Tiempo de plataforma (implantación, ~6 semanas-persona) ~24.000 €
Mantenimiento continuo (~15 % de una persona) ~9.000 €/año

Es una inversión seria. La pregunta que la justifica no es técnica: ¿cuánto cuesta a Rutas Norte una hora caída durante el puente de mayo? Si son 40.000 € de ventas perdidas más el daño reputacional, la inversión se paga con evitar un solo incidente regional. Si el negocio puede absorber medio día caído sin consecuencias graves, no.

9.2. Cuándo NO hacerlo

  • Cuando un solo clúster todavía funciona. Si los namespaces, cuotas y políticas de red cubren tus necesidades de aislamiento, añadir clústeres solo añade deriva.
  • Cuando no hay GitOps. Con despliegues manuales, N clústeres significan N oportunidades de divergir. GitOps es requisito previo, no complemento.
  • Cuando no hay observabilidad centralizada. Diagnosticar un incidente saltando entre paneles de tres clústeres es inviable. Los logs y las métricas de toda la flota deben verse en un sitio.
  • Cuando el equipo no da abasto con uno. Multiplicar la infraestructura no multiplica la capacidad del equipo, la divide.
  • Cuando la razón es "por si acaso". Sin un escenario concreto que resolver, la complejidad se paga sin recibir nada.
  • Cuando el dato no se puede replicar. Si la aplicación no tolera coherencia eventual y no hay presupuesto para rediseñarla, un segundo clúster da una falsa sensación de seguridad.

9.3. El orden correcto de las inversiones

Antes de plantearse el segundo clúster, conviene tener resuelto lo anterior, porque casi todo mejora la fiabilidad más por menos dinero:

  1. Copias probadas y restauración cronometrada (11-02).
  2. GitOps con todo en Git (10-05).
  3. Observabilidad y alertas con runbooks (07-04, 11-06).
  4. Alta disponibilidad dentro del clúster: multi-zona, PDB, topologySpread (09-05).
  5. Despliegues seguros con canario (11-04).
  6. Y entonces sí, el segundo clúster.

Un equipo que salta al paso 6 sin los cinco anteriores acaba con dos clústeres frágiles en lugar de uno.

Errores Comunes y Consejos

  • Crear el segundo clúster con el mismo rango CIDR que el primero. Impide el enrutamiento entre ellos y obliga a reconstruir. Planifica el direccionamiento de toda la flota antes de crear el segundo.
  • Poner Argo CD solo dentro del clúster de producción. Cuando ese clúster desaparece, desaparece la capacidad de desplegar en cualquier sitio. Instancia secundaria o clúster de gestión aparte.
  • Suponer que el DNS conmuta al instante. Los TTL cortos no siempre se respetan; cuenta con uno a tres minutos como mínimo.
  • Un secundario a cero réplicas. Un camino frío no se sabe si funciona. Capacidad mínima permanente y tráfico real, aunque sea poco.
  • No alertar sobre el retraso de replicación entre regiones. Es exactamente el fallo que Rutas Norte descubrió en el simulacro: once días sin replicar y nadie lo sabía.
  • Duplicar el overlay entero al añadir una región. Solo lo que difiere va en el overlay; si crece mucho, la base está mal factorizada.
  • Instalar una malla de servicio multi-clúster por defecto. Es una de las piezas más complejas del ecosistema. Solo se justifica con mucha comunicación entre regiones.
  • Consejo: pon el contexto en el prompt hoy mismo, con fondo rojo para producción. Es la medida de mayor retorno por minuto invertido de toda la lección.
  • Consejo: haz que los contextos de producción sean de solo lectura por defecto. Escribir debe exigir un acto deliberado y registrado.
  • Consejo: escribe el procedimiento de conmutación y el de vuelta. La vuelta es más difícil que la ida y casi nadie la documenta.

Ejercicios

Ejercicio 1: decidir si desdoblar

Una empresa tiene un clúster con dev, pre y pro en namespaces separados, GitOps con Argo CD, copias probadas mensualmente y observabilidad centralizada. Ha sufrido dos incidentes en seis meses: uno por un operador instalado por desarrollo que consumió toda la memoria de un nodo compartido, y otro por una actualización de la versión del clúster que dejó pro degradado durante 25 minutos. El equipo de plataforma son dos personas. Recomienda una topología, justifícala y di explícitamente qué NO recomiendas y por qué.

Ejercicio 2: escribir el ApplicationSet

Rutas Norte añade el clúster rutasnorte-pro-w3 con las etiquetas entorno=pro, region=eu-west-3 y rol=secundario. Escribe un ApplicationSet que despliegue api-reservas en todos los clústeres con entorno=pro, usando el overlay overlays/pro-{{region}}, y que en los clústeres con rol=secundario arranque con menos réplicas. Indica además qué debe cumplirse en el repositorio para que funcione.

Ejercicio 3: analizar el simulacro

En el simulacro anual, un equipo obtiene: paso 3 (promoción de PostgreSQL) 4 min, paso 4 (Argo CD y escalado) 22 min, paso 5 (verificación) 6 min, paso 6 (DNS) 14 min. RTO total 46 min frente a un objetivo de 30. Identifica los dos pasos problemáticos, propón una causa probable para cada uno y una acción correctora concreta.

Soluciones

Solución 1. Recomendación: topología por entorno, dos clústeres — uno para pro y otro para dev+pre. Justificación: los dos incidentes sufridos son exactamente los que esta topología resuelve; el primero es contención de recursos y del plano de control entre entornos, el segundo es la imposibilidad de ensayar la actualización antes de aplicarla a producción. Además, los requisitos previos están cubiertos: hay GitOps, copias probadas y observabilidad centralizada, así que la deriva es manejable. El coste incremental para dos personas es asumible porque la mayor parte de la operación ya está automatizada.

Lo que no se recomienda: (a) un clúster por unidad de negocio, porque con dos personas de plataforma la proliferación es inasumible y no hay ningún problema que lo motive; (b) una segunda región, porque ninguno de los incidentes fue regional y el coste (infraestructura más replicación del dato) no está justificado por la evidencia disponible; (c) un clúster de gestión dedicado, innecesario con solo dos clústeres, donde Argo CD puede vivir en el de producción gestionando ambos.

Solución 2.

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: api-reservas-flota
  namespace: argocd
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
    - clusters:
        selector:
          matchLabels: { entorno: pro }
  template:
    metadata:
      name: 'api-reservas-{{index .metadata.labels "region"}}'
    spec:
      project: rutas-norte
      source:
        repoURL: https://github.com/rutasnorte/manifiestos
        targetRevision: main
        path: 'overlays/pro-{{index .metadata.labels "region"}}'
        kustomize:
          components:
            # Componente adicional solo en los secundarios.
            - '{{if eq (index .metadata.labels "rol") "secundario"}}../../componentes/capacidad-reducida{{else}}../../componentes/capacidad-completa{{end}}'
      destination:
        server: '{{.server}}'
        namespace: rutas-norte-pro
      syncPolicy:
        automated: { prune: true, selfHeal: true }

Requisitos en el repositorio: (a) deben existir los directorios overlays/pro-eu-west-1 y overlays/pro-eu-west-3; (b) deben existir los componentes capacidad-reducida y capacidad-completa; (c) todos los clústeres destino deben estar registrados en Argo CD con las etiquetas entorno, region y rol; (d) el AppProject rutas-norte debe tener ambos servidores en su lista de destinos permitidos, o Argo CD rechazará las Application generadas. Nota adicional: como el HPA gobierna las réplicas, el componente debería ajustar minReplicas/maxReplicas del HPA, no el campo replicas del Deployment.

Solución 3. Pasos problemáticos: el 4 (22 min sobre unos 5 esperados) y el 6 (14 min sobre 2).

Paso 4: causa probable, la región secundaria no tenía capacidad de nodos disponible y hubo que esperar a que el autoescalador aprovisionara nodos nuevos, incluyendo la descarga de imágenes que no estaban en caché en esos nodos. Acción correctora: mantener capacidad mínima permanente y precalentar las imágenes (por ejemplo, con un DaemonSet que las descargue, o reservando nodos con las imágenes ya presentes). Alternativa complementaria: sobreaprovisionar con pods de baja prioridad que Karpenter desaloje al escalar (09-03).

Paso 6: catorce minutos para un cambio de DNS indica que no estaba automatizado; probablemente alguien editó registros a mano en una consola web bajo presión, o hubo que buscar las credenciales. Acción correctora: guionizar el cambio con el fichero de cambio ya escrito y versionado en el repositorio, ejecutable con un solo comando, y probarlo fuera del simulacro. Comprobar además que el TTL de los registros es de 60 segundos y no el valor por defecto, que suele ser mucho mayor.

Conclusión

Hemos visto por qué una empresa acaba con varios clústeres —aislamiento del plano de control, radio de impacto, latencia por regiones, residencia del dato, versiones distintas y el simple hecho de que un clúster también se actualiza— y qué cuesta eso realmente. Recorrimos las topologías habituales con la recomendación de empezar por uno y desdoblar primero producción frente al resto; las cinco capas de defensa para no aplicar nunca en el clúster equivocado, de las que la más rentable es poner el contexto en el prompt y la más efectiva es que producción sea de solo lectura por defecto; el gobierno de la flota con el generador de clústeres de ApplicationSet combinado con las superposiciones de Kustomize; la idea de clúster como producto para combatir la deriva; los tres niveles de conectividad entre clústeres y el criterio honesto sobre cuándo una malla de servicio se justifica; y la recuperación ante desastres, donde lo difícil nunca son los manifiestos sino el dato, con su replicación asíncrona, su RPO no nulo y el procedimiento de vuelta que casi nadie escribe.

El plan de Rutas Norte, con dos clústeres hoy y una región secundaria aprobada, ilustra lo esencial: la decisión no se toma por elegancia arquitectónica, sino comparando el coste de la redundancia con el coste de una hora caída en el puente de mayo. Y el simulacro de octubre, con su RTO real de 71 minutos y sus once días de replicación rota que nadie había detectado, deja la lección más importante: un procedimiento no ejecutado no es un plan.

Ya tenemos la plataforma desplegada, con estado, con canalización, con despliegues seguros y con una flota gobernada. Falta lo que ningún tutorial cuenta y lo que ocupa realmente el día a día: mantenerla viva. En la última lección del módulo, Operación en Producción: Incidencias, Runbooks y Costes, veremos cómo se gestiona una incidencia, cómo se escribe un runbook útil, cómo el presupuesto de error decide qué hace el equipo el mes que viene, cómo se planifica la capacidad del puente de mayo y por qué la factura de Kubernetes se dispara y qué hacer al respecto.

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