Capitulo 12: Bases de Datos en K3s

Por: Artiko
k3skubernetespostgresqlstatefulsetbases-de-datos

Capitulo 12: Bases de Datos en K3s

< Volver al Indice del Tutorial

StatefulSets vs Deployments

En los capitulos anteriores usamos Deployments para desplegar aplicaciones. Los Deployments son ideales para aplicaciones stateless donde cualquier pod es intercambiable. Pero las bases de datos necesitan garantias adicionales que un Deployment no ofrece.

CaracteristicaDeploymentStatefulSet
Nombre del podAleatorio (app-7f8d4)Ordenado (db-0, db-1)
Orden de arranqueSimultaneoSecuencial (0, luego 1)
AlmacenamientoCompartido o efimeroPVC unico por pod
Identidad de redVariableEstable via Headless Svc
Estrategia de actualizacionRolling updateOrden inverso (N, N-1…)

Las bases de datos necesitan StatefulSets porque requieren identidad estable, almacenamiento que no cambie entre reinicios y un orden predecible de arranque para la replicacion.

Anatomia de un StatefulSet

Un StatefulSet tiene tres caracteristicas unicas:

Identidad estable: los pods se nombran secuencialmente. Si el StatefulSet se llama postgres, los pods seran postgres-0, postgres-1, postgres-2. Tras un reinicio, cada pod mantiene su mismo nombre.

Almacenamiento persistente por pod: usa volumeClaimTemplates para crear un PVC independiente para cada pod. El pod postgres-0 siempre se vincula al PVC data-postgres-0.

Orden de arranque: los pods arrancan de forma secuencial. postgres-0 debe estar Running antes de que postgres-1 se cree. Al escalar hacia abajo, se eliminan en orden inverso.

Headless Service

Las bases de datos necesitan un Headless Service (sin ClusterIP) para que cada pod tenga un nombre DNS estable:

apiVersion: v1
kind: Service
metadata:
  name: postgres
spec:
  clusterIP: None
  selector:
    app: postgres
  ports:
    - port: 5432
      targetPort: 5432

Con clusterIP: None, Kubernetes no asigna una IP virtual. En su lugar, crea registros DNS individuales para cada pod:

postgres-0.postgres.default.svc.cluster.local
postgres-1.postgres.default.svc.cluster.local

Esto permite que una replica sepa exactamente como contactar al primario (postgres-0) para replicacion. Con un Service normal, el DNS resuelve a cualquier pod aleatorio, lo cual no funciona para bases de datos.

Desplegar PostgreSQL con StatefulSet

Primero necesitamos un Secret para la contrasena:

apiVersion: v1
kind: Secret
metadata:
  name: postgres-secret
type: Opaque
stringData:
  POSTGRES_PASSWORD: "mi-password-seguro"
  POSTGRES_USER: "appuser"
  POSTGRES_DB: "appdb"

El Headless Service:

apiVersion: v1
kind: Service
metadata:
  name: postgres
spec:
  clusterIP: None
  selector:
    app: postgres
  ports:
    - port: 5432
      targetPort: 5432

El StatefulSet completo:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16-alpine
          ports:
            - containerPort: 5432
          envFrom:
            - secretRef:
                name: postgres-secret
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
              subPath: pgdata
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "500m"
          readinessProbe:
            exec:
              command: ["pg_isready", "-U", "appuser"]
            initialDelaySeconds: 5
            periodSeconds: 10
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 10Gi

El campo subPath: pgdata es importante. PostgreSQL requiere que el directorio de datos este vacio al inicializar. Sin subPath, los archivos del PVC (como lost+found) causan un error de inicializacion.

Aplica todo:

kubectl apply -f postgres-secret.yaml
kubectl apply -f postgres-headless.yaml
kubectl apply -f postgres-statefulset.yaml

Verifica el estado:

kubectl get statefulset,pods,pvc

El pod postgres-0 debe estar Running y el PVC data-postgres-0 debe estar Bound.

Prueba la conexion:

kubectl exec -it postgres-0 -- psql -U appuser -d appdb -c "SELECT version();"

Conectar una App al PostgreSQL

Para conectar una aplicacion al PostgreSQL desplegado, usa el DNS del Headless Service como host. Dentro del mismo namespace:

postgres-0.postgres.default.svc.cluster.local

O simplemente:

postgres-0.postgres

En un Deployment de aplicacion, pasa la conexion como variable de entorno:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mi-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: mi-api
  template:
    metadata:
      labels:
        app: mi-api
    spec:
      containers:
        - name: api
          image: mi-api:latest
          env:
            - name: DATABASE_URL
              value: "postgresql://appuser:[email protected]:5432/appdb"

En produccion, la contrasena debe venir de un Secret en lugar de estar hardcodeada en el manifest.

Servicio Adicional para Conexiones

Ademas del Headless Service, puedes crear un Service normal para que las aplicaciones se conecten sin especificar el pod exacto:

apiVersion: v1
kind: Service
metadata:
  name: postgres-rw
spec:
  selector:
    app: postgres
  ports:
    - port: 5432
      targetPort: 5432

Las aplicaciones se conectan a postgres-rw:5432 y Kubernetes enruta al pod disponible. Esto es mas simple cuando solo tienes una replica.

Operadores de Bases de Datos

Para produccion, gestionar bases de datos manualmente con StatefulSets puede ser insuficiente. Los operadores automatizan tareas complejas:

CloudNativePG

CloudNativePG es el operador de PostgreSQL mas maduro para Kubernetes, desarrollado bajo la CNCF:

kubectl apply --server-side -f \
  https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.25/releases/cnpg-1.25.1.yaml

Con CloudNativePG, defines un cluster PostgreSQL como un recurso declarativo:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: mi-cluster-pg
spec:
  instances: 3
  storage:
    size: 10Gi
  backup:
    barmanObjectStore:
      destinationPath: "s3://mi-bucket/backups"

El operador se encarga de crear las replicas, configurar streaming replication, manejar failovers y ejecutar backups automaticamente.

Consideraciones para Produccion

Backups

Si usas StatefulSets manuales, los backups son tu responsabilidad:

kubectl exec postgres-0 -- pg_dump -U appuser appdb > backup.sql

Automatiza esto con un CronJob de Kubernetes o usa un operador que lo haga por ti.

Replicacion

PostgreSQL soporta streaming replication, pero configurarla manualmente en Kubernetes requiere scripts de inicializacion complejos. Los operadores simplifican esto drasticamente.

Cuando NO Poner la DB en K3s

Hay escenarios donde una base de datos externa es mejor opcion:

La regla general: si tu aplicacion es tolerante a un poco de downtime de la base de datos y tienes backups automatizados, K3s es viable. Para cero tolerancia a perdida de datos, usa un servicio gestionado o un operador robusto con almacenamiento replicado.


Siguiente: Capitulo 13: Helm —>