Optimizando la imagen de un VPS

En este artículo hablaré un poco sobre cómo optimizar el servicio de VPS para personas naturales que proporciona ETECSA, la empresa de telecomunicaciones de Cuba. El procedimiento es aplicable para cualquier VPS que se base en una máquina virtual, pero quise ejemplificarlo con este caso concreto, aprovechando que me lo tropecé.

Una breve introducción

Un servidor privado virtual (Virtual Private Server, de ahí las siglas VPS) es muy útil para un administrador de redes, porque a los efectos prácticos equivale a disponer de un servidor tan funcional como uno real, pero hospedado en un centro de datos usualmente con buen hardware y buenos enlaces, respaldo eléctrico y posibilidad de hacer salvas de seguridad, por un precio razonable. Por estos motivos, me entusiasmé cuando vi que ETECSA había comenzado a comercializar este servicio para personas naturales, y lo contraté.

Impresiones y motivación

El equipamiento físico como tal es bueno, al menos según informan lspci y lshw los equipos usados tienen microprocesador Intel(R) Xeon(R) E5-2658A v3 de 128 núcleos, y cuentan con 64 bancos de memoria, o sea que potencia y escalabilidad deben poder ofrecer. No obstante, la elección predeterminada de hardware virtual es un poco extraña.

Como controlador de almacenamiento el servicio tiene declarado un Broadcom / LSI 53c1030 PCI-X Fusion-MPT Dual Ultra320 SCSI que obliga a cargar módulos del kernel que de otra forma serían innecesarios, cuando vSphere desde hace tiempo permite elegir como controlador SCSI uno conocido como VMware Paravirtual, específicamente diseñado para integrarse eficientemente a un entorno corporativo de virtualización, o al menos podría haberse elegido el controlador LSI Logic SAS, que también es eficiente.

Como adaptador gráfico hay declarado un VMWare SVGA II con regiones de memoria establecidas en 128 MB y 8 MB. Como el adaptador es virtual, seguramente esto es lo que provoca que la instancia nunca termine teniendo la capacidad de RAM con que se solicite implementar, porque una parte se reserva para uso del adaptador gráfico. Dado que el panel no permite modificar la capacidad de RAM reservada para este, si se desea crear una instancia con una capacidad de memoria mínima específica, más vale sumar memoria extra para compensar la pérdida, pero lamentablemente ETECSA acaba de restringir el redimensionamiento por saltos de 512 MB.

