Dia 10 - Pod con ConfigMap, Variable de Entorno y Volume Mount
Problema / Desafio
Crear un Pod llamado time-check en el namespace devops que registre la fecha/hora actual en un archivo de log cada cierto intervalo. El intervalo se define mediante un ConfigMap. El Pod debe:
- Usar el namespace
devops - Crear un ConfigMap
time-configconTIME_FREQ=8 - Crear un Pod
time-checkcon un contenedortime-checkusandobusybox:latest - Inyectar
TIME_FREQcomo variable de entorno desde el ConfigMap - Ejecutar
while true; do date; sleep $TIME_FREQ; doneescribiendo en/opt/security/time/time-check.log - Montar un volumen
log-volumeen/opt/security/time
Conceptos clave
ConfigMap
Un ConfigMap almacena datos de configuracion en pares clave-valor, separando la configuracion del contenedor. Permite cambiar valores sin reconstruir la imagen.
ConfigMap (time-config) Pod (time-check)
┌─────────────────────┐ ┌──────────────────────┐
│ TIME_FREQ = 8 │ ──────→ │ env: TIME_FREQ = 8 │
└─────────────────────┘ └──────────────────────┘
Formas de consumir un ConfigMap en un Pod:
| Metodo | Uso | Cuando usarlo |
|---|---|---|
| Variable de entorno | env.valueFrom.configMapKeyRef |
Cuando necesitas una o pocas claves especificas |
| envFrom | envFrom.configMapRef |
Cuando necesitas todas las claves del ConfigMap como variables |
| Volumen | volumes.configMap |
Cuando necesitas los datos como archivos |
ConfigMap NO es para datos sensibles
Un ConfigMap almacena datos en texto plano. Cualquier persona con acceso al namespace puede leer su contenido con kubectl get configmap -o yaml. No se debe usar para contrasenas, tokens, API keys o certificados.
Para datos sensibles existe Secret:
| ConfigMap | Secret | |
|---|---|---|
| Proposito | Configuracion general | Datos sensibles |
| Almacenamiento | Texto plano | Base64 (codificado, no encriptado) |
| Ejemplos | Puertos, URLs, feature flags, intervalos | Contrasenas, tokens, llaves SSH, certificados TLS |
| Visibilidad | Visible con kubectl get configmap -o yaml |
Visible con kubectl get secret -o yaml (en base64) |
| Limite de tamano | 1 MB | 1 MB |
# ConfigMap — configuracion general
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
TIME_FREQ: "8"
LOG_LEVEL: "info"
DB_HOST: "postgres.default.svc"
---
# Secret — datos sensibles
apiVersion: v1
kind: Secret
metadata:
name: app-secret
type: Opaque
data:
DB_PASSWORD: cGFzc3dvcmQxMjM= # "password123" en base64
API_KEY: bXktc2VjcmV0LWtleQ== # "my-secret-key" en base64
Importante: base64 no es encriptacion, es solo codificacion. Cualquiera puede decodificarlo:
Para encriptar Secrets en reposo (en etcd), se necesita habilitar Encryption at Rest en el cluster o usar herramientas externas como Sealed Secrets, Vault o External Secrets Operator.
Como usar un Secret en un Pod
# Como variable de entorno
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secret
key: DB_PASSWORD
# Como volumen (crea archivos con el contenido del secret)
volumes:
- name: secret-volume
secret:
secretName: app-secret
La sintaxis es casi identica a ConfigMap: configMapKeyRef → secretKeyRef, configMap → secret.
Crear un Secret de forma imperativa
# Desde literales
kubectl create secret generic app-secret \
--from-literal=DB_PASSWORD=password123 \
--from-literal=API_KEY=my-secret-key
# Desde un archivo
kubectl create secret generic tls-secret \
--from-file=cert.pem \
--from-file=key.pem
Kubernetes codifica los valores a base64 automaticamente al crearlo de forma imperativa.
Volumenes en Kubernetes: emptyDir vs PVC
emptyDir
Un volumen emptyDir es efimero. Se crea vacio en el momento en que el Pod se asigna a un nodo y se destruye cuando el Pod se elimina.
No existe como un recurso independiente de Kubernetes. No se puede ver con kubectl get volumes. Vive y muere con el Pod.
volumes:
- name: log-volume
emptyDir: {} # {} = configuracion por defecto (almacena en disco del nodo)
Opciones de emptyDir:
# En disco del nodo (por defecto)
emptyDir: {}
# En memoria (RAM) — mas rapido pero limitado y se pierde al reiniciar
emptyDir:
medium: Memory
sizeLimit: 100Mi
Casos de uso: - Cache temporal - Logs de prueba (como en este ejercicio) - Compartir archivos entre contenedores del mismo Pod (sidecar pattern)
PersistentVolume (PV) y PersistentVolumeClaim (PVC)
Para datos que deben sobrevivir a la eliminacion del Pod, se usa el sistema de PV/PVC:
PersistentVolume (PV) PersistentVolumeClaim (PVC) Pod
┌──────────────────────┐ ┌──────────────────────┐ ┌─────────┐
│ Recurso de storage │ ←bound→ │ Solicitud de storage │ ←mount→ │ Container│
│ (disco real) │ │ (cuanto necesito) │ │ │
│ 10Gi, NFS, EBS, etc │ │ 5Gi, ReadWriteOnce │ │ │
└──────────────────────┘ └──────────────────────┘ └─────────┘
Lo crea el admin Lo crea el developer Usa el PVC
- PV (PersistentVolume): representa un recurso de almacenamiento real (disco en AWS EBS, NFS share, disco local del nodo). Lo crea un administrador o se provisiona dinamicamente.
- PVC (PersistentVolumeClaim): es una solicitud de almacenamiento. El developer dice "necesito 5Gi con acceso ReadWriteOnce" y Kubernetes busca un PV que cumpla esos requisitos.
# PersistentVolume — el disco real
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /data/my-pv
---
# PersistentVolumeClaim — la solicitud
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
---
# Pod — usa el PVC
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
volumes:
- name: data-volume
persistentVolumeClaim:
claimName: my-pvc # Referencia al PVC, no al PV directamente
containers:
- name: app
image: nginx
volumeMounts:
- name: data-volume
mountPath: /data
Access Modes
| Modo | Abreviado | Descripcion |
|---|---|---|
ReadWriteOnce |
RWO | Un solo nodo puede montar el volumen en lectura/escritura |
ReadOnlyMany |
ROX | Multiples nodos pueden montar el volumen en solo lectura |
ReadWriteMany |
RWX | Multiples nodos pueden montar el volumen en lectura/escritura |
No todos los tipos de storage soportan todos los modos. Por ejemplo, AWS EBS solo soporta ReadWriteOnce.
Reclaim Policy
Que pasa con el PV cuando se elimina el PVC:
| Politica | Comportamiento |
|---|---|
Retain |
El PV y los datos se conservan. Requiere limpieza manual |
Delete |
El PV y el storage se eliminan automaticamente |
Recycle |
Deprecated. Borra los datos y marca el PV como disponible |
Comparacion completa
| emptyDir | hostPath | PVC | |
|---|---|---|---|
| Persistencia | Muere con el Pod | Muere con el nodo | Independiente |
| Se crea cuando | El Pod se asigna al nodo | El Pod se asigna al nodo | Se crea el recurso PVC |
| Visible como recurso | No | No | Si (kubectl get pvc) |
| Compartir entre Pods | No (solo contenedores del mismo Pod) | Si (Pods en el mismo nodo) | Si (depende del access mode) |
| Caso de uso | Cache, temp, logs de prueba | Acceso a archivos del nodo | Bases de datos, datos permanentes |
| Produccion | Solo para datos temporales | Evitar (acoplado al nodo) | Recomendado |
StorageClass y aprovisionamiento dinamico
En clusters reales no se crean PVs manualmente. Se usa un StorageClass que provisiona PVs automaticamente cuando se crea un PVC:
# PVC con StorageClass — Kubernetes crea el PV automaticamente
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
storageClassName: standard # StorageClass del cluster (gp2, standard, etc.)
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
El flujo con aprovisionamiento dinamico:
Redireccion de salida en el comando
El comando usa >> para redirigir la salida de date al archivo de log:
| Operador | Comportamiento |
|---|---|
> |
Sobreescribe el archivo en cada escritura |
>> |
Agrega al final del archivo (append) |
Se usa >> para que cada iteracion agregue una linea nueva al log sin borrar las anteriores.
Pasos
- Crear el namespace
devops - Crear el ConfigMap
time-configconTIME_FREQ=8 - Crear el manifiesto del Pod con el volumen, variable de entorno y comando
- Aplicar los manifiestos
- Verificar que el Pod esta corriendo y escribiendo logs
Comandos / Codigo
1. Crear el namespace
2. Crear el ConfigMap
Forma imperativa
Forma declarativa (YAML)
Verificar:
3. Manifiesto del Pod
apiVersion: v1
kind: Pod
metadata:
name: time-check
namespace: devops
spec:
volumes:
- name: log-volume
emptyDir: {}
containers:
- name: time-check
image: busybox:latest
env:
- name: TIME_FREQ
valueFrom:
configMapKeyRef:
name: time-config
key: TIME_FREQ
command:
- /bin/sh
- -c
- while true; do date >> /opt/security/time/time-check.log; sleep $TIME_FREQ; done
volumeMounts:
- name: log-volume
mountPath: /opt/security/time
Estructura del manifiesto explicada
Pod (time-check)
├── metadata
│ ├── name: time-check
│ └── namespace: devops
└── spec
├── volumes # Definicion del volumen (a nivel de Pod)
│ └── log-volume (emptyDir)
└── containers
└── time-check
├── image: busybox:latest
├── env # Variable de entorno desde ConfigMap
│ └── TIME_FREQ → configMapKeyRef → time-config.TIME_FREQ
├── command # Comando que ejecuta el contenedor
│ └── while true; do date >> ...log; sleep $TIME_FREQ; done
└── volumeMounts # Montar el volumen en el contenedor
└── log-volume → /opt/security/time
Puntos importantes:
volumesse define a nivel despecdel Pod (no dentro del container)volumeMountsse define dentro del container y referencia el volumen por nombreenv.valueFrom.configMapKeyReftoma el valor de una clave especifica del ConfigMap- El
commandusa/bin/sh -cpara interpretar el script con variables y redireccion
4. Aplicar los manifiestos
Si todo esta en un solo archivo time-check.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: time-config
namespace: devops
data:
TIME_FREQ: "8"
---
apiVersion: v1
kind: Pod
metadata:
name: time-check
namespace: devops
spec:
volumes:
- name: log-volume
emptyDir: {}
containers:
- name: time-check
image: busybox:latest
env:
- name: TIME_FREQ
valueFrom:
configMapKeyRef:
name: time-config
key: TIME_FREQ
command:
- /bin/sh
- -c
- while true; do date >> /opt/security/time/time-check.log; sleep $TIME_FREQ; done
volumeMounts:
- name: log-volume
mountPath: /opt/security/time
5. Verificar
# Ver las variables de entorno del contenedor
kubectl exec time-check -n devops -- env | grep TIME_FREQ
# Ver el contenido del log (debe tener una fecha cada 8 segundos)
kubectl exec time-check -n devops -- cat /opt/security/time/time-check.log
# Ver los logs en tiempo real
kubectl exec time-check -n devops -- tail -f /opt/security/time/time-check.log
Verificar el ConfigMap asociado al Pod
En la salida buscar la seccion Environment:
Y la seccion Mounts:
Flujo completo del ejercicio
1. kubectl create namespace devops
2. ConfigMap time-config (TIME_FREQ=8) en namespace devops
3. Pod time-check:
├── env: TIME_FREQ ← ConfigMap time-config
├── command: while true; do date >> .../time-check.log; sleep $TIME_FREQ; done
└── volumeMount: log-volume → /opt/security/time
4. Verificar: kubectl exec → cat time-check.log
Otras formas de inyectar ConfigMaps
envFrom (todas las claves como variables)
Con envFrom, todas las claves del ConfigMap se convierten en variables de entorno automaticamente. Util cuando el ConfigMap tiene muchas claves.
Como volumen (claves como archivos)
volumes:
- name: config-volume
configMap:
name: time-config
containers:
- name: time-check
volumeMounts:
- name: config-volume
mountPath: /etc/config
Esto crea un archivo /etc/config/TIME_FREQ con contenido 8. Util para archivos de configuracion completos.
Troubleshooting
| Problema | Solucion |
|---|---|
Pod en CreateContainerConfigError |
El ConfigMap no existe o el nombre/key no coinciden. Verificar con kubectl get configmap -n devops |
Pod en CrashLoopBackOff |
El comando tiene error de sintaxis. Verificar con kubectl logs time-check -n devops |
| Log vacio | El Pod puede no haber tenido tiempo de escribir. Esperar al menos 8 segundos y verificar de nuevo |
Error from server (NotFound): namespaces "devops" not found |
Crear el namespace primero: kubectl create namespace devops |
Variable TIME_FREQ vacia en el contenedor |
Verificar que el ConfigMap tiene la clave correcta: kubectl get configmap time-config -n devops -o yaml |
| Archivo de log no existe | Verificar que el volumen esta montado: kubectl describe pod time-check -n devops y buscar la seccion Mounts |