Docker vs Máquinas Virtuales: Arquitectura, Rendimiento y Casos de Uso

DUGLAS MORENODUGLAS MORENO 👁 23

Introducción

En la actualidad, al desplegar aplicaciones, los equipos suelen elegir entre contenedores Docker y máquinas virtuales tradicionales. Ambos ofrecen aislamiento, pero difieren en arquitectura, consumo de recursos y velocidad de arranque. Este artículo está dirigido a desarrolladores, administradores de sistemas y arquitectos de nube que quieren entender cuándo usar cada tecnologia. Aprenderás las diferencias clave en arquitectura, rendimiento y casos de uso, con ejemplos comparativos que podés probar en tu máquina.

Arquitectura

Máquinas virtuales tradicionales

Una máquina virtual (VM) emula un hardware completo mediante un hipervisor. El hipervisor puede ser tipo 1 (bare‑metal) como VMware ESXi o Microsoft Hyper‑V, o tipo 2 (hosted) como VirtualBox o VMware Workstation. Cada VM incluye su propio sistema operativo invitado, con kernel, bibliotecas y aplicaciones. El hipervisor asigna recursos de CPU, memoria, almacenamiento y red a cada VM, pero el SO invitado debe gestionar esos recursos como si fuera una máquina física.

Contenedores Docker

Docker utiliza la tecnologia de contenedores del kernel Linux (namespaces y cgroups) para aislar procesos sin necesidad de un SO invitado completo. Cada contenedor comparte el kernel del host, pero tiene su propio sistema de archivos, red y espacio de nombres de procesos. La imagen de un contenedor incluye solo la aplicacion y sus dependencias, no un kernel completo. El motor Docker (daemon) gestiona la creacion, ejecucion y eliminacion de contenedores.

Diagrama comparativo (texto)

Host OS
|-- Hypervisor (tipo 1 o 2)
|   |-- VM 1
|   |   |-- Guest OS A
|   |   |   |-- App A
|   |-- VM 2
|       |-- Guest OS B
|           |-- App B
|
|-- Docker Engine
|   |-- Contenedor 1
|   |   |-- App A + deps
|   |-- Contenedor 2
|       |-- App B + deps

Rendimiento

Consumo de memoria y almacenamiento

Una VM necesita cargar un SO completo, lo que suele consumir varios gigabytes de RAM y espacio en disco solo por el sistema operativo invitado. Por ejemplo, una VM Ubuntu minima puede ocupar 512 MiB de RAM y 2 GiB de disco antes de instalar cualquier aplicacion.

Un contenedor, en cambio, solo incluye las bibliotecas y la aplicacion. Una imagen de Alpine Linux con una aplicacion simple puede pesar menos de 10 MiB y usar apenas unos pocos megabytes de RAM en reposo.

Tiempo de arranque

Arrancar una VM implica iniciar el hipervisor, cargar el BIOS virtual, iniciar el SO invitado y luego los servicios. Este proceso puede tardar desde varios segundos hasta minutos, dependiendo del tamano del SO y la configuracion.

Un contenedor Docker se inicia en el orden de milisegundos a pocos segundos, porque solo se necesita lanzar el proceso principal y montar el sistema de archivos de la imagen. En pruebas tipicas, un contenedor con una aplicacion Node.js arranca en ~200 ms, mientras que una VM equivalente con el mismo software puede tardar ~8 s.

Sobrecarga de CPU

El hipervisor introduce una capa de virtualizacion que traduce instrucciones privilegiadas del invitado al hardware host. Esta traduccion implica un overhead de CPU que suele estar entre 2 % y 10 % segun el workload. Los contenedores, al compartir el kernel, no tienen esta capa de traduccion; el overhead es prácticamente nulo, limitado solo a la gestion de namespaces y cgroups (menos del 1 %).

Escalabilidad

