Ir al contenido

GKE (Google Kubernetes Engine)

Google Kubernetes Engine (GKE) es el corazón de HERA. Como nuestra plataforma de orquestación de contenedores gestionada, nos permite desplegar, escalar y gestionar aplicaciones en contenedores con alta disponibilidad y seguridad.


GCP ofrece varias opciones para ejecutar workloads. La decisión de usar GKE como plataforma principal de HERA es deliberada. Esta sección explica el porqué y cuándo aplican las alternativas.

PlataformaCuándo usarlaCuándo NOEstado en HERA
GKE (estándar HERA)Microservicios complejos, control fino de runtime, escalado avanzado, mTLS, network policies, multi-tenantWorkloads triviales que no necesitan KubernetesEstándar para todos los servicios HERA
Cloud RunAPIs REST stateless, scale-to-zero, picos irregulares, ejecuciones cortasWorkloads que requieren persistencia local, sidecars complejos, statefulCaso de uso aprobado para servicios data con bursting (sandbox)
Cloud FunctionsTriggers de eventos puntuales (Pub/Sub, Storage), glue code, transformaciones simplesAplicaciones con state, procesos largos, dependencias pesadasCaso de uso para transformaciones de eventos en event services
App Engine StandardAplicaciones web monolíticas, prototipos rápidosMicroservicios distribuidos, observabilidad avanzada, multi-clusterNo usar — legacy approach para HERA
Compute Engine (VMs)Workloads que NO se pueden contenerizar (legacy enterprise software, GPU específico)Cualquier cosa que pueda correr en contenedorExcepción documentada — requiere aprobación de Arquitectura
RazónDetalle
PortabilidadKubernetes es estándar de la industria. Si mañana migramos de GCP, los workloads se mueven con menor fricción
Ecosystem maduroHelm, Operators, Service Mesh, GitOps, observabilidad — todo el ecosystem CNCF disponible
Control de runtimerequests/limits, probes, security contexts, network policies, RBAC granular
Multi-tenant seguroNamespaces + Network Policies + Workload Identity permiten múltiples productos en un cluster
Escalado avanzadoHPA + VPA + Cluster Autoscaler combinados — no disponible al mismo nivel en otras opciones
Estándar de la industriaEl equipo de plataforma puede contratar talent que ya conoce Kubernetes

Cuándo NO usar GKE (excepciones documentadas)

Sección titulada «Cuándo NO usar GKE (excepciones documentadas)»

GKE no es siempre la mejor opción. Estas son las excepciones aprobadas:

  1. Workloads serverless puros con scale-to-zero crítico → Cloud Run

    • Ejemplo: API pública que recibe 100 requests al día con picos eventuales
    • Razón: GKE tiene baseline cost (mínimo 1 nodo); Cloud Run baja a cero
  2. Procesamiento de eventos puntuales y ligeros → Cloud Functions

    • Ejemplo: redimensionar imágenes al subir a Cloud Storage
    • Razón: setup más simple, billing por invocación
  3. Workloads con dependencias no contenerizables → Compute Engine

    • Ejemplo: software enterprise que requiere kernel modules específicos
    • Razón: GKE no permite drivers custom de kernel
    • Requiere aprobación formal del equipo de Arquitectura

En HERA, la configuración de nuestros clusters varía según el ambiente para balancear costo, rendimiento y resiliencia.

Arquitectura GKE HERA
PRD Production Cluster (Standard · Regional · us-central1)
Node Pool: default-pool (n2-standard-4) — Apps críticas
Zone A
Node
Node
min: 2 / max: 5
Zone B
Node
Node
min: 2 / max: 5
Zone C
Node
Node
min: 2 / max: 5
Node Pool: spot-pool (e2-standard-4) — Workloads Batch
QA QA Cluster (Autopilot)
Capacidad basada en demanda · Gestionado por Google
Pruebas de integración y rendimiento
DEV DEV Cluster (Autopilot)
Capacidad basada en demanda · Gestionado por Google
Desarrollo activo y pruebas unitarias
Standard Control granular sobre nodos · Optimización con spot pools · Usado en PRD
Autopilot Gestión simplificada · Pago por pod · Ideal para QA y DEV
Mensaje clave Autopilot para DEV/QA (paga por pod). Standard para PRD (control granular). Separación por ambiente desde el cluster.
AmbienteTipoNodos / CapacidadMachine typesUso principal
DevelopmentRegional Standard3-6 nodose2-standard-4 (spot)Desarrollo y pruebas unitarias.
QARegional Standard3-6 nodose2-standard-4 (spot)Pruebas de integración y rendimiento.
ProductionRegional Standard3-12 nodosn2-standard-4 (default), e2-standard-4 (spot)Aplicaciones críticas y de cara al cliente.

En GKE Standard (PRD), no todos los workloads son iguales. Los node pools permiten segmentar la infraestructura para que cada tipo de carga tenga los recursos apropiados y el aislamiento necesario.

PoolMachine typeMin / Max nodosPropósitoTaints
defaultn2-standard-43 / 10Workloads generales (backends, BFFs)Ninguno
spot-workerse2-standard-40 / 8Batch jobs, workers, procesamiento asynccloud.google.com/gke-spot=true:NoSchedule
tier1-criticaln2-standard-82 / 6Servicios Tier 1 (checkout, pagos, auth)hera.cloudherdez.com/tier=critical:NoSchedule
data-intensiven2-highmem-41 / 4Servicios de datos (analytics, ETL, ML inference)hera.cloudherdez.com/workload=data:NoSchedule

Los taints en nodos aseguran que solo los pods con la toleration correspondiente puedan ejecutarse ahí. Esto previene que workloads no críticos consuman recursos reservados para Tier 1.

# deployment-checkout-service.yaml — solo corre en pool tier1-critical
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-checkout-service
spec:
template:
spec:
tolerations:
- key: "hera.cloudherdez.com/tier"
operator: "Equal"
value: "critical"
effect: "NoSchedule"
nodeSelector:
cloud.google.com/gke-nodepool: tier1-critical
# deployment-worker-etl.yaml — corre en pool spot (batch tolerante a interrupción)
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker-etl-inventarios
spec:
template:
spec:
tolerations:
- key: "cloud.google.com/gke-spot"
operator: "Equal"
value: "true"
effect: "NoSchedule"
nodeSelector:
cloud.google.com/gke-nodepool: spot-workers
# Spot VMs pueden ser interrumpidas — el pod debe manejar SIGTERM gracefully
terminationGracePeriodSeconds: 60

HERA define node pools separados por tier de criticidad. Cada tier usa un perfil de recursos y aislamiento específico.

ParámetroValor estándar PRDJustificación
Machine typen2-standard-88 vCPU / 32 GB RAM — capacidad para workloads críticos sin sobredimensionar
Disco100 GB SSD (pd-ssd)IOPS predecibles, necesarias para logs y caché local del pod
Autoscalingmin 2 / max 6 nodosCapacidad base para HA multi-zona + crecimiento durante picos
Regiónus-central1 (regional)3 zonas activas para tolerancia a fallas de zona
Tainthera.cloudherdez.com/tier=critical:NO_SCHEDULESólo pods con toleration explícito pueden aterrizar aquí
Labelstier=critical, environment=prdSelección explícita desde deployments (nodeSelector)
  • Shielded VM: enable_secure_boot y enable_integrity_monitoring activados — garantiza boot auditable y detección de rootkits.
  • Workload Identity: mode=GKE_METADATA — única forma autorizada de acceder a GCP (no se permiten Service Account keys montadas en disco).
  • Auto-repair: activo — GKE reemplaza automáticamente nodos unhealthy.
  • Auto-upgrade: activo — parches del SO aplicados durante la ventana de mantenimiento configurada.
TierUsoMachine type típicoMin/Max nodos
Tier 1 — CriticalCheckout, payments, APIs coren2-standard-82 / 6
Tier 2 — ImportantCatalog, search, BFFn2-standard-42 / 4
Tier 3 — StandardAdmin panels, batch, reportingn2-standard-21 / 3

Alta disponibilidad en GKE no es solo “cluster regional”. Es una combinación de estrategias a nivel cluster, nodo y pod que garantizan que los servicios sobrevivan a fallas de zona, nodo o pod individual.

