South Park en Linux

Sorprendente lo fácil que resulta disfrutar de “The stick of Truth” (“la vara de la verdad”) en Linux con un Wine “moderno” (por ejemplo, 1.7.42).

Básicamente basta con instalar el cliente Windows de Steam usando Wine (instrucciones detalladas para instalar Steam en ArchLinux):

# Instalar tipos de letra
winetricks corefonts

# Instalar Steam
wine SteamSetup.exe

# Ejecutar Steam con el "truco para mostrar fuentes"
wine ~/.wine/drive_c/Program\ Files\ \(x86\)/Steam/Steam.exe -no-dwrite

Combinar discos duros

Hay múltiples formas de combinar dos o más discos duros para disponer del espacio del conjunto en un sólo sistema de ficheros (en lugar de tenerlo fragmentado en varios)

La estrategia clásica es RAID, desde una concatenación secuencial (modo lineal) hasta un reparto que soporte el fallo de dos discos y aproveche todos para acelerar las operaciones de entrada salida (RAID 6). No obstante, con los discos actuales (de varios terabytes) los tiempos de reconstrucción son larguísimos, lo cual hace que la protección frente a fallo sea relativa.

Otra opción versátil es LVM, cuyas últimas versiones han integrado los diversos modos RAID: “a tiras” (stripping), espejo (mirroring o RAID1)… Y además permite combinar trozos (o discos) de distinto tamaño. Y hacer instantáneas (snapshots). Y usar transparentemente un disco de estado sólido (SSD) para acelerar las operaciones E/S del conjunto de discos.

Para el caso más simple, combinar dos discos, podemos optar por…

  • RAID0 (lineal o a tiras)
  • RAID 1
  • Un grupo de volúmenes LVM (VG)
  • una “mancomuna” ZFS (pool)
  • un sistema de ficheros btrfs multidispositivo
  • repartir “a mano” el espacio, montando el sistema de ficheros de uno de los discos dentro del sistema de ficheros del otro

Gestión de recursos en Debian

Una forma sencilla de controlar el uso de recursos es con ulimit o pam_limits (en /etc/security/limits.conf, y los servicios de /etc/pam.d pertinentes)

Si queremos gestionar recursos como el tráfico de red, los dispositivos o la entrada/salida; o necesitamos flexibilidad para afinar a qué procesos aplicar restricciones, podemos recurrir a los grupos de control (cgroups)

Las opciones con cgroups son múltiples (ver documentación al final). Una de ellas es asignar las restricciones por usuarios / grupos (en la tradición pam). Para ello, los pasos a seguir son:

  1. Instalar los paquetes de cgroups+pam. En Debian, cgroup-bin y libpam-cgroup
  2. Si vamos a usar el controlador de memoria, habilitarlo en grub: añadir “cgroup_enable=memory swapaccount=1” a GRUB_CMDLINE_LINUX_DEFAULT en /etc/default/grub, ejecutar update-grup y reiniciar
  3. Definir los grupos en /etc/cgconfig.conf:
    # Ejemplo sencillo: el grupo common_mem_pool solo puede usar 192GB de RAM.
    # Si un proceso del grupo se encuentra se recursos, se queda en pausa
    # (en lugar de recurrir al OOM killer)
    # Es importante limitar también el swap, ya que si no los procesos empiezan a tirar de él
    # cuando copan la RAM delimitada...
    group common_mem_pool {
            memory {
                   memory.oom_control = 1;
                   memory.limit_in_bytes = 210G;
                   memory.memsw.limit_in_bytes = 210G;
            }
    }
    
  4. Incorporar los cambios de cgconfig.conf con
    cgconfigparser -l /etc/cgconfig.conf
    

    Para configurar la ejecución automática de cgconfigparser durante el arranque, podemos añadir la línea anterior a /etc/rc.local

  5. Probar las definiciones con cgexec (y un programa adecuado, que intente sobrepar las limitaciones):
    cgexec -g memory:common_mem_pool mem_eater
    
  6. Definir las reglas de asignación de grupos en /etc/cgrules.conf:
    # Sintaxis:
    # usuario:nombre_proceso controlador grupo
    # Si el grupo de control no está definido en cgconfig, pam se quejará con algo tipo
    # "cannot make/remove an entry for the specified session"
    
    # Aplicar al usuario alex y al grupo devel las restricciones de common_mem_pool
    alex        memory          common_mem_pool
    @devel        memory          common_mem_pool
    
  7. Habilitar pam-cgroup en los ambitos pertinentes. Por ejemplo, si los usuarios acceden por ssh, en /etc/pam.d/sshd:
    session    required   pam_cgroup.so
    

    (otras posibilidades interesantes son /etc/pam.d/su y /etc/pam.d/sudo)

  8. Configurar el montaje del sistema de ficheros /sys/fs/cgroup. Si tenemos instalado Docker en el sistema, /etc/init.d/docker ya se encarga de montarlo. Si no, lo más sencillo es añadir a cgconfig.conf algo tipo…
    # Si ya está montado, cgconfigparser da un error (tipo "request came in from non owner").
    # En cuyo caso, lo más simple es comentar esta sección
    mount {
            cpu = /sys/fs/cgroup;
            memory = /sys/fs/cgroup;
            cpuacct = /sys/fs/cgroup;
    }
    

