Poesía Binaria

Crea rápidamente servicios con Systemd para iniciar demonios o ejecutar scripts


Hay quien lo odia, hay personas a quienes les gusta y también a quien no le importa. Pero es un sistema que ha entrado en las vidas de muchos de nosotros. Tanto usuarios como sysadmins. Ya que este post va a ser un post pequeño, quiero mostrar un ligero ejemplo paso a paso de la creación de un servicio utilizando systemd. El servicio será sencillo y se limitará a ejecutar un script durante el arranque y el apagado de nuestro sistema, nuestro servidor o nuestro cacharro (si hablamos de IoT).

¿Para qué podemos utilizarlo?

Systemd tiene muchísimas opciones, y esta configuración va a ser muy básica. En principio ejecutar un script de este tipo al arranque puede servir para actualizar automáticamente programas de nuestro servidor, o nuestra Raspberry. Por ejemplo yo lo suelo utilizar para:

Ejecutar scripts al inicio

Imaginemos un script sencillo como este (lo colocamos en /usr/local/bin/mystartup:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/bash

cd /var/www/mi_proyecto
git pull origin master
STATUS=$?
if (( $STATUS!=0 )); then
   logger "Error al actualizar web"
   exit
fi

npm update
STATUS=$?
if (( $STATUS!=0 )); then
   logger "Error al actualizar npm"
   exit
fi

composer update
STATUS=$?
if (( $STATUS!=0 )); then
   logger "Error al actualizar composer"
   exit
fi

curl -X POST https://axon/machines/webserver -d status=up

Con este script actualizaremos el código de nuestra web (que lo tenemos en un repositorio git), luego actualizaremos los paquetes npm y composer de la misma. Además, tengo un demonio en otro equipo que controla todas las máquinas que tengo conectadas, avisa de posibles errores, da de alta y baja servidores en balanceadores de carga, etc. Es sólo un ejemplo.

A mí también me gusta hacer un script muy sencillo que se ejecuta cuando se apaga o reinicia el ordenador (/usr/local/bin/myshutdown):

1
2
3
4
#!/bin/bash

logger "Shutting down"
curl -X POST https://axon/machines/webserver -d status=down

Con esto, le digo al servidor que controla el estado de mis máquinas que ésta va a ser apagada.

Luego crearemos un archivo en /etc/systemd/system/mystartup.service:

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=My startup scripts
After=networking.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/mystartup
ExecStop=/usr/local/bin/myshutdown
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Con este archivo:

Activar e iniciar

Si no queremos reiniciar el ordenador (cosa que no nos gusta mucho a los usuarios de GNU/Linux, podemos utilizar (podemos utilizar sudo si no nos deja ejecutar los comandos con nuestro usuario actual):

systemctl daemon-reload

Con esto, habremos recargado la configuración del demonio y systemd tiene que haber reconocido nuestro nuevo servicio. Ahora podemos hacer:
systemctl start mystartup.service

O (en modo compatibilidad):
sudo service mystartup start

Una vez iniciado nuestro servicio y viendo que funciona bien podemos hacer lo siguiente:

systemctl enable mystartup.service

Para iniciarlo siempre que iniciemos nuestro sistema. Y, si queremos consultar información sobre el estado del servicio, debemos ejecutar:
systemctl status mystartup.service
● mystartup.service -- My startup scripts
Loaded: loaded (/etc/systemd/system/mystartup.service; disabled; vendor preset: enabled)
Active: active (exited) since dom 2017-11-19 17:58:25 CET; 29min ago
Process: 22085 ExecStart=/usr/local/bin/mystartup (code=exited, status=0/SUCCESS)
Main PID: 22085 (code=exited, status=0/SUCCESS)
nov 19 17:58:25 gaspy-RedDragoN systemd[1]: Starting My startup scripts…
nov 19 17:58:25 gaspy-RedDragoN systemd[1]: Started My startup scripts.

Tipos de servicio

Systemd no está limitado a esto, en los tipos (Type=) de servicio podemos utilizar, además de oneshot:

Reiniciar automáticamente el servicio

Al contrario que cuando ejecutamos un script, como en el ejemplo. Un script que tiene un comienzo y su ejecución tiene también un final. Nuestro servicio sea un demonio, es decir, un programa que deba ejecutarse en segundo plano y mantenerse ejecutando todo el tiempo. Por ejemplo, servidores web, websockets, cgi, bases de datos, APIs, servidores de archivos, y cosas así. En ese caso, necesitamos proporcionar un servicio constante en el tiempo. Y durante el tiempo de servicio pueden ocurrir desastres que hagan que nuestro demonio deje de ejecutarse. Estos pueden ser fallos de software, otro proceso lo ha cerrado (SIGINT, SIGTERM, SIGQUIT, SIGKILL, etc), Out Of Memory Kill, etc. En ese tipo de casos podemos estar dejando de proporcionar un servicio esperando que nuestro servidor sea atendido por un administrador que reinicie el servicio.

Es cierto que como administrador nos interesaría saber que ese servicio se ha reiniciado (y de hecho podemos consultar el log del demonio o incluso enviarnos un e-mail cuando eso pase), pero la solución inmediata del problema es reiniciar el servicio. Seguro que habrá veces que reiniciar el servicio no es suficiente, pero por experiencia son las menos. Muchas veces, incluso puede ocurrir una corrupción de memoria en un programa, o un memory leak que haga que nuestro demonio consuma cada vez más memoria y nada más reiniciar vuelve a funcionar. No estoy diciendo que dejemos así nuestros programas, pero a veces nos vemos obligados a hacer chapucillas para asegurar la continuidad del servicio. Otras veces, puede que el programa llegue a un punto que no hemos controlado en su desarrollo o simplemente se ha consumido la memoria del servidor y el kernel ha cerrado el programa. Son casos en los que no podemos permitir una parada, porque estas cosas siempre pasan cuando el sysadmin duerme…

Para eso tenemos la orden Restart= en la que indicamos en qué caso se reiniciará el servicio automáticamente por systemd. Sus posibles valores son:

Casi siempre podemos utilizar:

Restart=always

Aunque con todas estas opciones podemos afinar un poco la configuración.

También es importante indicar que RestartSec= nos permite especificar el tiempo de espera antes de reiniciar. Además, puede darse el caso de un servicio que se reinicie todo el rato, por lo que podemos configurar StartLimitBurst= con el número de veces que se reiniciará el servicio y StartLimitIntervalSec= con el tiempo máximo en el que podemos reiniciar ese número de veces. Por tanto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Unit]
Description=My startup scripts
After=networking.target

[Service]
Type=simple
ExecStart=/usr/local/bin/mydaemon
Restart=always
RestartSec=10
StartLimitBurst=5
StartLimitInterval=600

[Install]
WantedBy=multi-user.target

En este caso, si el servicio, durante 10 minutos (600 segundos), sólo se podrá reiniciar 5 veces (esperando 10 segundos antes de reiniciarse cada vez). Y si el servicio en esos 10 minutos no termina de arrancar, systemd desistirá y no lo intentará más hasta dentro de 10 minutos.

Por poder, podemos poner un script que cada diez minutos se reinicie dos veces y se pare. Como no es un demonio y va a finalizar siempre estaremos siempre reiniciándolo. Aunque para eso podemos utilizar por ejemplo cron, o hacer que nuestro script tenga esperas (sleep) con el fin de no recargar constantemente el programa, ya que todo esto tiene un coste para el sistema operativo.

Tareas previas y posteriores

Podemos, además, ejecutar programas antes y después de iniciar servicio con ExecStartPre=, ExecStartPost= que podemos poner todas las veces que queramos para ejecutar todos los programas o scripts que sean necesarios antes de iniciar el servicio y una vez éste esté iniciado.

También disponemos de Use ExecStopPost= para ejecutar comandos una vez se haya detenido el servicio.

Más información

Lo mejor para completar la información es ver el manual. Aquí vemos dos páginas que nos ayudarán:

Foto principal: Denys Nevozhai

También podría interesarte....