NivelDEV/QA (Autopilot)PRD (Standard Regional)
ClusterZonal (1 zona)Regional (3 zonas: us-central1-a/b/c)
Réplicas mínimas13 (Tier 1), 2 (Tier 2), 1 (Tier 3)
Pod anti-affinityNo requeridoObligatorio para Tier 1 y Tier 2
PDBNo requeridoObligatorio para Tier 1
Topology spreadNo requeridoObligatorio para Tier 1

Distribución multi-zona con Topology Spread Constraints

Sección titulada «Distribución multi-zona con Topology Spread Constraints»

Para servicios Tier 1, las réplicas deben distribuirse entre zonas. Si una zona cae, las otras siguen sirviendo.

deployment-checkout-ha.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-checkout-service
spec:
replicas: 3 # Mínimo 3 para distribución en 3 zonas
template:
spec:
# Distribuir pods uniformemente entre zonas
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule # NO aceptar desbalance
labelSelector:
matchLabels:
app: backend-checkout-service
# Evitar que 2 réplicas corran en el mismo nodo
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: backend-checkout-service
topologyKey: kubernetes.io/hostname

Un PDB garantiza que durante mantenimientos voluntarios (upgrades de nodos, drain, autoscaler scale-down), siempre haya un mínimo de réplicas disponibles.

pdb-checkout.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: backend-checkout-service-pdb
namespace: tienda-herdez
spec:
minAvailable: 2 # Siempre al menos 2 pods vivos
selector:
matchLabels:
app: backend-checkout-service
Servicio tierRéplicasPDB minAvailableResultado
Tier 1 (checkout, auth)32Solo 1 pod puede estar down a la vez
Tier 2 (catálogos, CMS)21Solo 1 pod puede estar down a la vez
Tier 3 (reportes, batch)1N/ANo necesita PDB

Los servicios stateless se resuelven con réplicas. Los stateful (bases de datos, caches) requieren estrategia adicional:

ComponenteEstrategia HADetalle
Cloud SQLRegional con failover automáticoRéplica standby en zona distinta — failover en menos de 60s
Redis (Memorystore)HA con réplica readFailover automático, datos persistidos
Pub/SubRegional por defectoGoogle gestiona la replicación entre zonas
GCS (Storage)Multi-regionalReplicación automática en 2+ regiones

Unschedulable Pods: Diagnóstico y Resolución

Sección titulada «Unschedulable Pods: Diagnóstico y Resolución»

Cuando un pod queda en estado Pending con el evento FailedScheduling, significa que el scheduler no encontró un nodo que cumpla todos los requisitos. Esta es una de las situaciones más comunes en operación de clusters.

Ventana de terminal
# 1. Ver pods en Pending
kubectl get pods --all-namespaces --field-selector=status.phase=Pending
# 2. Ver los eventos del pod específico
kubectl describe pod <pod-name> -n <namespace> | grep -A 10 Events
# 3. Ver estado de nodos
kubectl get nodes -o wide
kubectl describe node <node-name> | grep -A 5 "Allocated resources"
# 4. Ver si Cluster Autoscaler tiene problemas
kubectl get events -n kube-system --field-selector source=cluster-autoscaler --sort-by='.lastTimestamp'
CausaMensaje típico en EventsSolución
Recursos insuficientesInsufficient cpu / Insufficient memoryReducir requests del pod o aumentar maxNodeCount del pool
Taint sin tolerationnode(s) had taint {key=value:NoSchedule}Agregar tolerations al Deployment o verificar que el pod apunta al pool correcto
Node selector sin matchnode(s) didn't match Pod's node affinity/selectorVerificar que el nodeSelector coincide con labels de los nodos
Anti-affinitynode(s) didn't match pod anti-affinity rulesVerificar que hay suficientes nodos para distribuir todas las réplicas
PDB bloqueando drainPod pending durante upgrade de nodosVerificar PDB minAvailable vs número de réplicas — debe haber margen
PVC sin provisionarpersistentvolumeclaim not foundVerificar que el StorageClass existe y tiene capacidad
Cluster Autoscaler lentoCluster Autoscaler status: ScaleUpEsperar 2-5 min para provisión de nodo. Si persiste: verificar cuota de GCE
Cuota de GCP agotadaQUOTA_EXCEEDED en Cluster Autoscaler eventsSolicitar aumento de cuota en la consola de GCP (CPU, IP addresses, SSD)

Cuando no hay recursos disponibles, Kubernetes puede desalojar pods de menor prioridad para hacer espacio a pods más importantes. HERA define PriorityClasses para este propósito:

priority-classes.yaml
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: hera-tier1-critical
value: 1000
globalDefault: false
description: "Servicios Tier 1 — máxima prioridad de scheduling"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: hera-tier2-standard
value: 500
globalDefault: true # Default para pods sin PriorityClass explícita
description: "Servicios Tier 2 — prioridad estándar"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: hera-tier3-low
value: 100
globalDefault: false
description: "Servicios Tier 3 — prioridad baja, pueden ser preempted"
# Uso en Deployment
spec:
template:
spec:
priorityClassName: hera-tier1-critical # Este pod tiene prioridad máxima

Con estas PriorityClasses, si el cluster está lleno y un pod Tier 1 necesita recursos, Kubernetes desaloja pods Tier 3 para hacer espacio. Esto garantiza que los servicios críticos (checkout, pagos, auth) siempre tengan recursos disponibles.


Workload Identity es la forma recomendada y más segura para que las cargas de trabajo dentro de GKE accedan a los servicios de GCP.

  1. Crear y anotar el SA de Kubernetes:

    k8s-service-account.yaml
    apiVersion: v1
    kind: ServiceAccount
    metadata:
    name: mi-app-ksa
    namespace: production
    annotations:
    # Vincula el SA de K8s al SA de Google
    iam.gke.io/gcp-service-account: mi-app-gsa@mi-proyecto.iam.gserviceaccount.com
  2. Vincular el SA de IAM (con Terraform):

    # Permitir que el SA de K8s impersonifique al SA de Google
    resource "google_service_account_iam_member" "workload_identity" {
    service_account_id = google_service_account.my_app.name
    role = "roles/iam.workloadIdentityUser"
    member = "serviceAccount:mi-proyecto.svc.id.goog[production/mi-app-ksa]"
    }
  3. Usar el SA en el Deployment:

    deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    # ...
    spec:
    template:
    spec:
    serviceAccountName: mi-app-ksa # <-- Aquí se usa el SA de K8s
    # ...

Además de los SAs de aplicación (vinculados con Workload Identity), el clúster tiene Service Accounts del sistema que requieren gobernanza especial.

Service accountNamespacePropósitoNivel de privilegio
defaultCada namespaceSA por defecto de pods sin SA explícitoRestringir — no debe tener permisos
cluster-autoscalerkube-systemEscalar nodos automáticamenteAlto — gestiona VMs en GCE
argocd-serverargocdDeploy de manifests al clusterAlto — CRUD en todos los namespaces
argocd-application-controllerargocdReconciliación GitOps continuaAlto — lectura/escritura de recursos
asm-controlleristio-systemGestión del service meshAlto — inyección de sidecars
external-dnskube-systemSincronizar DNS records en Cloud DNSMedio — modifica records DNS
cert-managercert-managerGestionar certificados TLSMedio — accede a Cloud DNS para ACME
monitoring-agentmonitoringScraping de métricasBajo — solo lectura
# Restringir el SA "default" — NUNCA debe tener permisos
# Aplicar en CADA namespace
apiVersion: v1
kind: ServiceAccount
metadata:
name: default
namespace: tienda-herdez
automountServiceAccountToken: false # No montar token automáticamente
Ventana de terminal
# Ver qué permisos tiene un SA específico
kubectl auth can-i --list --as=system:serviceaccount:tienda-herdez:bff-web-store
# Ver todos los ClusterRoleBindings (permisos globales)
kubectl get clusterrolebindings -o custom-columns='NAME:.metadata.name,SA:.subjects[*].name,ROLE:.roleRef.name' | grep -v system:
# Verificar que ningún SA de aplicación tenga cluster-admin
kubectl get clusterrolebindings -o json | jq '.items[] | select(.roleRef.name=="cluster-admin") | .subjects[]'

