Debugeando Contenedores Distroless: Cuando Tu Contenedor No Tiene Shell


Introducción

En este artículo vamos a explorar cómo debugear contenedores distroless en Kubernetes cuando tu contenedor de aplicación no tiene shell, no tiene package manager, y básicamente no tiene herramientas de debugging. Si alguna vez intentaste hacer kubectl exec en un contenedor distroless solo para recibir “executable file not found in $PATH: /bin/sh”, conocés el dolor.


Los contenedores distroless son increíbles para seguridad y tamaño - contienen solo tu aplicación y sus dependencias de runtime, nada más. Sin shell, sin package managers, sin herramientas de debugging. Son perfectos para producción… hasta que algo sale mal y necesitás curiosear adentro.


¡Pero no te preocupes! Kubernetes tiene una solución: contenedores efímeros via kubectl debug. No solo vamos a ver cómo usar esta característica, sino también cómo configurar manualmente un entorno de usuario y acceder al filesystem del contenedor principal a través del truco /proc/1/root.


¿Qué son los Contenedores Distroless?

Antes de meternos en el debugging, entendamos rápidamente con qué estamos lidiando. Los contenedores distroless, popularizados por Google, contienen solo:

  • Tu binario de aplicación
  • Dependencias de runtime (librerías, certificados, datos de zona horaria)
  • Una configuración mínima de usuario (usualmente solo root o un usuario dedicado)

Lo que NO contienen:

  • Package managers (apt, yum, apk)
  • Shells (bash, sh, zsh)
  • Herramientas de debugging (ps, netstat, curl, wget)
  • Editores de texto (vi, nano)
  • Prácticamente cualquier cosa que haga el debugging fácil

Esto es fantástico para seguridad (menor superficie de ataque) y performance (imágenes más pequeñas), pero terrible cuando necesitás debugear un contenedor corriendo.


El Problema

Digamos que tenés una aplicación Go corriendo en un contenedor distroless y se está comportando extraño. Tu instinto natural es:

kubectl exec -it mi-pod -- /bin/sh

Pero te recibe con:

OCI runtime exec failed: exec failed: unable to start container process: 
exec: "/bin/sh": executable file not found in $PATH: unknown

Incluso intentar diferentes shells no ayuda:

kubectl exec -it mi-pod -- bash
kubectl exec -it mi-pod -- /bin/bash
# Mismo error, diferente shell

¿Y ahora qué? Acá es donde kubectl debug viene al rescate.


Entra kubectl debug

Kubernetes 1.18+ introdujo contenedores efímeros, y kubectl debug los hace fáciles de usar. Pensalo como adjuntar un sidecar de debugging a tu pod corriendo temporalmente.


Acá está la sintaxis básica:

kubectl debug -it mi-pod --image=ubuntu --target=mi-contenedor

Este comando:

  • Crea un contenedor efímero usando la imagen ubuntu
  • Se adjunta a él interactivamente (-it)
  • Comparte el namespace de procesos con el contenedor objetivo

Pero hay una trampa - incluso con esta configuración, todavía no podés acceder directamente al filesystem de tu aplicación. Ahí es donde entra la magia de /proc/1/root.


El Truco /proc/1/root

En Linux, /proc/1/root es un enlace simbólico al filesystem raíz del proceso ID 1. Cuando los contenedores comparten un namespace de procesos (que kubectl debug hace por defecto), podés acceder al filesystem del contenedor principal a través de esta ruta.


Acá está el flujo completo de debugging:


Paso 1: Crear el Contenedor de Debug
kubectl debug -it mi-pod --image=ubuntu --target=mi-contenedor --share-processes

Vas a caer en una shell en el contenedor Ubuntu. La flag --share-processes asegura que puedas ver todos los procesos de ambos contenedores.


Paso 2: Verificar el Compartir de Procesos
ps aux

Deberías ver tanto tu proceso de aplicación (PID 1) como los procesos de shell del contenedor de debug. Si tu app está corriendo como PID 1, estás listo.


