Blog ·
¿Máquina virtual o contenedor en Proxmox?
Un invitado KVM y un contenedor LXC comparten hipervisor, espacio de numeración y lista de precios, y poco más. Las diferencias que cuestan dinero.
Por Provibr
Proxmox VE ejecuta dos tipos de invitado. Una máquina virtual KVM arranca su propio kernel detrás de un firmware emulado, y un contenedor LXC comparte el kernel del host sobre el que se asienta. Para el cliente que compra un VPS, ambos son «un servidor con root», y por eso la comparación suele acabar en una tabla de marketing con filas sobre densidad y sobrecarga.
Esas filas son ciertas y no son las que van a costar dinero. Lo que cuesta dinero es el conjunto de sitios donde los dos parecen idénticos en la API y se comportan de forma distinta por debajo, porque ahí es donde un panel, un sistema de facturación o un flujo de pedidos hace en silencio lo que no debe y nadie se entera durante un mes.
Hace poco añadimos un segundo tipo de cómputo a nuestra propia integración con Proxmox, lo que obligó a leer la API con una pregunta concreta: ¿dónde divergen estos dos? Esto es lo que salió, en el orden en que fue sorprendiendo.
Un clúster, un espacio de numeración
Conviene empezar por el identificador, porque todo lo demás cuelga de él. Un invitado de Proxmox se direcciona con un número, y resulta tentador suponer que las máquinas virtuales y los contenedores tienen cada uno su propio rango: 100 podría ser una máquina virtual y 100 podría ser también un contenedor, distinguidos por el tipo.
No pueden. El espacio de numeración es compartido por todo el clúster, y tres propiedades de la API lo dicen. El endpoint que entrega el siguiente identificador libre no acepta ningún parámetro de tipo, así que no puede saber cuál de los dos se va a crear. El listado de recursos del clúster devuelve máquinas y contenedores juntos en el mismo campo numérico. Y la propia lista de invitados del clúster está indexada por ese número.
Esto tiene una consecuencia directa para cualquier cosa que guarde invitados de Proxmox en su propia base de datos. Al meter el tipo en la restricción de unicidad, de modo que «máquina 5001» y «contenedor 5001» sean dos filas distintas, se obtiene un esquema capaz de albergar dos registros para un mismo invitado y de dejar que se peleen por él. El identificador es único por sí solo, y el tipo es una observación que se guarda al lado. Para quien esté eligiendo rangos para una flota, la aritmética de elegir un rango de VMID en Proxmox no cambia cuando llegan los contenedores, y de eso se trata.
Un contenedor se desempaqueta, no se clona
La segunda divergencia es la vía de creación, y es mayor de lo que parece.
Una máquina virtual se produce normalmente clonando una plantilla: se nombra un origen, se elige entre clon completo o enlazado, y se obtiene una copia de una imagen de disco. Un contenedor se produce desempaquetando un archivo comprimido sobre un volumen nuevo. No hay invitado de origen, no hay elección entre completo y enlazado, y no hay unidad de cloud-init, porque no hay nada a lo que enganchar una unidad.
Así que el cuerpo de la petición casi no tiene campos en común con el que crea una máquina. Un contenedor quiere un ostemplate que apunte a un archivo en un almacenamiento, una marca unprivileged, un hostname, un rootfs y un valor de swap. Una máquina quiere scsi0, scsihw, bios, boot y un ide2 cuando arranca desde una ISO. Reutilizar un vocabulario para el otro es el error que hay que evitar a propósito: las palabras se parecen lo bastante como para que un campo llamado disk signifique dos cosas distintas dentro de la misma base de código antes de que pase un año.
Dos detalles de ese cuerpo merece la pena robarlos, sea cual sea la base sobre la que se construya.
rootfs=<storage>:<size> es una asignación, no una referencia. Significa «crear un volumen nuevo de tantos gigabytes en este almacenamiento». La forma que nombra un volumen existente es casi idéntica y hace algo categóricamente distinto: adopta un volumen que puede seguir conteniendo datos de otra persona.
Omitir un parámetro es la respuesta segura. El anidamiento, keyctl, FUSE, los montajes bind y el paso de dispositivos están todos desactivados salvo que se envíen, y no hay ningún valor «vacío» que haya que acordarse de escribir. La postura correcta por defecto para una plataforma de hosting es, por tanto, una petición corta y no una larga. El campo de arquitectura cuenta lo mismo: Proxmox lo deduce del archivo, y enviar uno que no concuerde falla en el momento de desempaquetar.
Tres cosas que un contenedor nunca hará
Algunas carencias son puntos de una hoja de ruta. Tres son estructurales, y confundir ambas categorías en el producto propio es la manera de acabar prometiendo una función que no puede existir.
| Acción | Invitado KVM | Contenedor LXC |
|---|---|---|
| Arrancar, parar, apagar, eliminar | sí | sí, misma forma, otro segmento de ruta |
| Pausar o hibernar | sí | no: eso es el monitor de QEMU |
| Arrancar por red con PXE | sí | nunca |
| Instalar desde una ISO | sí | nunca |
| Elegir orden de arranque, BIOS o UEFI | sí | nunca |
| Consola | framebuffer VNC | un terminal, sobre otro protocolo |
| Swap | invisible para el host | un ajuste del hipervisor con un número |
Cinco de esas filas necesitan más que una celda, en tres grupos.
El arranque por red, la instalación desde una ISO y la selección de firmware son imposibles por una razón, no por tres: un contenedor no tiene firmware. No ejecuta ningún gestor de arranque, no tiene orden de arranque, y ponerlo en marcha no implica nada a lo que se le pueda pedir que pruebe primero la red. Quien venda un producto donde «reinstalar desde nuestra biblioteca de imágenes» es una función, que tenga presente que la vía PXE, de la respuesta DHCP a la instalación acabada, no tiene equivalente en contenedores. Reconstruir un contenedor es otro mecanismo con otras garantías, y merece su propia pantalla en lugar de un botón atenuado.
La consola es el mismo tipo de diferencia con un nombre familiar. Proxmox dará una consola para un contenedor, pero es un terminal sobre un websocket, no el framebuffer de un adaptador gráfico, y un cliente VNC no va a dibujarlo. Ahora hay una pestaña de consola con dos implementaciones detrás, y la respuesta honesta mientras tanto es decir que la pestaña todavía no está disponible, en lugar de abrir un visor que muestra un rectángulo negro.
La pausa y la hibernación merecen una nota al pie porque son una trampa para la lógica que aplica los límites. Si la plataforma suspende una máquina cuando un cliente se pasa de un límite, y «suspender» significa pausar, entonces los contenedores se quedan enteramente fuera: el comando se rechaza, ningún trabajador informa de nada, y el cliente sigue funcionando. La frontera pertenece al modo, no al comando. Parar un contenedor es perfectamente posible.
El swap es una decisión de producto, no un valor por defecto
Y aquí llega el que va a aparecer en los tickets de soporte.
Proxmox le da a un contenedor nuevo 512 MiB de swap salvo que se indique otra cosa. Al heredar eso en silencio, un plan vendido como «2 GiB de memoria» entrega 2,5 GiB de memoria utilizable, y la diferencia no aparece como un número mayor en ninguna parte: aparece como un contenedor que se vuelve lento en lugar de llegar a una condición honesta de falta de memoria, meses después, en un host que ha empezado a hacer swap.
Nosotros enviamos el valor de forma explícita en cada creación, con cero como valor por defecto. El cero es una elección y no una ausencia. Sea cual sea la elección, hay que hacerla a conciencia, escribirla en el plan y asegurarse de que el número que ve el cliente es el número que recibe el hipervisor.
Medir el swap tiene su propio precio. El listado masivo que da los contadores de CPU, memoria, disco y red de todos los invitados de un clúster no lleva ningún campo de swap, para ninguno de los dos tipos. El único sitio donde Proxmox lo publica es el endpoint de estado por invitado, así que es una llamada HTTP adicional por contenedor en marcha, para siempre. Nosotros lo leemos una vez por minuto: tres veces por minuto triplica la carga sobre el hipervisor de otra persona para obtener un número que se mueve en minutos, y una vez cada cinco minutos deja exactamente una muestra en un agregado de cinco minutos, lo que hace que la columna del máximo sea igual a la de la media y, por tanto, decoración.
Dos fallos que publicamos por dar por hecho que eran lo mismo
Los dos llevaban meses en producción antes de la ronda que fue a buscarlos.
El primero: nuestra pantalla de importación listaba los contenedores como máquinas virtuales. El listado de recursos del clúster con type=vm no es un filtro de QEMU, devuelve los dos tipos, y el campo que decía cuál era sencillamente no se leía. No falló nada. La lista estaba mal.
El segundo era más silencioso. Cada contenedor en marcha recibía dos llamadas al agente de invitado por ronda de sondeo, más una después de cada comando, contra una ruta que solo existe para QEMU. Cada una era un fallo garantizado, y no salió nada a la superficie, porque se supone que un enriquecimiento opcional fallido es sobrevivible y lo era. Lo encontró una prueba que cuenta qué rutas se llaman en lugar de afirmar sobre resultados, que es una costumbre que vale la pena copiar: si solo se afirma sobre resultados, toda una categoría de trabajo inútil sigue siendo invisible.
Lo que dejaríamos por escrito antes de vender cualquiera de los dos
Para quien esté decidiendo qué debe ofrecer un producto de hosting sobre Proxmox, cuatro preguntas llevan casi todo el camino.
- ¿El tipo es una propiedad del plan o una elección en el momento de pagar? Ambas son defendibles. Un valor por defecto por debajo de la elección no lo es: un cliente que pidió un contenedor y recibió una máquina obtuvo un modelo de seguridad distinto, un kernel distinto y un conjunto de capacidades distinto del que eligió.
- ¿Qué negativas son permanentes y cuáles temporales? Hay que decirlo en la interfaz, con esas palabras. «No es posible en un contenedor» y «todavía no está construido» son promesas distintas.
- ¿De dónde sale la protección contra la suplantación? La carencia honesta de nuestro propio trabajo: una máquina clonada recibe filtrado de IP en el momento de la entrega y un contenedor todavía no. Proxmox tiene los mismos endpoints de cortafuegos para ambos, así que es un paso de construcción y no un obstáculo, pero un contenedor entregado sin eso puede enviar tráfico diciendo ser otra persona.
- ¿Qué muestra la monitorización de un contenedor? Todo lo que derive de un agente de invitado, de un framebuffer o del firmware está vacío. Vacío está bien. Vacío presentado como un cero saludable, no.
Tenemos el campo del plan, la vía de pedido y la petición de creación construidos y medidos contra la forma HTTP en crudo de la API. Lo que no hemos hecho es crear un contenedor en el hipervisor de producción de nadie, así que la descripción honesta de nuestro soporte de contenedores es «la forma es correcta y el viaje de ida y vuelta está sin probar», que es algo útil que poder decir en voz alta sobre la plataforma propia.
Esto salió de construir la integración con Proxmox para Provibr, donde el agente se ejecuta dentro de la red del propio cliente y solo conecta hacia fuera. La costumbre relacionada de no fiarse de la respuesta de éxito de un hipervisor está en por qué un 200 de Proxmox no es un resultado.