Mantener los nodos actualizados es crítico para seguridad y estabilidad. GKE ofrece release channels que gestionan upgrades automáticamente, pero la estrategia de upgrade debe minimizar el impacto en workloads.

ChannelVelocidadEstabilidadUso en HERA
RapidAcceso temprano a nuevas versionesMenor — puede tener bugsNo usar en HERA
RegularBalance velocidad/estabilidadMediaDEV/QA — probar versiones antes de PRD
StableVersiones probadas por semanasAltaPRD — máxima estabilidad

HERA define ventanas de mantenimiento para que los upgrades automáticos del cluster y sus nodos NO ocurran durante horario de alto tráfico.

AspectoValorJustificación
Días permitidosMartes y miércolesMenor tráfico en ecommerce; se evitan lunes (picos post-fin-de-semana) y viernes–domingo (picos de compra)
Horario00:00–04:00 CSTFranja de mínimo tráfico; suficiente para completar upgrades de control plane + node pools
Duración máxima4 horas por ventanaEvita extender el upgrade al horario de tráfico matutino
FrecuenciaSemanalRecoge parches de Google al poco tiempo de publicarlos

Tipos de mantenimiento cubiertos por la ventana

Sección titulada «Tipos de mantenimiento cubiertos por la ventana»
  • Upgrades del control plane de GKE (automáticos según release channel).
  • Upgrades de nodos (respetando los surge settings — ver sección siguiente).
  • Parches de seguridad del sistema operativo de los nodos (auto-upgrade).
  • Migraciones de infraestructura subyacente ejecutadas por Google.

Exclusiones durante eventos comerciales críticos

Sección titulada «Exclusiones durante eventos comerciales críticos»

Cuando el negocio tiene un evento que no puede absorber ninguna disrupción, el equipo de Platform Engineering declara una exclusión formal. Durante ese periodo ningún upgrade se ejecuta, ni siquiera parches menores.

EventoPeriodo típicoAlcance
Buen FinDel miércoles previo al lunes siguienteTodos los clusters PRD
Hot Sale7 días calendario completosTodos los clusters PRD
Diciembre / Temporada alta15 de diciembre – 6 de eneroTodos los clusters PRD
Lanzamientos de campañaSegún calendario del productoCluster del producto afectado
ResponsabilidadRol
Mantener la ventana de mantenimiento y el calendario de exclusionesPlatform Engineer
Notificar eventos críticos que requieran exclusión (mín. 2 semanas antes)Product Owner + Tech Lead
Monitorear que los upgrades se completen dentro de la ventana y reportar desbordamientosSRE
Aprobar excepciones fuera del calendario estándarComité de Arquitectura (COMA)

Durante un upgrade de nodos, GKE usa surge upgrades para minimizar la disrupción:

ParámetroValor HERA (PRD)Efecto
max_surge1Crea 1 nodo extra durante upgrade (cuesta temporalmente más)
max_unavailable0Nunca deja nodos fuera de servicio — siempre capacidad completa
ParámetroValor HERAQué garantiza
EstrategiaSurgeCrea primero la capacidad nueva antes de retirar la vieja
max_surge1Durante el upgrade puede existir temporalmente 1 nodo extra (costo puntual)
max_unavailable0Nunca se retira un nodo antes de que el nuevo esté listo — capacidad completa todo el tiempo
  1. GKE crea un nodo nuevo con la versión actualizada.
  2. Marca el nodo viejo como cordon (deja de aceptar pods nuevos).
  3. Ejecuta drain sobre el nodo viejo (mueve los pods al nuevo, respetando los Pod Disruption Budgets).
  4. Elimina el nodo viejo.
  5. Repite el proceso con el siguiente nodo del pool.
  • max_unavailable=0 es obligatorio en PRD: garantiza que la capacidad del cluster nunca baja durante un upgrade. Un valor mayor permitiría que durante minutos parte del tráfico no tuviera nodos disponibles.
  • max_surge=1 equilibra velocidad y costo: más nodos simultáneos aceleran el upgrade pero multiplican el costo temporal. Un único nodo extra es suficiente para un cluster de tamaño estándar.
  • Estrategia Surge (no BlueGreen): la blue-green duplica todo el node pool en paralelo; es más cara y compleja. Para HERA, surge es adecuado.

Nuestros clusters se adaptan a la carga de trabajo de forma automática en tres niveles.

Escala el número de réplicas (pods) de un deployment basado en métricas como el uso de CPU o memoria.

hpa-basic.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: mi-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: mi-app
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 75 # Escala cuando el uso de CPU supera el 75%

Para servicios event-driven o APIs con SLAs de latencia, CPU no es la mejor señal. HERA usa custom metrics de Cloud Monitoring:

# hpa-custom-metrics.yaml — escalar por mensajes pendientes en Pub/Sub
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: worker-inventarios-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: worker-inventarios
minReplicas: 1
maxReplicas: 20
metrics:
# Escalar por profundidad de cola Pub/Sub
- type: External
external:
metric:
name: pubsub.googleapis.com|subscription|num_undelivered_messages
selector:
matchLabels:
resource.labels.subscription_id: inventarios-subscription
target:
type: AverageValue
averageValue: 100 # 100 mensajes pendientes por pod
# Combinado con CPU como safety net
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 80
# hpa-latency.yaml — escalar por latencia P99
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: bff-web-store-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: bff-web-store
minReplicas: 3
maxReplicas: 15
metrics:
- type: Pods
pods:
metric:
name: http_request_duration_seconds_p99
target:
type: AverageValue
averageValue: "500m" # 500ms — escalar si P99 sube de 500ms

Por defecto, HPA puede escalar hacia abajo agresivamente causando flapping. La sección behavior controla la velocidad:

hpa-with-behavior.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: backend-checkout-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: backend-checkout-service
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
behavior:
scaleUp:
stabilizationWindowSeconds: 30 # Esperar 30s antes de escalar up
policies:
- type: Percent
value: 100 # Duplicar pods si es necesario
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300 # Esperar 5 min antes de escalar down
policies:
- type: Pods
value: 1 # Máximo 1 pod a la vez
periodSeconds: 120 # Cada 2 minutos
ParámetroRecomendación HERARazón
Scale-up window30sReaccionar rápido a picos (ej: flash sales)
Scale-down window300s (5 min)Evitar flapping — los picos pueden volver
Scale-down rate1 pod cada 2 minGradual para evitar la pérdida abrupta de capacidad
Scale-up rate100% (duplicar)Agresivo para absorber tráfico repentino

Añade o elimina nodos (VMs) del cluster cuando no hay suficientes recursos para los pods (escala hacia arriba) o cuando hay nodos infrautilizados (escala hacia abajo). Esto aplica principalmente a GKE Standard.

Ajusta automáticamente las solicitudes (requests) de CPU y memoria de los pods para optimizar el uso de recursos. Lo usamos en modo “recomendación” en producción para evitar reinicios inesperados.


Las probes son el mecanismo de Kubernetes para saber si un pod está vivo, listo para recibir tráfico, o todavía arrancando. Configurarlas correctamente es la diferencia entre un servicio que se auto-recupera y uno que acumula errores silenciosos.

