Virtualizando gratis

En Educación Secundaria habían unos 12 servidores Windows para dar servicios generales a una red de unos 500 puestos de trabajo.

Con el objetivo de aumentar la oferta de servicios y mejorar la disponibilidad y rendimiento de los servicios existentes, fuimos migrando los servicios a servidores Linux virtualizados sobre VMWare.

Si bien se adquirieron licencias de VMWare (y las mismas son realmente necesarias por ejemplo para migrar una máquina virtual “en caliente” de un servidor a otro) la gran mayoría de los servicios se pueden gestionar perfectamente utilizando software gratis:

  • VMWare ESXi es gratis
  • La interfaz gráfica de administración vSphere es gratis
  • La API vim-cmd viene incluida

¿Y hay que comprar licencias? pues, para mover máquinas virtuales en caliente entre servidores se precisa vMotion que no viene gratis. Los servicios de alta disponibilidad tampoco son gratis, pero se puede lograr bastante utilizando la API.

VMWare provee varias API que permiten automatizar la gestión. Nosotros elegimos la interfaz “vim-cmd” que viene incluida en la consola de VMWare ESXi y permite hacer shell scripts para automatizar tareas tales como:

  • respaldar una máquina virtual mientras está funcionando
  • mover una máquina virtual de un servidor a otro con mínima interrupción del servicio (del orden de medio minuto, las conexiones tcp no se llegan a cortar)
  • copiar (clonar) una máquina virtual

Los requemientos para poder utilizar VMWare de esta forma básicamente son:

  • Tener hardware de servidores de marca (por ejemplo Dell, IBM, etc.)
  • Tener un data storage accesible por todos los servidores (por ejemplo iSCSI)

El script que mueve una máquina virtual de un host a otro requiere que ambos hosts tengan acceso a los archivos de la máquina virtual, y básicamente lo que hace es lo siguiente:

#!/usr/bin/perl
#
# movervm.pl
#
#       Mueve una VM de un host a otro

use strict;
use vm;

if ($#ARGV != 2) {
 print "uso: movervm.pl VM host-actual host-nuevo\n";
 exit 1;
}

my $VM = $ARGV[0];
my $HOST_ACTUAL = $ARGV[1];
my $HOST_NUEVO = $ARGV[2];

my %path = getallvms($HOST_ACTUAL);
if (!defined($path{$VM})) {
 print "ERROR: No esta registrada $VM en $HOST_ACTUAL\n";
 exit 1;
}

print "Moviendo VM '$VM' desde $HOST_ACTUAL hacia $HOST_NUEVO...\n";

print "Suspendiendo $VM en $HOST_ACTUAL...\n";
if (! suspend($HOST_ACTUAL,$path{$VM})) {
 print "ERROR: $VM no se pudo suspender en $HOST_ACTUAL\n";
 exit(1);
}

if (! unregistervm($HOST_ACTUAL,$path{$VM})) {
 print "ERROR: $VM no se pudo quitar del registro de $HOST_ACTUAL\n";
 exit (1);
}

print "Iniciando $VM en $HOST_NUEVO...\n";
if (! registervm($HOST_NUEVO,$path{$VM},$VM)) {
 print "ERROR: $VM no se pudo agregar al registro de $HOST_NUEVO\n";
 if (! registervm($HOST_ACTUAL,$path{$VM})) {
 print "ERROR: $VM tampoco se pudo volver a agregar al registro de $HOST_ACTUAL\n";
 exit(1);
 } else {
 print "ROLLBACK: $VM se volvio a registrar en $HOST_ACTUAL\n";
 exit(1);
 }
}

my %vmid;

print "Obteniendo el nuevo id...\n";
for $_ (1..3) {
 %vmid = getvmids($HOST_NUEVO);

 last if (defined($vmid{$VM}));
 sleep(5);
}

if (!defined($vmid{$VM})) {
 print "ERROR: No se pudo obtener el vmid para $VM en $HOST_NUEVO\n";
 print "Falta iniciarla y configurar autostart\n";
 exit(1);
}

print "Iniciando $VM...\n";
if (! on($HOST_NUEVO,$vmid{$VM})) {
 print "ERROR: No se pudo encender $VM en $HOST_NUEVO\n";
 exit(1);
}

if (! autostart($HOST_NUEVO,$vmid{$VM})) {
 print "ERROR: No se pudo activar autostart para $VM en $HOST_NUEVO\n";
 exit(1);
}

exit(0);

La biblioteca de funciones la tenemos en el archivo vm.pm cuyo contenido es:

#
# vm.pm
#
#       Modulo de funciones auxiliares de interfaz con VMWARE

use strict;

$::USER = 'root';
$::PWD = 'xxx';
$::ARGS="-U $::USER -P $::PWD";

