Problema

En entornos de desarrollo y producción basados en Kubernetes es frecuente que los equipos necesiten separar claramente los componentes de una aplicación: un frontend (React, Angular, etc.), un backend (API en Node, Go, Python…) y la base de datos. La dificultad surge al decidir dónde y cómo almacenar la configuración (URLs, puertos, credenciales) y cómo exponer la base de datos sin acoplarla al pod del backend. Muchos usuarios intentan colocar variables en archivos .env dentro del contenedor o montar la base de datos como un contenedor adicional dentro del mismo pod, lo que genera problemas de escalabilidad, seguridad y mantenimiento.

Causa

  1. Uso de archivos .env dentro del contenedor
    Copiar un .env al contenedor rompe la idea de inmutabilidad; cualquier cambio requiere reconstruir la imagen o redeployar el pod.

  2. Variables de entorno hardcodeadas en el manifiesto
    Definir DB_HOST, DB_PORT directamente en el Deployment obliga a mantener varios manifiestos para cada entorno (dev, staging, prod).

  3. Base de datos como contenedor “side‑car”
    Ejecutar PostgreSQL o MySQL dentro del mismo pod que el backend funciona en pruebas locales, pero en producción impide escalar el backend de forma independiente y complica la persistencia de datos.

  4. Falta de separación entre datos sensibles y no sensibles
    Guardar contraseñas en ConfigMaps o en el mismo archivo de configuración expone secretos a usuarios que solo deberían ver la configuración pública.

Solución

Adoptar el modelo nativo de Kubernetes:

  1. ConfigMap para la configuración no sensible (host, puerto, nombres de colecciones).
  2. Secret para credenciales y cualquier dato que deba estar cifrado o al menos codificado en base64.
  3. PersistentVolumeClaim (PVC) para la base de datos, de forma que el pod del backend solo consuma el servicio, mientras que el PVC se asocia a un StatefulSet o a un Deployment dedicado a la base de datos.
  4. Service para exponer la base de datos dentro del clúster y permitir que el backend la descubra mediante DNS (postgres-service.default.svc.cluster.local).

Paso a paso

1. ConfigMap con parámetros de conexión

kubectl apply -f - <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  DB_HOST: postgres-service
  DB_PORT: "5432"
  DB_NAME: myapp
EOF

2. Secret con credenciales

kubectl create secret generic db-credentials \
  --from-literal=username=appuser \
  --from-literal=password=$(openssl rand -base64 12)

3. PVC para PostgreSQL

kubectl apply -f - <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pg-data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
EOF

4. Deployment del backend que consume ConfigMap y Secret

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
        - name: api
          image: myregistry/backend:latest
          env:
            - name: DB_HOST
              valueFrom:
                configMapKeyRef:
                  name: app-config
                  key: DB_HOST
            - name: DB_PORT
              valueFrom:
                configMapKeyRef:
                  name: app-config
                  key: DB_PORT
            - name: DB_NAME
              valueFrom:
                configMapKeyRef:
                  name: app-config
                  key: DB_NAME
            - name: DB_USER
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: username
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: password
          ports:
            - containerPort: 8080
EOF

5. Deployment (o StatefulSet) de PostgreSQL usando el PVC

kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: "postgres-service"
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:15
          env:
            - name: POSTGRES_DB
              valueFrom:
                configMapKeyRef:
                  name: app-config
                  key: DB_NAME
            - name: POSTGRES_USER
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: username
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: password
          ports:
            - containerPort: 5432
          volumeMounts:
            - name: pg-storage
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
    - metadata:
        name: pg-storage
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 10Gi
EOF

6. Service para la base de datos

kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: postgres-service
spec:
  selector:
    app: postgres
  ports:
    - protocol: TCP
      port: 5432
      targetPort: 5432
EOF

Con este esquema:

  • Los pods del backend pueden escalar sin tocar la base de datos.
  • La base de datos persiste en un PVC independiente, lo que permite migraciones o backups sin afectar al backend.
  • Los cambios de entorno (dev → prod) solo requieren actualizar los ConfigMaps/Secrets o crear nuevos objetos con nombres diferentes, sin tocar los despliegues.

Cuándo aplicar esta solución

  • Entornos multi‑ambiente (dev, staging, prod) donde la configuración varía pero la imagen del contenedor es la misma.
  • Aplicaciones con requisitos de alta disponibilidad que necesitan escalar el backend sin reiniciar la base de datos.
  • Equipos que siguen la filosofía GitOps: los manifiestos declarativos pueden versionarse y aplicar cambios mediante CI/CD.
  • Escenarios donde la seguridad es crítica: separar secretos en objetos Secret permite habilitar encriptación en reposo (KMS) y limitar su exposición mediante RBAC.

No aplicar

  • Proyectos extremadamente pequeños o pruebas rápidas donde la sobrecarga de crear ConfigMaps/Secrets no justifica el beneficio.
  • Bases de datos externas gestionadas (Cloud SQL, RDS) donde el almacenamiento persiste fuera del clúster; en ese caso solo se necesita un Secret con la cadena de conexión.

Verificación

  1. Comprobar que el backend recibe variables

    kubectl exec -it $(kubectl get pod -l app=backend -o jsonpath="{.items[0].metadata.name}") -- env | grep DB_
    

    Deberían aparecer DB_HOST, DB_PORT, DB_NAME, DB_USER y DB_PASSWORD.

  2. Validar la conectividad

    kubectl exec -it $(kubectl get pod -l app=backend -o jsonpath="{.items[0].metadata.name}") -- curl -s http://postgres-service:5432
    

    La respuesta debe ser el banner de PostgreSQL o un código 200 si el backend expone un endpoint de healthcheck.

  3. Revisar la persistencia

    • Inserta un registro mediante el backend.
    • Elimina el pod del backend (kubectl delete pod -l app=backend).
    • Vuelve a crear el pod y verifica que el registro sigue presente.
  4. Auditar secretos

    kubectl get secret db-credentials -o yaml
    

    Asegúrate de que los valores aparecen codificados en base64 y que el RBAC restringe el acceso a los equipos que realmente lo necesiten.

Notas adicionales

  • Encriptación de Secrets: habilita la encriptación a nivel de etcd o usa un proveedor KMS (AWS KMS, GCP CMEK) para cumplir con requisitos de compliance.
  • Versionado de ConfigMaps: Kubernetes no versiona objetos; si necesitas historial, guarda los archivos YAML en Git y aplica cambios mediante kubectl apply.
  • Backup de PVC: para bases de datos críticas, programa snapshots del PVC con la herramienta del proveedor de almacenamiento (Velero, CSI snapshots).
  • Health checks: define livenessProbe y readinessProbe en el Deployment del backend para que Kubernetes retire pods que no puedan conectar a la base de datos.
  • Escalado horizontal: una vez que la base de datos está desacoplada, puedes usar HorizontalPodAutoscaler para el backend sin preocuparte por la carga en la base de datos.

Con estos pasos tienes una arquitectura modular, segura y preparada para escalar, aplicable tanto a proyectos personales como a entornos empresariales que eventualmente migrarán a servicios gestionados en la nube.