Limitar los recursos requeridos por los trabajos en HTCondor usando cgroups

HTCondor divide los nodos de ejecución en «slots» que están compuestos por CPUs, RAM y disco. Cuando se lanza un trabajo, éste solicita una cantidad determinada de cada uno de esos recursos. La asignación lo que hace es buscar un slot que satisfaga la solicitud del trabajo. Sin embargo, una vez asignados, HTCondor no limita la cantidad de recursos que el trabajo usa, pudiendo un trabajo consumir más de lo que solicitó en un principio. Esto puede llevar a situaciones en las que pueda consumir toda la RAM y cuelgue el nodo, perjudicando a otros trabajos que están corriendo en el mismo nodo. Igual pasa con el caso de la CPU: puede crear más hebras que las solicitadas también perjudicando la ejecución de otros trabajos.

Hasta ahora, para evitar este comportamiento, se ha usado USER_JOB_WRAPPER con ulimit para limitar el tamaño máximo de memoria que puede usar sea igual al que ha solicitado. Para CPU, no tenía ninguna ninguna forma de limitarlo simplemente porque no se ejecutaban programas con más de 1 hebra.  Ahora, al empezar a usar programación paralela, se hace necesario controlar también este recurso.

Con ulimit, no he encontrado la manera de controlar el uso del número de CPUs. Además, con esta técnica, cuando se rebasaba la memoria, el programa era eliminado y no indicaba la causa. En el log, aparecía segmentation fault pero puede indicar también un error en el acceso a memoria.

Para paliar estos problemas, he cambiado la forma en la que se limitan los recursos de los trabajos en los nodos de ejecución, usando cgroups, que es una forma de aislar los recursos que pueden consumir un conjunto de procesos, soportado por el kernel.

Lo primero que tenemos que hacer es instalar las herramientas para poder usar esta característica, si no la tenemos. En centos, sería:

yum install libcgroup-tools.x86_64

Y configurarlo para crear el grupo de HTCondor, tal y como se explica en su documentación. Creamos un fichero en /etc/cgconfig.d/ con el siguiente contenido:

group htcondor {
cpu {}
cpuacct {}
memory {}
freezer {}
blkio {}
}

Para indicarle a Condor que use cgroups, sólo hay que añadir esta línea en la configuración de los nodos de ejecución:

BASE_CGROUP=htcondor

Además, vamos a configurar el modo en que queremos que sean tratados los trabajos si superan los límites de memoria. cgroup limita la cantidad de memoria física que puede usar un programa, pero puede aumentar su memoria virtual usando paginación. Para evitarlo, voy a usar la siguiente configuración:

CGROUP_MEMORY_LIMIT_POLICY=hard

hard indica que no puede usar más memoria de la solicitada, incluso si hay disponible en el nodo (al contrario que soft) y en la configuración de los slots, desactivo el uso de SWAP:

SLOT_TYPE_1 = cpus=100%, ram=100%, swap=0, disk=100%

 

Con esta configuración, usará todas las CPUS libres del sistema, pero si no hay, estará limitado al número de CPUs solicitadas. Por ejemplo, en un sistema con solo 4 cores libres, si nuestro programa crea 16 hebras y hemos reservado esos 4 cores, las 16 hebras correrán en esos 4 cores, compitiendo por usarlo y, a la postre, perjudicando a su rendimiento. Para la memoria, si sobrepasa su límite se pondrá en HOLD con el mensaje »

Job has gone over memory limit»

Como funcionalidad adicional, queremos que un trabajo que haya pasado a HOLD porque haya excedido la cantidad de memoria solicitada vuelve a ejecutarse aumentando la cantidad de memoria que solicita de manera automática. Para esto, usaremos el atributo SYSTEM_PERIODIC_RELEASE del scheduler:

SYSTEM_PERIODIC_RELEASE=(JobRunCount < 5 ) && (HoldReasonCode == 34)

Con esa línea, el programa se pondrá de nuevo en IDLE si ha sido ejecutado hasta en 5 ocasiones y está en estado de HOLD por el motivo 34 (este código es el que pone Conder al pasar a HOLD por superar el límite de memoria usando cgroups).