ProbePregunta que respondeAcción si fallaCuándo aplica
startupProbe¿Ya terminó de arrancar?No evalúa liveness/readiness hasta que paseApps con arranque lento (JVM, carga de modelos ML)
readinessProbe¿Puede recibir tráfico?Quita el pod del Service (no recibe requests)Siempre — obligatoria en HERA
livenessProbe¿Sigue vivo?Reinicia el pod (kill + recreate)Siempre — obligatoria en HERA
deployment-with-probes.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-catalogo-service
spec:
template:
spec:
containers:
- name: catalogo
image: us-docker.pkg.dev/hera-prd/hera/backend-catalogo-service:v2.1.0
ports:
- containerPort: 8080
# Probe de arranque — para apps que tardan en iniciar
startupProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 30 # 30 x 5s = 150s máximo para arrancar
# Probe de readiness — ¿puede recibir tráfico?
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 0 # Sin delay — startupProbe ya cubrió el arranque
periodSeconds: 10
successThreshold: 1
failureThreshold: 3 # 3 fallas = quitar del balanceador
timeoutSeconds: 3
# Probe de liveness — ¿sigue vivo?
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 0
periodSeconds: 15
failureThreshold: 3 # 3 fallas = reiniciar el pod
timeoutSeconds: 3
EndpointQué validaQué NO debe validarCódigo OKCódigo error
/healthz (liveness)El proceso responde, no está en deadlockDependencias externas (DB, cache, APIs)200503
/ready (readiness)Conexión a DB, cache warm, dependencias listasNada externo al pod que sea transient200503
Tipo de serviciostartupProbereadinessProbelivenessProbe
BFF / Frontend (Node.js)failureThreshold: 6 (30s)period: 10s, timeout: 3speriod: 15s, timeout: 3s
Backend (Java/Spring)failureThreshold: 30 (150s)period: 10s, timeout: 5speriod: 15s, timeout: 5s
Worker (Python/Go)failureThreshold: 12 (60s)period: 15s, timeout: 3speriod: 20s, timeout: 3s
Data pipeline (Spark/dbt)failureThreshold: 60 (300s)period: 30s, timeout: 5speriod: 30s, timeout: 5s
Anti-patternConsecuenciaCorrección
Liveness valida la base de datosDB lenta = Kubernetes reinicia TODOS los pods = cascading failureLiveness solo valida el proceso local; readiness valida DB
Sin startupProbe en apps JVMLiveness mata el pod antes de que termine de arrancarAgregar startupProbe con failureThreshold alto
timeoutSeconds: 1 (default)GC pause de 2s = pod reiniciado innecesariamenteSubir a 3-5s según el runtime
periodSeconds: 1Overhead de health checks satura el servicioMínimo 10s para readiness, 15s para liveness
Mismo endpoint para liveness y readinessNo puedes distinguir “el proceso murió” de “la DB está lenta”Endpoints separados: /healthz (local) y /ready (dependencias)

Por defecto, todos los pods en un namespace pueden comunicarse entre sí. Implementamos Network Policies para restringir este comportamiento, siguiendo un modelo de confianza cero (Zero Trust).

  1. Denegar todo por defecto: La primera regla en cada namespace es denegar todo el tráfico de entrada (ingress) y salida (egress).

    default-deny.yaml
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
    name: default-deny-all
    spec:
    podSelector: {} # Aplica a todos los pods
    policyTypes:
    - Ingress
    - Egress
  2. Permitir tráfico explícitamente: Luego, creamos reglas específicas para permitir únicamente el tráfico necesario.

    allow-ingress-to-app.yaml
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
    name: allow-ingress-to-mi-app
    spec:
    podSelector:
    matchLabels:
    app: mi-app # Aplica solo a los pods de nuestra aplicación
    policyTypes:
    - Ingress
    ingress:
    - from:
    # Permite tráfico solo desde pods en el namespace del Ingress Controller
    - namespaceSelector:
    matchLabels:
    name: ingress-nginx
    ports:
    - protocol: TCP
    port: 8080 # Permite tráfico solo al puerto 8080

Las Network Policies cubren autorización L4 (¿qué pod puede hablar con qué pod?) pero NO cubren observabilidad L7, mTLS automático, retries inteligentes, traffic splitting o políticas de autorización por request. Para esto, HERA usa Anthos Service Mesh (ASM) — la implementación gestionada de Istio para GKE.

CapabilitySin Service MeshCon Anthos Service Mesh
mTLS entre serviciosManual, requiere código en cada servicioAutomático, transparente al código
Observabilidad L7Cada servicio implementa logging/tracing manualmenteMétricas, logs y traces automáticos por request
Retries inteligentesLógica en cada clienteConfiguración declarativa central
Traffic splitting (canary, A/B)Múltiples deployments con load balancersDeclarativo en VirtualService
Autorización por requestCada servicio valida tokensAuthorizationPolicy centralizada
Circuit breakingImplementado en cada servicioDestinationRule centralizada

Por qué Anthos Service Mesh y no Istio open-source

Sección titulada «Por qué Anthos Service Mesh y no Istio open-source»
CriterioIstio open-sourceAnthos Service Mesh
InstalaciónCompleja (manifiestos manuales)gcloud container fleet mesh enable
UpgradesManuales y disruptivosGestionados por Google
SoporteComunidadSLA enterprise de Google
Integración GCPManualNativa con Cloud Logging, Cloud Trace, IAM
Costo”Gratis” + 100% del costo operacionalSubscription + costo operacional reducido

Decisión HERA: Anthos Service Mesh es el estándar para clusters PRD. El TCO es menor por el ahorro operacional.

Ventana de terminal
# Habilitar Anthos Service Mesh con management automático
gcloud container fleet mesh enable --project=hera-prd
gcloud container fleet mesh update \
--management automatic \
--memberships=cluster-prd-us-central1 \
--project=hera-prd

2. Habilitar inyección automática de sidecar en namespaces

Sección titulada «2. Habilitar inyección automática de sidecar en namespaces»
Ventana de terminal
# Inyectar el proxy Envoy automáticamente en todos los pods del namespace
kubectl label namespace tienda-herdez istio-injection=enabled

A partir de este momento, todo pod nuevo en tienda-herdez recibe automáticamente un sidecar Envoy sin tocar el código de la aplicación. mTLS, observabilidad y traffic management son transparentes.

strict-mtls.yaml
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: tienda-herdez
spec:
mtls:
mode: STRICT # Solo acepta tráfico mTLS

Con esto, cualquier pod sin sidecar (sin mTLS) es rechazado automáticamente. Es Zero Trust dentro del cluster, sin código adicional.

auth-policy-checkout.yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: checkout-allow-bff
namespace: tienda-herdez
spec:
selector:
matchLabels:
app: backend-checkout-service
rules:
- from:
- source:
principals:
- "cluster.local/ns/tienda-herdez/sa/bff-web-store"
- "cluster.local/ns/tienda-herdez/sa/bff-mobile-app"
to:
- operation:
methods: ["GET", "POST"]
paths: ["/api/orders", "/api/orders/*"]

Esta policy dice: solo los BFFs identificados pueden llamar a backend-checkout-service con métodos GET/POST en paths específicos. Todo lo demás es rechazado.

canary-checkout.yaml
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: backend-checkout-service
namespace: tienda-herdez
spec:
hosts:
- backend-checkout-service
http:
- route:
- destination:
host: backend-checkout-service
subset: stable
weight: 90
- destination:
host: backend-checkout-service
subset: canary
weight: 10 # 10% del tráfico al canary
SituaciónRecomendación
Cluster de DEV con < 10 serviciosNo es necesario; el overhead de ASM no se justifica
POCs o sandboxes temporalesNetwork Policies son suficientes
Cluster monolítico (1-2 servicios)Overhead innecesario

ASM aplica para clusters PRD y QA con 10+ servicios donde la observabilidad y mTLS automático aportan valor real.


Los pipelines de HERA siguen un patrón push-based: GitLab CI ejecuta kubectl apply o helm upgrade desde el pipeline hacia el cluster. Esto funciona pero tiene limitaciones:

  • El cluster no es la fuente de verdad — Git lo es, pero el cluster puede divergir
  • No hay drift detection automático (alguien puede cambiar manualmente con kubectl edit)
  • Rollback requiere re-ejecutar pipeline
  • La auditoría está distribuida entre Git y el cluster

GitOps invierte el modelo: el cluster observa Git y se sincroniza continuamente. Si alguien cambia algo manualmente, GitOps lo revierte automáticamente. Si Git cambia, el cluster lo aplica solo.

AspectoPipeline push-based (HOY)GitOps con ArgoCD (próxima fase)
Fuente de verdadGit, pero el cluster puede divergirGit es la única verdad — el cluster siempre se reconcilia
Drift detectionManual o post-mortemAutomático y continuo
RollbackRe-ejecutar pipelinegit revert y ArgoCD aplica solo
AuditoríaDistribuida (GitLab + cluster)Centralizada en Git
Multi-clusterPipeline por clusterUna sola configuración en Git, ArgoCD distribuye
Disaster recoveryRe-ejecutar pipelines en ordenApuntar ArgoCD nuevo al mismo Git, sincronización automática