Debido al bajo consumo de recursos y al rapido arranque, los contenedores permiten una mayor densidad de instancias por host. En un servidor con 32 GiB de RAM, podés ejecutar docenas de contenedores ligeros, mientras que la misma máquina podría alojar apenas unas pocas VMs pesadas.

Casos de uso

Cuando usar máquinas virtuales

  • Necesidad de sistemas operativos distintos: Si necesitás ejecutar Windows y Linux en el mismo host, o distintas versiones de un mismo SO con configuraciones de kernel personalizadas, una VM es la opcion mas directa.
  • Aislamiento de nivel de hardware: Algunas cargas de trabajo requieren acceso directo a dispositivos de hardware (GPUs, dispositivos USB, tarjetas de red especializadas) que el hipervisor puede pasar mediante passthrough. Los contenedores, aunque pueden acceder a dispositivos mediante privilegios, no ofrecen el mismo nivel de aislamiento de hardware.
  • Legacy o aplicaciones con dependencias complejas: Aplicaciones que dependen de servicios del SO iniciados en tiempo de arranque (por ejemplo, servicios de impresion, drivers de kernel especificos) a veces son mas faciles de empaquetar en una VM.
  • Entornos de pruebas de seguridad: Para analisis de malware o pruebas de penetracion donde se quiere un entorno totalmente aislado a nivel de hardware, las VMs brindan una superficie de ataque mas reducida.

Cuando usar contenedores Docker

  • Microservicios y aplicaciones nativas de la nube: Los contenedores son ideales para empaquetar cada servicio de forma independiente, escalar rapidamente y orquestar con Kubernetes o Docker Swarm.
  • Desarrollo y CI/CD: En pipelines de integracion continua, los contenedores permiten construir, testear y desplegar de forma consistente en cualquier entorno, eliminando el problema "funciona en mi máquina
  • Alta densidad de instancias: Cuando necesitás ejecutar muchas instancias de una misma aplicacion (por ejemplo, workers de una cola de tareas) en un mismo host, los contenedores reducen el costo de infraestructura.
  • Aplicaciones con ciclos de vida cortos: Jobs batch, funciones de tipo serverless simuladas, o tareas de procesamiento de datos que se ejecutan brevemente se benefician del rapido arranque y bajo overhead.
  • Portabilidad entre plataformas: Una imagen Docker funciona igual en Linux, Windows (con el subsistema de Linux) y en proveedores de nube que soporten Docker, sin necesidad de cambiar la configuracion del hipervisor.

Ejemplos comparativos

A continuacion, se presentan dos ejemplos sencillos que ilustran las diferencias de arranque y consumo de recursos. Los comandos asumen que tenés Docker instalado y un hipervisor como VirtualBox o VMware Workstation disponible.

Ejemplo 1: Arranque de una aplicacion web simple

Usando Docker

  1. Creamos un Dockerfile que usa la imagen oficial de Node.js y sirve un archivo estatico.
# Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 3000
CMD node server.js
  1. Construimos la imagen y la ejecutamos.
docker build -t mi-web .
docker run -d -p 3000:3000 mi-web
  1. El contenedor queda listo en menos de un segundo. Podés verificar con:
docker ps

Usando una VM (VirtualBox)

  1. Creamos una Vagrantfile que provisiona una VM Ubuntu con Node.js.
