Cómo escribir archivos de servicio Systemd
Escribir el servicio Systemd
Estructura básica
El contenido del servicio Systemd se divide principalmente en tres partes: definición de unidad, definición de servicio y parte de instalación.
Diferencias con el script de inicio de SysV
En el pasado, los servicios *nix (asistentes de demonios) se iniciaban usando el script de inicio de SysV, que es un script Bash generalmente ubicado en /etc/ En el directorio init.d, se puede llamar con parámetros estándar como iniciar, detener y reiniciar. Iniciar este script generalmente significa iniciar un demonio en segundo plano. Las desventajas comunes de los scripts de shell son la lentitud, la mala legibilidad, la verbosidad excesiva y la arrogancia. Si bien son flexibles (después de todo, son código), hay algunas cosas que son difíciles de hacer solo con scripts, como programar ejecuciones paralelas, monitorear procesos adecuadamente o configurar entornos de ejecución detallados.
El script de inicio de SysV también sufre de hinchazón y duplicación de código. Porque los "parámetros estándar" mencionados anteriormente deben implementarse mediante un solo script, y la implementación de cada script es similar (básicamente a partir de un esqueleto). Systemd implementa estos parámetros de manera uniforme, lo que significa que estos parámetros no son necesarios ni visibles en el servicio Systemd. Esto hace que los servicios de Systemd sean muy concisos y fáciles de leer. Por ejemplo, el servicio del pesado programa NetworkManager tiene sólo 19 líneas incluyendo comentarios. Y las primeras 100 líneas del script de inicio SysV correspondiente ni siquiera implementan parámetros estándar.
Systemd es compatible con los scripts de inicio de Sysv, por lo que siempre necesitamos el paquete systemd-sysvinit. Sin embargo, por estos motivos, es mejor utilizar el servicio Systemd nativo para iniciar todas las instalaciones de demonios. Además, los servicios Systemd son perfectamente compatibles con todas las distribuciones que utilizan Systemd, lo que significa que los scripts escritos en Arch seguirán funcionando.
Normalmente, los upstreams publicarán los servicios Systemd con el código fuente, pero si el upstream no hace esto, puedes seguir este tutorial para escribirlos y contribuir.
Sintaxis de Systemd
La sintaxis de Systemd es similar a la sintaxis de los archivos .desktop y .ini en Windows, por lo que es muy fácil de usar tanto para los empaquetadores como para los usuarios finales.
El formato principal se muestra en el pequeño ejemplo siguiente, pero hay tres advertencias:
El contenido después de la línea que comienza con "#" en el archivo de unidad Systemd se considera un comentario.
p>
Valores booleanos en Systemd, 1, sí, activado, verdadero significa abierto, 0, no, apagado significa cerrado, etc. Nota:
Solo para archivos Systemd, por ejemplo:
RemainOnExit=yes
No se aplica a declaraciones de shell incrustadas en el archivo, por ejemplo: p>
ExecStartPre=/usr/bin/test "x${NETWORKMANAGER}" = xyes
El sí aquí no se puede reemplazar. Esto se debe a que el signo igual va seguido de una declaración de shell incrustada.
La unidad de tiempo predeterminada de Systemd es segundos, así que especifique milisegundos (ms), minutos (m), etc. explícitamente.
Un pequeño ejemplo
Servicio Systemd de NetworkManager:
[Unidad]
Descripción=Network Manager
After=syslog.target
Wants=remote-fs.target network.target
[Servicio]
Tipo=dbus
Nombre del bus =org.freedesktop.NetworkManager
ExecStart= /usr/sbin/NetworkManager --no-daemon
EnvironmentFile=/etc/sysconfig/network/config
ExecStartPre=/usr/bin/test "x${ NETWORKMANAGER}" = xyes
# Suprime stderr para eliminar información duplicada en el registro del sistema. Cuando se ejecuta en el registro del sistema, NM llamará a openlog() con LOG_PERROR
# pero systemd redirigirá stderr a syslog de forma predeterminada, lo que hará que cada mensaje se registre dos veces.
StandardError=null
StandardError=null
[Instalar el nuevo syslog es una buena idea p>[Instalación]
WantedBy. =multi-user.target
También=NetworkManager-wait-online.service
El siguiente es un ejemplo de cómo escribir un archivo unitario He.net IPv6.
Definir unidad de control [unidad]
En Systemd, todo lo que Systemd necesita controlar durante el inicio es una unidad. Los tipos de unidades Systemd son
Servicio del sistema
Socket
Dispositivo
Punto de montaje
Puntos de montaje automático
Archivos SWAP
Particiones
Destinos de inicio
Rutas del sistema de archivos
Temporizadores
En pocas palabras En resumen, Systemd cambia el nombre de las cosas que están esparcidas por todo *nix para que, en el gran esquema de las cosas, se desorganicen. El nombre de la unidad es el nombre del archivo .service que está escribiendo. Pero no sólo los archivos con el sufijo .service pueden ser una unidad; las unidades también pueden tener .target, .path y otros sufijos, como puede ver en /usr/lib/systemd/system. Pero estos sufijos son escritos por desarrolladores de Systemd y publicados con el paquete systemd, o agregados por nuestro equipo Base:system, por lo que el usuario promedio en realidad no necesita escribir unidades de control con otros sufijos.
Primero explicamos que estamos definiendo una unidad de control:
[Unidad]
No se requiere ningún nombre de unidad, sólo una descripción:
[ Unidad]
Descripción=Iniciar el proceso demonio de He.net IPv6
A continuación explicaremos cómo Systemd controla el sistema. Esto es muy similar a la sintaxis de control de dependencia del archivo de especificaciones de RPM (después de todo, todos son productos de Red Hat):
Requisitos: si la unidad está iniciada, inicie la unidad que "requiere"; requiere" la unidad; detiene la unidad que "requiere". Si una "unidad" se detiene, no sobrevivirá. Sin embargo, tenga en cuenta que esta configuración no controla el orden de inicio de la unidad y sus unidades "requeridas" (su orden de inicio se controla por separado), es decir, Systemd no lo hará. Inicie las unidades "requeridas" antes de iniciar la unidad, pero inícielas en paralelo cuando la unidad comience. Esto crea una carrera contra el tiempo: si Requires comienza primero, todos están contentos; si Requires comienza lentamente, la unidad falla (Systemd no vuelve a intentarlo). automáticamente). No informará un error incluso si el servicio no se inicia. Es más tolerante a fallas que Requires, pero debe asegurarse de que su servicio tenga una función de espera. Además, si el usuario no inicia el servicio. pero se inicia con el sistema al inicio, seguirá teniendo el mismo problema que Requires.
Requisito: una versión de fuerza bruta de Requires si el servicio requerido aquí no se inicia correctamente, fallará inmediatamente independientemente de si la unidad. El archivo puede detectar la espera.
Wants: Se recomienda usarlo cuando se inicia la unidad, pero no si lo hace. No tiene ningún efecto en la unidad.
Conflicto: el inicio de una unidad se detendrá. la unidad con la que "entra en conflicto", y viceversa. Tenga en cuenta que el orden de inicio aquí y después es "ortogonal": p>
Dos unidades en conflicto comienzan al mismo tiempo, o no pueden comenzar (ambas son requisitos de). la tercera unidad), o una de ellas puede iniciar (una es un requisito de la tercera unidad, la otra no), y no La unidad solicitada se detiene Si ambas unidades no son necesarias, la unidad que entra en conflicto con la otra unidad. se inicia primero y la unidad en conflicto se inicia después
OnFailure ): Es obvio qué unidad se iniciará como compromiso si esta unidad falla
Bien, ahora imaginemos cuál es nuestro. unidad (túnel Ipv6). Una red conectada. Systemd proporciona un objetivo predeterminado llamado network-online.target (se proporciona una lista de objetivos predeterminados en systemd.special, asegúrese de verificarlo, porque la mayoría de las veces desea un estado fijo del sistema y no es objeto de ningún otro servicio systemd), puede proporcionar muy bien el entorno que necesitamos:
[Unidad]
Descripción=Iniciar He.net Demonio IPv6. Process
Wants=network-online.target
Necesitamos definir la secuencia de inicio de los servicios, de lo contrario ni siquiera podremos comenzar a trabajar sin cargar el disco duro donde está / se encuentra el directorio.
La secuencia de inicio del servicio de Systemd está definida por las siguientes dos etiquetas:
Antes/Después (antes/después): si un servicio se inicia antes que otro, entonces en el proceso de inicio paralelo (Systemd siempre comienza con el Proceso 0 inicia todos los servicios en paralelo y luego usa estas dos etiquetas para esperar dos clasificaciones), un servicio se iniciará antes que el otro. Luego, otro servicio espera el servicio que se inició primero y devuelve el estado. Tenga en cuenta que se inició primero y no tuvo éxito, porque el error también es un estado y debe tener éxito antes de que se pueda iniciar otro servicio definido por la dependencia. Lo mismo ocurre con Después.
El siguiente es el orden de cierre del servicio: si ambos servicios se cierran, Antes los cerrará y Después los cerrará. Esto es fácil de entender, pero si un servicio se cierra y. el otro servicio se activará, luego se desactivará lo que sea Antes, se desactivará Después, se activará Después, luego lo que diga Antes/Después, se desactivará primero, no se activará. Es decir, por ejemplo, si el servicio A precede al servicio B, pero el servicio B se cierra y el servicio A se reinicia, entonces el servicio B precede al servicio A en el orden.
Entonces, ¿antes o después de qué se debe poner en marcha nuestra unidad? No es necesario ejecutarlo antes de ningún servicio, no es como ifup o dhcp, donde no se puede activar la red para obtener una IP y es inútil. Todo lo que necesitamos es una red.
Definir la ontología del servicio [servicio]
Después de definir la unidad utilizada por Systemd para identificar los servicios, definimos la ontología del servicio y aún declaramos:
[Servicio]
Luego declare el tipo de servicio:
[Servicio]
Type=
Los tipos de servicio admitidos por Systemd son los siguientes:
simple Predeterminado, este es el tipo de servicio más simple. Esto significa que el programa que inicia es el programa principal y, si el programa sale, termina. Esto se entiende bien en la GUI, abrí Amarok, lo salí y desapareció. Pero la mayoría de los programas de línea de comandos no están diseñados de esta manera, porque uno de los principios más básicos de la línea de comandos es que un buen programa no puede monopolizar la ventana de la línea de comandos. Por lo tanto, después de ingresar el comando, el programa volverá inmediatamente al mensaje después de completar la entrada, pero el programa ya completó la ejecución. Por lo tanto, sólo unos pocos programas como python xxx.py todavía utilizan este método. En este tipo, si su programa principal va a responder a otros programas, entonces su canal de comunicación debe configurarse antes de iniciar este servicio (sockets, etc.), por lo que para este tipo de servicio, Systemd lo ejecutará inmediatamente después de eso. ejecute el siguiente servicio (el servicio que lo necesita), y luego el servicio fallará si no hay un socket. Escribir después es inútil, porque el tipo simple no tiene el proceso principal para salir. Después es inútil porque con tipos simples no es necesario que el proceso principal salga o regrese al estado, por lo que una vez iniciado se considerará exitoso a menos que no se inicie.
La bifurcación es el método estándar de inicio del demonio de Unix. Después de iniciar el programa, se llama a la función fork(), se configuran los canales de comunicación necesarios y luego el proceso padre sale, dejando atrás el proceso hijo demonio. Si utiliza este enfoque, es mejor especificar también PIDFILE= y no dejar que Systemd adivine, pero si es necesario, configure GuessMainPID en yes.
La forma de saber si es un fork o uno simple es muy sencilla: ejecuta tu programa en la línea de comando, y si tienes que presionar Ctrl C para seguir ocupando la línea, entonces no lo haces. tener un tenedor.
Crear un PIDFILE es tarea del programa que escribe el servicio por usted, no una característica de Systemd o incluso de los scripts Sysvinit. Para obtener más información, consulte los problemas de startproc con la creación de archivos pid. Por lo tanto, si su programa es realmente del tipo fork pero no implementa la función de crear PIDFILE, se recomienda usar ExecStartPost= en combinación con comandos de shell para obtener manualmente el número de proceso y escribirlo en /var/run/xxx.pid. .
Oneshot, como su nombre indica, es disparar en la oscuridad. Por lo tanto, este tipo de servicio simplemente comienza, finaliza y no tiene más procesos. Una situación común es que si configura la red y ifup eth0 up es un evento único, no habrá ningún subproceso ifup (bifurcación o similar) ni ningún proceso principal (simple o similar). trazar una vez completado. La última configuración significa que incluso si no hay procesos, queremos que Systemd piense que el servicio existe y se ejecuta correctamente. Entonces, si tiene un servicio como este, después de que se inicia el servicio, presiona ifup eth0 y luego mira el servicio, todavía está ejecutándose. Porque mientras no haya errores al ejecutar ese comando único, siempre lo considerará exitoso y se ejecutará hasta que cierre el servicio.
dbus es un programa que necesita obtener espacio DBus al inicio, por lo que debe usarse con BusName=. Sólo después de que adquiera con éxito espacio DBus podrán iniciarse los programas que dependen de él.
Los cuatro tipos anteriores son los únicos tipos que se pueden utilizar, y hay dos tipos raros:
notificar Después de iniciar el programa, enviará un mensaje de notificación a través de sd_notify. Por lo tanto, también debe usarse con NotifyAccess para que Systemd reciba mensajes. NotifyAccess tiene tres niveles: ninguno, que ignora todos los mensajes; principal, que solo acepta mensajes enviados por el proceso principal del programa; todos, que cuenta todos los mensajes enviados por todos los procesos del programa.
El programa debe enviar un mensaje de notificación vía sd_notify al finalizar el programa.
El programa inactivo esperará hasta que otros procesos en su programador hayan terminado de ejecutarse antes de ejecutarse. Por ejemplo, si ejecuta ExecStart con un script de shell, es posible que ejecute otros programas y, si no lo hace, lo más probable es que los otros programas reciban un mensaje Systemd en la salida de la consola como "Iniciar con éxito".
Dado que el IPv6 de He.net se completa mediante el comando iproute2 ip, este es un servicio único.
[Servicio]
Type=oneshot
RemainAfterExit=yes
A continuación, debe configurar ExecStart y ExecStop, si el programa lo admite. , puede continuar configurando ExecReload, Restart, etc. Tenga en cuenta que las configuraciones aquí son para sus métodos de recarga/reinicio, pero esto no significa que Systemd no pueda realizar operaciones como systemctl restart xxx.service sin estas configuraciones. Es mejor si el programa admite estas configuraciones, pero si no es así, puede detenerlo y luego iniciarlo nuevamente. Si tiene requisitos especiales, también puede configurar otras funciones, como ExecStartPre/ExecStartPost, RestartSec, TimeoutSec, etc.
Aquí hay una nota especial sobre ExecStart:
Si su tipo de servicio no es onehot, entonces solo puede aceptar un comando con algún parámetro. Por ejemplo, si usa ip Start with. crear túnel, luego comenzar con ip Tunnel0 arriba, luego hay dos comandos ip. Si su tipo de servicio no es onehot, estos dos comandos no funcionarán.
Si tiene varios comandos (del tipo onehot), sepárelos con punto y coma; se pueden usar barras invertidas entre líneas.
A menos que su tipo de servicio sea un tipo bifurcado, el comando que ingrese aquí será tratado como el proceso principal, independientemente de si es el proceso principal.
Entonces nuestro [Servicio] se escribe como:
[Servicio]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/ip túnel agregar modo he-ipv6 sentarse remoto 66.220.18.42 local 108.170.7.158 ttl 255; \
/usr/sbin/ip enlace configurar he-ipv6; \
/usr/sbin/ip addr add 2001:470:c:.1184::2/64 dev he-ipv6;\
/usr/sbin/ip route add ::/0 dev he-ipv6; \
/usr/sbin/ip -6 addr
ExecStop=/usr/sbin/ip ruta eliminar ::/0 dev he- ipv6;\
/usr/sbin/ip -6 addr del 2001:470:c:1184::2/64 dev he-ipv6;\
/usr/sbin/ ip link set he-ipv6 down;
/usr/sbin/ip tunel del he-ipv6
Instalando servicios [instalar]
Esta puede ser una rotonda manera, pero obtuve los archivos de servicio en /etc/systemd/system (para administradores de sistemas y usuarios), /usr/lib/systemd/system (para distribuciones y empaquetadores), pero no sé qué hacer con ellos. (distribución y empaquetador) y listo.
Aquí hay un estado interno, por defecto si lo pones en el lugar correcto se mostrará deshabilitado, no cargado, así que tenemos que cargarlo dentro de Systemd, lo cual nadie quiere. Las cosas no tienen. para ser instalado (no recolectamos basura), por lo que tenemos que decirle a Systemd que alguien lo quiere y quién. Generalmente
[Instalación]
WantedBy=multi-user.target
Wanted (multi-user.target indica que el sistema multiusuario está listo, simplemente Significa que puedes iniciar sesión). De esta manera, cuando multi-user.target esté habilitado, nuestro servicio también estará habilitado.
Además de WantedBy, hay dos propiedades en la sección [Instalar]:
Alias= Asigne un alias para poder utilizar el comando systemctl xxx.service No es necesario escriba el nombre completo de la organización. Por ejemplo, si le da a NetworkManager un alias llamado Alias=nm, puede ver el contenido real de NetworkManager.service a través de systemctl status nm.service.
También= Instalar contenido adicional al instalar este servicio. Por ejemplo, nuestro script He.net también debería instalar iproute2.service, pero iproute2 en realidad no necesita ser controlado por systemd, por lo que no existe. A diferencia de las dependencias en la definición de [Unidad], no gestiona dependencias de tiempo de ejecución, sino instalaciones. No tiene nada que ver con También= quién inicia quién primero, o quién depende de quién después de la instalación.