Para obtener información relacionada con cgroups, disponemos de los siguientes comandos:

# ver grupos de control de los procesos
ps -O cgroup
# grupos disponibles
cat /proc/cgroups
lscgroup
# informacion sobre un grupo en particular
cgget common_mem_pool

Los controladores a priori más apetecibles son cpu, memory (ver ejemplo más arriba) y blkio:

  • cpu.shares: porcentaje de CPU para el grupo
  • blkio.weight: porcentaje de E/S para el grupo
  • blkio.throttle.read_bps_device, blkio.throttle.read_iops_device, blkio.throttle.write_bps_device, blkio.throttle.write_iops_device: límite de velocidad (en bytes por segundo) en acceso a un dispositivo

Para adaptar el funcionamiento del OOM killer, podemos modificar indirectamente oom_score ajustando el parametro oom_adj de los procesos (-17 si queremos apartar al proceso de la mira de OOM, -16 si queremos que su muerte sea muy poco probable, 15 si muy probable). Ruth (root) juega con ventaja, por defecto a sus procesos se les reduce su probabilidad de ser exterminados por OOM.

Para saber más…

cgroups:

Memoria:

NFS

NFS permite compartir directorios por red de forma sencilla en entorno Linux, aunque como todo tiene sus pegas (la principal, es sensible por defecto a cortes en la conexión cliente-servidor)

A continuación un pequeño ejemplo, en el que compartimos el directorio /nas/work/dir1 en server2 para que esté accesible en el directorio /mnt/server2/dir1 en client.

En NFS, configuramos lo que queremos hacer en ambos extremos. En el servidor, el fichero clave es /etc/exports:

# Una chuleta rápida de las opciones disponibles está en "man exports"
# Generalmente se puede omitir el fsid, pero si el sistema de ficheros donde se encuentra
# /nas/work/dir1 es XFS, <a href="https://www.mmacleod.ca/blog/2014/02/nfs-exports-and-xfss-inode64-mount-option/">mejor especificarlo</a> (a fin de evitar esotéricos problemas al montar
# o acceder al directorio...)
# rw -> lectura y escritura
# no_subtree_check -> "as a general guide, disable subtree_check"
# no_root_squash -> no "mapear" uid/gid 0 al uid/gid "anonymous"
/nas/work/dir1 client.xyz.com(rw,no_subtree_check,no_root_squash,fsid=1)

Ojo con utilizar fsid=0, ya que tiene un significado especial (“NFS root”)

Luego notificamos los cambios con exportfs:

# -r notifica y también hace limpieza (directorios que se han eliminado de /etc/exports)
# -f limpia completamente la tabla de exportacion en el kernel, y va añadiendo nuevas 
#    entradas segun los clientes hacen peticiones
exportfs -r

En el cliente, modificamos /etc/fstab:

# Una chuleta rápida de las opciones disponibles está en "man nfs"
# defaults -> opciones por defecto (se pueden comprobar concretamente con "mount" o "nfsstat -m"
#    Habitualmente son: relatime, vers=4, rsize=1048576, wsize=1048576, namlen=255, 
#    proto=tcp, port=0, sec=sys, minorversion=0, local_lock=none
# soft,retrans=N -> despues de N retransmisiones, se aborta la operación (hay una cierta
#    probabilidad de "corrupción de datos silenciosa")
# timeo=D -> esperar D decimas de segundo antes de reintentar mandar la petición (retransmisión)
# bg, retry=M -> permanecer intentando "montar" el recurso NFS en segundo plano (background)
#    durante M minutos
# intr -> permitir que señales interrumpan operaciones NFS (se ignora desde el kernel 2.6.25)
server2.xyz.com:/nas/work/dir1 /mnt/server2/dir1 nfs defaults,bg,retry=5,timeo=5,soft,retrans=5 0 0

Resolución de problemas

  • Añade la opción “-v” a mount
  • Verifica que el cliente puede ver los puertos del servidor (con nmap o similar)
  • showmount -e SERVER
  • rpcinfo -p SERVER
  • rpcdebug

Mas info: NFS Troubleshooting

Why file and directory operations are synchronous in NFS

Cuotas XFS

Una de las ventajas del sistema de ficheros XFS es que permite establecer cuotas de disco por directorio (“proyecto”, en terminología XFS). A continuación la chuleta para ello.