En cuando al servicio, he estado experimentando con el desde marzo para comprobar su fiabilidad, usando varias instancias con diferentes distribuciones, y funciona razonablemente bien, con algunas observaciones:

  • Por algún motivo que desconozco, hay días que el panel de gestión Web se pasa horas dando un error de certificado (el navegador reporta que el certificado excede la longitud máxima permitida). Esto impide siquiera cargar la página, por tanto a los efectos prácticos se pierde el control de las instancias en caso de algún fallo en estas.
  • Iniciar sesión por SSH falla tanto que llega a volverse un fastidio; para que esto ocurra tan frecuentemente, la configuración del cortafuegos o sistema de detección de intrusos en la red nacional de ETECSA no debe estar bien ajustada, porque de accederse a través de una VPN internacional, suele funcionar al instante, sin fallar.
  • Las imágenes de sistemas operativos disponibles son mas bien escasas, por ejemplo no aparecen Debian o FreeBSD, muy utilizados para servicios. Podría argumentarse que esto es porque carecen de respaldo corporativo, pero aparece CentOS, que dejó de existir como proyecto para ser reemplazado por Alma Linux, que no está disponible en el catálogo. También aparece Ubuntu 16.04 al cual Canonical dejó de darle soporte oficial estándar en abril (en otras palabras, ya no se proporcionarán gratuitamente más actualizaciones de seguridad para esta versión). El servicio para naturales tampoco permite utilizar imágenes alternativas, como sí lo permite el servicio para instituciones.
  • El servicio no permite acceso al cortafuegos a nivel de centro de datos (conocido como Edge, y disponible sólo para el servicio de Centros de Datos Virtuales para instituciones), de modo que si una instancia recibe un ataque coordinado de inundación de paquetes, aunque uno declare reglas en el cortafuegos de la propia instancia para bloquearlo, el tráfico sigue llegando a la interfaz pública de red y ETECSA lo descuenta de la cuota mensual.
  • Para la asistencia técnica aún no se proporciona al cliente un sistema de tickets, solo una dirección de correo electrónico. Esto tiene varios inconvenientes: no puede conocerse si una incidencia reportada ha sido revisada o asignada a algún técnico, si se está trabajando en ella o se ha descartado por improcedente, etc. Además, algunos problemas reportados no se verifican de manera realista. Por poner un ejemplo, cuando Cuba cambió al horario de verano, reporté que el panel de gestión estaba desfasado y cualquier operación que uno hiciera, aparecía como iniciada hacía una hora. Me respondieron diciendo que sus equipos estaban debidamente sincronizados y que no encontraban discrepancia alguna entre la hora que reportaba el panel y la real. De modo que desde el 14 de marzo el panel continúa dando un estimado incorrecto de la duración de las operaciones y estamos a 30 de junio.
  • Las opciones de Apagar y Reiniciar en el panel Web, dependen de que el sistema esté funcionando correctamente y con VMTools en ejecución (a nadie se le ocurra ejecutar el comando halt). Si por algún motivo la máquina virtual queda en un estado corrupto, se dependerá del soporte técnico para solucionar el problema.

En cualquier caso, la imagen de Ubuntu 20.04 disponible en el catálogo parecía adecuada para mi, pero tenía un consumo de recursos más bien alto, comparado con lo que estaba habituado a ver con otros proveedores internacionales de este tipo de servicio.

Habría preferido utilizar una de las imágenes cloud distribuidas por la propia Canonical para consumo reducido de recursos, pero como no estaban disponibles en el catálogo ni yo podía subirlas, solo me quedó intentar optimizar la imagen proporcionada, eliminando paquetes que no suelo utilizar. No obstante, incluso tras varios intentos de ensayo-error con diferentes instancias aprovisionadas incluso en los 3 centros de datos disponibles en el servicio, no quedé del todo satisfecho, pues el consumo de RAM aun seguía siendo alto para mi gusto.

Entonces, concluí que lograr una buena limpieza con una imagen ya cargada de aplicaciones y configuraciones sería más trabajoso que preparar desde cero una imagen virtual y luego reemplazar con ésta en caliente el sistema en uso. De manera que esta fue la variante que finalmente elegí, y que terminó convirtiéndose en mi motivación para escribir este artículo.

Comenzando

El primer paso debería ser realizar una salva o snapshot desde el panel de gestión Web, para tener un punto funcional al cual revertir, en caso de problemas. Como yo haría el procedimiento en limpio, salté este paso, así que después de haber solicitado la implementación de una nueva instancia de Ubuntu 20.04 en el centro de datos de La Habana, puse manos a la obra.

Para no tener que estar ejecutando constantemente los comandos con sudo, cambié a superusuario:

Luego, comprobé el tamaño de la imagen:

Como me entró curiosidad por conocer cómo se estaba distribuyendo casi la mitad del espacio de almacenamiento (el servicio asigna 20 GB por instancia), pensé que sería buena idea instalar ncdu, que permite visualizar cómodamente en un árbol el uso del almacenamiento. Pero antes, necesitaba actualizar la lista de repositorios y verificar si la imagen activa tenía paquetes corruptos:

Este último comando devolvió algo curioso:

De modo que había en el sistema paquetes residuales innecesarios; aparentemente no tuvieron la precaución de limpiarlos antes de colocar la imagen en el catálogo, así que lo hice yo, para entonces instalar ncdu:

Una vez instalado, ejecuté ndcu sobre la raíz del disco:

distribución del espacio en disco

El directorio /usr estaba un poco cargado, lo cual no es del todo atípico, pero al examinarlo, vi que bajo el subdirectorio /usr/lib había más de 1 GB entre módulos del kernel y archivos de firmware:

espacio usado en /usr/lib

Tanto espacio usado para firmware me llamó la atención, y al examinar este subdirectorio, noté que había muchos archivos genéricos para varios adaptadores gráficos, e incluso para controladores WiFi, cuando el servicio no tiene este tipo de interfaz:

espacio usado en /usr/lib/firmware

Lo otro sobre lo cual tomé nota, es que snapd ocupa su buen espacio en disco y personalmente no lo utilizo en un VPS. Tan sólo el directorio /snap consumía una cantidad de espacio considerable:

espacio usado en /snap

Al explorar el árbol, noté que también en /var/lib había más de 500 MB ocupados en el subdirectorio snapd/:

espacio usado en /var/lib/

Ejecuté entonces htop para ver como se desglosaba el consumo de RAM:

consumo de RAM

Una buena parte de la memoria la consumía el software de aprovisionamiento de aplicaciones de vSphere, que personalmente tampoco uso (prefiero instalar y configurar manualmente los paquetes, para tener mejor control).

Para mayor consistencia con la imagen que pretendía crear, elegí entonces vi como editor por defecto:

Luego purgué el paquete snapd para aligerar la base sobre la cual estaría trabajando, y eliminé cualquier paquete residual:

Entonces, actualicé mi instancia y reinicié para asegurarme de que se cargara el kernel recién actualizado:

Tras volver a iniciar sesión por SSH, volví a modo superusuario e instalé los paquetes necesarios para preparar la nueva imagen:

Elegí schroot porque es más flexible y fácil de usar que chroot, ya que realiza automáticamente tareas engorrosas como el montaje de los directorios /dev y /proc, entre otras ventajas.

Creé entonces un directorio para el chroot:

Creación de la base

El siguiente paso fue preparar con debootstrap una base mínima con solo algunos paquetes adicionales a fin de poder realizar ciertos ajustes y recibir información sobre posibles comandos faltantes:

Preparación del entorno para schroot

A continuación preparé el entorno para schroot. Los archivos de configuración para el perfil predetermindo están en /etc/schroot/default/, pero creé un directorio personalizado (custom) para colocar archivos específicos:

El archivo fstab predeterminado me convenía casi tal cual, a excepción del punto de montaje /home (que no me interesaba preservar):

De los archivo nssdatabases y copyfiles realmente no me interesaba nada, así que los creé vacíos:

Finalmente creé el archivo del perfil:

Como contenido dejé esto, incluyendo el usuario cloud para consistencia con mi instancia:

Cambio a chroot

Entonces, ejecuté schroot declarando el entorno custom que había preparado:

El cambio al entorno virtual (una operación donde es fácil cometer errores, a veces irrecuperables) funcionó sin incidencia alguna, lo cual reafirmó lo acertado de haber elegido el comando schroot en lugar de chroot.

Configuración inicial

Una vez en la instancia virtual, edité /etc/sources.list para agregarle los repositorios security, updates y backports:

Quedó de esta manera:

Luego actualicé la lista de repositorios, y me aseguré de que todos los paquetes estuvieran actualizados:

A continuación creé el usuario cloud y le asigné la misma contraseña de mi instancia activa:

Luego me cercioré de marcar las opciones de pam (exceptuando cualquiera relativa a systemd), para entonces deshabilitar los servicios systemd-logind y getty-static (no necesitaba múltiples instancias de agetty consumiendo recursos):

Lo próximo que hice fue modificar la configuración de los logs para que se reenviaran a syslog:

Una vez terminado esto, creé un archivo de configuración para optimizar ciertos parámetros del kernel:

Como contenido, dejé estas entradas (entre ellas, deshabilité IPv6, ya que muchas subredes cubanas tienen equipamiento que no permite usarlo):