sub getallvms ($) {
 my ($host) = @_;
 my %path;

 open(CMD,"vim-cmd $::ARGS -H $host vmsvc/getallvms|");
 while(<CMD>) {
 next if (/\sName\s/);
 chop();
 @_ = split(/  +/);
 $path{$_[1]} = $_[2]; # $_[1] es el nombre de la VM
 }
 close(CMD);

 return %path;
}

sub suspend ($$) {
 my ($host,$path) = @_;
 my $ok = 1;

 $path =~ s%\[(.*?)\] *%/vmfs/volumes/$1/%;
 open(CMD,"vim-cmd $::ARGS -H $host vmsvc/power.suspend '$path' 2>&1 |");
 while(<CMD>) {
 next if (/^\s*$/);
 next if (/^Suspending/);
 print;
 $ok=0;
 }
 close(CMD);

 if ($ok) {
   my $counter=0;
   while (getstate($host,$path) =~ /on/i) {
     sleep(10);
     if ($counter++ > 12) {
       $ok=0;
       last;
     }
   }
 }
 return $ok;
}

sub unregistervm ($$) {
 my ($host,$path) = @_;
 my $ok = 1;

 $path =~ s%\[(.*?)\] *%/vmfs/volumes/$1/%;
 open(CMD,"vim-cmd $::ARGS -H $host vmsvc/unregister '$path' 2>&1 |");
 while(<CMD>) {
   next if (/^\s*$/);
   print;
   $ok=0;
 }
 close(CMD);

 return $ok;
}

sub registervm ($$;$) {
 my ($host,$path,$name) = @_;
 my $ok = 1;

 if (!$name) {
   $path=~ m%([^/]+)\.vmx%;
   $name = $1;
 }
 $path =~ s%\[(.*?)\] *%/vmfs/volumes/$1/%;
 open(CMD,"vim-cmd $::ARGS -H $host solo/registervm '$path' '$name' 2>&1 |");
 while(<CMD>) {
   next if (/^\s*$/);
   print;
   $ok=0;
 }
 close(CMD);

 return $ok;
}

sub getvmids {
 my ($host) = @_;
 my %vmid;

 open(CMD,"vim-cmd $::ARGS -H $host vmsvc/getallvms|");
 while(<CMD>) {
   next if (/\sName\s/);
   chop();
   @_ = split(/  +/);
   $vmid{$_[1]} = $_[0];
 }
 close(CMD);

 return %vmid;
}

sub on ($$) {
 my ($host,$path) = @_;
 my $ok = 1;

 $path =~ s%\[(.*?)\] *%/vmfs/volumes/$1/%;
 open(CMD,"vim-cmd $::ARGS -H $host vmsvc/power.on '$path' 2>&1 |");
 while(<CMD>) {
   next if (/^\s*$/);
   next if (/^Powering/);
   print;
   $ok=0;
 }
 close(CMD);

 if ($ok) {
   my $counter=0;
   while (getstate($host,$path) !~ /on/i) {
     message($host,$path,0,1) || sleep(10);
     if ($counter++ > 12) {
       $ok=0;
       last;
     }
   }
 }
 return $ok;
}

sub autostart ($$) {
 my ($host,$vmid) =@_;

 open(CMD,"vim-cmd $::ARGS -H $host hostsvc/autostartmanager/update_autostartentry $vmid 'PowerOn' ' -1' ' -1' 'SystemDefault' ' -1' '
systemDefault' |");
 while(<CMD>) {
   chop();

   if (/Updated AutoStart order/) {
     return 1; # OK
   }
 }
 close(CMD);
 return 0; # ERROR
}

sub message ($$$$) {
 my ($host,$path,$messageid,$response) = @_;
 my $ok = 1;
 my $found = 0;

 $path =~ s%\[(.*?)\] *%/vmfs/volumes/$1/%;

 open(CMD,"vim-cmd $::ARGS -H $host vmsvc/message '$path' $messageid 2>&1 |");
 while(<CMD>) {
   next if (/^$/);
   if(!/No message/) {
     $found = 1;
     last;
   }
 }
 close(CMD);

 if ($found) {
   sleep 1;
   # Envio la respuesta
   open(CMD,"vim-cmd $::ARGS -H $host vmsvc/message '$path' $messageid $response 2>&1 |");
   while(<CMD>) {
     next if (/^\s*$/);
     print;
     $ok=0;
   }
 close(CMD);
 }

 return (!$found || $ok);
}

sub getstate ($$) {
 my ($host,$path) = @_;
 my $estado;

 open(CMD,"vim-cmd $::ARGS -H $host vmsvc/power.getstate '$path' |");
 while(<CMD>) {
   next if (/Retrieved runtime info/);
   chop();
   if (/Powered (.*)/) {
     $estado=$1;
   } elsif (/Suspended/) {
     $estado="Suspended";
   }
 }
 close(CMD);

 return $estado;
}

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s