El síntoma es aburrido. “TASK ERROR: startup for container ‘101’ failed.” El impacto no lo es. Estás de guardia, un servicio está caído y el error parece una nota escrita por tres subsistemas que no se llevan: LXC, cgroups y AppArmor.
Si tratas esos mensajes como ruido, vas a dar vueltas—alternando “nesting”, reiniciando nodos y rezando al kernel. Si aprendes a leerlos, normalmente arreglarás el problema en minutos y sabrás si tratas con una falla del plano de control del host (cgroups), una falla de política (AppArmor) o un simple tropiezo con filesystem/ID map.
Un modelo mental: qué debe funcionar para que arranque un LXC
El arranque de LXC en Proxmox es una cadena de dependencias. Cuando falla, el error suele ser preciso—simplemente no es contextual. Necesitas la cadena en la cabeza para localizar el eslabón roto.
La cadena, en términos simples
- Proxmox llama a LXC vía
lxc-start(envuelto por la herramientapve-container), creando un árbol de procesos de contenedor. - Se configuran los namespaces del kernel: mount, PID, red, usuario (para no privilegiados), etc.
- Se crean los cgroups y se adjuntan controladores (CPU, memory, pids, io). Si los cgroups están mal montados, faltan controladores o son incompatibles con las expectativas de configuración, el arranque se detiene.
- Se aplica la política de seguridad: se carga/aplica el perfil de AppArmor; posiblemente se adjuntan filtros seccomp.
- Se monta el sistema de ficheros raíz: bind mounts (como
/dev,/proc,/sys), se adjunta el volumen de almacenamiento, montajes opcionales desdemp0, etc. - Arranca el init dentro del contenedor (a menudo systemd). Si systemd no puede montar cgroups o es denegado, puede salir inmediatamente—haciendo que parezca que “el contenedor no arranca”.
Conclusión práctica: los errores de cgroups suelen ser a nivel de host (configuración del nodo, flags de arranque del kernel, disposición de los mounts), mientras que los errores de AppArmor son de política (el perfil deniega una acción específica, comúnmente tras activar nesting, FUSE, CIFS o acceso a dispositivos privilegiados).
Una cita para tener a la vista: “La esperanza no es una estrategia.”
— idea parafraseada atribuida a menudo a líderes de operaciones. El punto es: deja de adivinar y sigue la pista de evidencia.
Broma corta #1: Si tu contenedor no arranca por cgroups, enhorabuena—has descubierto un problema que no se arregla con más YAML.
Guía de diagnóstico rápido (comprobar primero/segundo/tercero)
Este es el camino “tengo cinco minutos”. No es exhaustivo; está diseñado para encontrar el cuello de botella rápido y decidir si puedes arreglar in situ o necesitas drenar el nodo.
Primero: confirma que no es un fallo simple de configuración o almacenamiento
- Revisa el log de tarea de Proxmox (por lo general incluye la primera falla concreta).
- Revisa la configuración del contenedor buscando montajes sospechosos, nesting y ajustes de no privilegiado.
- Confirma que el volumen rootfs existe y puede montarse en el host.
Segundo: decide si huele a cgroups o a AppArmor
- Si ves cadenas como
cgroup,cgroup2,controllers,cgroupfs,failed to mount /sys/fs/cgroup: ve primero por cgroups. - Si ves
apparmor="DENIED",profile=,operation=,audit: ve primero por AppArmor. - Si dice
permission deniedpero sin líneas de auditoría de AppArmor, sospecha mapas de ID, propiedad en puntos de montaje o permisos de almacenamiento.
Tercero: aísla si es un contenedor o el nodo
- Arranca un contenedor pequeño conocido y bueno. Si eso también falla, es el nodo.
- Comprueba si otros contenedores arrancan; si no, deja de tocar configuraciones individuales y arregla el sustrato del host.
- Busca cambios recientes: actualización del kernel, actualización de Proxmox, activación de nesting, cambio de modo de AppArmor, cambio de modo de cgroups, nuevos puntos de montaje.
Regla decisiva: si es un problema de montaje/controlador de cgroups a nivel de nodo, no lo “arreglas” por contenedor. Arregla el nodo o evacua cargas. Cualquier otra cosa es trabajo improductivo con sonidos de teclado más agradables.
Cómo leer errores de cgroups y AppArmor sin adivinar
Dónde está escrita la verdad
La UI de Proxmox muestra un error resumido. Los detalles accionables suelen estar en uno de estos lugares:
- Task log (lo que muestra la UI, pero a veces truncado)
journalctlen el host (mensajes de systemd y kernel)/var/log/syslogen hosts basados en Debian- Buffer de anillo del kernel (
dmesg) - El propio logging de LXC (mayor verbosidad mediante arranque debug)
- Logs de auditoría para denegaciones de AppArmor (a menudo visibles vía journal)
Leer errores de cgroups: las palabras clave importan
Los errores de cgroups tienden a caer en unos pocos cubos:
- Problemas de montaje: LXC no puede montar o acceder a
/sys/fs/cgroup(común con expectativas de cgroup v2 frente a la configuración del host). - Disponibilidad de controladores: controladores requeridos (como
memoryopids) no están disponibles o no están delegados correctamente. - Problemas de permisos/propiedad: problemas de delegación al usar cgroups gestionados por systemd y contenedores no privilegiados.
- Confusión por jerarquía mixta: incompatibilidades v1 vs v2 vs híbrido, a veces tras actualizaciones.
Cuando ves un error como Failed to mount "cgroup" o cgroup2: No such file or directory, no lo interpretes como “el contenedor está roto”. Interprétalo como: la disposición del sistema de ficheros de cgroup del host no coincide con lo que LXC espera para la configuración de ese contenedor y el init del host.
Leer errores de AppArmor: la línea de denegación es un mini informe de incidente
Una línea de denegación de AppArmor incluye: marca temporal, perfil, operación, recurso solicitado y a veces el “nombre” (ruta). Suele ser suficiente para decidir si debes:
- Ajustar características del contenedor (nesting, keyctl, fuse, tipos de montaje)
- Corregir un problema de permisos/etiquetado de archivos en el host
- Cambiar temporalmente el modo del perfil de AppArmor (complain vs enforce) mientras coleccionas evidencia
- Dejar de hacer lo arriesgado (mi opción preferida en producción)
AppArmor no está “rompiendo contenedores al azar”. Está haciendo exactamente lo que pediste: bloquear acciones que no están en la política. La parte molesta es que a menudo no nos dimos cuenta de que lo pedimos.
Tareas prácticas: comandos, salida esperada y decisiones
Estos son los movimientos que realmente uso bajo presión. Cada uno incluye la decisión que debes tomar a partir de la salida. Hazlos en orden si estás perdido; selecciona según convenga si ya tienes una pista clara.
Task 1: Extraer la falla exacta del output de tarea de Proxmox (CLI)
cr0x@server:~$ pct start 101
starting container 101
lxc-start: 101: conf.c: run_buffer: 312 Script exited with status 1
lxc-start: 101: start.c: __lxc_start: 2107 Failed to initialize container "101"
TASK ERROR: startup for container '101' failed
Qué significa: LXC falló; Proxmox solo lo reporta. El mensaje es genérico; necesitas logs más profundos.
Decisión: Ve inmediatamente a los logs de journal y a la evidencia de AppArmor/cgroups (tareas siguientes). No modifiques la configuración al azar todavía.
Task 2: Obtener la config del contenedor y buscar “bordes afilados”
cr0x@server:~$ cat /etc/pve/lxc/101.conf
arch: amd64
cores: 2
hostname: app-101
memory: 2048
net0: name=eth0,bridge=vmbr0,firewall=1,ip=dhcp,type=veth
ostype: debian
rootfs: local-lvm:vm-101-disk-0,size=8G
unprivileged: 1
features: nesting=1,keyctl=1
mp0: /srv/shared,mp=/mnt/shared,backup=0
Qué significa: Este contenedor es no privilegiado, con nesting+keyctl, y un bind mount del host (mp0). Esos tres son fuentes comunes de errores de AppArmor y permisos.
Decisión: Si recientemente activaste nesting o añadiste mp0, sospecha de AppArmor o de propiedad/mapeo de IDs primero.
Task 3: Comprobar el modo de cgroup del host (v1 vs v2)
cr0x@server:~$ stat -fc %T /sys/fs/cgroup
cgroup2fs
Qué significa: El host usa la jerarquía unificada cgroups v2.
Decisión: Si el contenedor o LXC espera montajes legacy v1 (raro en PVE moderno, pero puede pasar con configs antiguas), planifica alinear. También comprueba la disponibilidad de controladores (tarea siguiente).
Task 4: Verificar que los controladores cgroup requeridos estén disponibles y habilitados
cr0x@server:~$ cat /sys/fs/cgroup/cgroup.controllers
cpuset cpu io memory hugetlb pids rdma
Qué significa: Estos son controladores soportados por el kernel y disponibles en el cgroup raíz. Si falta memory o pids, contenedores que definan límites de memoria/pids pueden fallar o comportarse mal.
Decisión: Controladores faltantes significan un problema de configuración del host/kernel. Deja de depurar por contenedor y arregla la configuración de arranque/kernel del nodo.
Task 5: Comprobar si systemd ha delegado controladores apropiadamente
cr0x@server:~$ systemctl show -p DefaultControllers
DefaultControllers=cpu cpuacct io memory pids
Qué significa: En sistemas con systemd, la delegación y configuración de controladores influyen en si LXC puede crear/gestionar cgroups. Systemd más nuevo usa v2 de forma distinta que versiones anteriores.
Decisión: Si ves rarezas (controladores esperados faltando) tras una actualización, sospecha de interacción systemd+cgroups. Revisa cambios de paquetes recientes y considera un reinicio controlado tras arreglar parámetros de arranque.
Task 6: Inspeccionar mounts por rarezas de cgroup (disposiciones híbridas, mount faltante)
cr0x@server:~$ mount | grep -E 'cgroup|cgroup2'
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
Qué significa: Montaje v2 limpio. Si ves múltiples montajes v1 además de cgroup2, podrías estar en modo híbrido.
Decisión: Si LXC se queja de montar cgroup2 pero estás en v1 o híbrido, alinea el host (parámetros de arranque) con lo que PVE espera para tu versión.
Task 7: Extraer el slice del journal relevante para el intento de arranque del contenedor
cr0x@server:~$ journalctl -u pve-container@101.service -n 200 --no-pager
Dec 26 10:18:01 pve1 systemd[1]: Starting PVE LXC Container: 101...
Dec 26 10:18:01 pve1 lxc-start[18277]: lxc-start: 101: cgfsng.c: cgroup_init: 846 Failed to create cgroup /lxc/101
Dec 26 10:18:01 pve1 lxc-start[18277]: lxc-start: 101: cgfsng.c: cgroup_init: 850 No such file or directory
Dec 26 10:18:01 pve1 systemd[1]: pve-container@101.service: Main process exited, code=exited, status=1/FAILURE
Dec 26 10:18:01 pve1 systemd[1]: pve-container@101.service: Failed with result 'exit-code'.
Qué significa: LXC no pudo crear la ruta de cgroup que quería. Normalmente es disposición del filesystem de cgroup, delegación o una configuración de raíz de cgroup obsoleta/incorrecta.
Decisión: Mantente en la pista de cgroups. Busca permisos faltantes en /sys/fs/cgroup, problemas de delegación o árbol de cgroups systemd roto.
Task 8: Comprobar denegaciones de AppArmor alrededor del momento de la falla
cr0x@server:~$ journalctl -k -g 'apparmor="DENIED"' -n 50 --no-pager
Dec 26 10:18:01 pve1 kernel: audit: type=1400 audit(1735208281.214:915): apparmor="DENIED" operation="mount" profile="lxc-container-default-cgns" name="/sys/kernel/security" pid=18290 comm="(mount)" flags="rw, nosuid, nodev, noexec"
Qué significa: El contenedor intentó una operación de mount bloqueada por el perfil aplicado. Eso puede impedir que systemd dentro del contenedor arranque.
Decisión: Decide si el montaje es necesario. Si viene de nesting/expectativas de systemd, arregla características del contenedor o ajusta la política de forma controlada. No deshabilites AppArmor globalmente.
Task 9: Verificar que AppArmor está habilitado y los perfiles cargados
cr0x@server:~$ aa-status
apparmor module is loaded.
60 profiles are loaded.
58 profiles are in enforce mode.
2 profiles are in complain mode.
0 processes are unconfined but have a profile defined.
Qué significa: AppArmor está activo. Si muestra “module is not loaded”, entonces AppArmor no es tu bloqueo—tus líneas “DENIED” no existirán.
Decisión: Si hay denegaciones y AppArmor está en enforcing, trata esas denegaciones como causas de primera clase, no como ruido de fondo.
Task 10: Ejecutar LXC con logging debug (quirúrgico, ruidoso, útil)
cr0x@server:~$ lxc-start -n 101 -F -l DEBUG -o /tmp/lxc-101.debug.log
lxc-start: 101: start.c: lxc_init: 846 Container "101" initialized
lxc-start: 101: cgfsng.c: cgroup_init: 846 Failed to create cgroup /lxc/101
lxc-start: 101: start.c: __lxc_start: 2107 Failed to initialize container "101"
Qué significa: Confirma que la falla ocurre durante la inicialización de cgroup, no en el montaje del rootfs o la configuración de red.
Decisión: Deja de perseguir AppArmor si los logs debug paran antes de aplicarse el perfil de seguridad. Arregla cgroups primero.
Task 11: Validar que el volumen rootfs del contenedor existe y es accesible
cr0x@server:~$ pvesm status
Name Type Status Total Used Available
local dir active 100.00G 42.10G 57.90G
local-lvm lvmthin active 60.00G 25.00G 35.00G
Qué significa: El backend de almacenamiento está activo. Si está inactivo, puedes obtener “permission denied” o fallos de montaje que parecen AppArmor.
Decisión: Si el almacenamiento está caído, arregla el almacenamiento primero. Los contenedores no pueden montar discos imaginarios.
Task 12: Confirmar que el LV rootfs existe realmente (ejemplo LVM-thin)
cr0x@server:~$ lvs -a -o lv_name,vg_name,lv_size,lv_attr | grep vm-101-disk-0
vm-101-disk-0 pve 8.00g Vwi-a-tz--
Qué significa: El volumen existe. Si no existe, el contenedor no puede arrancar.
Decisión: Volumen faltante significa un problema de restauración/backup o referencia errónea en 101.conf. Arregla la config o recrea el volumen.
Task 13: Comprobar las rutas fuente de bind mount y permisos en el host
cr0x@server:~$ ls -ld /srv/shared
drwxr-x--- 2 root root 4096 Dec 25 09:12 /srv/shared
Qué significa: Para un contenedor no privilegiado, un bind mount propiedad root:root con permisos restrictivos puede romper el arranque si LXC intenta configurar la propiedad del montaje o el init del contenedor espera acceso.
Decisión: Ajusta la propiedad para que coincida con los IDs mapeados del contenedor (ver tarea de ID map), o monta como solo lectura, o deja de bind-mountar rutas del host en contenedores no privilegiados a menos que hayas planificado la propiedad.
Task 14: Inspeccionar el mapeo de IDs para un contenedor no privilegiado
cr0x@server:~$ grep -E '^(root|lxc)' /etc/subuid /etc/subgid
/etc/subuid:root:100000:65536
/etc/subgid:root:100000:65536
Qué significa: El host tiene rangos de UID/GID subordinados para root, que los contenedores no privilegiados necesitan. Si faltan, los contenedores no privilegiados a menudo fallan con errores de permisos poco útiles.
Decisión: Si los rangos faltan o son demasiado pequeños para tu uso de idmap, arregla /etc/subuid y /etc/subgid, luego reinicia servicios relevantes o reinicia.
Task 15: Confirmar que el kernel soporta los namespaces y características de cgroup requeridos
cr0x@server:~$ zgrep -E 'CONFIG_CGROUPS=|CONFIG_USER_NS=|CONFIG_CGROUP_BPF=|CONFIG_CGROUP_FREEZER=' /boot/config-$(uname -r)
CONFIG_CGROUPS=y
CONFIG_USER_NS=y
CONFIG_CGROUP_BPF=y
# CONFIG_CGROUP_FREEZER is not set
Qué significa: Las características núcleo están presentes. Un CONFIG_USER_NS ausente mataría contenedores no privilegiados por completo. Que falte Freezer no suele ser fatal en entornos modernos, pero puede afectar comportamientos/herramientas.
Decisión: Si faltan configuraciones claves, estás en el kernel equivocado o en una compilación personalizada. Usa la serie de kernels soportada por Proxmox y evita improvisar.
Task 16: Comprobar si el systemd del contenedor falla por montaje de cgroups
cr0x@server:~$ pct start 101 --debug
lxc-start: 101: conf.c: run_buffer: 312 Script exited with status 1
lxc-start: 101: conf.c: run_buffer: 313 Script exited with status 1: mount: /sys/fs/cgroup: permission denied
Qué significa: La falla es al montar cgroups dentro del contenedor. Eso puede ser denegación de AppArmor, delegación faltante, o mismatch de configuración de LXC para la versión de cgroup.
Decisión: Correlaciona con denegaciones de AppArmor (Task 8). Si no hay denegaciones, comprueba la delegación/permisos de cgroup y las características del contenedor.
Modos de fallo de cgroups que detienen contenedores en seco
1) Jerarquía unificada cgroups v2 que no coincide con expectativas
cgroups v2 es la forma moderna: una jerarquía, semántica consistente y mejor delegación. Pero la realidad es desordenada: imágenes de contenedores antiguas, versiones viejas de LXC o configuraciones heredadas pueden asumir puntos de montaje v1 (como /sys/fs/cgroup/memory).
En Proxmox, el host suele ser gestionado por systemd y cada vez más v2. Si la secuencia de arranque del contenedor intenta montar controladores v1 o espera rutas v1, verás errores de montaje o “No such file or directory” en la inicialización de cgroup.
Qué hacer: Alinear. Prefiere ejecutar la combinación de kernel y stack LXC soportada por Proxmox. Evita forzar modos legacy de cgroup a menos que tengas una necesidad concreta de compatibilidad y un plan de reversión.
2) Controladores disponibles pero no delegados (problema del límite de systemd)
Incluso cuando los controladores existen, el proceso que crea el contenedor debe tener permiso para crear sub-cgroups y habilitar controladores. Con cgroups v2, habilitar controladores es explícito y puede ser bloqueado por la configuración del cgroup padre.
Los síntomas parecen “Failed to create cgroup” u “Operation not permitted”, a veces solo para algunos contenedores (aquellos que definen límites, o los que se inician mediante unidades diferentes).
Qué hacer: Trátalo como un problema de configuración del nodo. Valida la delegación de systemd y el árbol de cgroups. Si cambiaste recientemente cómo se inician contenedores (unidades personalizadas, wrappers), revierte eso primero.
3) /sys/fs/cgroup de solo lectura o roto dentro del contenedor
Algunas imágenes de contenedor están diseñadas para entornos estilo Docker y asumen que pueden gestionar cgroups o montar ciertos pseudo-filesystems. Los contenedores LXC suelen correr con límites más estrictos. Si systemd dentro del contenedor quiere montar y se le niega, sale rápido. El contenedor “arranca” y “se detiene” al instante, lo que parece un problema de Proxmox.
Qué hacer: Decide si systemd dentro de ese contenedor debe ser PID 1. Si debe serlo, ajusta las características del contenedor con cuidado. Si no, ejecuta un init más simple para esa carga de trabajo.
4) Regresiones del kernel y parámetros de arranque “útiles”
A veces el nodo se actualizó y el comportamiento de cgroups cambió. A veces alguien añadió parámetros de arranque para otro problema y accidentalmente rompió contenedores. cgroups es sensible a flags en la línea de comando del kernel y a los defaults de systemd.
Mi postura operativa: mantener los nodos consistentes. Si necesitas “caso especial” en un nodo, estás creando un incidente futuro con un retraso.
Modos de fallo de AppArmor: denegaciones, perfiles y la trampa “funcionaba ayer”
Conceptos básicos de AppArmor que realmente necesitas
AppArmor es un control de acceso obligatorio por perfil: limita lo que un proceso puede hacer incluso si los permisos Unix dicen “permitido”. LXC en Proxmox usa perfiles de AppArmor para restringir contenedores. Cuando algo dentro del contenedor intenta una operación bloqueada (montar, acceder interfaces del kernel, usar FUSE, etc.), obtienes una denegación en el log de auditoría del kernel.
1) Nesting habilita comportamientos que AppArmor bloquea por defecto
Nesting es la característica que hace a la gente valiente: “Corramos Docker dentro de este contenedor.” Amplía syscalls y comportamiento de montaje dentro del contenedor, lo que choca con políticas conservadoras. De repente el contenedor quiere montar overlayfs, acceder a /sys/kernel/security, usar keyrings o manipular cgroups de formas que AppArmor dice “no”.
Qué hacer: Si necesitas Docker/Kubernetes dentro de algo, usa una VM a menos que tengas una razón disciplinada para no hacerlo. Los contenedores anidados son bonitos hasta que se convierten en tu hobby de respuesta a incidentes.
2) Bind mounts y rutas del host: política + propiedad, doble problema
Los bind mounts (mp0, mp1, etc.) son la forma más rápida de hacer útil un contenedor LXC, y también la más rápida de romperlo. AppArmor puede restringir ciertas operaciones de montaje, y los contenedores no privilegiados pueden no poder acceder a los ficheros montados debido al mapeo UID/GID. Estas fallas pueden aparecer como denegaciones de AppArmor o errores genéricos de permiso.
Qué hacer: Trata los bind mounts como un contrato de interfaz. Decide la propiedad y los patrones de acceso desde el principio. Documenta en la configuración del contenedor. No “simplemente montes /srv” y esperes que funcione.
3) “Deshabilitar AppArmor” es el reflejo equivocado
Sí, apagar AppArmor puede hacer que el contenedor arranque. También puede hacer que la postura de seguridad del host ejecute un baile interpretativo dramático. Tu objetivo no es “estado verde”. Tu objetivo es control correcto.
Enfoque mejor: Usa las denegaciones de AppArmor para identificar exactamente qué operación está bloqueada y luego decide si la operación es necesaria y segura. A veces la solución correcta es eliminar la característica que la provocó. A veces es cambiar cómo se ejecuta la carga. Ocasionalmente es ajustar el perfil—con cuidado, mínimamente y de forma consistente entre nodos.
Broma corta #2: Desactivar AppArmor para arreglar un error de arranque es como quitar el detector de humo porque hace ruido.
Tres mini-historias corporativas desde el frente
Mini-historia 1: Un incidente causado por una suposición equivocada
Un equipo migró un servicio de procesamiento por lotes de VMs a LXC para “ahorrar overhead”. Su suposición fue simple: los contenedores son como VMs ligeras. No estaban equivocados en espíritu, pero sí en lo que importa—los planos de control del kernel son compartidos y systemd dentro de contenedores es exigente con los cgroups.
Tras una actualización rutinaria del SO en el nodo Proxmox, un subconjunto de contenedores dejó de arrancar. Los logs decían “Failed to create cgroup” y “permission denied” en /sys/fs/cgroup. El equipo se centró en los contenedores: reconstruyeron imágenes, revirtieron cambios de aplicación e incluso restauraron desde backup. Nada cambió. El nodo seguía negándose a proporcionar la disposición de cgroup que los contenedores esperaban.
El problema real fue un desajuste del modo de cgroups introducido en mantenimiento. El nodo había derivado a una configuración híbrida que no coincidía con el resto del clúster. La mayoría de contenedores arrancaron porque no definían límites estrictos; los workers por lotes sí. La suposición “si un contenedor arranca, el nodo está bien” era falsa. Algunas cargas son más sensibles a cgroups que otras.
La solución fue aburrida: alinear parámetros del kernel y comportamiento de systemd con el resto de la flota, y reiniciar en ventana de mantenimiento. La lección: en el mundo de contenedores, “funciona en uno” no es evidencia de corrección; es evidencia de cobertura incompleta.
Mini-historia 2: Una optimización que salió mal
Un equipo de plataforma quería aprovisionamiento más rápido. Estandarizaron contenedores no privilegiados por seguridad (bien) y añadieron un directorio compartido del host montado en docenas de LXCs para cachear artefactos (cuestionable). El directorio de cache era propiedad de root en el host, pero dentro de cada contenedor debía ser escribible por un usuario de la app.
Para “arreglar” permisos, alguien habilitó nesting y keyctl ampliamente, luego ajustó algunos contenedores para ejecutar scripts ayudantes al arrancar que hacían chown de directorios. Funcionó en pruebas. En producción, AppArmor empezó a bloquear operaciones relacionadas con mount disparadas por unidades systemd y scripts ayudantes. Los contenedores hacían flap: arrancaban, intentaban montajes, eran denegados y salían. El monitoreo los veía como “inestables” en lugar de “bloqueados por política”.
El problema no fue solo las denegaciones. El mayor problema fue el modelo operativo: docenas de contenedores dependían de una ruta compartida del host con traducción de propiedad implícita y mutación en tiempo de ejecución. Cada cambio en permisos de ese directorio se convertía en un evento de clúster. Depurar se volvió arqueología.
La solución fue dejar de intentar que los bind mounts funcionaran como un sistema de archivos distribuido. Movieron los artefactos de cache a un servicio adecuado y mantuvieron los bind mounts para datos simples de solo lectura. El arranque volvió a ser aburrido. Esa fue la victoria: lo aburrido es una característica.
Mini-historia 3: Una práctica aburrida pero correcta que salvó el día
Un grupo de infraestructura trató a los nodos Proxmox como ganado, no mascotas—al menos tanto como se puede en virtualización. Tenían dos hábitos que parecían tediosos: mantenían una plantilla de “contenedor pequeño conocido” para pruebas de humo, y registraban la deriva a nivel de nodo (versión del kernel, flags de arranque, estado de AppArmor, modo de cgroup) tras cada cambio.
Una mañana, tras una ola de actualizaciones de seguridad, un nodo empezó a rechazar nuevos arranques de contenedores con errores de cgroup. En lugar de explorar la configuración de una carga de producción, el on-call arrancó el contenedor de humo. Falló igual. Ese único dato colapsó el espacio de búsqueda: no era sobre imágenes de aplicación o puntos de montaje; era el sustrato del nodo.
Compararon el registro de deriva del nodo con un par sano. La única diferencia fue un cambio sutil en parámetros de arranque introducido durante una sesión previa de troubleshooting, dejado como cáscara de banana. Lo revirtieron, reiniciaron el nodo de forma controlada y las cargas se recuperaron.
Sin heroísmos. Sin hacks “temporales” que se vuelven permanentes. La práctica correcta no era glamorosa—era consistencia y una prueba repetible. En operaciones, el glamour suele ser señal de que estás a punto de hacer algo de lo que te arrepentirás.
Errores comunes: síntoma → causa raíz → solución
1) Síntoma: “Failed to create cgroup /lxc/101”
Causa raíz: Desajuste de disposición del filesystem de cgroup o delegación (a menudo habilitación de controladores v2 o montaje de cgroup faltante).
Solución: Verifica que /sys/fs/cgroup esté montado correctamente, que los controladores estén presentes y que la delegación de systemd sea sensata. Alinea la configuración del nodo; no parchees por contenedor.
2) Síntoma: “mount: /sys/fs/cgroup: permission denied” durante el arranque
Causa raíz: Denegación de AppArmor sobre el mount, o contenedor intentando montajes no permitidos por su perfil, a veces desencadenado por nesting.
Solución: Revisa las denegaciones de auditoría del kernel. Elimina o limita nesting/keyctl salvo que sean necesarios. Si lo son, usa una VM o un ajuste cuidadoso del perfil.
3) Síntoma: Contenedor arranca y luego se detiene inmediatamente; la UI no muestra error claro
Causa raíz: PID 1 dentro del contenedor (a menudo systemd) falla temprano por cgroups o política de seguridad.
Solución: Usa inicio en debug y logs del journal. Busca errores de systemd sobre cgroups. Decide si systemd es apropiado; si sí, arregla restricciones de cgroup/AppArmor.
4) Síntoma: Funciona como privilegiado, falla como no privilegiado
Causa raíz: Problemas de mapeo de IDs/subuid-subgid, desajuste de propiedad en bind mount, o rutas del host inaccesibles bajo los IDs mapeados.
Solución: Valida /etc/subuid//etc/subgid. Arregla la propiedad de las rutas de montaje para que coincidan con el rango mapeado. Evita usar privilegiado como “solución”; es otro modelo de seguridad.
5) Síntoma: Solo fallan contenedores con límites de recursos
Causa raíz: Controladores faltantes (memory/pids) o problemas de habilitación de controladores en v2.
Solución: Confirma presencia de controladores y delegación de systemd. Arregla configuración del nodo; mantén nodos uniformes.
6) Síntoma: Denegaciones de AppArmor mencionan /sys/kernel/security u operaciones de mount
Causa raíz: Contenedor intentando acceder interfaces de seguridad del kernel o montar pseudo-filesystems sensibles—a menudo nesting o cargas especiales.
Solución: Reevalúa por qué la carga necesita eso. Prefiere VM para runtimes anidados. Si debes hacerlo, acota los cambios de política y prueba frente a actualizaciones.
7) Síntoma: “No such file or directory” para rutas de cgroup
Causa raíz: Esperar rutas v1 en un sistema v2, o configuración LXC obsoleta que referencia mountpoints antiguos.
Solución: Elimina hacks de montaje legacy de la configuración del contenedor. Asegura que host y versiones de LXC sean compatibles y consistentes en el clúster.
8) Síntoma: Añadir mp0 hizo que el contenedor dejara de arrancar
Causa raíz: Ruta del host ausente, permisos erróneos o restricciones de mount por AppArmor.
Solución: Confirma que la ruta de origen en el host exista y que los permisos se alineen con el mapeo no privilegiado. Prueba quitar temporalmente el mount para confirmar causalidad; luego rediseña el acceso correctamente.
Listas de verificación / plan paso a paso
Checklist A: Triage de cinco minutos (un solo contenedor no arranca)
- Ejecuta
pct start <id>desde CLI para obtener el error bruto. - Inspecciona
/etc/pve/lxc/<id>.confbuscando:unprivileged,features,mp*, líneaslxc.*personalizadas. - Revisa
journalctl -u pve-container@<id>.service -n 200en busca de la primera falla concreta. - Busca denegaciones de AppArmor alrededor de la marca temporal:
journalctl -k -g 'apparmor="DENIED"'. - Si el error parece de cgroup, confirma modo y controladores de cgroup (
stat -fc %T /sys/fs/cgroup,cat /sys/fs/cgroup/cgroup.controllers). - Elimina temporalmente el cambio más reciente y arriesgado (un nuevo bind mount, nesting) para confirmar causalidad.
Checklist B: Fallo a nivel nodo (varios contenedores no arrancan)
- Arranca un contenedor pequeño conocido. Si falla igual, trátalo como problema del nodo.
- Verifica montaje y modo de cgroup:
mount | grep cgroupystat -fc %T /sys/fs/cgroup. - Verifica el conjunto de controladores:
cat /sys/fs/cgroup/cgroup.controllers. - Busca ruido obvio del kernel/auditoría:
dmesg -T | tail -n 200. - Confirma que el módulo AppArmor está cargado y en enforcing:
aa-status. - Compara paquetes/versión del kernel con un nodo sano (mismo cluster):
uname -r,pveversion -v. - Revierte la deriva (flags de arranque, unidades personalizadas) antes de intentar “arreglos creativos”.
- Si no puedes restaurar rápido, evacua cargas y repara en mantenimiento. Producción valora decisiones contundentes.
Checklist C: Cuando debes cambiar algo (disciplina de cambio seguro)
- Haz un cambio a la vez: elimina nesting, quita un mount, revierte un boot flag—nunca todo a la vez.
- Captura evidencia antes/después: slice del journal, líneas de denegación y lista de controladores de cgroup.
- Prueba con dos contenedores: uno “simple” y otro “complejo” (límites de recursos + bind mount + systemd).
- Propaga la solución consistentemente a todos los nodos; la inconsistencia genera incidentes.
Datos interesantes y contexto histórico (las partes que la gente olvida)
- cgroups empezaron a finales de los 2000 como mecanismo del kernel para contabilizar y limitar recursos por grupo de procesos—mucho antes de que “contenedores” fuera un término de marketing.
- LXC precede la popularidad de Docker; es uno de los enfoques más antiguos de “contenedor de sistema”, más cercano a VMs ligeras que a contenedores de aplicaciones.
- cgroups v2 no es solo v1 con otro nombre; cambia semánticas (como jerarquía unificada y habilitación de controladores), por eso los desajustes son dañinos.
- systemd se volvió un actor central en la gestión de cgroups en Linux; aunque nunca lo pidieras, ahora posee gran parte del árbol de cgroups en la mayoría de distros.
- AppArmor se basa en rutas (a diferencia del modelo por etiquetas de SELinux), lo que hace las denegaciones más legibles pero puede volver la política frágil con cambios de rutas.
- Los contenedores no privilegiados dependen de user namespaces; el host mapea “root” del contenedor a un rango de UID no root, lo cual es genial hasta que bind-mountas rutas del host sin planear la propiedad.
- La característica “nesting” es un trade-off: habilita cargas que quieren comportamiento más parecido al kernel, pero reduce las garantías de aislamiento que probablemente buscabas con LXC.
- Muchas fallas de arranque son en realidad fallas de PID 1: systemd dentro del contenedor sale porque no puede montar cgroups o es denegado, y LXC reporta “contenedor falló”.
- Existieron modos híbridos de cgroups por compatibilidad, pero son operativamente incómodos; cuando una flota deriva entre modos, obtienes fallos “funciona en el nodo A”.
Preguntas frecuentes
1) ¿Cómo sé si es cgroups o AppArmor en menos de un minuto?
Busca denegaciones en el log del kernel: journalctl -k -g 'apparmor="DENIED"'. Si obtienes entradas relevantes en el tiempo de la falla, es política. Si los logs muestran fallos de creación/montaje de cgroup sin denegaciones, es sustrato (cgroups/systemd).
2) La UI muestra “startup failed”, pero el contenedor funcionó ayer. ¿Qué cambió?
Normalmente: una actualización del kernel, actualización de systemd, actualización de Proxmox o un cambio en la config del contenedor (nesting, bind mounts, límites de recursos). Comienza comparando pveversion -v y uname -r con un nodo sano.
3) ¿Cambiar el contenedor a privilegiado es una solución aceptable?
Es una herramienta diagnóstica, no una solución. Si privilegiado arranca y no privilegiado falla, has aprendido que probablemente es mapeo de IDs, propiedad de bind mount o restricciones de user namespace. Arregla eso y vuelve a no privilegiado salvo que tengas aprobación formal de riesgo.
4) ¿Necesito nesting para ejecutar Docker dentro de un LXC?
A menudo sí, y ese es el problema. Si vas en serio con Docker/Kubernetes, prefiere una VM. El nesting en LXC puede hacerse funcionar, pero es frágil operativamente y suele interactuar mal con AppArmor y expectativas de cgroups.
5) ¿Qué suele significar “Failed to mount /sys/fs/cgroup” dentro del contenedor?
O bien el contenedor intenta montar cgroups pero no tiene permiso (AppArmor) o la configuración/delegación de cgroup del host no soporta lo que el init del contenedor espera. Correlaciona con denegaciones de AppArmor primero, luego valida modo de cgroup y controladores del host.
6) ¿Por qué solo algunos contenedores fallan después de una actualización del nodo?
Porque no todos los contenedores ejercen las mismas interfaces del kernel. Contenedores con límites de memory/pids, systemd, nesting o montajes complejos tocan más agresivamente cgroups y AppArmor.
7) ¿Se pueden ignorar las denegaciones de AppArmor si el contenedor aún arranca?
No. Una denegación que “te sale bien” hoy puede convertirse en una falla dura tras una actualización de paquetes que cambia el comportamiento. Trata las denegaciones como deuda técnica con tasa de interés.
8) Mi bind mount funciona en un contenedor privilegiado pero no en no privilegiado. ¿Por qué?
Los contenedores no privilegiados mapean UIDs/GIDs; “root” dentro no es root en el host. La propiedad y permisos en la ruta del host deben coincidir con el rango mapeado, o el contenedor no podrá acceder. Arregla la propiedad o rediseña la estrategia de montaje.
9) ¿Debo deshabilitar AppArmor para pasar un incidente?
Sólo como medida de contención de último recurso y sólo si entiendes el radio de impacto. Prefiere eliminar la característica que lo dispara (como nesting) o arreglar el comportamiento denegado específico. Deshabilitar AppArmor es un cambio de política, no un “reinicio”.
10) ¿Cuál es la forma más segura de probar cambios?
Usa una plantilla de contenedor pequeño conocido, aplica un cambio, prueba arrancar/detener, luego prueba con un contenedor “complejo” (systemd + límites + mount). La consistencia entre nodos importa más que depurar heroicamente en una sola máquina.
Conclusión: próximos pasos prácticos
Cuando un LXC no arranca en Proxmox y los logs mencionan cgroups o AppArmor, el sistema te está diciendo exactamente qué falló—solo que no en un orden amigable. Tu trabajo es clasificar la falla: sustrato (cgroups/systemd/kernel) vs política (AppArmor) vs propiedad/almacenamiento (bind mounts, idmaps).
Próximos pasos que realmente haría:
- Recopilar evidencia: salida de
pct start,journalctl -u pve-container@IDy cualquier denegación de AppArmor. - Si fallan varios contenedores, deja de depurar configs individuales y valida inmediatamente modo/controladores de cgroup del host.
- Si AppArmor está denegando mounts o acceso a seguridad del kernel, elimina primero la característica que lo dispara (nesting/bind mount), luego reintroduce con cuidado—o mueve la carga a una VM.
- Anota qué cambió (kernel, boot flags, características del contenedor). La deriva es como “un nodo raro” que se convierte en una interrupción recurrente.
Arregla la capa real. Mantén los nodos uniformes. Y cuando te tiente deshabilitar un sistema de seguridad para “hacerlo funcionar”, respira y vuelve a leer la línea de denegación. Normalmente tiene razón.