¿Qué es el sistema de notificación y por qué es útil?
Muchos equipos utilizan el sistema para la gestión del proceso de aplicación. Cuando systemd inicia un proceso, necesita saber si el proceso se inició con éxito. La forma más sencilla de hacerlo es que systemd espere un poco después de iniciar el proceso (tal vez 10 segundos) y simplemente compruebe que el proceso sigue en marcha. Dado que generalmente no se puede predecir exactamente cuánto tiempo tomará el servicio para iniciarse con éxito, esto es propenso a errores. Además, este método es lento. Si está haciendo reinicios sincrónicos para un despliegue rodante, esto añade mucho tiempo.
Hay una mejor manera con el sistema de notificación. Lo que sucede es que tu proceso utiliza IPC (comunicación entre procesos) para decirle a systemd cuando está funcionando. Systemd provee una biblioteca C para lograr esto.
Mi equipo está ejecutando servicios de nodejs, así que necesitamos usar el sistema de notificación de un proceso de nodejs. Tenemos algunas opciones para lograr esto, que yo evaluaré.
Soluciones existentes
paquete sd-notify npm
TL;DR – Esta es probablemente tu mejor opción. Esto provee de enlaces a la biblioteca C de notificación del sistema. Con este enfoque, puedes notificar a systemd cuando tu aplicación se inicie y puedes proporcionar un latido para que systemd sepa si tu aplicación deja de responder sin colapsar (en ese caso, puede reciclar tu aplicación).
Desventaja: Dependencia del node-gyp (menor)
node-gyp se utiliza para construir addons de C++ en la plataforma del objetivo. Esto es un poco lento comparado con la instalación de módulos JS puros. Además, node-gyp requiere un conjunto específico de herramientas de construcción disponibles que pueden ser un poco difíciles de configurar, especialmente en Windows. Sin embargo, no es tan malo y es probable que termines con algo más que depende de node-gyp de todos modos, por lo que es probablemente un costo hundido.
Si te interesan las opciones que no requieren la dependencia del nodo-gimnasio, sigue leyendo.
Descarguen a binario de notificación sistémica
systemd proporciona el binario de notificación sistémica al que se puede acceder. Mi equipo se encontró con un error de condición de carrera con él.
Hasta que eso se arregle, yo recomendaría que no se haga así.
Actualización 28 de octubre de 2019: Parece que hay formas de evitar esta condición de raza. Esto significa que puedes usar un paquete como node-systemd-notify para evitar una dependencia de node-gyp. Sin embargo, la historia del addon para node también ha mejorado mucho desde que se escribió este post, así que supongo que ahora tienes varias buenas opciones. 🙂
Inconveniente: el error de la condición de la carrera (mayor)
Los detalles de la condición de la carrera son los siguientes.
- Su proceso crea un proceso hijo para ejecutar el sistema de notificación
- El proceso de notificación del sistema envía un mensaje a través del IPC a systemd
- El proceso de notificación del sistema sale
- systemd (o sd-bus?) intenta obtener el PID del proceso que envió el mensaje. Falla porque el proceso ya ha salido.
La condición de la raza está entre los pasos 3 y 4. A veces el paso 4 se completa antes del paso 3, en cuyo caso todo funciona bien, pero no es determinante.
Para resolver esto, el sistema de notificación sólo necesita dormir un poco para darle al SDB la oportunidad de contraer la EPI. Es posible que este problema se haya solucionado desde la última vez que lo investigamos.
Shell out to python
Python tiene un módulo que llama a systemd-notify para que puedas escribir un poco de python un liner y desgranarlo en lugar de systemd-notify. La razón por la que esto ayuda es porque puedes añadir un pequeño sueño a tu script python para resolver la condición de la carrera.
exec($0027python -c "importar systemd.daemon, tiempo; systemd.daemon.notify($0027READY=1$0027); time.sleep(15)"$0027)
Desventaja: Dependencia de la pitón y del módulo de pitón sistematizado. Dormir durante un período arbitrario se siente un poco subóptimo. Pero realmente esta opción funciona bien.
¿Hay una mejor manera?
TL;DR
Esperábamos crear una solución de nodejs pura que no tuviera una dependencia de node-gyp, pero nos pareció imposible debido a la falta de apoyo de los nodejs al modo unix_dgram en su módulo de dgrama.
Detalles
systemd-notify utiliza el D-Bus para hablar con systemd. Bajo el capó, eso es sólo enviar datos al zócalo unix AF_UNIX situado en /run/systemd/notify. Me preguntaba cuán complicado era ese mensaje, y si era lo suficientemente simple, podría programar los nodejs para enviar los datos en bruto directamente a ese socket, evitando así la dependencia node-gyp.
El primer paso fue determinar cómo se veían los mensajes del D-Bus y qué sistema de datos estaba enviando. En lugar de tratar de entender el protocolo del D-Bus y escarbar en el código del sistema, decidimos llamar a systemd-notify y olfatear el enchufe para ver qué datos se enviaban.
Para ello, intentamos un enfoque de hombre en medio como el siguiente:
sudo mv /ruta/ a/medias /ruta/ a/medias.originalsudo socat -t100 -x -v UNIX-LISTEN:/ruta/ a/medias,mode=777,reuseaddr,fork UNIX-CONNECT:/ruta/ a/medias.original
No sé por qué exactamente, pero esto no funcionó. Mover el enchufe de esta manera causó que el sistema de notificación fallara. Así que nos dispusimos a encontrar otra forma de olfatear los datos. Creamos un script pitón que simplemente duerme durante 10 segundos (así que tuvimos tiempo de tomar su PID) y luego llamamos a systemd-notify.
Antes de que se acabaran los 10 segundos, tomamos el PID del archivo y empezamos una pista como esta: pista -ff -o log -s 20000 [pid]
Haciendo esto obtuvimos la siguiente salida:
socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0) = 3getsockopt(3, SOL_SOCKET, SO_SNDBUF, [212992], [4]) = 0setsockopt(3, SOL_SOCKET, SO_SNDBUFFORCE, [8388608], 4) = 0sendmsg(3, {msg_name={sa_family=AF_UNIX, sun_path="/run/systemd/notify"}, msg_namelen=21, msg_iov=[{iov_base="READY=1", iov_len=7}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 7
Aquí podemos ver que el enchufe se abre y envía el mensaje «Listo=1». ¡Eso es todo! Nos sentíamos esperanzados. Todo lo que teníamos que hacer era averiguar cómo abrir un socket de dominio unix y enviarle el texto «READY=1».
Nuestro primer intento fue usando net.connect. Es capaz de conectarse a enchufes de dominio unix. El código es algo así:
constnet=require($0027net$0027)constclient=net.connect($0027/tmp/echo.sock$0027,()= >{client.write($0027i wrote something$0027)})
Hacer esto falló con el EPROTOCOLERR. Para darle un poco más de color a esto, decidimos usar el guión de los nodejs y compararlo con el de la pitón. Haciendo esto, encontramos que python abría el socket con socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0) = 3 y el nodo lo abría con socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 10.
Así que el nodo estaba usando un modo de flujo de algún tipo y pitón estaba usando un modo de datagrama.
Después de escarbar un poco, encontramos que la manera apropiada de hacer esto en los nodejs es usar el módulo de dgrama en lugar del módulo de red. Es similar, pero soporta protocolos de datagramas como udp4, udp6 y unix_dgram. Bueno, los nodejs solían soportar unix_dgram, pero ya no.
Si revisa los documentos del nodo actual, verá que los documentos sólo mencionan udp4 y udp6. Resulta que el soporte para unix_dgram fue eliminado en la versión 0.6 en 2011. Puedes ver los detalles en este hilo. También pude encontrar la opción unix_dgram documentada en una vieja rama de nodejs.
Sin el apoyo del protocolo unix_dgram, sentimos que esto era un callejón sin salida.
Conclusión
Si unix_dgram estuviera todavía soportado en nodo, creo que sería fácil implementar el IPC para decirle a systemd cuando nuestro proceso está arriba sin una dependencia de node-gyp, pero como no lo está, creo que su mejor opción es quedarse con el paquete sd-notify npm.
Gracias
Gracias a Theo Cowan y David Thai por hacer esta investigación conmigo.
Categorías: technicalTags: devops, deep dive, javascript