En el submit del trabajo vamos a utilizar la siguiente configuración

ifthenelse(( NumJobStarts == 0 ),200,1.5 * 200 * ( NumJobStarts ))

Con esto, se parte de una solicitud de 200MB. Por cada vez que el trabajo pasa a HOLD y se vuelve a poner en ejecución, la solicitud se aumenta 1,5 veces.

Compresión en LTO

A la hora de hacer backups es muy útil usar compresión para ahorrar espacio, sobre todo si se tratan de datos que no se van a usar como los archives o históricos.

Los sistemas de copias de seguridad como Bacula permiten indicar el tipo y nivel de compresión que se desea utilizar en cada copia. No obstante, si se trata de backup a cintas es muy posible que la unidad de cintas ya tenga su propio sistema de compresión; esto es, se puede hacer la compresión por hardware (unidad de cintas) en lugar de por software (Bacula). De hecho, todas las unidades de cinta LTO tienen soporte para compresión. Cuando se indica la capacidad de una cinta LTO dan 2 números, la capacidad nativa, sin comprimir y la capacidad con datos comprimidos. Aunque este valor depende de los datos que almacenará puesto que algunos tipos de datos tienen mayor tasa de compresión que otros, suelen mostrar un valor de compresión de 2:1, es decir, el doble de capacidad con datos comprimidos.

Usar ambos tipos de compresión a la vez (hardware+software) no solo consume más tiempo, sino que puede llegar a consumir más espacio que los datos originales. Comprimir algo que ya está comprimido no mejora, incluso empeora el ratio. Por esto, se recomienda usar únicamente la compresión hardware ya que es bastante buena y no supone ninguna sobrecarga.

Para comprobar si la unidad tiene la compresión activada podemos usar la utilidad tapeinfo.

# tapeinfo -f /dev/sg0 
Product Type: Tape Drive
Vendor ID: 'HP      '
Product ID: 'Ultrium 5-SCSI  '
Revision: 'Z6FS'
Attached Changer API: No
SerialNumber: 'HU1246T73R'
MinBlock: 1
MaxBlock: 16777215
SCSI ID: 3
SCSI LUN: 0
Ready: yes
BufferedMode: yes
Medium Type: Not Loaded
Density Code: 0x58
BlockSize: 0
DataCompEnabled: yes
DataCompCapable: yes
DataDeCompEnabled: yes
CompType: 0x1
DeCompType: 0x1
Block Position: 430690
Partition 0 Remaining Kbytes: 1470031
Partition 0 Size in Kbytes: 1470031
ActivePartition: 0
EarlyWarningSize: 0
NumPartitions: 0
MaxPartitions: 1

Si no estuviera activado, lo podemos activar con

# mt -f /dev/st0 compression 1

Cómo mantener un conjunto de volúmenes disponibles para cualquier pool en Bacula

En Bacula, cada volume ha de pertenecer a una determinada pool para que un trabajo la utilice. Siguiendo las reglas de Bacula y las especificaciones que indiquemos, un volume se puede reciclar, pero siempre pertenecerá a la misma pool. Se puede dar el caso de que una pool se quede sin volumes para usar, habiendo disponibles en otras pools. Hay una manera de corregir este comportamiento mediante el uso de la pool Scratch: por defecto, Bacula usa los volúmenes de la pool Scratch cuando alguna de las pools necesita un nuevo volumen. Esto es, si una pool necesita un nuevo volumen para escribir y no puede encontrar ninguno, los coge de la pool Scratch, pero no los devuelve a la pool Scratch por defecto. Para ello, tenemos que indicar la pool a la que va el volumen cuando es reciclado.

Sabiendo esto, la solución que he adoptado es asignar un par de volúmenes a cada uno de mis pools, los cuales no cambiaran de pool al reciclarse, y crear la pool Scratch a la que le he asignado los volúmenes restantes configurando la pool para que una vez sean reciclados, estos volúmenes vuelvan a esta pool y puedan ser usados por otras pools.

La configuración sería la siguiente:

Pool {
   Name = Scratch
   Pool Type = Backup
   RecyclePool = Scratch
}

Copia de seguridad de bases de datos MariaDB consistentes

