Mes: noviembre 2013

Replicación, recuperación en linea y alta disponibilidad con pgpool2 (3.1.1) Parte II

A continuación os voy a exponer algunas cosas que he aprendido con pgpool en producción. Cosas que en una maqueta no ocurren, pero al utilizar la herramienta en el mundo real… peta y te cagas en su puta madre. Básicamente.


Pool de conexiones y conexiones persistentes


Si os leéis bien la documentación, no como yo, el creador de pgpool menciona que no se deben de utilizar conexiones persistentes a la base de datos porque se llena el pool de conexiones y os lo tira abajo, como un ataque de denegación pero vosotros solitos, sin ayuda de “juaquers” ni nada.

En mi caso existían unas conexiones idle persistentes, no muchas, pero las había. Y por la naturaleza de lo que hay montado, no se pueden cambiar. Deben de ser persistentes. ¿Qué hacemos si nos ocurre esto y no podemos utilizar conexiones no persistentes?, afortunadamente el creador de pgpool pensó en ello y disponemos de varios parámetros de la configuración que podemos modificar para que funcione nuestro pool:

#——————————————————————————
# POOLS
#——————————————————————————

# – Pool size –

num_init_children = 128
                                   # Number of pools
                                   # (change requires restart)
max_pool = 4
                                   # Number of connections per pool
                                   # (change requires restart)

# – Life time –

child_life_time = 300
                                   # Pool exits after being idle for this many seconds
child_max_connections = 0
                                   # Pool exits after receiving that many connections
                                   # 0 means no exit
connection_life_time = 300
                                   # Connection to backend closes after being idle for this many seconds
                                   # 0 means no close
client_idle_limit = 450
                                   # Client is disconnected after being idle for that many seconds
                                   # (even inside an explicit transactions!)
                                   # 0 means no disconnection
En la sección de “pool size” tenemos dos valores claros. El número de pools y las conexiones máximas por pool. Estos valores dependerán de vuestra instalación, en mi caso con esos dos valores es más que suficiente, de hecho con 64 hijos sería suficiente, pero me reservo el doble porque puedo (está montado en un pepino de servidor) y porque en epocas de trafico intenso no vendrá mal ir sobrado. Esto os dará margen de maniobra, tener el pool bastante libre, para jugar con los timeouts que están en la sección “Life time”.
Por defecto cada pool tiene un tiempo de vida de 300 segundos. Bien, este parámetro está bien así. El segundo parámetro tampoco es necesario tocarlo, pero tampoco pasa nada si se establece un límite de conexiones (dependerá de para que lo queráis). Ahora, lo importante. El tiempo de las conexiones están abiertas después de entrar en idle. Lo ideal sería que pusieseis aquí un valor elevado y monitorizaseis vuestra base de datos en producción. En mi caso he utilizado una gráfica de munin para saber la media de tiempo que están mis conexiones en idle y he ajustado este valor en función de esto mismo. Mi media estaba en torno a 230 segundos, pero me he dejado margen para no cortar nada vivo. Por otro lado está el parámetro “client_idle_limit”, para las persistentes. Lo mismo, averiguad a partir del punto en el cual vuestra persistente se vuelve idle y poned el valor con cierto coeficiente de seguridad extra. Esto cortará esas persistentes después de estar idle durante 450 segundos. Podréis elevar este valor más en función del número de pools que hayáis configurado en la sección “pool size”. Si vais muy justos de conexiones, no podréis elevar mucho este valor porque entonces la frecuencia de apertura de conexiones será mayor que la frecuencia de cierre de las idles y se llenará. Cuidado con esto.

Comandos PCP de recuperación de un nodo caido en producción.

