Supongo que ha llegado el momento de compartir con el mundo algunas de nuestras experiencias técnicas mientras sacamos nuevas funcionalidades en Storage OS. Me detendré en nuestra última incorporación a Storage OS: la virtualización local.
Pues bien, ahora puede ejecutar máquinas virtuales en el propio dispositivo de almacenamiento sin ninguna infraestructura adicional. Esta era una necesidad real que tenían la mayoría de nuestros clientes pequeños y medianos. Deshacerse de los 2-3 servidores que proporcionan compartición de archivos, directorio activo o algunos otros servicios y virtualizarlos. Pero, ¿cómo podemos llevar esto a nuestro sistema operativo de almacenamiento?
Obviamente, este concepto no es nuevo. Oracle tiene Virtual Box y es bastante bueno. Tuvimos algunos intentos con él, pero no estábamos realmente satisfechos con el rendimiento general y sus capacidades de gestión. Así que buscamos en otras direcciones. La opción mejor y más estable era KVM (kernel virtual machine). KVM es una tecnología que funciona en Linux desde hace mucho tiempo y es uno de los hipervisores más estables y eficaces que existen. Realmente tenemos que dar crédito a Joyent por hacer la portabilidad inicial de KVM a Illumos, hicieron un trabajo increíble, me quito el sombrero. También lo utilizan para sus máquinas inteligentes. Lo que realmente me sorprendió fue que durante la portabilidad los chicos mencionaron que no encontraron ni un solo error o problema en todo el KVM. Eso es algo, teniendo en cuenta que durante una portabilidad se toca cada línea de código.
Una vez portado el núcleo, tuvimos que crear la infraestructura para gestionar máquinas virtuales en Storage OS. Nuestro primer dilema fue: ¿cuál sería el almacén de respaldo de una máquina virtual? Inicialmente decidimos utilizar un Vdisk (un volumen ZFS) como almacén de respaldo. Al principio parecía una buena idea. Puedes configurarlo como thick o thin provisioning, lo que te apetezca. ¿Qué pasaría si necesitas más tamaño? Bueno, puedes ampliar fácilmente un Vdisk y luego asignar el nuevo espacio dentro del propio sistema operativo invitado. Usamos /dev/zvol/rdsk como la ruta del dispositivo raw para la imagen de disco.
Nuestra siguiente pregunta era: ahora que tenemos el almacén de respaldo en su lugar, ¿cómo podemos controlar el estado y la configuración de una máquina virtual? Trabajando con Illumos (ya que Storage OS utiliza el kernel de Illumos) desde hace unos años, entiendo que sólo hay una forma real de manejar los procesos de larga ejecución y que es el uso de SMF (Service Management Facility). SMF es una de las mejores cosas que hay en cuanto a gestión de servicios en distribuciones basadas en Solaris. Para cada máquina virtual creamos un servicio SMF. Mantuvimos la configuración en un grupo de propiedades config. La RAM configurada, las imágenes ISO montadas, el orden de arranque, la configuración de red eran todas propiedades del servicio. El método de inicio del servicio analizaba la configuración y generaba el comando para iniciar qemu-kvm para esa máquina virtual. Una cosa a notar es que ninguna de las herramientas para crear, desplegar o gestionar máquinas virtuales, desde distribuciones Linux KVM, están presentes en Illumos ya que libvirt no está portado. Esta fue otra razón por la que tuvimos que construir nuestras propias herramientas.
Nuestro siguiente gran reto era cómo comunicarnos con la máquina virtual mientras se ejecuta. La elección obvia era utilizar Qemu QMP. Se trata de un protocolo basado en JSON que permitiría a las aplicaciones comunicarse con instancias de Qemu (máquinas virtuales). Escribimos un nuevo módulo que usa sockets UNIX para comunicarse con las máquinas virtuales usando QMP. Tuvimos muchos desafíos aquí ya que QMP no está terminado, hay algunos comandos que faltan en el propio protocolo, pero nos las arreglamos para superarlos. Usando comandos generados por JSON pudimos controlar las máquinas virtuales: añadiendo nuevos dispositivos, emitiendo comandos ACPI y demás. Un problema interesante al que nos enfrentamos aquí fue cuando varios procesos diferentes se conectaban al mismo socket para operar cosas en la misma máquina virtual. Tuvimos que tener cuidado en cómo controlar el acceso de los procesos para que fueran exclusivos.
Bien, ahora que tenemos todas estas hermosas máquinas virtuales en funcionamiento, ¿cómo gestionaríamos la memoria del almacenamiento host? ¿Qué pasa cuando un usuario se pone frenético y empieza a encender suficientes máquinas virtuales para consumir toda la memoria del host? Otro problema interesante en un almacenamiento que utilice ZFS sería la gestión de la memoria utilizada por ZFS ARC. ZFS es muy agresivo en el uso de la RAM. Normalmente no limitarías el tamaño del ARC en un almacenamiento normal ya que ZFS liberaría la memoria a medida que otras aplicaciones la demanden. Pero cuando sabes de antemano que necesitas esa memoria, es normal dar instrucciones a ZFS para que no la use más. Lo que hicimos fue construir un provisionador de memoria dinámico para las máquinas virtuales. Tan pronto como se enciende una máquina virtual el provisionador de memoria comprobará si tiene suficiente memoria reservada para el propio sistema operativo y al menos 0,2 de la memoria total disponible para el ARC después de encender la máquina virtual. Si cumple las condiciones limitará automáticamente la CRA al valor más cercano posible de la memoria total menos la RAM utilizada por la máquina virtual.
A medida que enciendes máquinas virtuales el ARC será como un globo y se encogerá para liberar memoria para las máquinas virtuales en ejecución. Escribimos una herramienta que utiliza Kstat y MDB (el depurador modular) para ajustar dinámicamente el tamaño del ZFS ARC. Gestión de memoria: hecho.
A continuación pensamos en lo que significaría la recuperación ante desastres para una máquina virtual. Una de las funciones más utilizadas en Storage OS es la replicación de copias de seguridad de conjuntos de datos. Supongamos que replicas in situ o remotamente el Vdisk (volumen ZFS) que representa tu máquina virtual, ¿cómo podrías recrear fácilmente la máquina virtual que tenías antes sobre esto? No es tan fácil porque la configuración del servicio SMF que tenías antes no está replicada y sería un verdadero suplicio hacerlo. Se me ocurrió adjuntar la configuración como una propiedad ZFS del volumen. Pero entonces tuvimos una demostración con uno de nuestros clientes y realmente insistió en tener dos discos separados para la máquina virtual. Entonces tuvimos claro que utilizar un disco virtual como almacén de respaldo ya no era suficiente. No teníamos ninguna flexibilidad. ¿Qué hacer? Comprar una nueva pizarra grande de al menos 2 metros de ancho, un montón de rotuladores, y que comience el proceso de diseño y volcado de ideas :).
Lo que acabamos haciendo fue utilizar un sistema de ficheros ZFS para representar una máquina virtual, una especie de bundle. Dentro del punto de montaje del sistema de ficheros guardamos la configuración de la máquina virtual en formato JSON (hoy en día es bastante fácil transformar cualquier objeto en una representación JSON en cualquier lenguaje de programación). Tal vez alguien que quiera convertir la configuración de la máquina en una máquina inteligente Joyent, o transformar esto en un xml para libvirt en Linux podría hacerlo muy fácilmente. Además de la configuración creamos las imágenes de disco. Esta vez usamos el formato nativo de Qemu - qcow2, y cambiamos el tamaño usando qemu-img. Este tipo de representación de máquina virtual nos permitió enviar/recibir fácilmente un sistema de ficheros que encapsula un bundle de máquina virtual. Dentro del servicio SMF adjunto a la máquina virtual ya no guardamos ninguna configuración, sólo un enlace a la ruta del sistema de archivos ZFS que contiene el paquete. Al importar una máquina virtual, tu almacenamiento de copia de seguridad simplemente analizaría la configuración y generaría todos los servicios SMF adjuntos para controlarla. Esto resultó ser una solución estupenda y natural que nos permitió una gran flexibilidad.
Decidimos ofrecer una forma de conectarse directamente a la consola de una máquina virtual desde el navegador, como alternativa a un cliente VNC tradicional. Creamos proxies websocket que reenviarían cada pantalla VNC a un websocket. Las pantallas VNC se generarán automáticamente al arrancar las máquinas virtuales. Lo necesitábamos para asegurarnos de tener pantallas únicas para cada máquina virtual que se esté ejecutando. Al tener las pantallas VNC reenviadas a websockets, utilizamos un cliente VNC Javascript en el navegador que portamos, modificamos y rediseñamos completamente para poder utilizarlo en nuestra aplicación de gestión web. Nos comunicamos con la máquina virtual, tomamos una captura de pantalla del estado actual de la pantalla y tan pronto como se hace clic en esa imagen abrimos un cliente VNC en el navegador a la máquina virtual. Bastante limpio para ser honesto.
Esa es más o menos la historia de nuestra última iteración. Mucha diversión, mucha tecnología nueva. Esperamos que la gente disfrute con lo que hemos hecho. Siempre estamos abiertos a preguntas sobre cualquier tema, así que no dudéis en preguntar :). Gracias.