Init containers y sidecars
En la primera lección quedó una frase pendiente: un Pod puede tener más de un contenedor. Toca desarrollarla. Los contenedores de un Pod comparten red (mismo localhost) y pueden compartir volúmenes, y eso habilita dos patrones que verás en cualquier clúster real.
- Un init container se ejecuta antes que los contenedores principales, uno detrás de otro, y debe terminar con éxito para que el Pod continúe. Sirve para preparar el terreno: esperar a una dependencia, migrar una base de datos, descargar configuración.
- Un sidecar acompaña al contenedor principal durante toda su vida: recoge sus logs, hace de proxy, refresca un certificado.
Y aquí hay una novedad importante que cambia cómo se escriben los sidecars: desde las versiones recientes de Kubernetes, un sidecar es un init container con restartPolicy: Always. Suena raro y tiene toda la lógica del mundo, como verás en un momento.
Paso 1: Un Pod que espera a su dependencia
Crea app.yaml. Es un Pod con dos init containers y un contenedor principal:
cat << 'EOF' > app.yaml
apiVersion: v1
kind: Pod
metadata:
name: app
labels:
app: app
spec:
initContainers:
- name: espera-backend
image: ghcr.io/iximiuz/labs/nginx:alpine
command: ["sh", "-c"]
args:
- |
until wget -q -T 2 -O /dev/null http://backend; do
echo "el backend todavía no responde, esperando..."
sleep 2
done
echo "backend disponible"
- name: sidecar-logs
image: ghcr.io/iximiuz/labs/nginx:alpine
restartPolicy: Always
command: ["sh", "-c"]
args:
- |
while true; do
echo "$(date) latido del sidecar" >> /var/log/app/app.log
sleep 5
done
volumeMounts:
- name: logs
mountPath: /var/log/app
containers:
- name: app
image: ghcr.io/iximiuz/labs/nginx:alpine
volumeMounts:
- name: logs
mountPath: /var/log/app
volumes:
- name: logs
emptyDir: {}
EOF
El YAML, explicado con preguntas y respuestas
¿Por qué el sidecar está en initContainers y no en containers?
Porque un sidecar necesita dos garantías que un contenedor normal no da: arrancar antes que la aplicación (de poco sirve un recolector de logs que llega tarde) y terminar después (de poco sirve si se deja por el camino los últimos logs). Los init containers ya arrancan en orden y antes que nadie; solo faltaba decirle a Kubernetes que este no debe terminar. Eso es exactamente lo que significa restartPolicy: Always dentro de un init container: "no esperes a que acabe, este se queda".
¿Y cuál es la diferencia real con meterlo en containers, como se hacía antes?
Que un contenedor normal no tiene orden de arranque db, y que en los Jobs impedía terminar (el Pod nunca completaba porque el sidecar seguía corriendo). El sidecar nativo resuelve ambos problemas: arranca en orden, se apaga cuando el contenedor principal termina, y su ciclo de vida no cuenta para dar por acabado un Job.
¿Qué pasa si un init container falla?
Se reintenta según el restartPolicy del Pod, y el Pod no avanza. Verás la fase Init:0/2 o Init:Error. Es un bloqueo deliberado: si la dependencia no está, no tiene sentido arrancar la aplicación.
¿Se ejecutan los init containers en paralelo?
No, en orden y de uno en uno, en el orden en que aparecen en la lista. Los contenedores de containers, en cambio, arrancan todos a la vez.
¿Cómo se comunican el sidecar y la aplicación?
Por las dos vías que comparte un Pod: el volumen emptyDir que ambos montan (así viajan los logs de este ejemplo) y localhost, porque comparten espacio de red. Un proxy sidecar clásico intercepta el tráfico precisamente por eso.
Aplícalo y observa el bloqueo, que es lo interesante:
kubectl apply -f app.yaml
kubectl get pod app
kubectl logs app -c espera-backend
El Pod se queda en Init:0/2 y el init container repite su cantinela: nadie responde en http://backend. Normal, todavía no existe.
Paso 2: Levanta el backend y desbloquea al Pod
Crea el Deployment y el Service que el init container está esperando. A estas alturas ya lo haces con los ojos cerrados:
kubectl create deployment backend --image=ghcr.io/iximiuz/labs/nginx:alpine --port=80
kubectl expose deployment backend --port=80 --target-port=80
Y vuelve a mirar al Pod app, sin tocarlo:
kubectl get pod app --watch
En cuanto el Service responde, el init container termina con éxito, arranca el sidecar y detrás de él el contenedor principal. Init:0/2, Init:1/2, Running. Nadie ha recreado nada: el Pod estaba esperando, y ya está.
Paso 3: Comprueba que el sidecar trabaja
El sidecar lleva escribiendo su latido desde antes de que la aplicación existiera. Léelo desde los dos lados del Pod:
kubectl logs app -c sidecar-logs
kubectl exec app -c app -- cat /var/log/app/app.log
El segundo comando es el que importa: el contenedor principal ve el fichero que escribe otro contenedor, porque ambos montan el mismo emptyDir. En el mundo real la dirección suele ser la contraria (la aplicación escribe y el sidecar envía a Loki, Elastic o Datadog), pero la mecánica es exactamente esta.
Comprueba también cómo aparece en la contabilidad del Pod:
kubectl get pod app
kubectl get pod app -o jsonpath='{.status.initContainerStatuses}' | jq .
La columna READY dice 2/2: el sidecar cuenta como contenedor vivo, no como paso de inicialización terminado.
💡 ¿Y si el sidecar se cae? Al llevar restartPolicy: Always, el kubelet lo reinicia sin tocar el contenedor principal. Compruébalo si te apetece: kubectl exec app -c sidecar-logs -- pkill -f sleep y vigila la columna RESTARTS.
Resumen
- Un init container prepara el terreno y bloquea el arranque hasta terminar con éxito. Se ejecutan en orden.
- Un sidecar es un init container con
restartPolicy: Always: arranca antes que la aplicación, termina después y no impide que un Job termine. - Los contenedores de un Pod se comunican por volúmenes compartidos y por
localhost.
- Previous lesson
- DaemonSet
- Next lesson
- Jobs y CronJobs