Paso 3: Acceder al Filesystem del Contenedor Principal
ls /proc/1/root/

¡Esto te muestra el filesystem de tu contenedor distroless! Ahora podés navegar e inspeccionar archivos:

# Verificar tu binario de aplicación
ls -la /proc/1/root/app

# Mirar archivos de configuración
cat /proc/1/root/etc/ssl/certs/ca-certificates.crt

# Verificar variables de entorno
cat /proc/1/environ | tr '\0' '\n'

# Examinar el directorio de trabajo
ls -la /proc/1/root/workspace/

Paso 4: Crear un Entorno de Usuario Apropiado

A veces podrías querer trabajar más cómodamente. Acá está cómo configurar un entorno de usuario apropiado en tu contenedor de debug:

# Actualizar lista de paquetes e instalar herramientas útiles
apt update && apt install -y curl wget netstat-nat procps tree

# Crear un usuario (opcional, pero buena práctica)
useradd -m -s /bin/bash debuguser
usermod -aG sudo debuguser

# Cambiar al nuevo usuario
su - debuguser

Ahora tenés un entorno de debugging completo con todas las herramientas que necesitás, mientras seguís pudiendo acceder al filesystem de tu contenedor distroless.


Técnicas de Debugging Avanzadas

Una vez que tenés acceso, acá hay algunas técnicas de debugging poderosas:


Debugging de Red:

# Verificar en qué está escuchando tu app
netstat -tlnp

# Probar conectividad desde el contenedor de debug
curl http://localhost:8080/health

# Verificar resolución DNS
nslookup tu-servicio

Investigación del Sistema de Archivos:

# Verificar uso de disco
du -sh /proc/1/root/*

# Encontrar archivos modificados recientemente
find /proc/1/root/ -type f -mtime -1

# Buscar archivos de configuración
find /proc/1/root/ -name "*.conf" -o -name "*.yaml" -o -name "*.json"

Análisis de Procesos:

# Verificar qué archivos tiene abiertos tu app
lsof -p 1

# Monitorear system calls (si strace está disponible)
strace -p 1 -f

# Verificar uso de memoria
cat /proc/1/status | grep -E "(VmSize|VmRSS|VmPeak)"

Ejemplo del Mundo Real

Te muestro un ejemplo completo. Digamos que tenés una aplicación Go en un contenedor distroless que está fallando los health checks:

# Primero, identificar el pod problemático
kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
mi-app-7d4b8c8f5-xyz42   1/1     Running   5          2h

# Crear contenedor de debug
kubectl debug -it mi-app-7d4b8c8f5-xyz42 --image=ubuntu --target=mi-app

# Dentro del contenedor de debug:
apt update && apt install -y curl procps

# Verificar si la app está respondiendo
curl http://localhost:8080/health
# Connection refused - ¡ajá!

# Verificar en qué está escuchando realmente la app
netstat -tlnp
# Muestra que está escuchando en 0.0.0.0:3000, no 8080

# Verificar las variables de entorno de la app para pistas
cat /proc/1/environ | tr '\0' '\n' | grep PORT
# PORT=3000

# Probar el puerto correcto
curl http://localhost:3000/health
# {"status": "ok"} - ¡ahí está nuestro problema!

El problema era un endpoint de health check mal configurado - el servicio estaba configurado para verificar el puerto 8080, pero la app estaba escuchando en 3000.


Cuando kubectl debug No Está Disponible

Si estás corriendo una versión anterior de Kubernetes (< 1.18) o tu cluster no soporta contenedores efímeros, tenés algunas alternativas:


Opción 1: Agregar un sidecar de debug a tu spec de pod

apiVersion: v1
kind: Pod
spec:
  shareProcessNamespace: true
  containers:
  - name: app
    image: gcr.io/distroless/java:11
    # configuración de tu app
  - name: debug
    image: ubuntu
    command: ["/bin/sleep", "infinity"]
    stdin: true
    tty: true

Opción 2: Usar kubectl cp para sacar archivos

# Copiar archivos del contenedor para investigar localmente
kubectl cp mi-pod:/app/config.json ./config.json
kubectl cp mi-pod:/var/log/ ./logs/

Debugeando Problemas Comunes

El contenedor de debug no puede ver los procesos del contenedor principal: Asegurate de usar --share-processes o que shareProcessNamespace: true esté configurado en tu spec de pod.


Permiso denegado accediendo /proc/1/root: Esto puede pasar si tu contenedor de debug no tiene privilegios suficientes. Probá:

ls -hal /proc/1

Para determinar que UID/GID y luego crea un usuario con esos valores para poder leer/escribir.


El contenedor principal no es PID 1: Si tu app no está corriendo como PID 1, encontrá el proceso correcto:

ps aux | grep nombre-de-tu-app
# Usar el PID correcto en lugar de 1
ls /proc/PID/root/

Consideraciones de Seguridad

Mientras que kubectl debug es increíblemente útil, mantené estas consideraciones de seguridad en mente:

  • Los contenedores de debug pueden acceder información sensible del contenedor principal
  • Corren con los mismos permisos de service account
  • Los logs de contenedores de debug podrían contener datos sensibles
  • Siempre limpiá los contenedores de debug cuando termines (son efímeros por defecto)

Mejores Prácticas

Acá hay algunas mejores prácticas que aprendí a lo largo de los años:

  • Usar imágenes de debug mínimas: Empezar con alpine o ubuntu, agregar herramientas según necesites
  • Documentar tu proceso de debugging: Guardar comandos útiles para tu equipo
  • Crear runbooks de debugging: Problemas comunes y sus pasos de investigación
  • Usar labels: Etiquetar tus contenedores de debug para fácil identificación
  • Configurar límites de recursos: Los contenedores de debug también pueden consumir recursos del cluster

Crear un Template de Contenedor de Debug

Podrías querer crear una imagen de debug preconfigurada para tu equipo:

FROM ubuntu:22.04

RUN apt update && apt install -y \
    curl \
    wget \
    netcat \
    netstat-nat \
    procps \
    strace \
    tcpdump \
    tree \
    jq \
    && rm -rf /var/lib/apt/lists/*

# Agregar cualquier herramienta de debugging personalizada que tu equipo necesite
COPY debug-scripts/ /usr/local/bin/

CMD ["/bin/bash"]

Construir y pushear esto a tu registry, después usarlo para debugging:

kubectl debug -it mi-pod --image=tu-registry/debug-toolkit:latest

Conclusión

Debugear contenedores distroless no tiene que ser una pesadilla. Con kubectl debug y la técnica /proc/1/root, podés investigar problemas en incluso los contenedores más mínimos. La clave es entender que no estás tratando de agregar herramientas al contenedor distroless - estás trayendo tu propia caja de herramientas y accediendo al filesystem del contenedor desde afuera.


Este enfoque te da los beneficios de seguridad de los contenedores distroless en producción mientras mantenés la habilidad de debugear cuando las cosas salen mal. Es lo mejor de ambos mundos - contenedores seguros y mínimos con capacidades completas de debugging cuando las necesitás.


Recordá, el objetivo no es evitar problemas completamente (aunque sería lindo), sino poder identificar y resolverlos rápidamente cuando inevitablemente ocurren. Con estas técnicas en tu toolkit, los contenedores distroless se vuelven mucho menos aterradores de debugear.


¡Espero que te haya sido útil y hayas disfrutado leyéndolo, hasta la próxima!


No tienes cuenta? Regístrate aqui

Ya registrado? Iniciar sesión a tu cuenta ahora.

Iniciar session con GitHub
Iniciar sesion con Google
  • Comentarios

    Online: 0

Por favor inicie sesión para poder escribir comentarios.

by Gabriel Garrido