VMware: Mejores prácticas en la sobresuscripción de CPU

Como es bien sabido, una de las ventajas de la virtualización es la de poder asignar mas recursos virtuales de los que realmente disponemos sobre un determinado hardware. Esto hace que el uso de los recursos sea mas eficiente y los aprovechemos mejor. Si bien es posible hacer una sobresuscripción con un ratio elevado, esto nos puede llevar a tener problemas de rendimiento en las máquinas virtuales. He encontrado una guía muy completa donde se establecen algunas mejores prácticas a la hora de dimensionar las máquinas virtuales.

pCPU vs vCPU

Lo primero a tener en cuenta es que se entiende por pCPU y vCPU:
  • pCPU se refiere a las CPU físicas. El número de CPUs físicas presentes en un determinado host depende de varios factores como son el número de sockets, el número de cores y si tiene hiperthreading y está habilitado. 
  • vCPU son las CPU que se asignan a una máquina virtual y no tiene porque está ligado al número de pCPU. Cuando se crea una máquina virtual las vCPU se asignan a una pCPU. Debe haber un número de pCPU disponibles para soportar el número de vCPU asignadas a una máquina virtual o esta no podrá arrancar.

En vSphere 5 el número máximo de vCPU por pCPU es de 25:1. Aunque se recomienda que por temas de rendimiento esta relación no sobrepase el 5:1. No obstante esto siempre dependerá del tipo de carga de trabajo que ejecuten las máquinas virtuales.

    Cuando se supera la relación 1:1 debe entrar en funcionamiento el “scheduler” para distribuir los tiempos de procesador entre las máquinas que lo necesiten.

    A la hora de dimensionar el ratio vCPU:pCPU la guía recomienda seguir dos estrategias:

    1. Empezar con una vCPU en la máquina virtual. Una vez analizada la carga de la máquina virtual ir añadiendo vCPU según la necesidad. Cuando la máquina virtual necesite realizar una operación, esta tendrá que esperar a que haya disponibles un número de pCPU igual al número de vCPU. Con lo que, al añadir mas vCPU se incrementa el riesgo de que el tiempo de espera de pCPU aumente provocando un rendimiento peor.
    2. Como la relación vCPU:pCPU depende de la carga de trabajo, la relación puede ser mayor en hosts que ejecuten máquinas virtuales con necesidades de procesamiento marginal. Por otro lado, en hosts que requieran intensivas cargas de trabajo, el ratio deberá ser inferior.

    Métricas

    Existirán ciertas métricas que nos servirán para determinar si el host está teniendo algún problema de rendimiento y para hacer un uso mas eficiente de los recursos mientras se mantienen las cargas de trabajo.

    • Uso de CPU de la máquina virtual: Se deberán añadir vCPU adicionales cuando el uso medio de CPU se mantenga en altos niveles.
    • CPU Ready en el host: Desde el punto de vista de la salud del host, esta es la métrica mas importante. Esta métrica determina el tiempo que una máquina virtual esta esperando para obtener vCPU disponibles que necesita. Por ejemplo, si una máquina virtual tiene asignadas cuatro vCPU, esta métrica indica el tiempo que una máquina virtual ha esperado para que cuatro pCPU estén disponibles en el mismo instante de tiempo.
    • Uso de CPU del host: permite analizar la carga que está soportando el host.

    En el White paper se establecen estos criterios:

    • Una relación entre 1:1 y 3:1 no provoca problemas
    • Entre 3:1 y 5:1 puede empezar a causar alguna degradación de rendimiento
    • Si la relación es superior a 6:1 se suelen tener problemas.

    Adicionalmente se recomienda que el CPU Ready este por debajo del 5%.

    Calculo del CPU Ready

    Como hemos comentado esta métrica es realmente importante para determinar si estamos teniendo problemas de rendimiento. En el cliente de vSphere esta métrica se representa en segundos, mientras que con un ESXTOP se devuelve en porcentaje.

    Para realizar la conversión debemos tener en cuenta el tiempo de muestreo de la gráfica, 20 segundos en la gráfica de real time. La fórmula será la siguiente:
    (x ms  / 20.000 ms) * 100 = %rdy
    Por ejemplo, con esta formula sabremos que:

    • 1% = 200ms
    • 5% = 1000ms
    • 10% = 2000ms
    • 100% = 20000ms

    Hay que tener en cuenta que estas medidas se dan por vCPU, co lo que una máquina con 4 vCPU podrá llegar a tener un 400% de tiempo de planificación. La misma máquina con un 10% de CPU Ready implicará que cada vCPU tenga un CPU Ready de 2.5%.

    Caso Real

    En el siguiente ejemplo tenemos un host HP Prolian DL580 G7 con 4 CPU octacore y con Hyperthreading activado:

    En dicho host hay máquinas virtuales de todo tipo, pero especialmente hay varias “Monster Machines” con hasta 128GB de RAM y 32 vCPU. Como se puede ver en la siguiente gráfica, estas máquinas están sobredimensionadas, ya que el uso de CPU medio es muy bajo:

    Otras tienen un uso de CPU mas elevado, la media en torno al 65%.

    Sin embargo en momentos puntuales llegamos a tener contención y el CPU ready se dispara como podemos ver en esta gráfica donde aparece el acumulado en el host. Esto es debido a que estas máquinas forman parte de un servicio que hace que todas tengan una carga intensiva de CPU en momentos puntuales haciendo que el CPU Ready suba tanto que al final el rendimiento de las máquinas es peor del esperado:

     Como vemos en las gráficas de rendimiento de estas  “Monster Machines”, en momentos puntuales el CPU Ready se dispara a la vez, llegando a los 13 segundos:

     
    Según la fórmula de conversión al %rdy esos 13 segundos corresponden a un 65% de CPU Ready. Para la máquina de 32 vCPU es un 2% por CPU y para la máquina de 8 vCPU corresponde a un 8%. Esta última es la que realmente está teniendo un peor rendimiento y la que se vé mas afectada. Lo ideal sería bajar el número de vCPU asignadas a estas máquinas virtuales para adecuarse mas al consumo real que están teniendo.


    Algunos enlaces interesantes respecto a este tema:

    Reconfiguración de interfaces en entornos virutales con máquinas virtuales Linux

    En entornos virtuales es habitual clonar servidores. En Linux el sistema udev, al detectar un nuevo hardware con una MAC diferente, le asigna un número de interfaz secuencial. Da igual si se le ha borrado alguna interfaz previa, la nueva que se configure seguirá la numeración. Con este comportamiento, es posible que haya cosas que no funcionen, por ejemplo si tenemos un script que haga referencia al nombre de interfaz eth0. Si queremos recuperar la numeración deberemos modificar el fichero de reglas de udev y luego además revisar los scripts de configuración de las interfaces ya que probablemente las MACs no coincidan.
    He creado un nuevo script, que se puede poner en el .bashrc del usuario root por ejemplo. Dicho script detectará si existe la interfaz eth0, en caso de no existir realizará la reconfiguración de udev, limpiará configuraciones de red previas y finalmente creará los nuevos ficheros de configuración. Para crear estos ficheros tendrá en cuenta si queremos utilizar un direccionamiento estático o dinámico (DHCP). 
    Utilizando este script, siempre que clonemos la máquina y accedamos con el usuario root nos preguntará la configuración de red que queremos.
    Ejemplo de uso (desde una consola ya que baja las interfaces):
    Verificamos que el nombre de la interfaz eth0 no existe:

    Configuración de red tras el clonado

    Lanzamos el script, donde veremos que baja las inerfaces de red y pide si queremos borrar configuraciones previas:

    Llamada al script netconfig.sh

    Pide que tipo de configuración queremos en todas las interfaces:

    Reconfiguración de las interfaces

    Una vez terminada la ejecución del script vemos que la interfaz eth0 ya aparece y mantiene la MAC de la interfaz que veiamos en la primer imágen:

    Configuración de red final

    El script:

    #!/bin/bash
    function static {
    hostname=`hostname`
    read -r -p “Hostname: [${hostname}] ” hostname
    if [ -z “$hostname” ]; then hostname=`hostname`; fi
    read -r -p “IP address: ” ip
    read -r -p “Netmask: ” mask
    grep dhcp /etc/sysconfig/network-scripts/ifcfg-eth* 2>/dev/null >/dev/null
    if [ $? -eq 0 ] ; then
    echo “there is a network configured with dhcp which should configure your gateway”
    else
    read -r -p “Gateway: ” gate
    if [ $? -eq 0 ] ; then
    sed -i -e ‘s#^\(GATEWAY=\).*$#\1′”${gate}”‘#’ /etc/sysconfig/network
    else
    echo “GATEWAY=\”${gate}\”” >>/etc/sysconfig/network
    fi
    fi
    #echo “HOSTNAME: ${hostname}\tIP: ${ip}\tNETMASK: ${mask}”
    mac=`ifconfig ${iface} | grep eth | awk ‘{ print $5}’`
    if [ -f /etc/sysconfig/network-scripts/ifcfg-${iface} ]; then
    rm -r /etc/sysconfig/network-scripts/ifcfg-${iface}
    fi
    echo “DEVICE=\”${iface}\”” >> /etc/sysconfig/network-scripts/ifcfg-${iface}
    echo “BOOTPROTO=\”static\”” >> /etc/sysconfig/network-scripts/ifcfg-${iface}
    echo “NM_CONTROLLED=\”yes\”” >> /etc/sysconfig/network-scripts/ifcfg-${iface}
    echo “ONBOOT=\”yes\”” >> /etc/sysconfig/network-scripts/ifcfg-${iface}
    echo “TYPE=\”Ethernet\”” >> /etc/sysconfig/network-scripts/ifcfg-${iface}
    echo “IPADDR=\”${ip}\”” >> /etc/sysconfig/network-scripts/ifcfg-${iface}
    echo “NETMASK=\”${mask}\”” >> /etc/sysconfig/network-scripts/ifcfg-${iface}
    echo “HWADDR=\”${mac}\”” >> /etc/sysconfig/network-scripts/ifcfg-${iface}
    echo “DNS1=\”X.X.X.X\”” >> /etc/sysconfig/network-scripts/ifcfg-${iface}
    echo “DNS2=\”Y.Y.Y.Y\”” >> /etc/sysconfig/network-scripts/ifcfg-${iface}
    echo “DOMAIN=\”enterprise.com\”” >> /etc/sysconfig/network-scripts/ifcfg-${iface}
    sed -i -e ‘s#^\(HOSTNAME=\).*$#\1′”${hostname}”‘#’ /etc/sysconfig/network
    grep GATEWAY /etc/sysconfig/network 2>/dev/null >/dev/null
    }

    function dhcp {
    echo “Configuring DHCP for ${iface}”
    hostname=`hostname`
    read -r -p “Hostname: [${hostname}] ” hostname
    if [ -z “$hostname” ]; then hostname=`hostname`; fi
    mac=`ifconfig ${iface} | grep eth | awk ‘{ print $5}’`
    if [ -f /etc/sysconfig/network-scripts/ifcfg-${iface} ]; then
    rm -r /etc/sysconfig/network-scripts/ifcfg-${iface}
    fi
    echo “DEVICE=\”${iface}\”” >> /etc/sysconfig/network-scripts/ifcfg-${iface}
    echo “BOOTPROTO=\”dhcp\”” >> /etc/sysconfig/network-scripts/ifcfg-${iface}
    echo “HWADDR=\”${mac}\”” >> /etc/sysconfig/network-scripts/ifcfg-${iface}
    echo “NM_CONTROLLED=\”yes\”” >> /etc/sysconfig/network-scripts/ifcfg-${iface}
    echo “ONBOOT=\”yes\”” >> /etc/sysconfig/network-scripts/ifcfg-${iface}
    echo “TYPE=\”Ethernet\”” >> /etc/sysconfig/network-scripts/ifcfg-${iface}
    sed -i -e ‘s#^\(HOSTNAME=\).*$#\1′”${hostname}”‘#’ /etc/sysconfig/network
    }


    ifconfig eth0 2>/dev/null >/dev/null
    if [ $? -ne 0 ] ; then
    echo “eth0 not found. Interface configuration…”
    echo “Recreating interfaces”
    # Rename eth1 with eth0    
    echo “Stopping network”
    service network stop

    #Clearing devices and renaming
    echo “UDEV Config…”
    rm -f /etc/udev/rules.d/70-persistent-net.rules
    #ls -l  /etc/udev/rules.d/70-persistent-net.rules
    udevadm trigger
    sleep 2
    #ls -l  /etc/udev/rules.d/70-persistent-net.rules
    ifdevs=`udevadm info –export-db | grep “INTERFACE=eth” | cut -d “=” -f2`
    #echo “Interfaces ${ifdevs}”
    count=0
    for ifdev in ${ifdevs}; do
    sed -i -e “s/${ifdev}/eth${count}/g” /etc/udev/rules.d/70-persistent-net.rules
    # echo “sed -i -e ‘s/${ifdev}/eth${count}/g’ /etc/udev/rules.d/70-persistent-net.rules”
    count=$((count+1))
    # echo “count: ${count}”
    done
    udevadm trigger –attr-match=subsystem=net
    #echo “udev Net rules”
    #grep eth /etc/udev/rules.d/70-persistent-net.rules
        echo “—————————————————“
        
    #Clearing configuration    
    echo “Remove previous configuration”
    for cfgfile in `ls /etc/sysconfig/network-scripts/ifcfg-eth*`; do
    read -r -p “Remove previous configuration file ${cfgfile}? [y/N] ” response
    case $response in
    [yY])
    rm -f ${cfgfile}
    ;;
    *)
    echo “Not removing ${cfgfile}”
    ;;
    esac
    done
        
    #Setup ehternet devices
    sleep 2
    ifaces=`ifconfig -a | grep eth | awk ‘{ print $1}’`
    #echo ${ifaces}
    for iface in ${ifaces}; do
    read -r -p “Do you want to configure a static IP for ${iface}? [y/N] ” response
    case $response in
    [yY])
    static
    ;;
    *)
    read -r -p “Do you want to configure DHCP for ${iface}? [y/N] ” respdhcp
    case $respdhcp in
    [yY])
    dhcp
    ;;
    *)
    echo “Not configuring ${iface}.”
    #rm -r /etc/sysconfig/network-scripts/ifcfg-${iface}
    ;;
    esac
    ;;
    esac
    done
    #Start networking
    echo “Starting networking”
    service network start
    fi