Guía Completa de Kubernetes para Aplicaciones Stateful: Gestión Avanzada de Datos Persistentes

Las aplicaciones stateful representan uno de los desafíos más complejos en la orquestación de contenedores con Kubernetes. A diferencia de las aplicaciones stateless, que no mantienen información de estado entre sesiones y pueden ser fácilmente escaladas horizontalmente, las aplicaciones stateful requieren persistencia de datos, orden en el despliegue, identidades estables y estrategias sofisticadas de gestión del estado.

En el ecosistema cloud-native moderno, donde la mayoría de aplicaciones fueron inicialmente diseñadas para ser stateless, la necesidad de ejecutar bases de datos, message queues, sistemas de almacenamiento distribuido y otras aplicaciones que requieren persistencia de estado ha impulsado el desarrollo de herramientas y patrones especializados en Kubernetes.

Esta guía exhaustiva explora todos los aspectos de la gestión de aplicaciones stateful en Kubernetes, desde los conceptos fundamentales hasta implementaciones avanzadas de sistemas distribuidos complejos, proporcionando las herramientas y conocimientos necesarios para operar exitosamente cargas de trabajo con estado en entornos de producción.

Fundamentos de las Aplicaciones Stateful

Este punto requiere consideración cuidadosa en la implementación.

Diferencias Fundamentales: Stateful vs Stateless

La distinción entre aplicaciones stateful y stateless es fundamental para comprender las complejidades de la orquestación en Kubernetes. Las aplicaciones stateless son inherentemente simples de gestionar: cada instancia es idéntica e intercambiable, pueden ser iniciadas o terminadas sin consecuencias para el estado del sistema, y el escalado horizontal es trivial.

Las aplicaciones stateful, por el contrario, mantienen información persistente que debe sobrevivir a reinicios del contenedor, actualizaciones del sistema y fallos de hardware. Esta persistencia introduce dependencias temporales, requiere ordenamiento en operaciones de inicio y parada, y necesita identidades estables que permitan a las instancias reconocerse entre sí y mantener relaciones consistentes.

Casos de Uso Críticos para Aplicaciones Stateful

Los casos de uso más comunes para aplicaciones stateful incluyen sistemas de bases de datos relacionales como PostgreSQL, MySQL y Oracle, que requieren almacenamiento persistente para tablas, índices y logs de transacciones. Las bases de datos NoSQL como MongoDB, Cassandra y Elasticsearch necesitan persistencia para sus estructuras de datos distribuidas y mecanismos de replicación.

Los message queues como Apache Kafka, RabbitMQ y Apache Pulsar requieren almacenamiento persistente para buffers de mensajes y metadatos de particiones. Los sistemas de cache distribuido como Redis Cluster necesitan persistencia para snapshots y AOF logs. Las aplicaciones de análisis de datos como Apache Spark y sistemas de machine learning requieren almacenamiento persistente para datasets, modelos entrenados y checkpoints.

Desafíos Únicos en Kubernetes

Kubernetes fue diseñado inicialmente con un modelo cloud-native que favorecía aplicaciones stateless. La introducción de aplicaciones stateful requirió el desarrollo de nuevos primitivos y patrones que pudieran manejar las complejidades del estado persistente mientras mantenían los beneficios de la orquestación automatizada.

Los desafíos principales incluyen la gestión de identidades estables para pods, donde cada instancia debe tener un identificador consistente que persista a través de reinicios y reschedules. El ordering de operaciones es crítico, ya que muchas aplicaciones stateful requieren que las instancias se inicien en un orden específico y que las operaciones de shutdown sigan secuencias determinísticas.

La gestión de volúmenes persistentes añade complejidad significativa, requiriendo provisioning dinámico, binding apropiado de volúmenes a pods específicos, y estrategias de backup y recovery. La red debe proporcionar endpoints estables que permitan a las instancias comunicarse de manera consistente.

StatefulSets: El Primitivo Fundamental

Este punto requiere consideración cuidadosa en la implementación.

Arquitectura y Características de StatefulSets

StatefulSets es el recurso de Kubernetes diseñado específicamente para gestionar aplicaciones stateful. A diferencia de los Deployments, que crean pods idénticos e intercambiables, StatefulSets proporciona garantías de identidad estable, almacenamiento persistente y ordenamiento de operaciones.

Cada pod en un StatefulSet recibe un identificador ordinal que forma parte de su nombre (pod-0, pod-1, pod-2, etc.), proporcionando una identidad estable y predecible. Esta identidad persiste a través de reprogramaciones, asegurando que las aplicaciones puedan mantener relaciones consistentes entre instancias.

Los StatefulSets garantizan ordenamiento en las operaciones de scaling y updating. Durante el scale-up, los pods se crean secuencialmente en orden numérico, esperando que cada pod esté Ready antes de crear el siguiente. Durante el scale-down, los pods se eliminan en orden inverso. Las actualizaciones rolling siguen patrones similares, asegurando estabilidad durante los cambios.

# Ejemplo básico de StatefulSet para PostgreSQL
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres-cluster
  namespace: database