El objetivo es hacer copias de seguridad de un servidor de bases de datos MariaDB de manera que sean consistentes y minimizando el efecto de hacer los backups sobre el funcionamiento normal del servidor.

Como solución inicial, vamos a utilizar mysqldump que vuelca la estructura y datos de los esquemas en sentencias sql. Como ventaja tiene que es muy fácil de usar y que se pueden editar las setencias resultantes, por ejemplo, antes de exportarlas a otra base de datos. Como desventaja es que el recrear una base de datos de esta forma lleva mucho tiempo.

mysqldump se conecta como cliente al servidor para hacer el volcado de los datos. Para evitar problemas de seguridad, vamos a crear un nuevo usuario con los privilegios mínimos para que mysqldump pueda funcionar. Dependiendo del tipo de motor, necesitaremos unos privilegios u otros. Por ejemplo, estos son los privilegios que se necesitan para los motoros InnoDB y MyISAM:

InnoDB

CREATE USER ‘backup’@’localhost’ IDENTIFIED BY ‘secret’; GRANT SELECT, SHOW VIEW, RELOAD, REPLICATION CLIENT, EVENT, TRIGGER ON *.* TO ‘backup’@’localhost’;

MyISAM

GRANT LOCK TABLES ON *.* TO 'backup'@'localhost';


 

Monitorizar cambios en Galera Cluster

Para chequear el estado de un cluster Galera se puede inspeccionar cada uno de los nodos que lo componen y comprobar que se hayan en funcionamiento y sincronizados. Pero hay una alternativa que el propio Galera implementa. Está muy bien documentado en los siguientes enlaces:

  • http://galeracluster.com/documentation-webpages/monitoringthecluster.html
  • http://galeracluster.com/documentation-webpages/notificationcmd.html

Como se explica ahí, Galera permite enviar notificaciones cuando detecta algún cambio en el estado del cluster, a través de la llamada a un script o programa con los argumentos que ahí se comentan (--status, --uuid, --members, --index). El script que viene como ejemplo puede ser suficiente para la mayoría de los casos. Cada vez que Galera detecta un cambio, invoca al script pasándole los respectivos argumentos. Éste crea un schema dentro de la propia base de datos, desactivando para ello la replicación en esta sesión, de modo que cada uno de los nodos tienen información propia en los registros que crean. Al script por defecto yo simplemente he añadido una línea para que, además de actualizar las tablas con los cambios, envíe un mail al administrador para que pueda actuar si es necesario.

LVM en rbd Ceph

Queremos usar LVM sobre un dispositivo de bloques Ceph. Por defecto, LVM ignora este tipo de dispositivos (lo primero que hemos hecho ha sido mapear la imagen a dispositivo del sistema con rbd map obteniendo el dispositivo /dev/rbd0).

Para permitirlo, hay que editar el fichero de configuración de LVM (/etc/lvm/lvm.conf)y añadir/editar estas líneas:

preferred_names = [ "^/dev/mpath/", "^/dev/mapper/mpath", "^/dev/[hs]d" , "^/dev/rbd" ]
types = ["rbd", 1024]

Map de imágenes RBD de Ceph con kernels antiguos

Al intentar mapear una imagen rbd de la última versión de ceph (jewel) en el kernel que incluye la distribución de centos 7, da un error como el siguiente:

[bash]

rbd: sysfs write failed
RBD image feature set mismatch. You can disable features unsupported by the kernel with "rbd feature disable".
In some cases useful info is found in syslog – try "dmesg | tail" or so.
rbd: map failed: (6) No such device or address

[/bash]

Esto sucede porque el módulo del kernel (krbd) no soporta las nuevas funciones de jewel. He intentado desactivar esas funciones con rbd feature disable, pero tampoco me ha dejado. Así que he tenido que volver a crear la imagen indicando a la hora de la creación las features que nos interesan:

[bash]rbd create -s 100G –image-feature layering one/almacen[/bash]

He sido bastante restrictivo y sólo he dejado la funcionalidad básica de layering.

Parece ser que esto se debe a que ahora por defecto la imágenes rbd se crean con el formato versión 2 (antes 1).