En la maqueta todo funciona fetén, ¿verdad?, probamos cuarenta veces la simulación de un nodo caído y la recuperación en línea. Y todo marchaba viento en popa a toda vela. Lo ponemos en producción y ocurre, un nodo se sale del pool. Genial, vamos a probar la recuperación en linea en producción. Te excitas, se te pone el vello de punta, los pezones endurecen, vas a recuperar un nodo en linea sin downtime. Te dispones a ello, escribes el comando tecleando como si estuvieses tocando en un piano la pieza más difícil de Rachmaninov ante un público exigente y en el mejor teatro del mundo (respirad). Respiras unos segundos y relees el comando, está correcto, el tiempo se para y… le das a intro esperando los aplausos y la gloria pero…
Una mierda. Te comes una mierda. Y bien gorda.
No solo el nodo no recupera, no… además te causa un downtime en producción. Los lobos se te echan encima y, menos mal Diocito Santo, existe un timeout para los comandos pcp que avispadamente e inadvertidamente habías puesto a 90 segundos y, solamente en ese tiempo, vuelve todo a funcionar.
Menos mal. Respiras. Los usuarios vuelven a lo suyo después de amenazarte con ir a tu casa a violar a tu mujer y matar a tus hijos mientras miras. Y tu no entiendes que coño pasa. Si en pruebas todo iba genial. Hiciste millones de pruebas: apagando la máquina, reiniciando el servicio, parando el servicio e incluso apagando de botón. Y siempre, siempre, recuperaba el nodo; ¡y rapidisimo!
Y te pasas una semana no entiendo nada, intentando diferentes cosas hasta que; una vez más, te lees bien la documentación y descubres que el pool no recupera un nodo si hay conexiones idle abiertas. Cojones. Otra vez topamos con las peuteas idles.
Deduciréis, amigos, que a pgpool no le gustan las persistentes. Pero es que a nadie le gustan las persistentes y no deberían de usarse. Pero se usan a veces. Aunque sea esa sesión abierta en un screen para hacer pruebas de vez en cuando y consultas a pelo. Eso es una persistente idle, amigos. Y vuestros devops seguro que tienen más de un screen de esos abierto desde 2011 y ni se acuerdan.
Otra vez más, amigos, parametritos pensados para salvar esta inconveniencia:
#——————————————————————————
# ONLINE RECOVERY
#——————————————————————————

recovery_user = ‘postgres’
                                   # Online recovery user
recovery_password = ‘postgres’
                                   # Online recovery password
recovery_1st_stage_command = ‘base-backup’
                                   # Executes a command in first stage
recovery_2nd_stage_command = ‘pgpool-recovery-pitr’
                                   # Executes a command in second stage
recovery_timeout = 500
                                   # Timeout in seconds to wait for the
                                   # recovering node’s postmaster to start up
                                   # 0 means no wait
client_idle_limit_in_recovery = 10
                                   # Client is disconnected after being idle
                                   # for that many seconds in the second stage
                                   # of online recovery
                                   # 0 means no disconnection
                                   # -1 means immediate disconnection
En la sección de “Online recovery” encontramos nuestros famosos comandos de primera y segunda fase que ya comentamos en su momento (ver el archivo de este blog si no sabes de que mierda hablo). Y luego dos maravillosos parámetros, recovery_timeout que podeis poner el tiempo máximo que asumis que vais a esperar a que vuestro nodo recupere y será exactamente el mismo tiempo que vuestra base de datos no acepte conexiones.
Sí amigos, pgpool se vende como algo maravilloso con lo que no sufriréis más downtime pero es mentira. Como todo en esta vida, siempre hay letra pequeña. Esto es falso pero puede parecer verdadero, como las tetas operadas. Supongamos que tenéis una WEB que se conecta a un postgresql. Bien, vuestra WEB es normalita y no tenéis muchas conexiones simultaneas ni nada. Lanzáis una recuperación de nodo y no notáis ese downtime. ¿Por qué?, porque pgpool va a encolar las peticiones hasta que el nodo recupere, son muy pocas y recupera en un segundo. Por tanto, a lo sumo, notáis un pequeño retraso en la consulta. Retraso que puede ocurrir por otros motivos como que vuestra conexión sea lenta o cualquier otra situación cotidiana utilizando Internet. Y pensáis, pgpool es la hostia. Pero, ¿qué pasa cuando tenéis una WEB grande con mucho trafico?. Pues ocurre que pgpool para las peticiones hasta que el nodo recupera (es normal si queréis tener consistencia en los datos, que de eso va esta movida), como tenías chorrocientas idles esperando, pcp no recupera hasta que esas idles no terminan. Aquí entra en juego el parámetro “client_idle_limit_in_recovery” que cortará todas las idles en un máximo de 10 segundos (en mi caso, que si sois valientes deberíais de poner vuestro tiempo idle medio). Pero ojo. Hay persistentes en transacción que puede que no sean cortadas o que puede que sean cortadas pero originen errores. Lo más recomendable es parar vuestra WEB (y aquí está la mentira cazada, hay downtime), cortar a mano las idles y aseguraros de que no tenéis a nadie pidiendo nada. Pero bueno, todo dependerá de vuestro caso concreto y de lo osados que seáis.
Si no tenéis persistentes abiertas y todo va como debe y de verdad de deporte, recuperará el nodo correctamente.
Y por hoy, amigos, esto es todo. Espero que estas aclaraciones os sirvan de algo.