Flujo GitOps en HERA

El pipeline construye y escanea. ArgoCD observa Git y sincroniza el cluster automáticamente.

Developer Push código a GitLab
GitLab CI Build + Test + Security scan
Artifact Registry Imagen firmada con Cosign
Config Git Repo Manifest actualizado (values.yaml)
ArgoCD Observa Git → sincroniza cluster
GKE Cluster Pods desplegados y operando
Mensaje clave GitLab CI ejecuta build, test y security. ArgoCD ejecuta deploy y reconciliación. Son herramientas complementarias, no competidoras.

HERA propone separar repos de código y de configuración:

RepoContenidoQuién modifica
backend-checkout-service (código)Código fuente, Dockerfile, testsDevelopers
infra-helm-checkout-service (config)Helm chart con valores por ambientePipeline CI (auto) + Platform
infra-argocd-applications (apps)ArgoCD Application definitionsPlatform Engineering
infra-argocd-applications/tienda-herdez/backend-checkout-service.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: backend-checkout-service-prd
namespace: argocd
spec:
project: tienda-herdez
source:
repoURL: https://hera.gitlab.cloudherdez.com/products/ecommerce/tienda-herdez/_shared/infra-helm-checkout-service.git
targetRevision: HEAD
path: helm
helm:
valueFiles:
- values-prd.yaml
destination:
server: https://kubernetes.default.svc
namespace: tienda-herdez
syncPolicy:
automated:
prune: true
selfHeal: true # Si alguien cambia manualmente, ArgoCD lo revierte
syncOptions:
- CreateNamespace=false
- PruneLast=true
OperaciónPipeline push-based (HOY)GitOps con ArgoCD
Deploy nuevoPipeline ejecuta helm upgradePipeline modifica values.yaml en Git → ArgoCD sincroniza
RollbackRe-ejecutar pipeline con versión anteriorgit revert en repo de config → ArgoCD aplica
Cambio manual con kubectl editPersiste hasta el próximo deployArgoCD lo revierte automáticamente en segundos
Auditar qué corre en PRDInspeccionar cluster + comparar con GitEl repo de Git ES lo que corre
Multi-cluster (multi-región)Pipeline distinto por clusterUna sola Application, multiple destinations

GitOps con ArgoCD está en la hoja de ruta de plataforma. Hoy opera con pipelines push-based; la adopción de ArgoCD es parte del camino hacia self-service total.


GKE auto-escala los nodos y pods, pero el auto-scaling no es magia. Si los requests están mal definidos, el cluster gasta de más o sufre throttling. Esta sección define el proceso de capacity planning de HERA.

NivelQué se optimizaQuiénFrecuencia
1. Pod requests/limitsCPU y memoria por contenedorDeveloperPor release
2. Cluster node poolsTipo de máquina, mín/máx nodosPlatform EngineerTrimestral
3. Workload distributionBin packing entre nodosCluster AutoscalerContinuo
4. Commitment plansReserved capacity con descuentoFinOpsAnual

Nivel 1 — Pod requests/limits (responsabilidad del Developer)

Sección titulada «Nivel 1 — Pod requests/limits (responsabilidad del Developer)»

El error más común: definir requests igual a limits.

Anti-patternPor qué es maloRecomendación
requests = limits = 2 CPUReserva 2 CPU aunque solo use 0.3 — cluster wasterequests = 0.5, limits = 2
Sin requests definidosCluster Autoscaler no puede planificarSiempre definir requests
limits muy bajosOOM kills frecuentesDefinir limits con margen 2x sobre uso real
Sin VPA recomendacionesNo sabes cuánto usa realmenteHabilitar VPA en modo Off (solo recomendaciones)
  1. Habilita VPA en modo Off para que recomiende sin aplicar:
vpa-recommender.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: backend-checkout-service-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: backend-checkout-service
updatePolicy:
updateMode: "Off" # Solo recomienda, no aplica
  1. Espera 7-14 días para que VPA aprenda los patrones reales

  2. Consulta las recomendaciones:

Ventana de terminal
kubectl describe vpa backend-checkout-service-vpa
  1. Aplica las recomendaciones en el manifest del deployment:
resources:
requests:
cpu: 250m # Recomendación de VPA
memory: 512Mi
limits:
cpu: 1000m # 4x el request — margen para picos
memory: 1Gi # 2x el request

Nivel 2 — Cluster node pools (responsabilidad del Platform Engineer)

Sección titulada «Nivel 2 — Cluster node pools (responsabilidad del Platform Engineer)»
DecisiónCuándo aplicaAhorro típico
Spot instances para batch/devWorkloads tolerantes a interrupción60-80%
N2 vs E2E2 más barato pero menos performante30% para workloads no críticos
Right-sizing de node poolsCuando hay nodos infrautilizados crónicos20-40%
Multi-zonal vs regionalRegional cuesta 3x pero da HA(no es ahorro, es resiliencia)
GKE Autopilot vs StandardAutopilot paga por pod, Standard por nodoVariable según density

Nivel 3 — Workload distribution (Cluster Autoscaler)

Sección titulada «Nivel 3 — Workload distribution (Cluster Autoscaler)»

El Cluster Autoscaler hace bin packing, pero hay que ayudarlo:

# Hint para que pods relacionados vivan cerca
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: backend-checkout-service
topologyKey: kubernetes.io/hostname
# Hint para distribuir réplicas en nodos distintos (HA)
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: backend-checkout-service

Nivel 4 — Commitment plans (responsabilidad del FinOps)

Sección titulada «Nivel 4 — Commitment plans (responsabilidad del FinOps)»

Para workloads predecibles y de larga duración, los Committed Use Discounts (CUDs) ahorran 20-55%:

TipoDescuentoCompromiso
Resource-based CUDs (CPU/RAM)20-25%1 año
Resource-based CUDs (CPU/RAM)37-55%3 años
Spend-based CUDsMenor descuento, más flexibilidad1-3 años
Spot instances60-91% (descuento dinámico)Sin compromiso, pueden ser interrumpidos

Estrategia HERA: combinar baseline con CUDs (workloads críticos predecibles) + spot para batch/workers.

MétricaQué indicaThreshold de alerta
CPU request utilization% del CPU reservado que se usa> 80% sostenido = subir requests; < 30% = bajar requests
Memory request utilization% de la memoria reservada que se usaMismo criterio
Pod pending countPods esperando nodo> 0 sostenido = cluster underprovisioned
Node CPU/MemorySaturación a nivel nodo> 80% sostenido = aumentar node pool
Cost per serviceCosto mensual por servicioCualquier cambio > 30% mes a mes requiere investigación

Kubernetes asigna automáticamente una Quality of Service class a cada pod basándose en sus requests y limits. Esto determina qué pods se sacrifican primero cuando un nodo se queda sin recursos.

QoS ClassCondiciónPrioridad de evictionUso en HERA
Guaranteedrequests == limits para CPU y memoriaÚltima — nunca se sacrifica si hay pods Burstable/BestEffortServicios Tier 1 (checkout, pagos, auth)
Burstablerequests definidos pero requests != limitsMedia — se sacrifica después de BestEffortServicios Tier 2 y Tier 3 (default)
BestEffortSin requests ni limitsPrimera — se sacrifica primeroProhibido en HERA — todo pod debe tener requests
# QoS Guaranteed — para Tier 1
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 500m # requests == limits → Guaranteed
memory: 512Mi
# QoS Burstable — para Tier 2/3
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 1000m # requests != limits → Burstable (puede burst hasta 1 CPU)
memory: 1Gi

Para evitar que un equipo consuma recursos sin control, HERA define guardrails a nivel namespace:

# limitrange.yaml — defaults y topes por contenedor
apiVersion: v1
kind: LimitRange
metadata:
name: hera-default-limits
namespace: tienda-herdez
spec:
limits:
- type: Container
default: # Limits por defecto si el pod no los especifica
cpu: 500m
memory: 512Mi
defaultRequest: # Requests por defecto
cpu: 100m
memory: 128Mi
max: # Máximo que un contenedor puede pedir
cpu: 4
memory: 8Gi
min: # Mínimo (evita requests irrisoriamente bajos)
cpu: 50m
memory: 64Mi
# resourcequota.yaml — cuota total para el namespace
apiVersion: v1
kind: ResourceQuota
metadata:
name: hera-quota
namespace: tienda-herdez
spec:
hard:
requests.cpu: "20" # Máximo 20 CPUs en requests totales
requests.memory: 40Gi # Máximo 40 GiB en requests totales
limits.cpu: "40" # Máximo 40 CPUs en limits totales
limits.memory: 80Gi
pods: "100" # Máximo 100 pods en el namespace
services.loadbalancers: "0" # CERO LoadBalancers — forzar uso de Ingress

La instrumentación a nivel de servicio (logs, métricas RED/USE, traces) sigue los estándares de DevSecOps. Esta sección complementa con la observabilidad del clúster como infraestructura — lo que el equipo de plataforma necesita monitorear para que GKE funcione correctamente.

El control plane de GKE (API Server, etcd, Scheduler, Controller Manager) es gestionado por Google, pero sus métricas son visibles y deben monitorearse:

ComponenteMétrica claveQué indicaThreshold de alerta
API Serverapiserver_request_duration_secondsLatencia de llamadas a la API de KubernetesP99 mayor a 5s por 5 min
API Serverapiserver_request_total{code=~"5.."}Errores del API ServerMayor a 1% de requests totales
etcdetcd_db_total_size_in_bytesTamaño de la base de datos de estadoMayor a 6 GB (límite default: 8 GB)
etcdetcd_request_duration_secondsLatencia de lectura/escritura de estadoP99 mayor a 200ms
Schedulerscheduler_pending_podsPods esperando schedulingMayor a 0 sostenido por 5 min
Schedulerscheduler_scheduling_duration_secondsTiempo que tarda en schedulear un podP99 mayor a 10s
Ventana de terminal
# Habilitar exportación de métricas del control plane a Cloud Monitoring
gcloud container clusters update cluster-prd-us-central1 \
--enable-managed-prometheus \
--monitoring=SYSTEM,API_SERVER,SCHEDULER,CONTROLLER_MANAGER \
--project=hera-prd
MétricaQué monitorearThreshold
CPU allocatable vs requestedQué % de CPU disponible está reservadaMayor a 85% = cluster near-capacity
Memory allocatable vs requestedQué % de memoria está reservadaMayor a 85% = riesgo de OOM
Node readinessNodos en estado NotReadyCualquier nodo NotReady por 2+ min
Disk pressureNodos con DiskPressure=TrueCualquier nodo — indica disco lleno
PID pressureNodos con PIDPressure=TrueCualquier nodo — indica process leak
Node countNodos activos vs esperadosDesviación mayor a 20% del baseline

HERA define un dashboard de plataforma con paneles organizados por capa:

HERA GKE Platform Dashboard

Paneles organizados por capa. El equipo de plataforma monitorea este dashboard continuamente.

Control Plane
  • API Server P99
  • API Server errors
  • etcd DB size
  • Scheduler pending
Node Health
  • Nodes ready / total
  • CPU alloc vs requested
  • Memory alloc vs requested
  • Disk / PID pressure
Pod Health
  • Pods running
  • Pods pending
  • Pod restarts
  • OOMKilled events
Autoscaling
  • HPA current vs desired
  • CA scale-up events
  • Unschedulable pods
  • Node pool size vs limits
Networking
  • Ingress latency
  • Ingress error rate
  • DNS resolution
  • Service Mesh P99
Security
  • Network Policy denies
  • PSA violations
  • Failed auth requests
  • Audit log anomalies
Mensaje clave Los equipos de desarrollo monitorean sus servicios. El equipo de plataforma monitorea el cluster. Ambas capas son necesarias.

Los events de Kubernetes son una fuente valiosa de diagnóstico que muchas veces se ignora. HERA los exporta a Cloud Logging para retención y análisis:

Ventana de terminal
# Ver events recientes de todo el cluster
kubectl get events --all-namespaces --sort-by='.lastTimestamp' | tail -20
# Filtrar events problemáticos
kubectl get events --all-namespaces --field-selector type=Warning --sort-by='.lastTimestamp'
Event tipo WarningQué indicaAcción
FailedSchedulingPod no puede ser asignado a un nodoVer sección Unschedulable Pods
OOMKilledContenedor excedió su limit de memoriaAumentar limits.memory o investigar memory leak
BackOff (CrashLoopBackOff)Pod falla repetidamente al arrancarRevisar logs del contenedor: kubectl logs <pod> --previous
FailedMountVolumen no pudo montarseVerificar PVC, StorageClass, permisos de SA
Unhealthy (Liveness/Readiness)Probes fallandoVer sección Health Checks
NodeNotReadyNodo dejó de responderVerificar con kubectl describe node, posible issue de VM

Estas alertas son responsabilidad del equipo de plataforma, no de los equipos de desarrollo:

AlertaCondiciónSeveridadCanal
Cluster near capacityCPU o memoria requested mayor a 85% de allocatableP2Google Chat #hera-platform-alerts
Node NotReadyCualquier nodo en estado NotReady por 2+ minP1PagerDuty → SRE on-call
API Server degradedAPI Server P99 mayor a 5s por 5 minP1PagerDuty → SRE on-call
etcd approaching limitetcd DB size mayor a 6 GBP2Google Chat #hera-platform-alerts
Pod restart stormMás de 10 pod restarts en un namespace en 5 minP2Google Chat #hera-platform-alerts
Cluster Autoscaler stuckPods Pending + CA sin escalar por 10 minP1PagerDuty → SRE on-call
Certificate expiringTLS cert expira en menos de 14 díasP3Google Chat #hera-infra

Exposición de Servicios: Del Pod al Usuario

Sección titulada «Exposición de Servicios: Del Pod al Usuario»

Uno de los aspectos más críticos es cómo los servicios internos del cluster se exponen al tráfico externo. HERA sigue un modelo de capas donde cada nivel agrega control y seguridad.

Stack de Exposición: del Usuario al Pod

Cada capa agrega control y seguridad al tráfico entrante antes de llegar a la aplicación.