Montar el sistema de ficheros con las opciones de quotas (previsoramente añadiendolas a fstab):

# habilitar quotas de usuario y de proyecto
/dev/sdd3 /mnt/projects xfs defaults,uquota,pquota 0 3

Después, para cada proyecto seguir este protocolo:

# vincular el id de proyecto (100) con su directorio (p1)
echo 100:/mnt/projects/p1 >> /etc/projects
# asociar el nombre del proyecto (project1) al id
echo project1:100 >> /etc/projid
# habilitar la quota para el proyecto
xfs_quota -xc 'project -s project1' /mnt/projects
# limitar el proyecto a 20 GB
xfs_quota -xc 'limit -p bhard=20g project1' /mnt/projects
# informe de las quotas del sistema de ficheros (/mnt/projects)
xfs_quota -xc 'report -p' /mnt/projects

Obviamente, con XFS también se pueden hacer operaciones más “básicas”:

# informe de las cuotas, por usuario 
xfs_quota -xc report /mnt/projects
# definir una cuota de 500GB para "joe"
xfs_quota -xc 'limit bsoft=490g bhard=500g joe' /mnt/projects
# desactivar las cuotas para "joe"
xfs_quota -xc 'limit bsoft=0 bhard=0 joe' /mnt/projects

Prioridades procesos Linux

A veces nos puede interesar priorizar los procesos corriendo en un Linux (por ejemplo, en un servidor concurrido en el que hay tareas de diversa importancia)

Para casos puntuales, lo más fácil es (impersonándose como la superusuaria Ruth) cambiar la prioridad indirectamente con (re)nice. Indirectamente porque los comandos nice y renice alteran el valor “niceness” (“majeza” :-P) del proceso. Cuánto más alto el valor, más majo es el proceso, o sea, más cede el paso a otros (menor prioridad para él mismo). El rango de valores para la “majeza” es de -20 a a 19.

En el caso de la prioridad de los procesos, funciona al revés: cuanto menor el valor, mayor la prioridad. Típicamente, una prioridad 0 sería la más alta, y 40 la menor. La prioridad por defecto sería 20. Pero ojo, todos estos valores son un poco relativos… Lo que los comandos de usuario (como top) muestran en realidad es el “raw nice value” para el espacio de usuario (que como hemos dicho, va de 0 a 40). Los valores “reales” de prioridades van definidos en el kernel (prio.h), con lo cual son susceptibles de cambio entre distribuciones y/o versiones del kernel (lo que a su vez podría afectar potencialmente a los rangos mencionados).

Para profundizar en estas sutilezas: “Understanding the Linux Kernel”, en O’Reilly; el paper “Understanding the Linux 2.6.8.1 CPU Scheduler” de Josh Aas (aunque ya tiene 10 años, sirve para los conceptos fundamentales); y/o el artículo “Understanding Linux Processes”

Si queremos hacer ajustes por defecto, podemos recurrir a PAM:

1) asegurarse de que se está cargando el módulo pam_limits para la aplicación que nos interesa. Por ejemplo, en /etc/pam.d/login debería haber una línea (sin comentar) tipo…

session    required   pam_limits.so

2) especificar la prioridad por defecto en /etc/security/limits.conf:

usuario_enchufado      -       priority        -10

En el ejemplo, se establece la niceness por defecto a -10, con lo que la prioridad por defecto para usuario_enchufado es 10 (más alta)

Podemos comprobar si los cambios han surgido efecto con ps o alguna variante de top, columna “priority”

Variables de entorno en bash

TLDR: coloca las definiciones en bashrc (/etc/bash.bashrc, o ~/.bashrc), antes del (posible) chequeo de interactividad.

Bash distingue entre entorno de inicio (login) y entorno interactivo.

En la mayoría de los casos, el entorno es interactivo (obviamente, ya que introducimos comandos “a mano”). Si ejecutamos un script bash (directamente, tipo “./mi_script”, o explicitando bash, tipo “bash -c mi_script” o “bash mi_script”), obviamente se considera “no interactivo”. Tampoco se considera interactivo la ejecución a través de la entrada estándar (por ejemplo, “echo ls | bash”). Si llamamos explícitamente a bash (por ejemplo, dentro de un terminal ejecutamos “bash”), sí se considera interactivo. En este sentido, a la primera ejecución de bash se le asigna el nivel 1 (“top level“), y a las sucesivas “sub-ejecuciones” se les va incrementando el nivel (con lo que dejan de ser “top-level”)

El entorno “de inicio” sólo lo “experimentamos” cuando entramos en el sistema (en local) o abrimos un terminal (en local). Estas sesiones se pueden identificar por el guión prefijo (“-bash” en lugar de “bash”).

Cuando ejecutamos un comando “directamente” (con su o ssh), el entorno no es ni de inicio ni interactivo.

