Blog
¿Qué clientes realmente necesitan su propia VM? Un plan de aislamiento de Docker por niveles
Una VM para cada cliente es excesivo. Aquí te mostramos cómo decidir cuánto aislamiento necesita cada inquilino—y automatizar la decisión.
Resumen
Las agencias a menudo entran en pánico cuando un cliente pregunta qué tan aislados están realmente sus datos de otros inquilinos. Los namespaces y cgroups de Docker ofrecen un aislamiento real, pero no son lo mismo que una barrera de hardware. En lugar de ejecutar cada cliente en una VM—o peor, tratar a todos los clientes por igual—construye un pequeño conjunto de niveles de aislamiento y asigna cada cliente a uno según la sensibilidad de los datos, la confianza y el cumplimiento normativo. Un contenedor bloqueado (sin root, capacidades eliminadas, seccomp, raíz de solo lectura) cubre la mayoría de los sitios; las cargas de trabajo reguladas u hostiles obtienen una VM o un híbrido de contenedor en VM. Esta publicación ofrece un flujo de decisión repetible, una tabla comparativa y una mirada honesta a cuándo más aislamiento es excesivo.
¿Estás en el punto de una llamada de ventas donde el nuevo cliente dice “somos del sector salud, muéstranos que nuestros datos están aislados de tus otros clientes” y preferirías hablar de cualquier otra cosa?
Este es el problema de las agencias: no un despliegue perfecto, sino el mismo despliegue confiable repetido en una docena de clientes con diferentes presupuestos, perfiles de riesgo y demandas de cumplimiento. Aquí está la versión honesta. El aislamiento de Docker es real, pero es específico. Los namespaces le dan a cada contenedor su propia vista de procesos, red y sistema de archivos; los cgroups limitan CPU, memoria y E/S de disco para que los inquilinos no se priven entre sí. Lo que eso no te compra es una pared de hardware entre el contenedor y el kernel del host. Si un atacante escapa del contenedor, está dentro del único kernel que tienes. El resto de este artículo convierte ese hecho incómodo en una decisión repetible: clasifica a cada cliente por sensibilidad de datos y confianza, aplica un perfil de endurecimiento base, y solo recurre a una VM cuando el costo de una brecha es mayor que el costo de la VM.
Espera, ¿no están los contenedores ya aislados?
Docker se ejecuta sobre namespaces y cgroups de Linux, y esas palabras están haciendo un trabajo real. Los namespaces separan IDs de procesos, pilas de red, puntos de montaje y usuarios, de modo que un proceso en un contenedor no pueda ver la tabla de procesos de otro. Los cgroups establecen límites: dale a un contenedor 0.5 de CPU, 512 MB de memoria y un peso fijo de E/S de bloque, y eso es exactamente lo que obtiene. Un bucle descontrolado en un inquilino se limita en lugar de derribar al vecino. Si no has configurado límites, te has saltado lo más básico para lo que sirven los cgroups.
Toma una aplicación PHP simple en el contenedor A. Ve su propio sistema de archivos, su propia interfaz de red, su propio PID 1. El contenedor B tiene lo mismo, pero una vista diferente. Eso son los namespaces. Ahora aléjate y omite el límite de memoria: el contenedor A puede llenar la RAM del host y hacer que el contenedor B vaya lento. Para eso existen los cgroups. Pero dos contenedores pueden estar aislados entre sí por namespaces y aún así compartir el kernel del host, que es la parte de la que tratan todas las historias de escape de contenedores. Un exploit que alcanza el kernel puede potencialmente alcanzar a todos los inquilinos en ese host.
“Docker está aislado” es una frase a medias. La versión precisa es “Docker aísla con namespaces y cgroups, y una vulnerabilidad del kernel es el radio de explosión.” Antes de confiar en un inquilino para ejecutar código no confiable, quédate con ese pensamiento un minuto. La respuesta no es “nunca uses contenedores”—eso es el pánico fácil. La respuesta es un sistema de niveles.
Entonces, ¿por qué algunos clientes necesitan más que namespaces?
La respuesta honesta es que el aislamiento no es un interruptor, es un espectro. En un extremo tienes un contenedor completamente compartido donde todos están efectivamente en una sola aplicación. En el otro extremo tienes una VM separada por inquilino con su propio kernel. La mayor parte del trabajo de agencia vive en el incómodo medio, y el medio no es una elección binaria entre “Docker está bien” y “ejecuta una VM para todos.”
Lo que empuja a un cliente hacia la derecha no es su tamaño. Son cuatro preguntas:
- ¿Almacenan datos regulados? Registros de salud, detalles de tarjetas de pago, cualquier cosa que un regulador llamaría sensible.
- ¿Una brecha en su inquilino tiene un camino realista hacia otro inquilino? Si pueden ejecutar código arbitrario, sí.
- ¿Confías en el código y en las personas que lo despliegan? Un cliente que contrata al freelancer más barato no es el mismo nivel de confianza que un cliente cuyo equipo de desarrollo conoces.
- ¿Su contrato dice “dedicado,” “aislado,” o “privado”? Si lo dice, ya has prometido un nivel; el único trabajo ahora es elegir el correcto.
Si aún no puedes responder esas preguntas, coloca al cliente en un nivel base y anota las suposiciones. Eso no es una auditoría de seguridad; es una verificación de cordura que repites en cada incorporación.
¿Cómo decido por cliente sin ejecutar una auditoría de seguridad cada vez?
Haz una tabla pequeña y comprométete con ella. No necesitas una matriz con cuarenta celdas. Cuatro niveles cubrirán casi todos los clientes que ve una agencia.
| Posición del cliente | Qué los separa realmente | Úsalo cuando |
|---|---|---|
| Nivel 1: Aplicación/contenedor compartido | Solo lógica de aplicación | Utilidades internas, datos de bajo riesgo, proyectos donde todos están explícitamente en un solo sistema de inicio de sesión |
| Nivel 2: Mismo host, contenedores separados | Namespaces y cgroups | La mayoría de los sitios de marketing, formularios de contacto, sin datos sensibles |
| Nivel 3: Contenedor bloqueado | Nivel 2 + no root, capacidades eliminadas, seccomp, raíz de solo lectura, segmentación de red | Comercio electrónico, PII, código personalizado que no confías del todo |
| Nivel 4: VM por inquilino | Hipervisor y un kernel separado | Salud, finanzas, documentación de cumplimiento, código no confiable, vecinos ruidosos |
Así se ve en la práctica. Un cliente panadería con un formulario de contacto y un enlace de Instagram va al Nivel 2: un contenedor en un host compartido, red Docker predeterminada, límites de recursos, trabajo hecho. Una tienda en línea que almacena nombres de clientes, direcciones y redirecciones de pago va al Nivel 3: el mismo host compartido, pero el contenedor se ejecuta como un usuario no root, no tiene capacidades adicionales del kernel, usa un perfil de seccomp y solo expone el puerto 443. Un portal de admisión médica que almacena información de salud protegida va al Nivel 4: una VM por inquilino, porque el costo de una brecha no es “lo limpiaremos” sino “no podemos mostrarle al cliente que lo tomamos en serio.”
El truco es que no estás repensando la arquitectura para cada cliente. Estás eligiendo una fila de una tabla que ya acordaste. Así es como una agencia de cinco personas puede ejecutar cien sitios sin cien obsesiones de seguridad separadas. También significa que el próximo cliente no obtiene una respuesta que depende de qué miembro del equipo contestó el teléfono. Para el debate más profundo sobre la arquitectura detrás de esas decisiones, esta guía sobre cómo diseñar niveles de aislamiento multiinquilino cubre las compensaciones con más detalle.
¿Cómo se ve realmente un contenedor bloqueado?
Dejemos de decir “bloqueado” y seamos concretos. Esto es lo que significa el Nivel 3 para un cliente típico de WordPress o PHP.
Primero, cambia el usuario. La mayoría de las imágenes oficiales aún se ejecutan como root por defecto; en tu Dockerfile, crea un usuario no root y ejecuta la aplicación como ese usuario. Eso elimina inmediatamente la forma más común en que un compromiso del contenedor se convierte en un compromiso del host. Segundo, elimina las capacidades que no necesitas. Ejecuta con --cap-drop ALL y agrega solo una, usualmente NET_BIND_SERVICE para que la aplicación pueda escuchar en el puerto 80. Solo eso es un cambio mayor de lo que la mayoría espera. Tercero, haz que el sistema de archivos raíz sea de solo lectura con --read-only, y monta los directorios escribibles (subidas, el directorio de datos de la base de datos) como volúmenes o tmpfs. Cuarto, aplica un perfil de seccomp y, si tu host lo soporta, AppArmor o SELinux. Finalmente, coloca el contenedor en una red Docker dedicada y expón solo los puertos que realmente necesitan ser accesibles.
Repasemos un ejemplo de WordPress. La imagen base probablemente se ejecuta como root, así que agregas un paso de useradd y una directiva USER. Ejecutas el contenedor con un límite de memoria y un límite de CPU, para que una ráfaga de tráfico de plugins no perjudique al vecino. Montas /var/www/html/wp-content/uploads como un volumen escribible. Configuras --read-only. Lo adjuntas a una red que no tiene ninguna bandera --privileged cerca. El resultado es un contenedor que solía ser “un sitio de WordPress” y ahora es “un sitio de WordPress que resulta estar más bloqueado que la mayoría de los servidores privados virtuales.”
Si hacer todo eso manualmente se siente frágil, hay un camino intermedio más fácil: el Enhanced Container Isolation de Docker, que utiliza aislamiento de namespaces de usuario y un runtime de contenedores seguro. Es un atajo legítimo, pero no es un pase libre para omitir el no root o la eliminación de capacidades. El inquilino todavía necesita una imagen sensata. La diferencia es que la superficie de ataque orientada al kernel se reduce sin que te conviertas en un experto en seccomp de la noche a la mañana. Si quieres la secuencia exacta para un solo inquilino, la guía paso a paso de endurecimiento de aislamiento convierte esta sección en comandos de copiar y pegar.
¿Cuándo dejo de agregar capas y simplemente les doy una VM?
Aquí está la parte contraria: más aislamiento no es automáticamente mejor. Las VMs te dan aislamiento a nivel de hardware, un kernel separado y una superficie de ataque mucho más pequeña si el kernel invitado cae. Eso es exactamente lo que los clientes de salud y finanzas esperan cuando dicen “queremos estar aislados.” Pero cada VM agrega costos de parcheo, respaldo y cómputo, y multiplica el trabajo de mantener una flota actualizada. Si le das VM a cada cliente porque un cliente una vez te dijo que Docker le asustaba, has comprado teatro de seguridad con dinero real.
Una VM es la respuesta correcta cuando el riesgo por inquilino es mayor que el costo operativo de una VM por inquilino. Eso significa datos regulados, requisitos de cumplimiento por escrito, código de terceros no confiable, o un cliente que necesita que se elimine un vecino ruidoso. También es la respuesta correcta cuando el contrato del cliente promete literalmente un entorno dedicado, porque “contenedor” no es lo que imaginan cuando firman “dedicado.”
Pero una VM no excusa un contenedor descuidado. Una trampa común es poner al cliente en una VM y luego omitir el endurecimiento porque “la VM los protege.” La VM protege al host del inquilino, no al inquilino de su propia mala imagen. Aún quieres no root, capacidades eliminadas y seccomp dentro de esa VM. El enfoque híbrido—contenedores dentro de una VM—suele ser el punto óptimo: la VM proporciona el límite para las conversaciones de cumplimiento, y el contenedor te da el flujo de trabajo de despliegue que ya conoces. Hay una versión más larga de ese debate en ¿Debería cada inquilino tener su propia VM?, pero la respuesta corta es que la VM es para el contrato, no para el miedo.
¿Cómo hago que esto sea repetible en cada cliente?
Lo haces repetible convirtiendo el sistema de niveles en una plantilla, no en un recuerdo. Mantén un directorio de archivos Compose, uno por nivel: tier2-baseline, tier3-locked, tier4-vm-hybrid. Cuando aparezca un nuevo cliente, copia la plantilla, cambia las variables de entorno, y ya sabes la forma de aislamiento antes de haber escrito una línea de nueva infraestructura.
Luego escribe la decisión. No un informe de seguridad de 400 páginas, sino un párrafo corto en el repositorio del cliente: qué datos almacenan, en qué nivel están, por qué, y qué los movería a un nivel superior. Ese párrafo vale más que cien reglas de firewall, porque es lo que puedes mostrar al próximo auditor o al próximo cliente preocupado. También te evita tener que recordar por qué la panadería obtuvo el Nivel 2 y la tienda de comercio electrónico el Nivel 3 después de que la llamada de ventas original se haya desvanecido.
Automatiza las comprobaciones aburridas. Haz que tu CI escanee cada imagen de cliente y falle una compilación si se ejecuta como root, si tiene todas las capacidades, o si intenta publicar un puerto distinto a los que el nivel permite. Nada de eso es exótico; solo es asegurarse de que la plantilla no se rompa accidentalmente por un desarrollador bien intencionado. Si de todos modos estás construyendo el flujo de trabajo de hosting circundante, el artículo sobre estrategias de hosting Docker listas para producción cubre la parte que viene después de que los contenedores estén definidos.
Nada de esto es glamoroso. Ninguna publicación de blog va a hacer que “aislamiento de inquilinos” suene tan emocionante como un diagrama de arquitectura greenfield. Pero esta es la diferencia entre una agencia que responde “¿qué tan aislados estamos?” con un “totalmente” cruzando los dedos y una que puede mostrar un nivel, una configuración y una razón. Los contenedores no son una pared mágica. Las VMs no son una bala mágica. Un sistema de niveles es solo una decisión que escribes y reutilizas—y para una agencia, repetible es todo el juego.

