9.2.2. Software de virtualización

De las múltiples soluciones disponibles en Linux incluimos guía de unas pocas de ellas, que en algunos casos serán meras notas, y en otros relaciones más extensas.

9.2.2.1. Virtualización completa

Al utilizar un software de virtualización para instalar y probar sistemas operativos nos será muy útil conocer varios aspectos (además de los evidentes):

  • Manipular el modo en que la máquina virtual se conecta a la red para poder simular distintos escenarios. Por ejemplo, no es lo mismo que queramos virtualizar un sistema de escritorio en que lo único que se necesita por lo general es salida a internet que un servidor en que muy probablemente necesitemos que el anfitrión pueda conectarse a él.

  • Preservar estados de disco para recuperarlos cuando sea preciso (lo que se conoce como instantáneas).

  • Crear discos que puedan utilizarse como plantilla para la creación de varias máquinas virtuales. Por ejemplo, crear un único disco con una instalación limpia de Windows 10, y usar éste como base para todos los discos de las máquinas virtuales en que vayamos a virtualizar un Windows 10.

  • Virtualizar tanto firmware BIOS como firmware UEFI.

  • Tener mecanismos sencillos para poder transferir datos entre el anfitrión y el huésped (p.e. un directorio compartido por ambos).

  • Exportar máquinas virtuales a otros anfitriones o importarlas desde ellos.

Trataremos dos hipervisores: Virtualbox porque ofrece la posibilidad de ejecutarse en otros sistemas operativos como Windows, y KVM que es una solución integrada oficialmente en el propio núcleo de Linux:

9.2.2.2. Contenedores

Los contenedores son una solución mucho más ligera[1] y eficiente con la limitación de que tendrán que contener un sistema de idéntica naturaleza a la del anfitrión, puesto que carecen de sistema operativo propio.

En los sistemas Linux este tipo de virtualización, muy esquemáticamente, se basa en dos conceptos:

  1. El aislamiento a través de:

    • los espacios de nombres (namespaces) del núcleo, que proporcionan a los procesos una visión parcial de los procesos y recursos que gestiona el sistema operativo. Todo aquello que no haya sido incluido en el mismo espacio de nombres, no es accesible ni aparentemente existe. Para entender este conceptos resulta indispensable la lectura del artículo Digging into Linux namespaces (y su segunda parte)[2].

    • el enjaulamiento (chroot, pivot_root) dentro de un árbol de directorios, que logra su aislamiento del resto del sistema de archivos.

  2. La limitación de recursos a través de los grupos de control (abreviado cgroups). Gracias a ellos, por ejemplo, podremos impedir que un proceso ocupe más de 128MiB de memoria RAM, aunque la máquina disponga de mucha más. Para profundizar en ellos es conveniente el artículo Cgroups Introduction. Tenga presente que la versión predeterminada en Debian a partir de Bulleye es la v2, así que es ésta versión la que se tomará como referencia.

En consecuencia, la creación de un contenedor supone:

  • Crearle un conjunto de espacios de nombres propios (uno para su red, otro para sus procesos, etc). De esta manera, dentro del contenedor sólo se tiene acceso a aquello que el sistema operativo del anfitrión gestiona para el propio contenedor.

  • Enjaularlo dentro la porción del árbol de directorios que constituya su "sistema de archivos".

  • Crearle los grupos de control propios para limitar su uso de recursos.

Es relativamente frecuente utilizar el término VM para referir la máquina virtual de la virtualización completa y VE (entorno virtual) para referir al contenedor. Antes de comenzar, no obstante, es preciso saber que existen distintos tipos de contenedores:

Según los permisos de sus usuarios

Hay dos tipos distintos:

Contenedor privilegiado (privileged container)

es aquel en que no se hace uso del concepto de espacio de nombres de usuario y, en consecuencia, los usuarios y grupos del contenedor se corresponden con los usuarios y grupos del anfitrión. Dicho de otro modo, la acción que realice el usuario root en el contenedor es una acción que el sistema operativo del anfitrión considera que está ejecutando su propio root.

Contenedor no privilegiado (unprivileged container)

es aquel que hace uso de tal concepto, por lo que se mapean identificadores de usuarios y grupos; y, en consecuencia, el administrador del contenedor (UID 0 en el contenedor) no se corresponde con el administrador del anfitrión (UID 0 en el anfitrión). Esto evita la posibilidad de que el administrador escape del confinamiento del contenedor con permisos de administrador.

Nota

Todos estos conceptos se entienden muy bien si se leen con detenimiento las explicaciones ya recomendadas del artículo Digging into Linux namespaces y, en especial, su segunda parte en que se desarrolla el concepto de espacio de nombres de usuario (user namespace). Más adelante, además, se expone un pequeño ejemplo de espacio de nombres de usuario.

Según los procesos que encierre

De nuevo, son dos los tipos:

contenedor de sistema

Es aquel diseñado para ejecutar aisladamente múltiples procesos a la manera en que lo hace un sistema completo por lo que, en consecuencia, ejecutará como primer proceso un programa init (como systemd). Es, por tanto, una solución más cercana al de una virtualización completa, ya que dispondremos de un espacio de usuario completo que recrea fielmente una distribución de Linux. LXC provee contenedores de este tipo.

contenedor de aplicaciones

Es aquel diseñado para ejecutar aisladamente una única aplicación (por lo general, un servicio), por lo que ejecutará como primer proceso tal aplicación y no un programa init. Estos contenedores, por tanto, no ofrecen un sistema completo que gestionar. Docker provee contenedores de este tipo.

Trataremos ambos tipos de contenedores:

Notas al pie