Internet Tráfico de usuarios finales
Cloud Armor / WAF Protección DDoS y reglas WAF
Cloud Load Balancer TLS termination + certificado gestionado
GKE Ingress / Gateway Routing por host y path
Kubernetes Service / ClusterIP Solo accesible dentro del cluster
Pod (contenedor) La aplicación
Mensaje clave Todo tráfico externo entra por un solo Ingress con TLS y WAF. Los Services internos usan ClusterIP — nunca LoadBalancer.
CaracterísticaService LoadBalancerIngress (GKE)Gateway API (GKE)
CapaL4 (TCP/UDP)L7 (HTTP/HTTPS)L4-L7 (ambas)
1 IP por servicioSí — 1 IP pública por ServiceNo — 1 IP comparte múltiples serviciosNo — 1 Gateway comparte múltiples rutas
TLS terminationNo (manual)Sí (certificados gestionados)Sí (certificados gestionados)
Routing por path/hostNoSí (/api/v1 → Service A, /web → Service B)Sí (más expresivo que Ingress)
WAF (Cloud Armor)No soportadoSí (via BackendConfig)Sí (via Policy)
CostoCostoso — 1 LB por Service (~$18/mes cada uno)Eficiente — 1 LB para todos los ServicesEficiente — similar a Ingress
Estado en HERAProhibido para tráfico externoEstándar actualPróxima adopción (reemplaza Ingress)
ingress-tienda-herdez.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tienda-herdez-ingress
namespace: tienda-herdez
annotations:
kubernetes.io/ingress.class: "gce" # Usa GKE Ingress Controller (Cloud LB)
kubernetes.io/ingress.global-static-ip-name: "tienda-herdez-ip"
networking.gke.io/managed-certificates: "tienda-herdez-cert"
# Cloud Armor WAF
cloud.google.com/backend-config: '{"default": "tienda-herdez-backend-config"}'
spec:
rules:
- host: tienda.herdez.com.mx
http:
paths:
- path: /api/*
pathType: ImplementationSpecific
backend:
service:
name: bff-web-store
port:
number: 8080
- path: /*
pathType: ImplementationSpecific
backend:
service:
name: frontend-tienda
port:
number: 3000
# managed-cert.yaml — Google gestiona la emisión y renovación
apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
name: tienda-herdez-cert
namespace: tienda-herdez
spec:
domains:
- tienda.herdez.com.mx
- www.tienda.herdez.com.mx
# backend-config.yaml — habilitar Cloud Armor en el Ingress
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
name: tienda-herdez-backend-config
namespace: tienda-herdez
spec:
securityPolicy:
name: hera-waf-policy # Referencia a la policy de Cloud Armor
healthCheck:
requestPath: /ready
port: 8080
connectionDraining:
drainingTimeoutSec: 30

Para comunicación entre servicios dentro del cluster, siempre usar Services tipo ClusterIP (el default):

# service-checkout.yaml — solo accesible dentro del cluster
apiVersion: v1
kind: Service
metadata:
name: backend-checkout-service
namespace: tienda-herdez
spec:
type: ClusterIP # Default — no expone nada al exterior
selector:
app: backend-checkout-service
ports:
- port: 8080
targetPort: 8080

Los BFFs llaman a backends internos por nombre DNS: http://backend-checkout-service.tienda-herdez.svc.cluster.local:8080.


Más allá de Network Policies (L4) y Service Mesh (L7), la seguridad dentro del pod es la última línea de defensa. Si un atacante compromete un contenedor, estas configuraciones limitan el daño.

Kubernetes define 3 niveles de seguridad para pods. HERA aplica el nivel Restricted en producción:

NivelQué permiteUso en HERA
PrivilegedTodo — contenedores root, host network, privileged modeProhibido — solo kube-system
BaselineBloquea lo más peligroso (hostPID, privileged), permite rootDEV/QA — balance entre seguridad y flexibilidad
RestrictedSolo non-root, read-only rootfs, sin capabilities extraPRD — máxima seguridad
# namespace-prd.yaml — enforce Pod Security Standards
apiVersion: v1
kind: Namespace
metadata:
name: tienda-herdez
labels:
# Pod Security Admission — nivel Restricted
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted

Con este label, Kubernetes rechaza cualquier pod que no cumpla con el nivel Restricted. No es un warning — es un hard block.

Este es el SecurityContext que todo Deployment en PRD debe usar:

# deployment-secure.yaml — template de seguridad HERA
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-catalogo-service
spec:
template:
spec:
# Pod-level security
securityContext:
runAsNonRoot: true # NUNCA correr como root
runAsUser: 1000 # UID no privilegiado
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault # Seccomp profile por defecto del runtime
containers:
- name: catalogo
image: us-docker.pkg.dev/hera-prd/hera/backend-catalogo-service:v2.1.0
# Container-level security
securityContext:
allowPrivilegeEscalation: false # No puede escalar privilegios
readOnlyRootFilesystem: true # Sistema de archivos read-only
capabilities:
drop:
- ALL # Eliminar TODAS las capabilities Linux
# Volúmenes temporales para apps que necesitan escribir
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /app/.cache
volumes:
- name: tmp
emptyDir:
sizeLimit: 100Mi
- name: cache
emptyDir:
sizeLimit: 500Mi
ControlQué haceObligatorio en PRD
runAsNonRoot: trueRechaza contenedores que corran como root
readOnlyRootFilesystem: truePreviene escritura en el filesystem del contenedor
allowPrivilegeEscalation: falseBloquea sudo, setuid y similares
capabilities.drop: [ALL]Elimina capabilities Linux innecesarias
seccompProfile: RuntimeDefaultLimita syscalls disponibles al proceso
automountServiceAccountToken: falseNo monta token de SA si no se necesitaRecomendado

Pod Security Standards (PSS) cubren lo básico (non-root, capabilities), pero HERA necesita policies más específicas: validar que las imágenes estén firmadas, que vengan del registry autorizado, que tengan labels obligatorias, que los recursos estén definidos. Para esto usamos Kyverno — un motor de policies nativo de Kubernetes.

CriterioKyvernoOPA Gatekeeper
Lenguaje de policiesYAML nativo de KubernetesRego (lenguaje propio de OPA)
Curva de aprendizajeBaja — cualquier dev que conozca K8s YAML puede escribir policiesAlta — Rego es un lenguaje lógico-declarativo
Image verificationNativo (verifyImages)Requiere integración externa
MutationSí — puede modificar recursos al admitirlosLimitado — principalmente validación
Generate resourcesSí — puede crear NetworkPolicies, LimitRanges automáticamenteNo
EcosystemCreciente, CNCF IncubatingMaduro, CNCF Graduated
DebuggingPolicies legibles, errores clarosRego puede ser críptico para no-expertos

Decisión HERA: Kyverno como motor de admission policies en GKE. La razón principal es accesibilidad: los equipos de desarrollo (internos y partners) deben poder leer y entender las policies que bloquean sus deployments. YAML nativo elimina la barrera de aprender Rego.

Estas policies se aplican a todos los clusters y son mantenidas por el equipo de plataforma:

Policies de Validación (bloquean si no cumplen)

Sección titulada «Policies de Validación (bloquean si no cumplen)»
PolicyQué validaScope
require-signed-imagesImágenes deben estar firmadas con CosignPRD
restrict-image-registriesSolo imágenes de us-central1-docker.pkg.dev/hera-*PRD, QA
require-labelsLabels obligatorias: app, team, tier, versionTodos
require-probesTodo Deployment debe tener readinessProbe y livenessProbePRD, QA
require-resourcesTodo contenedor debe tener requests y limits definidosTodos
disallow-privilegedRechaza contenedores privileged o con hostPID/hostNetworkTodos
disallow-latest-tagRechaza imágenes con tag :latestPRD, QA
restrict-service-typeBloquea Services tipo LoadBalancer (forzar Ingress)Todos
require-non-rootrunAsNonRoot: true obligatorioPRD
require-read-only-rootfsreadOnlyRootFilesystem: true obligatorioPRD

Policies de Mutación (corrigen automáticamente)

Sección titulada «Policies de Mutación (corrigen automáticamente)»
PolicyQué haceScope
add-default-security-contextAgrega SecurityContext estándar si no está definidoDEV
add-default-network-policyGenera NetworkPolicy default-deny al crear un namespaceTodos
inject-resource-defaultsAgrega requests default si no están definidos (via LimitRange)DEV

Policies de Generación (crean recursos asociados)

Sección titulada «Policies de Generación (crean recursos asociados)»
PolicyQué generaCuándo
generate-network-policyNetworkPolicy default-deny-allAl crear un namespace con label hera.cloudherdez.com/managed=true
generate-resource-quotaResourceQuota estándar por tierAl crear un namespace con label tier
kyverno-policy-restrict-registries.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-image-registries
annotations:
policies.kyverno.io/title: Restrict Image Registries
policies.kyverno.io/category: Security
policies.kyverno.io/severity: high
policies.kyverno.io/description: >-
Solo permite imágenes del Artifact Registry de HERA.
Imágenes de Docker Hub, quay.io u otros registries públicos
son rechazadas en PRD y QA.
spec:
validationFailureAction: Enforce
background: true
rules:
- name: validate-registries
match:
any:
- resources:
kinds:
- Pod
namespaces: "ns-*" # Solo namespaces de producto
validate:
message: >-
La imagen {{ "{{request.object.spec.containers[].image}}" }} no proviene
del registry autorizado. Solo se permiten imágenes de
us-central1-docker.pkg.dev/hera-*/
pattern:
spec:
containers:
- image: "us-central1-docker.pkg.dev/hera-*/*"
kyverno-policy-require-labels.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
spec:
validationFailureAction: Enforce
rules:
- name: require-deployment-labels
match:
any:
- resources:
kinds:
- Deployment
namespaces: "ns-*"
validate:
message: "El Deployment debe tener las labels: app, team, tier, version"
pattern:
metadata:
labels:
app: "?*"
team: "?*"
tier: "?*"
spec:
template:
metadata:
labels:
version: "?*"
Ventana de terminal
# 1. Instalar Kyverno CLI
brew install kyverno
# 2. Validar un manifest contra una policy (sin cluster)
kyverno apply policies/restrict-registries.yaml \
--resource deployment.yaml
# 3. Test con un manifest que DEBE fallar (negative test)
kyverno apply policies/restrict-registries.yaml \
--resource test/deployment-dockerhub.yaml \
--expected-result fail
# 4. Audit mode antes de enforce (ver qué fallaría sin bloquear)
# Cambiar validationFailureAction a "Audit" temporalmente
kubectl get policyreport -A # Ver violaciones sin bloqueo

