Capitulo 12: Bases de Datos en K3s
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.
| Caracteristica | Deployment | StatefulSet |
|---|---|---|
| Nombre del pod | Aleatorio (app-7f8d4) | Ordenado (db-0, db-1) |
| Orden de arranque | Simultaneo | Secuencial (0, luego 1) |
| Almacenamiento | Compartido o efimero | PVC unico por pod |
| Identidad de red | Variable | Estable via Headless Svc |
| Estrategia de actualizacion | Rolling update | Orden 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:
- Replicacion primario-replica.
- Failover automatico.
- Backups programados.
- Restauracion point-in-time.
- Actualizaciones de version con rolling updates.
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:
- Datos criticos sin backups automatizados: si no puedes automatizar backups confiables dentro del cluster.
- Cluster de un solo nodo: si el nodo falla, pierdes todo. Una base de datos gestionada (RDS, Cloud SQL) ofrece replicacion y backups integrados.
- Requisitos de rendimiento alto: el local-path provisioner no esta optimizado para I/O intensivo. Bases de datos con alta carga se benefician de storage dedicado.
- Compliance y regulaciones: algunos escenarios requieren que los datos esten en servicios certificados con SLAs especificos.
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 —>