Ethernet bonding

¿Tienes una máquina cualquiera, servidor, pc-doméstico y/o lavadora por red con dos interfaces ethernet y solo usas una?


Hazle bonding, que está feo que coja polvo el conector de red… ¿no?

Además, es sencillo. Hasta Paquirrín podría hacerlo.

¿Qué es el bonding de ethernet?

Pues bien, grosso modo, consiste en balancear una conexión de red por ethernet en dos tarjetas de red físicas. Cada una con su MAC. Convirtiéndolas en una sola interfaz con una sola MAC que se autogestiona y redirecciona la conexión por la que más le convenga o, en el caso de que ocurra, por la que no esté rota. Nos da protección ante rotura de una de las dos tarjetas y minimizamos así las posibilidades de quedarnos sin red en un servidor. Si queréis saber más, aquí en la Wikipedia hay más.

Al turrón:

Supongamos que tenemos dos interfaces, llamadas eth0 y eth1.
1.- Instalamos ifenslave con apt (recordad que en este blog todos los ejemplos son en Debian, pero os podéis apañar en otras distros)

apt-get install ifenslave

2.- Creamos el fichero bonding.conf en /etc/modprobe.d, añadimos estas lineas en él y lo cargamos:

alias bond0 bonding
options bonding mode=active-backup miimon=100 downdelay=150 updelay=150

3.- Modificamos /etc/network/interfaces para que quede de forma similar a esta, eliminaremos la configuración de eth0 y eth1 y la sustituiremos con esta pero con vuestra configuración particular (En el ejemplo la red es 192.168.1.0):

# The loopback network interface
auto lo bond0
iface lo inet loopback
# The primary network interface
allow-hotplug eth0
allow-hotplug eth1
iface bond0 inet static
    slaves eth0 eth1
    address     192.168.1.2
    netmask     255.255.255.0
    gateway     192.168.1.1
    network     192.168.1.0
    broadcast   192.168.1.255
    bond_mode active-backup
    bond_miimon 100
    bond_downdelay 150

4.- Si no estamos delante del servidor de forma corpórea tendremos que llevar cuidado en cómo ejecutamos el paso de reiniciar la red. Lo recomendable si no estamos físicamente presentes es levantar primero la interfaz bond0 con:

ifup bond0

Y después tiramos abajo eth0 y eth1:

ifdown eth0
ifdown eth1

Pero si estamos presentes (y no es un servidor en producción en el que se está trabajando, claro), es mejor tirar abajo networking:

/etc/init.d/networking stop

Y levantarlo:

/etc/init.d/networking start

5.- Comprobaremos que todo está correcto:

ifconfig
bond0     Link encap:Ethernet  HWaddr f0:1f:af:d3:48:a8
          inet addr:192.168.1.2  Bcast:192.168.101.255  Mask:255.255.255.0
          inet6 addr: fe80::f21f:afff:fed3:48a8/64 Scope:Link
          UP BROADCAST RUNNING MASTER MULTICAST  MTU:1500  Metric:1
          RX packets:29648237 errors:0 dropped:2523856 overruns:0 frame:0
          TX packets:30004957 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:20909383227 (19.4 GiB)  TX bytes:31281472852 (29.1 GiB)
eth0      Link encap:Ethernet  HWaddr f0:1f:af:d3:48:a8
          UP BROADCAST RUNNING SLAVE MULTICAST  MTU:1500  Metric:1
          RX packets:26476124 errors:0 dropped:0 overruns:0 frame:0
          TX packets:30004957 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:20383770558 (18.9 GiB)  TX bytes:31281472852 (29.1 GiB)
          Interrupt:16
eth1      Link encap:Ethernet  HWaddr f0:1f:af:d3:48:a8
          UP BROADCAST RUNNING SLAVE MULTICAST  MTU:1500  Metric:1
          RX packets:3172113 errors:0 dropped:2471750 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:525612669 (501.2 MiB)  TX bytes:0 (0.0 B)
          Interrupt:17
lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:1606767 errors:0 dropped:0 overruns:0 frame:0
          TX packets:1606767 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:438725905 (418.4 MiB)  TX bytes:438725905 (418.4 MiB)

Fijaos que todas las interfaces tienen la misma MAC y que eth0 y eth1 rezan “UP BROADCAST RUNNING SLAVE MULTICAST”.
Con esto ya dispondríais de bonding y podéis enchufar las dos interfaces de red físicas a vuestra red.