# Vagrantfile
Vagrant.configure("2\)) do |config|
  config.vm.box = 'ubuntu/focal64'
  config.vm.provision "shell", inline: <<-SHELL
    apt-get update
    apt-get install -y nodejs npm
    mkdir -p /app
    cp /vagrant/server.js /app/
    cd /app
    npm install
  SHELL
  config.vm.network 'forwarded_port', guest: 3000, host: 3000
end
  1. Inicializamos y levantamos la VM.
vagrant up
  1. La VM tarda entre 20 y 40 segundos en arrancar, dependiendo del host y la velocidad de disco. Luego podés acceder a la aplicación en localhost:3000.

Ejemplo 2: Medicion de consumo de memoria

Podés usar docker stats para ver el consumo de un contenedor y herramientas dentro de la VM para medir RAM.

# Contenedor
docker run -d --name mem-test alpine:latest sh -c 'while true; do sleep 1; done'
docker stats mem-test --no-stream

Salida aproximada (sin comillas):

CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
abc123 mem-test 0.00% 2MiB / 1.95GiB 0.10% 0B / 0B 0B / 0B 1

Para una VM Ubuntu minima con el mismo proceso sleep, el consumo de memoria suele ser alrededor de 80‑120 MiB solo por el SO invitado, mas lo que consuma el proceso.

Buenas prácticas

  • Usar imágenes oficiales y mantenerlas actualizadas: Reduce riesgos de vulnerabilidades y asegura compatibilidad.
  • Aprovechar el almacenamiento en capas: En Docker, cada instruccion en el Dockerfile crea una capa; ordená las instrucciones de menos a más cambiante para maximizar el reuso de caché.
  • Limitar recursos con cgroups: Tanto en Docker (--memory, --cpus) como en VMs (configuracion del hipervisor) podés definir límites claros para evitar que un consumo excesivo afecte a otras cargas.
  • Separar responsabilidades: En contenedores, cada proceso debería tener su propio contenedor (un proceso por contenedor). En VMs, podés agrupar servicios relacionados, pero evitá sobrecargar el SO invitado.
  • Monitorear y registrar: Utilizá herramientas como Prometheus + Grafana para contenedores y los agentes de hipervisor para VMs, a fin de obtener visibilidad de rendimiento y uso de recursos.
  • Considerar la seguridad: En Docker, ejecutá contenedores como usuario no root siempre que sea posible y revisá los permisos de los volúmenes montados. En VMs, mantení el hipervisor y los SO invitados parcheados.

Errores comunes

  • Crear imagenes demasiado grandes: Incluir paquetes innecesarios o múltiples capas de caché sin limpiarlas genera imagenes pesadas que ralentizan el despliegue.
  • Olvidar el .dockerignore: Al no excluir archivos como logs, directorios de node_modules o archivos de desarrollo, el contexto de construccion se vuelve innecesariamente grande.
  • Asignar recursos excesivos a VMs: Darle a una VM más RAM o CPU de la que realmente necesita desperdicia capacidad que podría usarse para otras cargas.
  • Confundir persistencia: En contenedores, los datos escritos dentro del sistema de archivos son efímeros a menos que uses volúmenes o montajes de host. En VMs, el disco persistente es el predeterminado, lo que puede llevar a confusiones al migrar.
  • No actualizar el hipervisor o el motor Docker: Las versiones antiguas pueden contener fallos de seguridad o incompatibilidades con nuevas versiones de imagenes o sistemas operativos invitados.

Conclusión

Tanto los contenedores Docker como las máquinas virtuales tradicionales son herramientas valiosas en el arsenal de infraestructura moderna. Las VMs ofrecen un aislamiento a nivel de hardware y la capacidad de ejecutar sistemas operativos completos y diversos, lo que las hace ideales para escenarios de legado, pruebas de seguridad o cargas de trabajo que requieren acceso directo a dispositivos especiales. Por otro lado, los contenedores destacan por su ligereza, velocidad de arranque y alta densidad, siendo la eleccion preferida para microservicios, pipelines de CI/CD y aplicaciones que necesitan escalar rapidamente.

Al comprender las diferencias en arquitectura, rendimiento y casos de uso, podés tomar decisiones informadas que optimicen costos, mejoren la eficiencia y se alineen con los requisitos específicos de tus proyectos. La práctica recomendada es comenzar con contenedores para las nuevas aplicaciones nativas de la nube y reservar las VMs para aquellos casos donde el aislamiento de hardware o la necesidad de un SO invitado completo sea indispensable.

Comentarios (0)

Sé el primero en comentar.

Dejá tu comentario