Entonces, modifiqué la configuración de initramfs para que se cargaran solo las dependencias requeridas:

Luego quité las opciones «quiet splash» de la configuración de grub:

Para que el proceso de generación de initramfs no causara errores, exporté esta variable:

Instalación y configuración de paquetes adicionales

A continuación instalé el otro grupo de paquetes que me faltaban, todo esto sólo con las dependencias estrictamente necesarias:

Tras instalar algunos paquetes, apareció un mensaje preguntando si se desea habilitar ferm al inicio; eso justamente era lo que quería, así que confirmé, y el proceso de instalación continuó hasta terminar sin errores.

Tras esto, elegí el idioma inglés estadounidense en UTF-8 (la mayor parte del software soporta esta localización):

El próximo paso fue ajustar el servidor de horario y especificar la zona horaria (elegí America/Havana):

Lo próximo fue configurar dropbear para escuchar en un puerto alto (por ejemplo el 60022) a fin de minimizar intentos no autorizados de acceso. Además, impedir acceso SSH como root y cerrar la conexión tras 20 minutos de inactividad.

También preparé un enlace simbólico para que gesftpserver funcionara de manera transparente como sftp-server:

El siguiente paso fue configurar dnsmasq, que entre otras, ofrece la ventaja de permitir declarar nameservers por zonas. Pero antes, necesitaba averiguar qué nameservers utiliza ETECSA para las diferentes zonas (en caso que se haga este procedimiento para otro ISP, deben usarse los nameservers que este brinda).

Comencé por la propia zona de los VPS:

Consulté entonces a un nameserver de la zona por las direcciones IP:

Luego, la zona etecsa.cu:

Repitiendo la resolución de las direcciones IP:

Finalmente, consulté los nameservers predeterminados para resolución del resto de las zonas:

Repetí la resolución de las direcciones IP solo para etecsa.net (los nameservers para etecsa.net y etecsa.net.cu coinciden):

Una vez obtenida la información necesaria, creé mi archivo de configuración para dnsmasq:

El contenido lo dejé así (los nameservers públicos de CloudFlare y Google los agregué como respaldo):

En este caso, excluí la escucha del servicio en la interfaz pública porque no pretendía montar un nameserver.

Entonces, deshabilité systemd-resolved para evitar conflictos con dnsmasq una vez reiniciara y eliminé /etc/resolv.conf (un enlace simbólico) para crearlo manualmente:

No hice este paso antes, para no arriesgarme a perder la resolución de nombres de dominio en medio del proceso y que esto afectara la descarga de los últimos paquetes.

Pasé entonces a modificar el archivo /etc/network/interfaces:

El siguiente paso fue obtener información sobre la interfaz pública y las tabla de rutas:

Con esta información, creé entonces el archivo para la interfaz pública:

El contenido lo dejé así (utilizando para la resolución de nombres de dominio el nameserver local que había preparado mediante dnsmasq):

A continuación pasé a configurar el cortafuegos, en este caso utilizando ferm, un excelente front-end a iptables. Como aun no tenía ningún servicio, solamente declaré los puertos UDP para traceroute y el puerto TCP para SSH que definí al configurar dropbear. Para esto, creé un archivo de reglas personalizadas:

El contenido lo dejé así:

Sobre las primeras líneas: tras probar anteriormente utilizar crontab para que se mantuvieran persistentes los ajustes de sysctl tras un reinicio, descubrí que usando un hook de ferm se lograba lo mismo de una manera más elegante. El uso de variables al inicio es para facilitar modificar fácilmente la configuración sin tener que estar tocando reglas.

Una vez creadas mis reglas personalizadas, modifiqué el archivo de configuración predefinido, así:

Luego, me cercioré de que initramfs no incluyese módulos del kernel innecesarios:

Con esto, quedó una base funcional con algunos de los comandos más frecuentemente utilizados. Me cercioré entonces de limpiar cualquier paquete residual para salir del entorno chroot:

Combrobación del chroot

Me quedaba entonces probar el entorno chroot, pero como usuario cloud, a fin de comprobar que una vez que remplazara los archivos, no perdería el acceso a la instancia:

Apareció un mensaje de error mencionando que no se encontró un grupo con cierto gid. Esto no tiene mayor importancia siempre y cuando el comando sudo funcione, así que para comprobar, intenté listar la raíz del sistema como superusuario:

Como todo parecía estar bien ya, salí definitivamente del entorno chroot:

Reemplazo del sistema

Antes de realizar el próximo paso, tomé la precaución de cambiar a superusuario y detener algunos servicios:

Una vez terminado esto, podía remplazar el sistema de archivos. En realidad había directorios que no necesitaba remplazar, como /dev, /proc, /sys, /run. Tampoco necesitaba incluir el directorio /boot porque en la imagen de Ubuntu 20.04 que proporciona ETECSA, este directorio se encuentra en una partición dedicada. Los directorios que sí quería reemplazar entre otros eran /usr, /var y /etc pero este último tomando precaución de no eliminar información como por ejemplo la configuración de LVM.

De modo que con todo ya listo, me dispuse a realizar el paso más delicado, consistente en reemplazar los archivos de la instancia en ejecución con la imagen que había preparado, para lo cual, me apoyé en rsync:

El proceso tardó poco menos de un minuto; rsync es eficiente, porque no reemplaza archivos si son iguales. Entonces, antes de proceder a regenerar el initramfs, listé los archivos en el directorio /boot:

Varios archivos estaban relacionados con una versión vieja del kernel, la 5.4.0-42-generic, y otros eran enlaces simbólicos con extensión «old». De modo que mandé a eliminar dichos archivos:

Entonces regeneré el initramfs y actualicé la configuración de grub:

Reinicio, comprobaciones y ajustes

Finalmente, mandé a reiniciar, y crucé los dedos:

En mi caso al menos, el sistema reinició sin problemas, solo que esta vez para el acceso SSH tuve que utilizar el puerto alto que había declarado para drobear.

Tras confirmar que el sistema funcionaba normalmente, cambié a superusuario y eliminé el directorio /chroot, que a estas alturas era innecesario:

Una vez más, me aseguré de que no hubiese actualizaciones o paquetes incorrectamente instalados:

Entonces, instalé los mismos paquetes que había utilizado para diagnosticar la imagen original, y vacié los archivos residuales:

Aunque a estas alturas la imagen había reducido notablemente su tamaño, me propuse reducir además el tamaño de la swap. En el caso de Ubuntu 20.04 ETECSA afortunadamente preparó la swap en un archivo dentro del volumen lógico principal de LVM, lo cual me facilitó redimensionarla a 2 GiB exactos:

Como último paso, eliminé los logs para liberar cualquier espacio que estuviesen ocupando (podría haberles hecho antes una salva, pero como la instancia era limpia, no me interesaba conservar el historial):

También podría haber optado por un acercamiento más conservador y vaciar los logs en lugar de eliminarlos (algo mucho más recomendable en un sistema en producción):

Resultados

Una vez terminado todos estos pasos, comprobé el tamaño resultante de la imagen:

distribución optimizada del espacio en disco

Finalmente, verifiqué el consumo de memoria tras un arranque en limpio:

consumo de RAM tras optimización

Ya con estos valores, finalmente me sentí satisfecho.

Espero que este artículo haya resultado de interés para quienes tengan este tipo de servicio y deseen optimizarlo; que por cierto, recomiendo realizar la operación de madrugada o a primera hora de la mañana, pues a partir de mediodía el servicio no siempre alcanza los 100 mbps que supuestamente debe garantizar.

Y bueno, incluso para quienes no tengan el servicio, con que el artículo haya servido para ilustrar la flexibilidad que permite GNU/Linux, es suficiente.