Todo este trabalenguas tiene su importancia a la hora de definir variables de entorno, como PATH o LD_LIBRARY_PATH. Normalmente, se pueden colocar indistintamente en profile o en bashrc. Pero si necesitamos dichas variables en un entorno “no de inicio”, profile ya no nos vale.

Nos queda bashrc, que se procesa siempre… siempre que no haya una comprobación tipo…


# If not running interactively, don't do anything
[ -z "$PS1" ] && return

en cuyo caso, se ignora todo el código después del return.

Con lo cual, lo más seguro es lo que dice el TLDR (ver comienzo del artículo 😉 )

La única pega de este modelo es que, si vamos abriendo un entorno dentro de otro (ejecutando bash dentro de otro bash), las definiciones se pueden ir acumulando.

Un primer paso a la hora de resolver problemas de carga de configuraciones es mirar que tipo de shell se está usando en el caso de interés.

Si por ejemplo se necesita que ciertas variables estén disponibles al acceder por ssh, comprobamos el tipo de shell que se usa al entrar con ssh. Si empieza con “-“, ya sabemos que es una “shell de inicio”, con lo cual es mejor definir las variables en ficheros como ~/.profile.

Otra forma de averiguar el tipo de shell es imprimiendo la variable $- (opciones activas en la shell). Si la shell es interactiva, una de las letras sera la “i”.

La versión larga (resumida :-p )

En el fichero de código shell.c está la función run_startup_files() que es la que gestiona la carga de ficherros de configuración.

Resumiendo:

  • los intérpretes “no interactivos” no cargan ficheros de definiciones. Excepto que no esté en “modo POSIX” y no se llame “sh”, en cuyo caso se carga el fichero indicado en la variable BASH_ENV
  • cuando el intérprete es interactivo, o bash es llamado por su, la carga varía dependiendo de si es “login shell”. Cuando funciona como “login shell”, se cargan /etc/profile, ~/.profile, ~/.bash_profile y ~/.bash_login. En otro caso, se cargan /etc/bash.bashrc y ~/.bashrc
  • si bash funciona como una “top-level shell” ejecutada vía SSH, se carga ~/.bashrc.
  • si bash no está en “modo POSIX” y se llama con la opción –login (-l), se cargan las definiciones en /etc/profile, ~/.profile, ~/.bash_profile y ~/.bash_login
  • si bash está en “modo POSIX”, carga el fichero indicado en ENV

Visto de otro modo, ~/.bashrc se carga en los siguientes casos:

  1. top-level shell remota (ssh)
  2. shell interactiva no-de-inicio (su, ssh)

Curiosamente, en Debian 7.9 y posteriores (quizá antes de 7.9) no parece que se cargue automáticamente ~/.bashrc, con lo cual hay que cargarlo a mano desde ~/.profile

En builtins/evalfile.c:_evalfile encontramos las características que debe cumplir la ruta a cargar: que no sea un directorio a cargar (obvio), si no un fichero “normal” (“regular file”), que no sea demasiado grande, que no sea “binario” (por ejemplo, el fichero contiene más de 256 caracteres nulos), y que sea “correcto” sintacticamente.

Cambiar tamaño sistema de ficheros

Probablemente la forma más fácil sea utilizando la herramienta gparted.

Alternativamente, también se puede hacer paso a paso.

Primero hay que ajustar el tamaño del sistema de ficheros en sí:

e2fsck -f RUTA_DISPOSITIVO
resize2fs RUTA_DISPOSITIVO NUEVO_TAMAÑO

Después hay que hacer lo mismo con el soporte del sistema de ficheros. Si es una partición MBR:

  1. Anotar parámetros de la partición (fdisk -l)
  2. Borrar partición
  3. Crearla con el mismo sector de inicio, pero con el nuevo tamaño

Si es un volumen, basta con ejecutar

lvresize -L NUEVO_TAMAÑO RUTA_DISPOSITIVO

Interfaces de red

El kernel de Linux permite agregar varios enlaces de red (módulo bonding). Esta idea ya venía recogida en los estándares IEEE (802.3ad, 802.1AX), que incluyen el protocolo LACP (Link Aggregation Control Protocol).

El módulo soporta varios modos:

  • Switch único: balance-rr (Round Robin), 8023ad (LACP)
  • Multiswitch: active-backup, , balance-tlb y balance-alb (equilibrio de carga)

Generalmente, los modos balance-rr y lacp necesitan ser configurados también en el propio switch.

Configuración bonding en Red Hat / CentOS

El estándar IEEE 802.1q define las VLAN (redes de área local virtuales).

El soporte VLAN en Linux lo gestiona el módulo 8021q

Configuración VLAN en interfaz de red (Red Hat / CentOS), Configuración VLAN en bonding para Red Hat / CentOS