NFS en alta redundancia

Queremos compartir un punto de montaje NFS con alta disponibilidad para evitar que la caída del server provoque que el servicio se detenga. Para evitar cualquier punto de fallo, para almacenar los datos se va a utilizar ceph. El sistema de replicación que hace ceph permite que se puedan seguir accediendo a los datos a pesar de sufrir la caída de parte de los servidores que conforman el cluster. Con la alta disponibilidad, conseguimos que si cae el servidor NFS, otro nodo del cluster asuma ese rol.

Partimos de un escenario en el que tenemos un cluster ceph funcionando y un cluster de alta disponibilidad con corosync, pacemaker y pcs.

En resumen, haremos lo siguiente: usaremos una imagen rbd de ceph para albergar los archivos que queremos compartir por NFS. Usando el cluster de alta disponibilidad, montaremos ese dispositivo de bloques e iniciaremos el servidor NFS que usará una IP virtual.

Pasemos a detallar paso a paso ese proceso:

  1. Creamos una imagen en ceph

    [bash] rbd create –size 100000 pool/nombreimagen # el tamaño se expresa en megabytes, así pues, crearíamos un disposivito de 100GB [/bash]

  2. Mapear la imagen a dispositivo de bloques. Con esto, la imagen se puede utilizar como cualquier otro dispositivo de bloques, como un disco duro.

    [bash] rbd map pool/nombreimagen [/bash]

    Se crea un dispositivo /dev/rbdX y un enlace simbólico en /dev/rbd/pool/nombreimagen a ese dispositivo. Este proceso se puede automatizar añadiendo al fichero /etc/ceph/rbdmap la imagen que queramos mapear y el usuario a usar:

    [bash] poolname/imagename     id=client,keyring=/etc/ceph/ceph.client.keyring [/bash]

    y llamar directamente al script

    [bash] rbdmap map [/bash]

    ó

    [bash] systemctl start rbdmap [/bash]

  3. Le damos formato a la imagen
    [bash] mkfs.ext4 /dev/rbd0 [/bash]
    y la montamos
    [bash] mount /dev/rbd0 /mountpoint [/bash]
    . Ahora ya podemos copiar o sincronizar los datos que queremos compartir

Mejorando la seguridad con Snort

snort-logoPara proteger la seguridad de nuestra red local y los servicios que suministramos al exterior (SSH y HTTP, principalmente), vamos a hacer una serie de modificaciones a la forma en que se puede acceder a ellos.
En primer lugar, hemos adquirido un nuevo equipo que nos servirá de gateway. Se trata del siguiente equipo:
https://www.supermicro.com/products/system/1u/5018/sys-5018a-ftn4.cfm

Tiene un procesador Atom de 8 cores, 8GB de RAM y 80GB de SSD. La placa tiene funcionalidades propias de un servidor, tales como puerto IPMI dedicado y memoria ECC, lo que facilita su gestión y nos garantiza un mejor funcionamiento.
Este equipo va centralizar las funciones de gateway, firewall y detección de intrusos.

Después de probar muchas distribuciones para este propósito tales como pfSense o ipfire, al final me decanté por Alpine Linux. Las funciones de gateway y firewall las he conseguido usando Shorewall, un firewall que facilita el uso de iptables. Está basado en zonas y cada interfaz de red se asocia a una zona. Se configuran las acciones por defecto y los puertos abiertos y redirecciones. Con ayuda de zenmap (interfaz gráfica de nmap) comprobamos que únicamente están disponibles los puertos que nosotros hemos permitido. No me extiendo mucho en este tema porque en esta entrada me interesa centrarme más en Snort.

Snort es el sistema de detección y prevención de intrusos de red y hosts de facto en el mundo open source. De hecho, es mejor que aplicaciones comerciales con el mismo fin. El fin de Snort es vigilar el tráfico de nuestra red o de un determinado host para detectar comportamientos extraños que puedan responder a intentos de ataques o intrusiones. Snort tiene tres funciones: sniffer al estilo de tcpdump, logger de paquetes (almacenar los paquetes a disco/BBDD) y modo de intrusión de intrusos. En este modo, analiza los paquetes que circulan por la red siguiendo las reglas que hemos definido y realizando acciones en base al tráfico identificado.
snort

