Despliegas un pequeño cambio. Un contenedor que funcionó ayer ahora muere instantáneamente. Docker muestra algo poco útil como OCI runtime create failed, containerd menciona un “shim”, y runc escupe permission denied como si quisiera ser misterioso.
Reinstalar Docker parece una acción, pero normalmente es solo pánico con pasos extra. Estas fallas casi siempre son diagnosticables in situ: un montaje roto, una falta de coincidencia de cgroup, un problema con el controlador de almacenamiento, un bloqueo de seccomp/AppArmor, o un disco que se quedó sin algo que no estabas vigilando.
El modelo mental: quién falla y dónde buscar
Cuando un contenedor no arranca, necesitas saber qué capa se está quejando. Docker es la recepción. containerd es el gerente del hotel. runc es el cerrajero. El kernel es el edificio.
Qué hace Docker (y qué no)
El daemon de Docker (dockerd) maneja peticiones API, gestión de imágenes, redes, volúmenes y orquesta las operaciones de runtime. En la mayoría de las instalaciones modernas, Docker no crea directamente tu proceso de contenedor; le pide a containerd que lo haga.
Qué hace containerd
containerd gestiona el ciclo de vida de los contenedores y el contenido. Crea “tasks” e invoca el runtime OCI (a menudo runc) para configurar namespaces, cgroups, montajes y luego ejecutar el proceso del contenedor. Si ves errores como failed to create shim task o shim exited, estás en territorio de containerd, pero la causa raíz suele estar en el kernel/cgroup/sistema de archivos.
Qué hace runc
runc es la implementación del runtime OCI. Lee un OCI config.json y pide al kernel que haga el trabajo duro: montar un rootfs, configurar namespaces, aplicar filtros seccomp, establecer capacidades, configurar cgroups y luego ejecutar el binario objetivo. Cuando runc dice permission denied puede significar “seccomp”, “LSM (AppArmor/SELinux)”, “permisos del sistema de archivos” o “el kernel rechazó la operación.” El mensaje rara vez es específico. Ese es nuestro trabajo.
El kernel es donde lo “imposible” se vuelve “obvio”
La mayoría de los incidentes de “Docker está roto” son en realidad: tu disco está lleno (o sin inodos), tu kernel tiene comportamiento de cgroup v2 que no consideraste, tu sistema de archivos no puede hacer montajes overlay, tu política de seguridad bloquea flags de clone(), o tus reglas de DNS/iptables fueron reorganizadas por alguien que “ordenó las cosas”.
Idea parafraseada (atribuida a John Allspaw): “El trabajo de fiabilidad trata de entender cómo fallan realmente los sistemas, no cómo desearíamos que lo hicieran.”
Una opinión que te ahorrará horas: no empieces reinstalando. Empieza demostrando qué capa está fallando y si el host está lo bastante sano para ejecutar cualquier cosa.
Guía rápida de diagnóstico (revisar primero/segundo/tercero)
Este es el orden de operaciones para “Estoy de guardia y el radio de efecto está creciendo”. El objetivo no es ser exhaustivo. El objetivo es encontrar rápidamente el cuello de botella y dejar de adivinar.
Primero: confirma que el host no te está mintiendo
- Espacio en disco e inodos en la ruta de datos de Docker (
/var/lib/docker) y en el sistema de archivos raíz. - Presión de memoria y actividad OOM.
- Registros del kernel por montajes, seccomp, denegaciones AppArmor/SELinux, fallos de cgroup.
Segundo: obtén el error de la boca correcta
- docker events alrededor del momento del fallo.
- registros de dockerd (journal de systemd) para trazas completas.
- registros de containerd por errores en la invocación del shim/runc.
Tercero: aísla el eje que falla
- Eje de almacenamiento: fallos de montaje overlay2, metadatos de capas corruptos, XFS d_type, rarezas de NFS/shiftfs.
- Eje de cgroups: cgroup v2, desajuste de driver systemd, problemas de permisos en modo rootless.
- Eje de seguridad: bloqueos AppArmor/SELinux/seccomp, no-new-privileges, capacidades faltantes.
- Eje del runtime: binario runc incompatible, configuración de plugin de containerd, shims atascados.
Una vez que sepas el eje, deja de hacer cambios a lo loco. Haz una sola cosa deliberada, valida y avanza. Así mantienes los cortes de servicio cortos y los postmortems aburridos.
Hechos interesantes y contexto (por qué esta pila es rara)
- Docker separó containerd en 2016 para hacer los componentes runtime reutilizables más allá de Docker; Kubernetes después estandarizó en containerd para muchas distros.
- runc surgió de libcontainer, el motor inicial de contenedores de Docker, y se convirtió en la implementación de referencia del runtime OCI.
- OCI (Open Container Initiative) se formó en 2015 para estandarizar el formato de imágenes y el comportamiento del runtime; por eso aparece “OCI runtime” en los errores hoy en día.
- El “shim” existe para que los contenedores sobrevivan reinicios del daemon; containerd puede morir y volver sin matar cada contenedor, porque el shim mantiene la relación de proceso hijo.
- overlay2 se volvió el driver de almacenamiento por defecto porque es rápido y usa OverlayFS del kernel, pero es exigente respecto a características del sistema de archivos y opciones de montaje.
- cgroup v2 cambió semánticas (especialmente alrededor de la delegación y controladores), y muchas suposiciones que “funcionaban en v1” fallan silenciosa o ruidosamente en v2.
- Docker sin root no es solo “Docker sin sudo”; usa user namespaces y redes diferentes, y falla de manera distinta (a menudo más educada) que el modo con root.
- Los valores por defecto de seccomp son conservadores; pueden romper syscalls “exóticos” usados por algunas cargas (o por glibc más reciente) en kernels antiguos.
- XFS necesita ftype=1 (d_type) para la corrección de overlay2; sin ello obtendrás fallos de capa y rename espectacularmente confusos.
Broma 1/2: Los contenedores son “ligeros” hasta que los depuras a las 3 a.m., cuando cada namespace pesa exactamente una tonelada.
Tareas prácticas de depuración (comandos, salidas, decisiones)
A continuación hay tareas reales y ejecutables. Cada una tiene tres partes: el comando, qué significa una salida típica y la decisión que tomas. Hazlas en orden cuando estés perdido; hazlas selectivamente cuando no lo estés.
Tarea 1: Captura el error exacto de Docker (no el resumen)
cr0x@server:~$ docker ps -a --no-trunc | head -n 5
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2b2c0f3b7a0c5f8f1c2c6d5a0a7a5f0a6e9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4 myapp:latest "/entrypoint.sh" 2 minutes ago Created myapp_1
cr0x@server:~$ docker start myapp_1
Error response from daemon: OCI runtime create failed: runc create failed: unable to start container process: error during container init: open /proc/self/fd: permission denied: unknown
Significado: Esto ya es más profundo que “falló al iniciar”. Apunta a runc y a una operación específica. /proc + permission denied suele significar LSM (AppArmor/SELinux), seccomp o una restricción rara de montaje/namespace.
Decisión: No toques los paquetes de Docker. Ve directamente a journald y a los registros del kernel para ver quién dijo “no”.
Tarea 2: Observa eventos de Docker en vivo mientras reproduces
cr0x@server:~$ docker events --since 10m
2026-01-03T11:12:08.123456789Z container create 2b2c0f3b7a0c (image=myapp:latest, name=myapp_1)
2026-01-03T11:12:08.234567890Z container start 2b2c0f3b7a0c (image=myapp:latest, name=myapp_1)
2026-01-03T11:12:08.345678901Z container die 2b2c0f3b7a0c (exitCode=127, image=myapp:latest, name=myapp_1)
Significado: El contenedor muere inmediatamente. Los códigos de salida a veces son a nivel de aplicación, a veces a nivel de runtime; el momento importa. Una muerte instantánea a menudo significa que init nunca arrancó o que falta el binario del entrypoint.
Decisión: Si el evento de die es inmediato y el código de salida parece 127/126, verifica el rootfs y que el entrypoint exista; si es un error de runtime, revisa los logs.
Tarea 3: Inspecciona rápidamente la configuración del contenedor (entrypoint, montajes, opciones de seguridad)
cr0x@server:~$ docker inspect myapp_1 --format '{{json .HostConfig.SecurityOpt}} {{json .HostConfig.CgroupnsMode}} {{.Path}} {{json .Args}}'
null "private" /entrypoint.sh ["--serve"]
Significado: Sin opciones de seguridad especiales, cgroup namespace privado, entrypoint es /entrypoint.sh. Si falla con 127, esa ruta podría no existir o no ser ejecutable dentro de la imagen.
Decisión: Si el error huele a “binario faltante”, ejecuta la imagen con una shell conocida o ls mediante un entrypoint diferente (si es posible). Si huele a “permission denied”, pasa a LSM/seccomp/cgroups.
Tarea 4: Lee los logs de dockerd para la traza completa
cr0x@server:~$ sudo journalctl -u docker --since "15 min ago" --no-pager | tail -n 30
Jan 03 11:12:08 server dockerd[1187]: time="2026-01-03T11:12:08.330123456Z" level=error msg="Error response from daemon: OCI runtime create failed: runc create failed: unable to start container process: error during container init: open /proc/self/fd: permission denied: unknown"
Jan 03 11:12:08 server dockerd[1187]: time="2026-01-03T11:12:08.330234567Z" level=info msg="Attempting next endpoint for containerd"
Jan 03 11:12:08 server dockerd[1187]: time="2026-01-03T11:12:08.330345678Z" level=error msg="Handler for POST /v1.45/containers/2b2c0f3b7a0c/start returned error: OCI runtime create failed: runc create failed: unable to start container process: error during container init: open /proc/self/fd: permission denied: unknown"
Significado: Docker está retransmitiendo un error del runtime OCI. La traza no siempre se muestra a menos que el logging en modo debug esté activado, pero incluso esto confirma que no es “Docker no puede hablar con containerd.”
Decisión: Próxima parada: logs de containerd y logs del kernel/audit. Las denegaciones de permiso son política, no plomería.
Tarea 5: Lee los logs de containerd (contexto shim y runc)
cr0x@server:~$ sudo journalctl -u containerd --since "15 min ago" --no-pager | tail -n 30
Jan 03 11:12:08 server containerd[1044]: time="2026-01-03T11:12:08.320111222Z" level=info msg="starting containerd" revision= version=1.7.12
Jan 03 11:12:08 server containerd[1044]: time="2026-01-03T11:12:08.328222333Z" level=error msg="RunPodSandbox for "2b2c0f3b7a0c" failed" error="failed to create task: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: open /proc/self/fd: permission denied: unknown"
Significado: containerd confirma la falla de runc. Si ves “shim exited” sin detalles de runc, puede que tengas un binario runtime que se estrelló o un directorio de estado corrupto.
Decisión: Si el error de containerd es escaso, habilita logging debug temporalmente (tarea posterior). En caso contrario, pivota a logs del kernel para encontrar quién denegó el acceso.
Tarea 6: Revisa logs del kernel por denegaciones AppArmor/SELinux/seccomp
cr0x@server:~$ sudo dmesg -T | tail -n 25
[Fri Jan 3 11:12:08 2026] audit: type=1400 audit(1735902728.332:312): apparmor="DENIED" operation="open" class="file" profile="docker-default" name="/proc/self/fd/" pid=22451 comm="runc:[2:INIT]" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
Significado: Esa es la prueba evidente: AppArmor bloqueó una apertura en /proc/self/fd/ para el perfil docker-default.
Decisión: Ajusta la política de AppArmor o ejecuta el contenedor con una modificación de perfil apropiada (con cuidado). No desactives AppArmor globalmente a menos que te guste dar explicaciones más adelante.
Tarea 7: Confirma el estado de AppArmor y los perfiles cargados
cr0x@server:~$ sudo aa-status | head -n 20
apparmor module is loaded.
73 profiles are loaded.
67 profiles are in enforce mode.
docker-default
/usr/sbin/cupsd
/usr/sbin/sshd
6 profiles are in complain mode.
0 processes are unconfined but have a profile defined.
Significado: AppArmor está activo y en modo enforce. Si docker-default está en enforce, probablemente tu contenedor esté bajo ese perfil salvo que se haya anulado.
Decisión: Si esto es un comportamiento nuevo, busca qué cambió: actualización del kernel, actualización de AppArmor o alguien que haya modificado perfiles. Para una mitigación rápida, usa --security-opt apparmor=unconfined para la carga afectada (luego redacta una política real).
Tarea 8: Comprueba espacio en disco e inodos (sí, cada vez)
cr0x@server:~$ df -hT / /var/lib/docker
Filesystem Type Size Used Avail Use% Mounted on
/dev/nvme0n1p2 ext4 96G 95G 120M 100% /
/dev/nvme0n1p2 ext4 96G 95G 120M 100% /
cr0x@server:~$ df -i /var/lib/docker
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 6553600 6551000 2600 100% /
Significado: Te quedaste sin espacio y sin inodos. Cualquiera de los dos puede romper la creación de contenedores, pulls de imágenes y montajes overlay. Cuando Docker falla con “no space left on device”, a veces significa “no inodos left”, que es una crueldad especial.
Decisión: Libera espacio/inodos primero. Haz prune de forma segura, borra logs antiguos, rota journals o mueve Docker data root. No pierdas tiempo en flags de runc mientras el disco está al rojo.
Tarea 9: Identifica el driver de almacenamiento y el Docker root dir
cr0x@server:~$ docker info --format 'Driver={{.Driver}} DockerRootDir={{.DockerRootDir}} CgroupDriver={{.CgroupDriver}} CgroupVersion={{.CgroupVersion}}'
Driver=overlay2 DockerRootDir=/var/lib/docker CgroupDriver=systemd CgroupVersion=2
Significado: overlay2 en cgroup v2 con driver systemd. Esa combinación está bien, pero acota tus modos de fallo: problemas de montaje overlay, delegación/permiso de cgroups, límites en unidades systemd.
Decisión: Si el error involucra montajes/capas, enfócate en overlay2 y el FS subyacente. Si involucra “cgroup” o “systemd”, concéntrate ahí.
Tarea 10: Comprueba características del sistema de archivos subyacente para overlay2 (XFS ftype, opciones de montaje)
cr0x@server:~$ findmnt -no FSTYPE,OPTIONS /var/lib/docker
ext4 rw,relatime,errors=remount-ro
cr0x@server:~$ sudo docker info | sed -n '/Backing Filesystem/,+5p'
Backing Filesystem: extfs
Supports d_type: true
Using metacopy: false
Native Overlay Diff: true
Significado: El FS subyacente soporta d_type; overlay2 debería estar estructuralmente OK. En XFS querrás ftype=1; en ext4 te importa principalmente estar en un sistema local con opciones sensatas.
Decisión: Si el FS subyacente es NFS, CIFS o algo “creativo”, espera problemas con overlay2. Mueve DockerRootDir a almacenamiento local o usa un driver compatible.
Tarea 11: Busca fallos de montaje overlay y “invalid argument” en logs del kernel
cr0x@server:~$ sudo dmesg -T | grep -E 'overlay|OverlayFS' | tail -n 10
[Fri Jan 3 10:58:41 2026] overlayfs: upper fs does not support RENAME_WHITEOUT
[Fri Jan 3 10:58:41 2026] overlayfs: failed to set xattr on upper
Significado: OverlayFS está descontento con las capacidades del sistema de archivos superior. Esto puede ocurrir con ciertas opciones de montaje, kernels antiguos o sistemas de archivos que no soportan xattrs/características requeridas.
Decisión: Arregla el sistema de archivos (opciones de montaje, soporte del kernel) o reubica DockerRootDir. Intentar “prune imágenes” no arreglará un sistema de archivos que no puede realizar las operaciones que overlay necesita.
Tarea 12: Valida el montaje de cgroup v2 y la disponibilidad de controladores
cr0x@server:~$ mount | grep cgroup2
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
cr0x@server:~$ cat /sys/fs/cgroup/cgroup.controllers
cpuset cpu io memory hugetlb pids rdma
Significado: cgroup v2 está montado y hay controladores. Los problemas aparecen cuando los controladores no se delegan o systemd bloquea cosas para Docker.
Decisión: Si los errores de inicio mencionan cgroups, confirma que el driver de cgroup de Docker coincide con systemd y que la versión de systemd soporta las características que necesitas.
Tarea 13: Encuentra kills por OOM que “misteriosamente” detienen contenedores
cr0x@server:~$ sudo journalctl -k --since "2 hours ago" --no-pager | grep -i -E 'oom|killed process' | tail -n 10
Jan 03 10:44:19 server kernel: Out of memory: Killed process 21902 (myapp) total-vm:812340kB, anon-rss:512000kB, file-rss:1200kB, shmem-rss:0kB, UID:0 pgtables:1400kB oom_score_adj:0
Significado: El kernel mató tu proceso. Docker podría informar que el contenedor “salió” sin un error runtime útil. Esto no es un problema de containerd. Es el host haciendo triage.
Decisión: Arregla la presión de memoria: sube límites, ajusta requests/limits, añade swap (con cuidado) o mueve la carga. Depurar runc no resucitará memoria.
Tarea 14: Comprueba shims atascados y procesos runtime zombificados
cr0x@server:~$ ps -eo pid,ppid,comm,args | grep -E 'containerd-shim|runc' | grep -v grep | head
22451 1044 containerd-shim-runc-v2 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 2b2c0f3b7a0c -address /run/containerd/containerd.sock
22460 22451 runc:[2:INIT] runc:[2:INIT]
Significado: Existen shims por contenedor. Si los shims permanecen después de que los contenedores desaparecen, o procesos INIT de runc cuelgan, puedes tener un montaje atascado o un bug del kernel. A menudo es un colapso del almacenamiento o un sleep no interrumpible.
Decisión: No mates PIDs al azar al principio. Identifica si están en estado D (siguiente tarea). Si lo están, estás ante problemas de I/O o del sistema de archivos.
Tarea 15: Comprueba el estado de procesos en D-state (colgado de I/O) y tareas bloqueadas
cr0x@server:~$ ps -o pid,state,wchan,comm -p 22460
PID S WCHAN COMMAND
22460 D ovl_wa runc:[2:INIT]
cr0x@server:~$ sudo dmesg -T | tail -n 5
[Fri Jan 3 11:05:12 2026] INFO: task runc:[2:INIT]:22460 blocked for more than 120 seconds.
Significado: El estado D significa sleep no interrumpible, usualmente esperando I/O. Matarlo no funcionará. Aquí es donde “Docker está caído” en realidad significa “tu almacenamiento está en llamas”.
Decisión: Deja de intentar reiniciar Docker. Investiga el almacenamiento: disco subyacente, RAID, almacenamiento en red, errores del sistema de archivos. Puede que necesites un reinicio del host si el kernel está atascado, pero entiende por qué antes de tirar de esa palanca.
Tarea 16: Valida la salud del socket de containerd y la capacidad de respuesta de la API
cr0x@server:~$ sudo ss -lxnp | grep containerd
u_str LISTEN 0 4096 /run/containerd/containerd.sock 12345 * 0 users:(("containerd",pid=1044,fd=9))
cr0x@server:~$ sudo ctr --address /run/containerd/containerd.sock version
Client:
Version: 1.7.12
Revision: 9a8b7c6d5e4f
Server:
Version: 1.7.12
Revision: 9a8b7c6d5e4f
Significado: containerd está escuchando y respondiendo. Si Docker dice que no puede conectarse a containerd, esto ayuda a separar “config del daemon” de “runtime muerto”.
Decisión: Si ctr funciona pero docker no, enfócate en la configuración de dockerd, sockets o permisos. Si ctr cuelga, containerd está enfermo o bloqueado (a menudo por almacenamiento).
Tarea 17: Usa ctr para listar tasks y detectar contenedores medio creados
cr0x@server:~$ sudo ctr -n moby containers list | head
CONTAINER IMAGE RUNTIME
2b2c0f3b7a0c docker.io/library/myapp:latest io.containerd.runc.v2
cr0x@server:~$ sudo ctr -n moby tasks list
TASK PID STATUS
2b2c0f3b7a0c 0 STOPPED
Significado: El objeto del contenedor existe, pero la task no está corriendo (PID 0). Esto concuerda con “create failed” o “init failed”.
Decisión: Si se acumulan muchas tasks en STOPPED, probablemente tengas un problema sistémico de runtime/almacenamiento/seguridad. Arregla el eje y luego limpia el estado colgado.
Tarea 18: Comprueba el uso de almacenamiento de Docker y prune con intención
cr0x@server:~$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 42 6 38.2GB 31.7GB (83%)
Containers 19 2 1.1GB 900MB (81%)
Local Volumes 15 8 120GB 0B (0%)
Build Cache 10 0 8.4GB 8.4GB
cr0x@server:~$ docker image prune -a --filter "until=168h"
Deleted Images:
deleted: sha256:...
Total reclaimed space: 28.4GB
Significado: Imágenes y cache de build son recuperables; volúmenes no lo son. La salida te dice dónde puedes recuperar espacio de forma segura sin borrar datos de clientes.
Decisión: Haz prune de imágenes/cache de build bajo control de cambios. Evita prune de volúmenes salvo que estés absolutamente seguro. Si estás sin inodos, el prune también puede ayudar, pero verifica con df -i.
Tarea 19: Verifica modo iptables/nftables cuando errores de red se disfrazan de fallos de runtime
cr0x@server:~$ sudo docker run --rm busybox:latest nslookup example.com
Server: 127.0.0.11
Address 1: 127.0.0.11
Name: example.com
Address 1: 93.184.216.34
cr0x@server:~$ sudo iptables -S | head -n 5
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-USER
Significado: La resolución DNS dentro del contenedor funciona; las cadenas de iptables existen. Si los contenedores no pueden alcanzar nada y ves FORWARD con política DROP sin reglas Docker apropiadas, puedes obtener síntomas que parecen “fallo al iniciar el contenedor” porque las apps fallan verificaciones de salud al instante.
Decisión: Si el runtime tiene éxito pero las apps mueren inmediatamente, valida la red y DNS. No todo “contenedor murió” es culpa de runc.
Tarea 20: Aumenta temporalmente la verbosidad de dockerd (de forma segura) para obtener más contexto
cr0x@server:~$ sudo mkdir -p /etc/docker
cr0x@server:~$ sudo cat /etc/docker/daemon.json
{
"log-level": "info"
}
cr0x@server:~$ sudo sh -c 'cat > /etc/docker/daemon.json <
Significado: Los logs en debug muestran dónde dockerd está gastando tiempo y qué le pidió a containerd. Esto es particularmente útil para colgados (verás el último paso exitoso).
Decisión: Usa el modo debug brevemente durante la respuesta al incidente, luego vuelve a info. Los logs debug son verbosos y pueden consumir disco—el mismo disco que probablemente acabas de descubrir que estaba lleno.
Broma 2/2: “Solo reinicia Docker” es el equivalente operativo de golpear una máquina expendedora—ocasionalmente efectivo, siempre sospechoso.
Tres mini-historias corporativas desde las trincheras
1) Incidente causado por una suposición errónea: “permission denied significa permisos de archivo”
Tenían una flota estable. Actualizaciones menores del SO se aplicaban semanalmente. Un martes, un despliegue en un subconjunto de hosts empezó a fallar con OCI runtime create failed: permission denied. La suposición inmediata: algún archivo en la imagen perdió el bit ejecutable. El equipo reconstruyó la imagen, la desplegó y… nada cambió. Mismo error, mismos hosts, distintos digests de imagen.
Alguien intentó la solución clásica: reinstalar Docker. “Funcionó” en un host, lo que bastó para convertir la solución en superstición. En el siguiente host no funcionó. Ahora tenían inconsistencia y tiempo de inactividad, que es lo peor de ambos mundos.
El avance vino de un SRE cansado que dejó de mirar Docker y empezó a revisar los logs de auditoría del kernel. Denegaciones de AppArmor. La actualización del SO había introducido un comportamiento por defecto más estricto para el perfil docker-default en esa compilación de la distro, y una carga en particular abría una ruta de /proc durante init que disparó la denegación.
La solución fue aburrida y específica: establecer una anulación de perfil AppArmor por contenedor para la carga afectada y luego trabajar con seguridad para redactar un cambio mínimo de política. Sin deshabilitar globalmente. ¿Por qué la “reinstalación funcionó una vez” en un host? Tenía una caché de políticas obsoleta que no se recargó hasta más tarde, y así nacen los mitos en producción.
Conclusión: “permission denied” es una categoría, no un diagnóstico. Si no revisas logs de audit/LSM, estás depurando con una venda y una linterna con pilas muertas.
2) Optimización que salió mal: mover DockerRootDir a “almacenamiento compartido rápido”
Un equipo de plataforma quería reemplazos de nodo más rápidos. Decidieron colocar /var/lib/docker en un montaje de almacenamiento compartido para que los hosts pudieran reprovisionarse sin volver a tirar imágenes. En papel: menos pulls, escalado más rápido, menos ancho de banda. En realidad: acababan de colgar un filesystem overlay sensible a latencia sobre un sistema de archivos de red con stalls ocasionales.
Al principio, parecía bien. Los contenedores arrancaban. Los pulls eran rápidos. Entonces un evento en la red de almacenamiento causó pausas de 2–5 segundos. OverlayFS no lo manejó bien. Procesos init de runc empezaron a quedarse atascados en D-state. Shims quedaron como fantasmas. Reiniciar Docker no ayudó porque los hilos del kernel estaban bloqueados en I/O.
El incidente escaló porque los síntomas eran engañosos. Ingenieros vieron logs de containerd sobre shims saliendo y asumieron bugs en el runtime. Otros vieron “context deadline exceeded” y asumieron red. La verdad era que el backend de almacenamiento hacía stalls ocasionales y las operaciones upperdir de overlay (rename/whiteout/xattr) amplificaban el problema.
Volvieron a SSD local para DockerRootDir. Mantuvieron el almacenamiento compartido, pero solo para volúmenes explícitos donde el patrón de I/O y el dominio de fallos estaban entendidos. El reemplazo de nodos quedó un poco más lento. Los incidentes fueron menos frecuentes. Todos durmieron mejor.
Conclusión: poner el almacén de capas de Docker en almacenamiento compartido es una optimización que a menudo se convierte en un impuesto de fiabilidad. Si debes hacerlo, prueba las semánticas de OverlayFS y el comportamiento bajo stalls, no solo el rendimiento.
3) Práctica aburrida pero correcta que salvó el día: mantener un runbook de “salud del host”
Una fintech ejecutaba jobs críticos por lotes en contenedores en un clúster pequeño. Un viernes, los contenedores empezaron a fallar con failed to mount overlay: no space left on device. La gente asumió que el disco estaba lleno. No lo estaba—al menos no según df -h. Había gigabytes libres.
Pero el de guardia tenía un runbook que empezaba con dos comandos: df -h y df -i. Los inodos estaban al 100%. El culpable eran millones de pequeños archivos generados por un sidecar de logging que escribía en una ruta del host (sí, de verdad). Docker no podía crear nuevos metadatos de capa porque fallaba la asignación de inodos.
Pararon al causante, limpiaron el directorio y los contenedores arrancaron inmediatamente. No hubo reinstalaciones. No movieron raíces de datos. No se hizo tuning desesperado del kernel. Luego añadieron monitorización de inodos y control de rotación de logs, y el problema no volvió en esa forma.
Conclusión: las comprobaciones básicas del host no están por debajo de ti. Son la diferencia entre “arreglo en cinco minutos” y “caza de fantasmas de dos horas”.
Errores comunes: síntoma → causa raíz → solución
1) Síntoma: OCI runtime create failed: permission denied
Causa raíz: A menudo denegación AppArmor/SELinux, o seccomp bloqueó una syscall; no permisos del sistema de archivos.
Solución: Revisa dmesg -T y logs de audit. Ajusta opciones de seguridad por contenedor o la política. Evita deshabilitar el LSM globalmente.
2) Síntoma: no space left on device pero df -h muestra GB libres
Causa raíz: Inodos agotados, o la ruta de datos de Docker está en un sistema de archivos distinto al que comprobaste.
Solución: df -i /var/lib/docker. Prune de imágenes/cache de build, limpia directorios de logs y añade monitorización de inodos.
3) Síntoma: failed to mount overlay: invalid argument
Causa raíz: Sistema de archivos subyacente no soportado (ej., XFS sin ftype=1, NFS/CIFS), incompatibilidad de features con OverlayFS.
Solución: Mueve DockerRootDir a un sistema de archivos local compatible. En XFS, asegura ftype=1. Evita montajes “ingeniosos” para el almacén de capas.
4) Síntoma: arranque del contenedor cuelga; comandos docker se agotan
Causa raíz: I/O atascado (D-state), stalls de almacenamiento o errores del sistema de archivos que bloquean operaciones overlay.
Solución: Revisa el estado del proceso (ps ... state), tareas bloqueadas en dmesg, salud del almacenamiento. Reiniciar Docker no desencallará el kernel.
5) Síntoma: containerd: failed to create task y shim exited unexpectedly
Causa raíz: Crash de runc, binario runtime incompatible, directorios de estado corruptos o una denegación subyacente (LSM/cgroup).
Solución: Lee logs de containerd y kernel. Confirma versiones del runtime; no borres estado al azar salvo que sepas qué quitas.
6) Síntoma: contenedores salen instantáneamente después de iniciar, sin error runtime
Causa raíz: La aplicación falla rápido (config faltante, fallo DNS, incapacidad de conectar), o kill por OOM.
Solución: Revisa logs de contenedor, comportamiento de healthchecks y logs OOM del kernel. No persigas runc cuando el kernel simplemente mató tu app.
7) Síntoma: daemon de Docker no arranca después de una actualización
Causa raíz: daemon.json roto, configuración de driver de almacenamiento incompatible, flags sobrantes de versiones antiguas.
Solución: Valida el JSON, revisa journalctl -u docker, regrésate temporalmente a una configuración mínima y reintroduce ajustes uno por uno.
8) Síntoma: contenedores rootless fallan con errores de cgroup
Causa raíz: delegación de cgroup no configurada, restricciones de sesiones systemd de usuario, controladores faltantes para el slice del usuario.
Solución: Verifica delegación de cgroup v2 y prerrequisitos de rootless. Rootless no es un reemplazo directo; trátalo como un runtime distinto.
Listas de verificación / plan paso a paso (no haga thrashing)
Guía paso a paso para triage cuando un contenedor no arranca
- Captura el error exacto con
docker startydocker ps -a --no-trunc. Pégalo en un sitio durable. - Comprueba la salud del host:
df -hT / /var/lib/dockerydf -i /var/lib/dockerfree -my logs OOM del kerneldmesg -T | tailpara denegaciones obvias o errores del sistema de archivos
- Extrae los logs desde la fuente:
journalctl -u docker --since ...journalctl -u containerd --since ...journalctl -k --since ...
- Elige el eje según la evidencia:
- Permission denied + logs de audit → eje de seguridad
- Errores overlay + dmesg overlayfs → eje de almacenamiento
- Errores de cgroup + indicios de cgroup v2 → eje de cgroup
- Colgados + procesos en D-state → eje de almacenamiento/I/O
- Haz un cambio, luego vuelve a probar inmediatamente. No combines “por si acaso” cambios.
- Revierte la verbosidad si la activaste. El logging debug es una linterna temporal, no tu nuevo diseño de iluminación.
Lista para “¿debemos reiniciar docker/containerd?”
- Sí si: los daemons están bloqueados pero el host está sano, sin D-state, sin errores de filesystem, y puedes tolerar una breve interrupción.
- No si: el kernel muestra tareas bloqueadas, operaciones overlay cuelgan, hay errores de almacenamiento o el filesystem raíz está lleno. Arregla el host primero.
- A veces si: existen shims atascados para contenedores muertos. Un apagado limpio y reinicio podría limpiar estado, pero solo después de confirmar que el I/O está sano.
Lista para higiene de almacenamiento (previene la mitad de los incidentes)
- Monitorea los inodos, no solo los bytes.
- Mantén DockerRootDir en almacenamiento local con características del filesystem conocidas y buenas.
- Rota journals y logs de aplicaciones; no dejes que
/var/logse convierta en un vertedero. - Programa prune de imágenes/cache de build acorde con tu ritmo de despliegue (con salvaguardas).
- Vigila los mensajes del kernel sobre advertencias de overlayfs; suelen aparecer antes del fallo total.
Preguntas frecuentes
1) ¿Necesito reinstalar Docker para arreglar errores de containerd/runc?
Rara vez. Reinstalar puede enmascarar el síntoma al resetear configuración/estado, pero no arregla agotamiento de disco, denegaciones LSM, delegación de cgroups o un sistema de archivos que no puede realizar operaciones overlay. Demuestra el eje primero.
2) ¿Cómo saber si es Docker, containerd o runc?
Usa los logs. Docker a menudo dirá “OCI runtime create failed” (eso es runc). Los logs de containerd mencionarán creación de shim/task. Los logs del kernel/audit te dirán si el kernel rechazó una operación (montaje, open, syscall, cgroup).
3) ¿Cuál es la forma más rápida de depurar “permission denied”?
Busca denegaciones LSM: dmesg -T y logs de audit. Si ves mensajes de AppArmor/SELinux, ya tienes la respuesta. Si no, considera permisos del sistema de archivos y seccomp.
4) ¿Qué significa realmente “shim exited unexpectedly”?
Significa que containerd arrancó un proceso shim para gestionar la task del contenedor y murió. Las causas incluyen errores de runc, directorios de estado corruptos o fallos del kernel subyacente. Es un síntoma, no una causa raíz.
5) ¿Por qué los problemas de overlay2 parecen fallos de runtime aleatorios?
Porque overlay2 está debajo de todo. Si los montajes overlay fallan, runc no puede crear un rootfs; si las operaciones overlay se bloquean por I/O, los procesos cuelgan; si no se soportan xattrs/semánticas de rename, obtienes “invalid argument” en lugares que no mencionan almacenamiento.
6) ¿Cómo depuro sin romper contenedores en ejecución?
Prefiere acciones de solo lectura primero: logs, docker info, ctr version, df, dmesg. Evita reiniciar daemons hasta haber evaluado si el problema es localizado o sistémico. Si el almacenamiento cuelga, los reinicios pueden empeorar las cosas.
7) Si habilito logging debug, ¿cuál es el riesgo?
Uso de disco y ruido. Los logs debug pueden crecer rápido durante un bucle de fallo. Si ya estás cerca de límites de disco/inodos, el logging debug puede ser el insulto final. Actívalo brevemente, captura lo necesario y apágalo.
8) ¿Por qué cgroup v2 causa fallos al iniciar contenedores?
Porque la delegación y la habilitación de controladores son más estrictas. Algunas configuraciones asumen el layout de cgroup v1 o dependen de controladores que no están disponibles para Docker/systemd slices. Desajustes de drivers de cgroup (systemd vs cgroupfs) también pueden causar problemas.
9) ¿Es más difícil depurar Docker rootless?
Es distinto, no necesariamente más difícil. Los errores suelen señalar claramente a user namespace, delegación de cgroup o restricciones de red. Pero debes depurarlo con supuestos rootless: rutas distintas, permisos distintos, límites distintos.
10) ¿Qué métrica única añadirías si sigues viendo errores de runtime?
Uso de inodos en el filesystem que contiene DockerRootDir, más conteo de “tareas bloqueadas” del kernel o alertas por advertencias repetidas de overlayfs. Monitorizar solo bytes es la forma de llevarte sorpresas.
Conclusión: siguientes pasos que realmente ayudan
Cuando Docker/containerd/runc empieza a lanzar errores, tu trabajo no es encontrar un comando mágico. Tu trabajo es identificar el eje que falla—almacenamiento, cgroups, política de seguridad o estado del runtime—y luego aplicar una solución dirigida sin detonar el resto del nodo.
Siguientes pasos prácticos:
- Adopta la guía rápida de diagnóstico y guárdala en tus notas de incidentes.
- Añade monitorización del uso de inodos, no solo bytes, en DockerRootDir.
- Incluye logs del kernel/audit en tu flujo estándar de depuración de arranque de contenedores.
- Mantén DockerRootDir en un filesystem local soportado; trata el “almacenamiento compartido rápido” para stores de capas como culpable hasta comprobar su fiabilidad.
- Cuando cambies políticas de seguridad (AppArmor/SELinux/seccomp), hazlo por carga primero. Las desactivaciones globales son como mitigaciones temporales que se convierten en arrepentimientos permanentes.
Si haces todo eso, seguirás teniendo incidentes—la producción siempre encuentra la forma. Pero dejarás de reinstalar tu runtime como si fuera un ritual y empezarás a arreglar el sistema real.