Binary Authorization es el servicio de GCP que verifica que las imágenes de contenedor estén firmadas por autoridades de confianza antes de permitir su despliegue en GKE. Es el equivalente nativo de GCP a lo que Kyverno + Cosign hacen a nivel Kubernetes.

Decisión: Cosign + Kyverno como estándar (no Binary Authorization)

Sección titulada «Decisión: Cosign + Kyverno como estándar (no Binary Authorization)»
CriterioBinary Authorization (GCP)Cosign + Kyverno (HERA actual)
Vendor lock-inAlto — solo funciona en GCP (GKE)Bajo — Cosign es CNCF, Kyverno es CNCF
PortabilidadSi migramos de GCP, hay que reemplazarFunciona en cualquier cluster Kubernetes
GranularidadPermite/rechaza imagen completaPuede validar firma + labels + registry + policies adicionales
Integración con pipelineRequiere Attestors de GCP + KMS de GCPCosign keyless con OIDC de GitLab (ya implementado)
CostoIncluido en GKE pero requiere Cloud KMS ($)Open source
ComplejidadConfiguración de attestors, KMS keys, policy YAMLCosign sign en pipeline + Kyverno ClusterPolicy
ScopeSolo validación de imágenesKyverno cubre imágenes + 15 policies adicionales

Decisión formal: HERA usa Cosign (firma) + Kyverno (enforcement) como mecanismo de verificación de integridad de imágenes. Binary Authorization queda como opción documentada pero no adoptada por las siguientes razones:

  1. Portabilidad — Cosign + Kyverno funcionan en cualquier cluster K8s, no solo GKE
  2. Consolidación — Kyverno ya es el motor de admission policies; agregar Binary Authorization como segundo motor crea complejidad sin beneficio adicional
  3. Pipeline integrado — Cosign keyless con OIDC de GitLab ya está implementado y funcional
  4. Costo — Binary Authorization requiere Cloud KMS para las keys; Cosign keyless elimina esa necesidad
EscenarioAcción
Requisito regulatorio que exija Binary Authorization específicamenteEvaluar adopción como capa adicional (no reemplazo)
Multi-cloud con clusters fuera de GCPCosign + Kyverno se confirma como la opción correcta
GCP ofrece Binary Authorization con soporte Cosign nativoEvaluar consolidación

Las herramientas de seguridad anteriores (SAST, SCA, container scanning, Kyverno) protegen antes y durante el despliegue. Pero una vez que el pod está corriendo, ¿quién detecta comportamiento anómalo? Esa es la función de Falco — un motor de detección de amenazas en runtime, proyecto CNCF Graduated.

Falco monitorea syscalls del kernel en tiempo real y genera alertas cuando detecta actividad sospechosa:

CategoríaReglaQué detectaSeveridad
Shell accessTerminal shell in containerAlguien abrió un shell (/bin/sh, /bin/bash) dentro de un contenedorCritical
Process executionUnexpected spawned processProceso no esperado ejecutándose en el contenedorHigh
File accessRead sensitive file untrustedLectura de /etc/shadow, /etc/passwd, keysCritical
File modificationWrite below binary dirEscritura en /bin, /sbin, /usr/binCritical
NetworkUnexpected outbound connectionConexión saliente a IP/puerto no autorizadoHigh
NetworkInbound connection to unexpected portProceso escuchando en puerto no declaradoMedium
PrivilegeContainer with sensitive mountContenedor con mount de /proc, /sys del hostCritical
PrivilegeChange namespace privilegesIntento de cambiar namespaces de Linux (escape de contenedor)Critical
Crypto miningDetect crypto minersProcesos conocidos de minería o conexiones a mining poolsCritical
Package managementPackage management in containerapt-get, yum, pip install ejecutado en runtimeHigh

Además de las reglas predefinidas de Falco, HERA agrega reglas específicas al contexto enterprise:

falco-rules-hera.yaml
- rule: HERA - Unauthorized external API call
desc: >
Detecta llamadas a APIs externas no autorizadas desde pods de producción.
Solo se permite tráfico a dominios whitelisted.
condition: >
outbound and container and
fd.sip.name != "*.googleapis.com" and
fd.sip.name != "*.cloudherdez.com" and
fd.sip.name != "*.herdez.com.mx" and
k8s.ns.name startswith "ns-"
output: >
Conexión externa no autorizada desde pod HERA
(pod=%k8s.pod.name ns=%k8s.ns.name dest=%fd.sip.name:%fd.sport
command=%proc.cmdline user=%user.name)
priority: WARNING
tags: [network, hera-custom]
- rule: HERA - kubectl exec in production
desc: >
Detecta kubectl exec a pods de producción.
Esto debería ser excepcional y requiere justificación.
condition: >
spawned_process and container and
k8s.ns.name startswith "ns-" and
proc.pname = "runc:[2:INIT]" and
proc.name in (sh, bash, ash)
output: >
Shell interactivo en pod de producción
(pod=%k8s.pod.name ns=%k8s.ns.name user=%user.name command=%proc.cmdline)
priority: CRITICAL
tags: [shell, hera-custom]
- rule: HERA - Secret file accessed
desc: >
Detecta lectura de archivos montados desde secrets de Kubernetes.
condition: >
open_read and container and
fd.directory = "/var/run/secrets" and
not proc.name in (node, java, python3, dotnet)
output: >
Acceso a secret file por proceso no esperado
(pod=%k8s.pod.name file=%fd.name process=%proc.name user=%user.name)
priority: HIGH
tags: [secrets, hera-custom]

Falco se despliega como DaemonSet (un pod por nodo) usando el Helm chart oficial:

Ventana de terminal
# Instalación con Helm
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update
helm install falco falcosecurity/falco \
--namespace falco-system \
--create-namespace \
--set driver.kind=ebpf \
--set falcosidekick.enabled=true \
--set falcosidekick.config.googlechat.webhookurl="https://chat.googleapis.com/v1/spaces/HERA_ALERTS/messages?key=..." \
--set falcosidekick.config.googlechat.minimumpriority=high \
--values falco-values-hera.yaml

Falco se integra con el stack de observabilidad de HERA via Falcosidekick:

Severidad FalcoCanal de notificaciónSLA de respuesta
CriticalPagerDuty → SRE on-call + Google Chat #hera-security-alerts5 min
HighGoogle Chat #hera-security-alerts1 hora
WarningCloud Logging (análisis posterior)Siguiente día hábil
Notice/InfoCloud Logging soloNo requiere acción

Para eventos críticos, Falco + Falcosidekick pueden ejecutar respuestas automatizadas:

EventoRespuesta automáticaJustificación
Shell in container (PRD)Aislar pod con NetworkPolicy deny-allContener posible intrusión sin matar el pod (preservar evidencia)
Crypto miner detectedKill pod + alerta CriticalNo hay caso legítimo para minería en producción
Write to binary directoryKill pod + alerta CriticalIndica posible compromiso del contenedor
Package management in PRDAlerta + log para análisisNo matar — puede ser un proceso legítimo mal configurado
# falcosidekick — respuesta automática para crypto mining
- rule: HERA - Auto-response crypto miner
action:
kubernetes:
delete:
pod:
name: "{{ .Output.k8s.pod.name }}"
namespace: "{{ .Output.k8s.ns.name }}"
notification:
googlechat:
priority: critical
message: "Pod eliminado automáticamente por detección de crypto mining"
ComponenteEstadoFase
Falco DaemonSet en PRDImplementadoFase 2 (actual)
Reglas predefinidasActivas (audit mode)Fase 2
Reglas custom HERAEn desarrolloFase 2 → 3
Respuestas automatizadasSolo crypto miningFase 3 (próxima)
Falco en DEV/QANo desplegadoFase 3