spec:
  serviceName: postgres-headless
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      initContainers:
      - name: postgres-init
        image: postgres:15-alpine
        command:
        - bash
        - -c
        - |
          set -ex
          # Generate postgres server id from pod ordinal
          [[ $(hostname) =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          echo $ordinal > /mnt/conf.d/server-id.conf
          
          # Copy base configuration
          cp /mnt/config-map/* /mnt/conf.d/ || true
          
          # Configure replication based on pod ordinal
          if [[ $ordinal -eq 0 ]]; then
            echo "Configuring as master"
            cat > /mnt/conf.d/master.conf <'EOF'
          wal_level = replica
          max_wal_senders = 3
          max_replication_slots = 3
          synchronous_commit = on
          synchronous_standby_names = 'postgres-cluster-1,postgres-cluster-2'
          EOF
          else
            echo "Configuring as replica"
            cat > /mnt/conf.d/replica.conf <'EOF'
          hot_standby = on
          max_connections = 1000
          EOF
          fi
        volumeMounts:
        - name: conf
          mountPath: /mnt/conf.d
        - name: config-map
          mountPath: /mnt/config-map
      containers:
      - name: postgres
        image: postgres:15-alpine
        ports:
        - containerPort: 5432
          name: postgres
        env:
        - name: POSTGRES_DB
          value: myapp
        - name: POSTGRES_USER
          value: postgres
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        - name: PGUSER
          value: postgres
        - name: POSTGRES_INITDB_ARGS
          value: "-A md5"
        resources:
          requests:
            cpu: 500m
            memory: 1Gi
          limits:
            cpu: 1000m
            memory: 2Gi
        livenessProbe:
          exec:
            command:
            - /bin/sh
            - -c
            - exec pg_isready -U postgres -h 127.0.0.1 -p 5432
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
          successThreshold: 1
          failureThreshold: 6
        readinessProbe:
          exec:
            command:
            - /bin/sh
            - -c
            - |
              pg_isready -U postgres -h 127.0.0.1 -p 5432 &&
              [ -f /var/lib/postgresql/data/PG_VERSION ]
          initialDelaySeconds: 5
          periodSeconds: 2
          timeoutSeconds: 1
          successThreshold: 1
          failureThreshold: 3
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
        - name: conf
          mountPath: /etc/postgresql/conf.d
          readOnly: true
      volumes:
      - name: conf
        emptyDir: {}
      - name: config-map
        configMap:
          name: postgres-config
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: ssd-retain
      resources:
        requests:
          storage: 100Gi

Gestión de Identidades y

La gestión de identidades estables es uno de los aspectos más críticos de StatefulSets. Cada pod obtiene un hostname predecible basado en el nombre del StatefulSet y su índice ordinal. Este hostname persiste a través de reprogramaciones, permitiendo que las aplicaciones mantengan configuraciones basadas en identidad.

Los Headless Services proporcionan la infraestructura de red necesaria para StatefulSets, creando registros DNS individuales para cada pod. Esto permite que las aplicaciones se conecten directamente a instancias específicas usando nombres DNS predecibles como postgres-cluster-0.postgres-headless.database.svc.cluster.local.

# Headless Service para StatefulSet
apiVersion: v1
kind: Service
metadata:
  name: postgres-headless
  namespace: database
  labels:
    app: postgres
spec:
  clusterIP: None  # Headless service
  selector:
    app: postgres
  ports:
  - port: 5432
    targetPort: 5432
    protocol: TCP
    name: postgres

---
# Service regular para acceso de aplicaciones
apiVersion: v1
kind: Service
metadata:
  name: postgres-read
  namespace: database
  labels:
    app: postgres
spec:
  selector:
    app: postgres
  ports:
  - port: 5432
    targetPort: 5432
    protocol: TCP
    name: postgres
  sessionAffinity: None

---
# Service para master writes
apiVersion: v1
kind: Service
metadata:
  name: postgres-write
  namespace: database
  labels:
    app: postgres
spec:
  selector:
    app: postgres
    role: master
  ports:
  - port: 5432
    targetPort: 5432
    protocol: TCP
    name: postgres

Persistent Volumes y Storage Classes

Este punto requiere consideración cuidadosa en la implementación.

Arquitectura de Almacenamiento en Kubernetes

El subsistema de almacenamiento de Kubernetes proporciona abstracción entre las aplicaciones y la infraestructura de storage subyacente. Esta abstracción es crucial para aplicaciones stateful, ya que permite portabilidad entre diferentes proveedores de cloud y tecnologías de almacenamiento mientras mantiene consistencia en el comportamiento.

Persistent Volumes (PV) representan recursos de almacenamiento físico en el cluster, abstraídos como objetos de API. Persistent Volume Claims (PVC) representan solicitudes de almacenamiento por parte de usuarios o aplicaciones. El binding dinámico entre PVCs y PVs permite provisioning automático de storage basado en requisitos específicos.

Storage Classes proporcionan plantillas para provisioning dinámico de volúmenes, definiendo parámetros como tipo de almacenamiento, políticas de replicación, niveles de performance y opciones de backup. Esta abstracción permite que las aplicaciones soliciten storage con características específicas sin conocer detalles de implementación.

# Storage Classes para diferentes casos de uso
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ssd-retain
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
  encrypted: "true"
reclaimPolicy: Retain
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
mountOptions:
- debug
- noatime

---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nvme-high-performance
provisioner: kubernetes.io/aws-ebs
parameters:
  type: io2
  iops: "10000"
  encrypted: "true"
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: Immediate

---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: network-storage
provisioner: kubernetes.io/aws-efs
parameters:
  provisioningMode: efs-utils
  fileSystemId: fs-12345678
  directoryPerms: "0755"
reclaimPolicy: Retain
allowVolumeExpansion: false
volumeBindingMode: Immediate

Estrategias de Volume Claims Templates

Los Volume Claim Templates en StatefulSets permiten que cada pod solicite su propio conjunto de volúmenes persistentes. Esta funcionalidad es esencial para aplicaciones que requieren almacenamiento dedicado por instancia, como bases de datos distribuidas.

Los templates definen especificaciones que se aplican a cada pod del StatefulSet, creando PVCs únicos con nombres predictibles. Esto permite configuraciones donde cada instancia de base de datos tiene su propio volumen de datos, logs de transacciones separados, y volúmenes de configuración específicos.

# StatefulSet con múltiples Volume Claim Templates
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: elasticsearch-cluster
  namespace: logging
spec:
  serviceName: elasticsearch-headless
  replicas: 6
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      initContainers:
      - name: configure-sysctl
        image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
        command: ['sh', '-c', 'sysctl -w vm.max_map_count=262144']
        securityContext:
          privileged: true
      - name: create-certs
        image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
        command:
        - bash
        - -c
        - |
          if [[ ! -f /usr/share/elasticsearch/config/certs/elastic-certificates.p12 ]]; then
            bin/elasticsearch-certutil ca --silent --pem -out /tmp/ca.zip
            unzip /tmp/ca.zip -d /tmp
            bin/elasticsearch-certutil cert --silent --pem --ca-cert /tmp/ca/ca.crt --ca-key /tmp/ca/ca.key --dns $ES_NODE_NAME --dns elasticsearch-headless --dns elasticsearch-headless.logging.svc.cluster.local --ip 127.0.0.1 --out /tmp/node.zip
            unzip /tmp/node.zip -d /tmp
            cp /tmp/ca/ca.crt /usr/share/elasticsearch/config/certs/
            cp /tmp/instance/instance.crt /usr/share/elasticsearch/config/certs/
            cp /tmp/instance/instance.key /usr/share/elasticsearch/config/certs/
            bin/elasticsearch-certutil cert --silent --pem --ca-cert /tmp/ca/ca.crt --ca-key /tmp/ca/ca.key -out /tmp/elastic-certificates.p12
            cp /tmp/elastic-certificates.p12 /usr/share/elasticsearch/config/certs/
          fi
        env:
        - name: ES_NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        volumeMounts:
        - name: certs
          mountPath: /usr/share/elasticsearch/config/certs
      containers:
      - name: elasticsearch
        image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
        ports:
        - containerPort: 9200
          name: rest
        - containerPort: 9300
          name: inter-node
        env:
        - name: node.name
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: cluster.name
          value: "elasticsearch-cluster"
        - name: discovery.seed_hosts
          value: "elasticsearch-headless"
        - name: cluster.initial_master_nodes
          value: "elasticsearch-cluster-0,elasticsearch-cluster-1,elasticsearch-cluster-2"
        - name: ES_JAVA_OPTS
          value: "-Xms2g -Xmx2g"
        - name: xpack.security.enabled
          value: "true"
        - name: xpack.security.transport.ssl.enabled
          value: "true"
        - name: xpack.security.transport.ssl.verification_mode
          value: "certificate"
        - name: xpack.security.transport.ssl.certificate_authorities
          value: "/usr/share/elasticsearch/config/certs/ca.crt"
        - name: xpack.security.transport.ssl.certificate
          value: "/usr/share/elasticsearch/config/certs/instance.crt"
        - name: xpack.security.transport.ssl.key
          value: "/usr/share/elasticsearch/config/certs/instance.key"
        resources:
          requests:
            memory: 4Gi
            cpu: 1000m
          limits:
            memory: 4Gi
            cpu: 2000m
        volumeMounts:
        - name: data
          mountPath: /usr/share/elasticsearch/data
        - name: logs
          mountPath: /usr/share/elasticsearch/logs
        - name: certs
          mountPath: /usr/share/elasticsearch/config/certs
          readOnly: true
        readinessProbe:
          httpGet:
            scheme: HTTP
            path: /_cluster/health?local=true
            port: 9200
          initialDelaySeconds: 5
        livenessProbe:
          httpGet:
            scheme: HTTP
            path: /_cluster/health?local=true
            port: 9200
          initialDelaySeconds: 90
          periodSeconds: 10
      volumes:
      - name: certs
        emptyDir: {}
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: ssd-retain
      resources:
        requests:
          storage: 100Gi
  - metadata:
      name: logs
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: ssd-retain
      resources:
        requests:
          storage: 20Gi

Políticas de Reclamación y Expansión

Las políticas de reclamación (Reclaim Policies) determinan qué sucede con los Persistent Volumes cuando sus Persistent Volume Claims correspondientes son eliminados. La política “Retain” preserva los datos para recovery manual, mientras que “Delete” elimina automáticamente el volumen y sus datos.

Para aplicaciones stateful críticas, la política “Retain” es generalmente preferible, ya que previene pérdida accidental de datos durante operaciones de mantenimiento o errores operacionales. Esto permite recovery manual de datos en caso de eliminación inadvertida de resources.

La expansión de volúmenes permite aumentar el tamaño de volúmenes persistentes sin downtime de aplicaciones. Esta capacidad es crucial para aplicaciones stateful que experimentan crecimiento de datos orgánico y necesitan storage adicional sin interrupciones de servicio.

# Script para expansión segura de volúmenes
apiVersion: v1
kind: ConfigMap
metadata:
  name: volume-expansion-script
data:
  expand-volume.sh: |
    #!/bin/bash
    set -e
    
    NAMESPACE=${1:-default}
    STATEFULSET=${2}
    NEW_SIZE=${3}
    
    if [[ -z "$STATEFULSET" || -z "$NEW_SIZE" ]]; then
      echo "Usage: $0 [namespace] <statefulset-name> <new-size>"
      echo "Example: $0 database postgres-cluster 200Gi"
      exit 1
    fi
    
    echo "Starting volume expansion for StatefulSet $STATEFULSET in namespace $NAMESPACE"
    echo "New size: $NEW_SIZE"
    
    # Get all PVCs for the StatefulSet
    PVCS=$(kubectl get pvc -n $NAMESPACE -l app=$STATEFULSET -o name)
    
    if [[ -z "$PVCS" ]]; then
      echo "No PVCs found for StatefulSet $STATEFULSET"
      exit 1
    fi
    
    # Expand each PVC
    for pvc in $PVCS; do
      echo "Expanding $pvc to $NEW_SIZE..."
      
      # Check if StorageClass supports volume expansion
      STORAGE_CLASS=$(kubectl get $pvc -n $NAMESPACE -o jsonpath='{.spec.storageClassName}')
      EXPANSION_SUPPORTED=$(kubectl get storageclass $STORAGE_CLASS -o jsonpath='{.allowVolumeExpansion}')
      
      if [[ "$EXPANSION_SUPPORTED" != "true" ]]; then
        echo "Warning: StorageClass $STORAGE_CLASS does not support volume expansion"
        continue
      fi
      
      # Patch the PVC
      kubectl patch $pvc -n $NAMESPACE -p '{"spec":{"resources":{"requests":{"storage":"'$NEW_SIZE'"}}}}'
      
      # Wait for expansion to complete
      echo "Waiting for expansion to complete..."
      kubectl wait --for=condition=FileSystemResizePending=false $pvc -n $NAMESPACE --timeout=300s
      
      echo "Successfully expanded $pvc"
    done
    
    # Restart StatefulSet pods to recognize new size
    echo "Restarting StatefulSet pods to recognize expanded storage..."
    kubectl rollout restart statefulset/$STATEFULSET -n $NAMESPACE
    kubectl rollout status statefulset/$STATEFULSET -n $NAMESPACE
    
    echo "Volume expansion completed successfully!"

Implementación de Bases de Datos Distribuidas

Esta implementación requiere atención a los detalles y seguimiento de las mejores prácticas.

PostgreSQL con Alta Disponibilidad

PostgreSQL en Kubernetes requiere configuración cuidadosa de replicación, failover automático y backup strategies. La implementación de un cluster PostgreSQL con alta disponibilidad involucra configuración de streaming replication, automated failover con herramientas como Patroni, y integration con sistemas de backup externos.

# ConfigMap para configuración de PostgreSQL con replicación
apiVersion: v1
kind: ConfigMap
metadata:
  name: postgres-config
  namespace: database
data:
  postgresql.conf: |
    # Basic settings
    max_connections = 200
    shared_buffers = 256MB
    effective_cache_size = 1GB
    maintenance_work_mem = 64MB
    checkpoint_completion_target = 0.9
    wal_buffers = 16MB
    default_statistics_target = 100
    random_page_cost = 1.1
    effective_io_concurrency = 200
    work_mem = 4MB
    min_wal_size = 1GB
    max_wal_size = 4GB
    
    # Replication settings
    wal_level = replica
    max_wal_senders = 10
    max_replication_slots = 10
    synchronous_commit = on
    
    # Logging
    log_destination = 'stderr'
    logging_collector = on
    log_directory = 'log'
    log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
    log_truncate_on_rotation = on
    log_rotation_age = 1d
    log_rotation_size = 100MB
    log_min_duration_statement = 1000
    log_line_prefix = '%t [%p-%l] %q%u@%d '
    
    # Monitoring
    shared_preload_libraries = 'pg_stat_statements'
    pg_stat_statements.track = all
    pg_stat_statements.save = on
    
  pg_hba.conf: |
    # TYPE  DATABASE        USER            ADDRESS                 METHOD
    local   all             all                                     trust
    host    all             all             127.0.0.1/32            md5
    host    all             all             ::1/128                 md5
    host    all             all             10.0.0.0/8              md5
    host    replication     all             10.0.0.0/8              md5
    
  setup-replication.sh: |
    #!/bin/bash
    set -e
    
    # Get pod ordinal
    [[ $(hostname) =~ -([0-9]+)$ ]] || exit 1
    ordinal=${BASH_REMATCH[1]}
    
    if [[ $ordinal -eq 0 ]]; then
      echo "Setting up as primary"
      
      # Initialize database if not exists
      if [[ ! -f "$PGDATA/PG_VERSION" ]]; then
        initdb -D "$PGDATA" -U postgres -A md5 --pwfile=<(echo "$POSTGRES_PASSWORD")
      fi
      
      # Configure as primary
      cat >> "$PGDATA/postgresql.conf" <'EOF'
    synchronous_standby_names = '*'
    hot_standby = on
    EOF
      
    else
      echo "Setting up as replica"
      
      # Wait for primary to be ready
      until pg_isready -h postgres-cluster-0.postgres-headless.database.svc.cluster.local -U postgres; do
        echo "Waiting for primary..."
        sleep 5
      done
      
      # Create base backup from primary
      if [[ ! -f "$PGDATA/PG_VERSION" ]]; then
        PGPASSWORD=$POSTGRES_PASSWORD pg_basebackup -h postgres-cluster-0.postgres-headless.database.svc.cluster.local -D "$PGDATA" -U postgres -v -P -W
        
        # Configure as replica
        cat > "$PGDATA/recovery.conf" <EOF
    standby_mode = on
    primary_conninfo = 'host=postgres-cluster-0.postgres-headless.database.svc.cluster.local port=5432 user=postgres password=$POSTGRES_PASSWORD'
    primary_slot_name = 'replica_slot_$ordinal'
    EOF
        
        # Create replication slot on primary
        PGPASSWORD=$POSTGRES_PASSWORD psql -h postgres-cluster-0.postgres-headless.database.svc.cluster.local -U postgres -c "SELECT pg_create_physical_replication_slot('replica_slot_$ordinal');"
      fi
    fi

---
# StatefulSet con configuración de replicación automática
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres-cluster
  namespace: database
spec:
  serviceName: postgres-headless
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      initContainers:
      - name: postgres-init
        image: postgres:15-alpine
        command: ['bash', '/scripts/setup-replication.sh']
        env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
        - name: config
          mountPath: /scripts
      containers:
      - name: postgres
        image: postgres:15-alpine
        ports:
        - containerPort: 5432
          name: postgres
        env:
        - name: POSTGRES_DB
          value: myapp
        - name: POSTGRES_USER
          value: postgres
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        resources:
          requests:
            cpu: 1000m
            memory: 2Gi
          limits:
            cpu: 2000m
            memory: 4Gi
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
        - name: postgres-config
          mountPath: /etc/postgresql
          readOnly: true
        livenessProbe:
          exec:
            command:
            - /bin/sh
            - -c
            - exec pg_isready -U postgres -h 127.0.0.1 -p 5432
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          exec:
            command:
            - /bin/sh
            - -c
            - |
              pg_isready -U postgres -h 127.0.0.1 -p 5432 &&
              psql -U postgres -h 127.0.0.1 -lqt | cut -d \| -f 1 | grep -qw myapp
          initialDelaySeconds: 10
          periodSeconds: 5
      volumes:
      - name: postgres-config
        configMap:
          name: postgres-config
      - name: config
        configMap:
          name: postgres-config
          defaultMode: 0755
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: ssd-retain
      resources:
        requests:
          storage: 200Gi

MongoDB Replica

MongoDB Replica Sets proporcionan alta disponibilidad y redundancia de datos mediante replicación automática entre múltiples instancias. La configuración en Kubernetes requiere inicialización cuidadosa del replica set, gestión de roles primary/secondary, y configuración de authentication y authorization.

# MongoDB Replica Set con autenticación
apiVersion: v1
kind: Secret
metadata:
  name: mongodb-secret
  namespace: database
type: Opaque
data:
  mongodb-root-username: YWRtaW4=  # admin
  mongodb-root-password: <base64-encoded-password>
  mongodb-replica-set-key: <base64-encoded-keyfile>

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: mongodb-config
  namespace: database
data:
  mongod.conf: |
    storage:
      dbPath: /data/db
      journal:
        enabled: true
      wiredTiger:
        engineConfig:
          cacheSizeGB: 1
    systemLog:
      destination: file
      logAppend: true
      path: /var/log/mongodb/mongod.log
      verbosity: 1
    net:
      port: 27017
      bindIp: 0.0.0.0
    processManagement:
      timeZoneInfo: /usr/share/zoneinfo
    replication:
      replSetName: rs0
    security:
      authorization: enabled
      keyFile: /etc/secrets/mongodb-replica-set-key
      
  init-replica-set.js: |
    try {
      rs.status();
      print("Replica set already initialized");
    } catch (e) {
      print("Initializing replica set...");
      rs.initiate({
        _id: "rs0",
        members: [
          { _id: 0, host: "mongodb-0.mongodb-headless.database.svc.cluster.local:27017", priority: 2 },
          { _id: 1, host: "mongodb-1.mongodb-headless.database.svc.cluster.local:27017", priority: 1 },
          { _id: 2, host: "mongodb-2.mongodb-headless.database.svc.cluster.local:27017", priority: 1 }
        ]
      });
      
      print("Waiting for replica set to be ready...");
      while (!rs.isMaster().ismaster) {
        sleep(1000);
      }
      
      print("Creating admin user...");
      db = db.getSiblingDB('admin');
      db.createUser({
        user: "admin",
        pwd: "$MONGO_INITDB_ROOT_PASSWORD",
        roles: [
          { role: "root", db: "admin" },
          { role: "clusterAdmin", db: "admin" },
          { role: "dbAdminAnyDatabase", db: "admin" },
          { role: "userAdminAnyDatabase", db: "admin" },
          { role: "readWriteAnyDatabase", db: "admin" }
        ]
      });
      
      print("Replica set initialized successfully");
    }

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mongodb
  namespace: database
spec:
  serviceName: mongodb-headless
  replicas: 3
  selector:
    matchLabels:
      app: mongodb
  template:
    metadata:
      labels:
        app: mongodb
    spec:
      initContainers:
      - name: install-tools
        image: busybox:1.35
        command:
        - sh
        - -c
        - |
          # Create directories
          mkdir -p /var/log/mongodb
          chown -R 999:999 /var/log/mongodb
          
          # Set up keyfile permissions
          cp /tmp/keyfile/mongodb-replica-set-key /etc/secrets/
          chmod 400 /etc/secrets/mongodb-replica-set-key
          chown 999:999 /etc/secrets/mongodb-replica-set-key
        volumeMounts:
        - name: secrets
          mountPath: /etc/secrets
        - name: keyfile-temp
          mountPath: /tmp/keyfile
        - name: logs
          mountPath: /var/log/mongodb
      containers:
      - name: mongodb
        image: mongo:7.0
        command:
        - mongod
        - --config=/etc/mongod.conf
        ports:
        - containerPort: 27017
          name: mongodb
        env:
        - name: MONGO_INITDB_ROOT_USERNAME
          valueFrom:
            secretKeyRef:
              name: mongodb-secret
              key: mongodb-root-username
        - name: MONGO_INITDB_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mongodb-secret
              key: mongodb-root-password
        resources:
          requests:
            cpu: 500m
            memory: 2Gi
          limits:
            cpu: 1000m
            memory: 4Gi
        volumeMounts:
        - name: data
          mountPath: /data/db
        - name: config
          mountPath: /etc/mongod.conf
          subPath: mongod.conf
        - name: secrets
          mountPath: /etc/secrets
          readOnly: true
        - name: logs
          mountPath: /var/log/mongodb
        livenessProbe:
          exec:
            command:
            - mongo
            - --eval
            - "db.adminCommand('ping')"
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
        readinessProbe:
          exec:
            command:
            - mongo
            - --eval
            - "db.adminCommand('ping')"
          initialDelaySeconds: 10
          periodSeconds: 5
          timeoutSeconds: 3
      - name: replica-set-init
        image: mongo:7.0
        command:
        - bash
        - -c
        - |
          # Wait for MongoDB to be ready
          until mongo --host localhost:27017 --eval "db.adminCommand('ping')"; do
            echo "Waiting for MongoDB to start..."
            sleep 5
          done
          
          # Initialize replica set only on first pod
          if [[ $(hostname) == "mongodb-0" ]]; then
            echo "Initializing replica set..."
            envsubst /scripts/init-replica-set.js > /tmp/init.js
            mongo /tmp/init.js
          fi
          
          # Keep container running
          tail -f /dev/null
        env:
        - name: MONGO_INITDB_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mongodb-secret
              key: mongodb-root-password
        volumeMounts:
        - name: config
          mountPath: /scripts
      volumes:
      - name: config
        configMap:
          name: mongodb-config
      - name: secrets
        emptyDir: {}
      - name: keyfile-temp
        secret:
          secretName: mongodb-secret
          items:
          - key: mongodb-replica-set-key
            path: mongodb-replica-set-key
      - name: logs
        emptyDir: {}
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: ssd-retain
      resources:
        requests:
          storage: 100Gi

Message Queues y Event Streaming

Este punto requiere consideración cuidadosa en la implementación.

Apache Kafka Cluster

Apache Kafka es una plataforma de streaming distribuida que requiere configuración cuidadosa de brokers, topics, replication factors y consumer groups. La implementación en Kubernetes involucra gestión de identidades de brokers, configuración de networking entre nodos, y integration con ZooKeeper para metadata management.

# ZooKeeper ensemble para Kafka
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: zookeeper
  namespace: messaging
spec:
  serviceName: zookeeper-headless
  replicas: 3
  selector:
    matchLabels:
      app: zookeeper
  template:
    metadata:
      labels:
        app: zookeeper
    spec:
      containers:
      - name: zookeeper
        image: confluentinc/cp-zookeeper:7.4.0
        ports:
        - containerPort: 2181
          name: client
        - containerPort: 2888
          name: server
        - containerPort: 3888
          name: leader-election
        env:
        - name: ZOOKEEPER_SERVER_ID
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: ZOOKEEPER_CLIENT_PORT
          value: "2181"
        - name: ZOOKEEPER_TICK_TIME
          value: "2000"
        - name: ZOOKEEPER_INIT_LIMIT
          value: "5"
        - name: ZOOKEEPER_SYNC_LIMIT
          value: "2"
        - name: ZOOKEEPER_SERVERS
          value: "zookeeper-0.zookeeper-headless.messaging.svc.cluster.local:2888:3888;zookeeper-1.zookeeper-headless.messaging.svc.cluster.local:2888:3888;zookeeper-2.zookeeper-headless.messaging.svc.cluster.local:2888:3888"
        resources:
          requests:
            cpu: 250m
            memory: 512Mi
          limits:
            cpu: 500m
            memory: 1Gi
        volumeMounts:
        - name: data
          mountPath: /var/lib/zookeeper/data
        - name: logs
          mountPath: /var/lib/zookeeper/log
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: ssd-retain
      resources:
        requests:
          storage: 10Gi
  - metadata:
      name: logs
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: ssd-retain
      resources:
        requests:
          storage: 10Gi

---
# Kafka Broker Cluster
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: kafka
  namespace: messaging
spec:
  serviceName: kafka-headless
  replicas: 3
  selector:
    matchLabels:
      app: kafka
  template:
    metadata:
      labels:
        app: kafka
    spec:
      initContainers:
      - name: init-config
        image: confluentinc/cp-kafka:7.4.0
        command:
        - bash
        - -c
        - |
          # Extract broker ID from hostname
          [[ $(hostname) =~ -([0-9]+)$ ]] || exit 1
          export KAFKA_BROKER_ID=${BASH_REMATCH[1]}
          
          # Generate server configuration
          cat > /tmp/server.properties <EOF
          broker.id=$KAFKA_BROKER_ID
          listeners=PLAINTEXT://0.0.0.0:9092,INTERNAL://0.0.0.0:9093
          advertised.listeners=PLAINTEXT://$(hostname).kafka-headless.messaging.svc.cluster.local:9092,INTERNAL://$(hostname).kafka-headless.messaging.svc.cluster.local:9093
          listener.security.protocol.map=PLAINTEXT:PLAINTEXT,INTERNAL:PLAINTEXT
          inter.broker.listener.name=INTERNAL
          
          num.network.threads=8
          num.io.threads=8
          socket.send.buffer.bytes=102400
          socket.receive.buffer.bytes=102400
          socket.request.max.bytes=104857600
          
          log.dirs=/var/lib/kafka/data
          num.partitions=3
          num.recovery.threads.per.data.dir=1
          offsets.topic.replication.factor=3
          transaction.state.log.replication.factor=3
          transaction.state.log.min.isr=2
          default.replication.factor=3
          min.insync.replicas=2
          
          log.retention.hours=168
          log.retention.bytes=1073741824
          log.segment.bytes=1073741824
          log.retention.check.interval.ms=300000
          
          zookeeper.connect=zookeeper-0.zookeeper-headless.messaging.svc.cluster.local:2181,zookeeper-1.zookeeper-headless.messaging.svc.cluster.local:2181,zookeeper-2.zookeeper-headless.messaging.svc.cluster.local:2181
          zookeeper.connection.timeout.ms=18000
          
          group.initial.rebalance.delay.ms=3000
          auto.create.topics.enable=false
          delete.topic.enable=true
          EOF
          
          cp /tmp/server.properties /config/
        volumeMounts:
        - name: config
          mountPath: /config
      containers:
      - name: kafka
        image: confluentinc/cp-kafka:7.4.0
        ports:
        - containerPort: 9092
          name: kafka
        - containerPort: 9093
          name: internal
        env:
        - name: KAFKA_OPTS
          value: "-Dlogging.level=INFO"
        - name: KAFKA_HEAP_OPTS
          value: "-Xmx2G -Xms2G"
        command:
        - /bin/bash
        - -c
        - |
          exec kafka-server-start /config/server.properties
        resources:
          requests:
            cpu: 1000m
            memory: 3Gi
          limits:
            cpu: 2000m
            memory: 4Gi
        volumeMounts:
        - name: data
          mountPath: /var/lib/kafka/data
        - name: config
          mountPath: /config
        readinessProbe:
          exec:
            command:
            - bash
            - -c
            - |
              kafka-broker-api-versions --bootstrap-server localhost:9092
          initialDelaySeconds: 60
          periodSeconds: 10
        livenessProbe:
          exec:
            command:
            - bash
            - -c
            - |
              kafka-broker-api-versions --bootstrap-server localhost:9092
          initialDelaySeconds: 120
          periodSeconds: 30
      volumes:
      - name: config
        emptyDir: {}
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: ssd-retain
      resources:
        requests:
          storage: 100Gi

Redis Cluster para Caching Distribuido

Redis Cluster proporciona sharding automático y alta disponibilidad para cargas de trabajo de caching intensivo. La configuración requiere inicialización del cluster, gestión de slots de hash, y configuración de replication entre master y slave nodes.

# Redis Cluster configuration
apiVersion: v1
kind: ConfigMap
metadata:
  name: redis-cluster-config
  namespace: cache
data:
  redis.conf: |
    port 6379
    cluster-enabled yes
    cluster-config-file nodes.conf
    cluster-node-timeout 5000
    appendonly yes
    appendfsync everysec
    
    # Memory management
    maxmemory 2gb
    maxmemory-policy allkeys-lru
    
    # Network optimization
    tcp-keepalive 60
    timeout 300
    
    # Logging
    loglevel notice
    logfile /var/log/redis/redis.log
    
    # Security
    protected-mode no
    requirepass ${REDIS_PASSWORD}
    masterauth ${REDIS_PASSWORD}
    
    # Persistence tuning
    save 900 1
    save 300 10
    save 60 10000
    stop-writes-on-bgsave-error yes
    rdbcompression yes
    rdbchecksum yes
    
    # AOF tuning
    auto-aof-rewrite-percentage 100
    auto-aof-rewrite-min-size 64mb
    aof-load-truncated yes
    
  cluster-init.sh: |
    #!/bin/bash
    set -e
    
    # Wait for all pods to be ready
    echo "Waiting for all Redis pods to be ready..."
    for i in {0..5}; do
      until redis-cli -h redis-cluster-$i.redis-headless.cache.svc.cluster.local -a $REDIS_PASSWORD ping; do
        echo "Waiting for redis-cluster-$i..."
        sleep 5
      done
    done
    
    # Check if cluster is already initialized
    if redis-cli -h redis-cluster-0.redis-headless.cache.svc.cluster.local -a $REDIS_PASSWORD cluster nodes | grep -q master; then
      echo "Cluster already initialized"
      exit 0
    fi
    
    # Create cluster
    echo "Initializing Redis cluster..."
    redis-cli --cluster create \
      redis-cluster-0.redis-headless.cache.svc.cluster.local:6379 \
      redis-cluster-1.redis-headless.cache.svc.cluster.local:6379 \
      redis-cluster-2.redis-headless.cache.svc.cluster.local:6379 \
      redis-cluster-3.redis-headless.cache.svc.cluster.local:6379 \
      redis-cluster-4.redis-headless.cache.svc.cluster.local:6379 \
      redis-cluster-5.redis-headless.cache.svc.cluster.local:6379 \
      --cluster-replicas 1 \
      --cluster-yes \
      -a $REDIS_PASSWORD
    
    echo "Redis cluster initialized successfully"

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-cluster
  namespace: cache
spec:
  serviceName: redis-headless
  replicas: 6
  selector:
    matchLabels:
      app: redis-cluster
  template:
    metadata:
      labels:
        app: redis-cluster
    spec:
      initContainers:
      - name: init-redis
        image: redis:7-alpine
        command:
        - sh
        - -c
        - |
          # Create config with password substitution
          envsubst /tmp/redis.conf > /etc/redis/redis.conf
          chown redis:redis /etc/redis/redis.conf
          
          # Create log directory
          mkdir -p /var/log/redis
          chown redis:redis /var/log/redis
        env:
        - name: REDIS_PASSWORD
          valueFrom:
            secretKeyRef:
              name: redis-secret
              key: password
        volumeMounts:
        - name: config
          mountPath: /etc/redis
        - name: config-template
          mountPath: /tmp
        - name: logs
          mountPath: /var/log/redis
      containers:
      - name: redis
        image: redis:7-alpine
        command:
        - redis-server
        - /etc/redis/redis.conf
        - --cluster-announce-ip
        - $(POD_IP)
        ports:
        - containerPort: 6379
          name: client
        - containerPort: 16379
          name: gossip
        env:
        - name: POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: REDIS_PASSWORD
          valueFrom:
            secretKeyRef:
              name: redis-secret
              key: password
        resources:
          requests:
            cpu: 250m
            memory: 3Gi
          limits:
            cpu: 500m
            memory: 4Gi
        volumeMounts:
        - name: data
          mountPath: /data
        - name: config
          mountPath: /etc/redis
        - name: logs
          mountPath: /var/log/redis
        livenessProbe:
          exec:
            command:
            - redis-cli
            - -a
            - $(REDIS_PASSWORD)
            - ping
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          exec:
            command:
            - redis-cli
            - -a
            - $(REDIS_PASSWORD)
            - ping
          initialDelaySeconds: 10
          periodSeconds: 5
      volumes:
      - name: config
        emptyDir: {}
      - name: config-template
        configMap:
          name: redis-cluster-config
      - name: logs
        emptyDir: {}
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: ssd-retain
      resources:
        requests:
          storage: 50Gi

---
# Job para inicializar el cluster
apiVersion: batch/v1
kind: Job     # Tipo de recurso Kubernetes
metadata:
  name: redis-cluster-init
  namespace: cache
spec:
  template:
    spec:
      restartPolicy: OnFailure
      containers:
      - name: cluster-init
        image: redis:7-alpine
        command:
        - bash
        - /scripts/cluster-init.sh
        env:
        - name: REDIS_PASSWORD
          valueFrom:
            secretKeyRef:
              name: redis-secret
              key: password
        volumeMounts:
        - name: scripts
          mountPath: /scripts
      volumes:
      - name: scripts
        configMap:
          name: redis-cluster-config
          defaultMode: 0755

Estrategias de Backup y Recovery

Este punto requiere consideración cuidadosa en la implementación.

Automated Backup Strategies

Las estrategias de backup para aplicaciones stateful deben considerar consistencia de datos, point-in-time recovery, y restoration procedures. Esto incluye scheduled snapshots, continuous archiving, y cross-region replication.

# CronJob para backups automatizados de PostgreSQL
apiVersion: batch/v1
kind: CronJob
metadata:
  name: postgres-backup
  namespace: database
spec:
  schedule: "0 2 * * *"  # Daily at 2 AM
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: postgres-backup
            image: postgres:15-alpine
            command:
            - bash
            - -c
            - |
              set -e
              
              TIMESTAMP=$(date +%Y%m%d_%H%M%S)
              BACKUP_NAME="postgres_backup_${TIMESTAMP}"
              
              # Create database backup
              echo "Starting backup: $BACKUP_NAME"
              PGPASSWORD=$POSTGRES_PASSWORD pg_dump \
                -h postgres-cluster-0.postgres-headless.database.svc.cluster.local \
                -U postgres \
                -d myapp \
                --verbose \
                --no-password \
                --format=custom \
                --compress=9 \
                > /backups/${BACKUP_NAME}.dump
              
              # Upload to S3
              aws s3 cp /backups/${BACKUP_NAME}.dump s3://$S3_BUCKET/postgres-backups/
              
              # Create WAL archive backup
              echo "Creating WAL archive backup..."
              mkdir -p /backups/wal_archive_${TIMESTAMP}
              
              # Get WAL files from primary
              PGPASSWORD=$POSTGRES_PASSWORD psql \
                -h postgres-cluster-0.postgres-headless.database.svc.cluster.local \
                -U postgres \
                -d myapp \
                -c "SELECT pg_start_backup('backup_${TIMESTAMP}', false, false);"
              
              # Copy WAL files
              rsync -av postgres-cluster-0.postgres-headless.database.svc.cluster.local:/var/lib/postgresql/data/pg_wal/ /backups/wal_archive_${TIMESTAMP}/
              
              PGPASSWORD=$POSTGRES_PASSWORD psql \
                -h postgres-cluster-0.postgres-headless.database.svc.cluster.local \
                -U postgres \
                -d myapp \
                -c "SELECT pg_stop_backup(false, true);"
              
              # Upload WAL archive
              tar czf /backups/wal_archive_${TIMESTAMP}.tar.gz -C /backups wal_archive_${TIMESTAMP}
              aws s3 cp /backups/wal_archive_${TIMESTAMP}.tar.gz s3://$S3_BUCKET/postgres-wal/
              
              # Cleanup old local files
              rm -rf /backups/wal_archive_${TIMESTAMP}
              rm -f /backups/${BACKUP_NAME}.dump /backups/wal_archive_${TIMESTAMP}.tar.gz
              
              # Cleanup old S3 backups (keep 30 days)
              aws s3api list-objects-v2 --bucket $S3_BUCKET --prefix postgres-backups/ --query 'Contents[?LastModified<`'$(date -d '30 days ago' --iso-8601)'`].Key' --output text | xargs -r -n1 -I {} aws s3 rm s3://$S3_BUCKET/{}
              
              echo "Backup completed: $BACKUP_NAME"
            env:
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-secret
                  key: password
            - name: S3_BUCKET
              value: "my-company-db-backups"
            - name: AWS_ACCESS_KEY_ID
              valueFrom:
                secretKeyRef:
                  name: aws-credentials
                  key: access-key-id
            - name: AWS_SECRET_ACCESS_KEY
              valueFrom:
                secretKeyRef:
                  name: aws-credentials
                  key: secret-access-key
            - name: AWS_DEFAULT_REGION
              value: "us-east-1"
            resources:
              requests:
                cpu: 500m
                memory: 1Gi
              limits:
                cpu: 1000m
                memory: 2Gi
            volumeMounts:
            - name: backup-storage
              mountPath: /backups
          volumes:
          - name: backup-storage
            emptyDir:
              sizeLimit: 10Gi

Mejores Prácticas y Patrones Avanzados

Este punto requiere consideración cuidadosa en la implementación.

Security y Compliance

La seguridad de aplicaciones stateful requiere consideraciones especiales para encryption at rest y in transit, access controls granulares, audit logging, y compliance con regulaciones de datos.

# Pod Security Policy para aplicaciones stateful
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: stateful-app-psp
spec:
  privileged: false
  allowPrivilegeEscalation: false
  requiredDropCapabilities:
    - ALL
  volumes:
    - 'persistentVolumeClaim'
    - 'secret'
    - 'configMap'
    - 'emptyDir'
  runAsUser:
    rule: 'MustRunAsNonRoot'
  seLinux:
    rule: 'RunAsAny'
  fsGroup:
    rule: 'RunAsAny'
  readOnlyRootFilesystem: false

---
# Network Policy para microsegmentación
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: database-network-policy
  namespace: database
spec:
  podSelector:
    matchLabels:
      app: postgres
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: application
    - podSelector:
        matchLabels:
          app: backend-api
    ports:
    - protocol: TCP
      port: 5432
  egress:
  - to: []
    ports:
    - protocol: UDP
      port: 53
  - to:
    - namespaceSelector:
        matchLabels:
          name: monitoring
    ports:
    - protocol: TCP
      port: 9090

Monitoring y Observabilidad

El monitoreo de aplicaciones stateful requiere métricas específicas para performance de storage, replication lag, connection pools, y health de clusters distribuidos.

# ServiceMonitor para PostgreSQL con métricas customizadas
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: postgres-metrics
  namespace: database
spec:
  selector:
    matchLabels:
      app: postgres
  endpoints:
  - port: postgres
    interval: 30s
    path: /metrics
---
# Grafana Dashboard ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: postgres-dashboard
  namespace: monitoring
data:
  postgres-dashboard.json: |
    {
      "dashboard": {
        "title": "PostgreSQL StatefulSet Dashboard",
        "panels": [
          {
            "title": "Database Connections",
            "type": "graph",
            "targets": [
              {
                "expr": "sum(pg_stat_database_numbackends) by (instance)",
                "legendFormat": "{{ instance }}"
              }
            ]
          },
          {
            "title": "Replication Lag",
            "type": "graph",
            "targets": [
              {
                "expr": "pg_replication_lag",
                "legendFormat": "Lag (seconds)"
              }
            ]
          }
        ]
      }
    }

Conclusión

La gestión de aplicaciones stateful en Kubernetes representa uno de los desafíos más complejos en la orquestación de contenedores moderna. Sin embargo, con las herramientas, patrones y mejores prácticas adecuadas, es posible operar bases de datos, message queues y otras aplicaciones con estado de manera confiable y escalable.

Los StatefulSets proporcionan la base fundamental para aplicaciones stateful, ofreciendo identidades estables, almacenamiento persistente y garantías de ordenamiento. La combinación de Persistent Volumes, Storage Classes y Volume Claim Templates permite gestión flexible y escalable del almacenamiento.

La implementación exitosa requiere consideración cuidadosa de patrones de replicación, estrategias de backup y recovery, security policies, y monitoring comprehensivo. Las organizaciones que dominan estas técnicas pueden aprovechar los beneficios de Kubernetes mientras mantienen la persistencia y confiabilidad requeridas por aplicaciones críticas de negocio.

El futuro de las aplicaciones stateful en Kubernetes continúa evolucionando con nuevos operadores, improved storage solutions, y better integration con cloud services. La inversión en comprender y dominar estos patrones proporcionará dividendos significativos en términos de operaciones más eficientes, mayor confiabilidad y mejor utilización de recursos.

Recursos Adicionales