Así, nuestro gateway haría de firewall e IDS, tal y como aparece en la figura. Después de pasar las reglas del cortafuegos, los paquetes son analizados por Snort antes de que se encaminen a nuestra red interna.

Veamos ahora lo que Snort es capaz de hacer con estos paquetes.
Snort se basa en el procesado de los paquetes mediante reglas definidas por el usuario. En estas reglas se definen las características que deben cumplir los paquetes para que sean considerados como peligrosos. Se pueden crear a mano estas reglas, pero también se pueden descargar reglas creadas por otros. Por ejemplo, la comunidad mantiene una serie de reglas actualizadas y que se pueden descargar gratuitamente. También se pueden descargar reglas adicionales si nos registramos en la página de la empresa de Snort. Te solicitan para ello una dirección de email. Estas reglas son las mismas que las de la edición de pago de Snort, pero con 30 días de antigüedad.

Pasemos a configurar Snort:

[bash] vi /etc/snort/snort.conf [/bash]

Es un fichero de configuración muy largo, lo principal que hay que cambiar es lo siguiente:

  • Cambiar «var HOME_NET any» a «var HOME_NET X.X.X.X/X» (rellenar con nuestra red local en la que confiemos)
  • Cambiar «var EXTERNAL_NET any» to «var EXTERNAL_NET !$HOME_NET» (esto indica que todo lo que no sea HOME_NET es red externa)
  • Ajustar «var RULE_PATH» al directorio donde vayamos a guardar las reglas (generalmente /etc/snort/rules o /var/lib/snort/rules)
  • Hacer lo mismo con «var SO_RULE_PATH» y «var PREPROC_RULE_PATH»
  • Comentar la línea que empieza con «dynamicdetection directory «
  • Añadir (o editar) la salida de datos: output unified2: filename snort.log, limit 128
  • Encontrar la línea «preprocessor http_inspect: global iis_unicode_map unicode.map 1252 compress_depth … » y quitarle la parte del final, a partir de «compress_depth». Debería quedar así: «preprocessor http_inspect: global iis_unicode_map unicode.map 1252»
  • Hallar «inspect_gzip» y comentarlo o eliminarlo
  • Hallar «unlimited_decompress» y comentarlo o eliminarlo

Prueba de la configuración

Podemos empezar a hacer pruebas de nuestra configuración ejecutando el programa en foreground y viendo los mensajes que nos muestra:

[bash] snort -v -c /etc/snort/snort.conf [/bash]

Vemos los mensajes de warning y errores que puedan aparecer y los resolvemos con ayuda de google. Para ayudar a resolver estos problemas y para mantener actualizada las reglas se puede usar la aplicación PulledPork

https://github.com/shirkdog/pulledpork

El programa es un script en Perl. Yo he necesitado instalar algunas dependencias para que funcione, en concreto, perl-lwp-useragent-determined, perl-lwp-protocol-https y perl-crypt-ssleay. Una vez que lo tengamos listo, podemos configurarlo usando su fichero de configuración.

Hay que cambiar <oinkcode> por el nuestro, que se puede encontrar en la página web de Snort, bajo nuestro perfil (siempre que nos hayamos registrado, claro está), corregir los path donde se almacenaran las reglas, el sid-msg.map y changelog. En mi instalación de Snort falta el programa snort_control que se usa a la hora de actualizar la blaclist de IPs, así que he tenido que volver a generar el apk con la opción –enable-control-socket.

 

http://sourceforge.net/projects/secureideas/files/BASE/base-1.4.5/base-1.4.5.tar.gz/download

Con ayuda de una máquina virtual con Alpine Linux usada para compilar programas y así mantener limpio la instalación, compilamos barnyard2. Hay que añadir la opción –with-mysql para dar soporte a esta base de datos.

El binario resultante lo copiamos en nuestra máquina de producción.

Lo siguiente a hacer es inicializar la base de datos. El script en SQL para esto se haya en el directorio schemas dentro del código fuente del programa.

 

BASE necesita php-adodb

https://ip/base/setup/