Nota: Ligera edición del procedimiento (2022-05-10).

¿De cuánta utilidad te ha parecido este contenido?

¡Haz clic en una estrella para puntuar!

Promedio de puntuación 5 / 5. Recuento de votos: 43

Hasta ahora, ¡no hay votos!. Sé el primero en puntuar este contenido.

Sobre Hugo Florentino 11 artículos
Administrador de redes y sistemas. Usuario regular de GNU/Linux desde Octubre de 2008. Miembro fundador del Grupo de Usuarios de Tecnologías Libres (GUTL).

12 comentarios

  1. Google Chrome 84.0.4147.89 Google Chrome 84.0.4147.89 Android 8.0.0 Android 8.0.0
    Mozilla/5.0 (Linux; Android 8.0.0; LDN-LX2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Mobile Safari/537.36

    Un pregunta, permite este servicio de tecsa virtualizacion anidada?

  2. Google Chrome 95.0.4638.69 Google Chrome 95.0.4638.69 Windows 10 x64 Edition Windows 10 x64 Edition
    Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36

    Excelente articulo me ha ayudado mucho, me gustaría contactar con el Autor, para algunas dudas que tengo y no encuentro información al respecto

      • Firefox 95.0 Firefox 95.0 Windows 10 x64 Edition Windows 10 x64 Edition
        Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0

        Como me gusta verte usando windows 10 pillin..jajajaja….regresa al grupo anda que me aburro sin tu oposicion y humor inteligente
        alex out

        • Google Chrome 97.0.4692.99 Google Chrome 97.0.4692.99 GNU/Linux x64 GNU/Linux x64
          Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36

          y tu tambien con win-bug 10 hahahah

  3. Firefox 90.0 Firefox 90.0 Windows 10 x64 Edition Windows 10 x64 Edition
    Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0

    Muy bueno el tutorial, felicidades al autor

  4. Firefox 78.0 Firefox 78.0 GNU/Linux GNU/Linux
    Mozilla/5.0 (X11; Linux i686; rv:78.0) Gecko/20100101 Firefox/78.0

    amigo usted seria un excelente maestro para etecsa a ver si algun día oyen a sus usuarios y arreglan esos errocitos que presentan a veces y no quieren arreglar jeje

    • Google Chrome 91.0.4472.124 Google Chrome 91.0.4472.124 Windows 10 x64 Edition Windows 10 x64 Edition
      Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36

      Dudo que ETECSA carezca de personal capacitado, lo que en una empresa con este nivel de complejidad estructural, probablemente cualquier cambio tiene que pasar por múltiples capas de aprobaciones burocráticas.

  5. Firefox 89.0 Firefox 89.0 Windows 10 x64 Edition Windows 10 x64 Edition
    Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0

    Buena Guía. Ya había hecho mi optimización solo eliminando todo lo instalado que NO es esencial así deje una instalación minima

    • Firefox 78.0 Firefox 78.0 Windows 10 x64 Edition Windows 10 x64 Edition
      Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0

      Esa fue la primera variante que utilicé. Incluso, descargué una imagen cloud y le extraje el listado de paquetes que usaba, para entonces hacer un diff contra los paquetes en el VPS, y obtener el listado de los que sobraban (mas de 200).

      Pero aun después de eliminar los paquetes sobrantes, no conseguía que el consumo de RAM bajara de 100 MB, sospecho que tenia que ver con ajustes y archivos residuales. Entonces, como no sabía exactamente que habian hecho ni si estaba bien, opté por generar yo una imagen en limpio a mi gusto; así estoy 100% seguro de que contiene.

  6. Firefox 89.0 Firefox 89.0 Windows 10 x64 Edition Windows 10 x64 Edition
    Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0

    usted es un genio colega, hice el proceso y quedo de maravilla la neuva imagen, optimizandome el hardware a full. saludos y gracias por el magnifico tuto.

Dejar una contestacion

Tu dirección de correo electrónico no será publicada.


*