32blogby StudioMitsu

Inicio automático con systemd en Yocto: Guía con 7 errores comunes

Añade servicios systemd a Yocto con inicio automático y timers. Incluye 7 errores comunes, basado en Scarthgap 5.0 LTS.

8 min read

This article contains affiliate links.

Contenido

Has compilado tu aplicación con Yocto. Ahora necesitas que se inicie automáticamente en el arranque. En Linux embebido, eso significa crear un servicio systemd.

Pero hacer que los servicios systemd funcionen correctamente en Yocto requiere que tres cosas estén alineadas: la configuración de la distribución, la receta y el archivo de servicio. Si falta alguna de ellas, tu servicio falla silenciosamente en el inicio.

Esta guía cubre todo desde añadir servicios systemd hasta 7 errores comunes, basada en Yocto Scarthgap 5.0 LTS (systemd 255).

Habilitar systemd con INIT_MANAGER

Yocto usa sysvinit como sistema init por defecto. Necesitas cambiar explícitamente a systemd.

Enfoque INIT_MANAGER (Zeus 3.0+)

Añade una sola línea a conf/local.conf o conf/distro/mydistro.conf.

bash
# conf/local.conf
INIT_MANAGER = "systemd"

Esta sola línea carga init-manager-systemd.inc, que configura automáticamente lo siguiente.

bash
# Contenido de init-manager-systemd.inc (rama Scarthgap)
DISTRO_FEATURES:append = " systemd usrmerge"
DISTRO_FEATURES_BACKFILL_CONSIDERED:append = " sysvinit"
VIRTUAL-RUNTIME_init_manager ??= "systemd"
VIRTUAL-RUNTIME_initscripts ??= "systemd-compat-units"
VIRTUAL-RUNTIME_login_manager ??= "shadow-base"
VIRTUAL-RUNTIME_dev_manager ??= "systemd"
ROOT_HOME ?= "/root"

El enfoque legacy (Pre-Zeus)

Antes de Zeus, tenías que configurar manualmente las variables DISTRO_FEATURES y VIRTUAL-RUNTIME_* en local.conf. Esto sigue funcionando pero el enfoque de una línea con INIT_MANAGER es más simple.

Verificar la configuración

bash
# Verificar que systemd esté en DISTRO_FEATURES
bitbake-getvar DISTRO_FEATURES | grep systemd

Escribir una receta de servicio systemd

Para añadir un servicio systemd en Yocto, escribe una receta que herede la bbclass systemd.

Ejemplo de receta completa

bash
# meta-mylayer/recipes-apps/myapp/myapp_1.0.bb
SUMMARY = "My custom application"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "file://myapp.sh \
           file://myapp.service"

S = "${WORKDIR}"

inherit systemd

SYSTEMD_SERVICE:${PN} = "myapp.service"
SYSTEMD_AUTO_ENABLE:${PN} = "enable"

do_install() {
    install -d ${D}${bindir}
    install -m 0755 ${WORKDIR}/myapp.sh ${D}${bindir}/myapp

    install -d ${D}${systemd_system_unitdir}
    install -m 0644 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/
}

FILES:${PN} += "${systemd_system_unitdir}/myapp.service"

Variables clave

VariablePropósitoValor predeterminado
SYSTEMD_SERVICE:${PN}Nombre(s) de archivo(s) de servicio a gestionar${PN}.service
SYSTEMD_AUTO_ENABLE:${PN}Si se habilita automáticamenteenable
SYSTEMD_PACKAGESPaquetes a gestionar con systemd${PN}

Lo que inherit systemd hace automáticamente:

  • Genera un script postinst que ejecuta systemctl enable
  • Crea enlaces simbólicos bajo multi-user.target.wants/
  • Genera un script prerm para la eliminación del paquete

Proteger tu receta para systemd

Para prevenir errores de compilación en distribuciones sin systemd, usa REQUIRED_DISTRO_FEATURES.

bash
REQUIRED_DISTRO_FEATURES = "systemd"

Esto hace que la receta se salte cuando DISTRO_FEATURES no incluye systemd. Si otra receta depende de ella, se genera un error de compilación, facilitando rastrear la causa raíz.

Añadir a una receta existente vía bbappend

Si necesitas añadir un servicio systemd a una receta existente, usa un archivo bbappend. Consulta el Patrón 3 en la guía práctica de bbappend para los detalles. Ten en cuenta que inherit systemd no puede usarse en archivos .bbappend — la receta base .bb ya debe heredar la clase systemd.

Diseño de archivos de servicio para embebidos

Cómo escribes el archivo de servicio afecta directamente la fiabilidad del arranque. Estos son los puntos clave para Linux embebido.

Estructura básica

ini
# myapp.service
[Unit]
Description=My Application Service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/myapp
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Sección [Unit]

After= controla el orden de arranque. Un error común con servicios que dependen de la red es usar After=network.target.

  • network.target — indica que los dispositivos de red han sido configurados. No garantiza que se haya asignado una dirección IP
  • network-online.target — indica que la red es utilizable. Usa esto cuando tu servicio necesite hacer llamadas API o conexiones de red

Sección [Service]

Type= especifica cómo se inicia el proceso.

TipoCaso de uso
simpleProceso en primer plano (predeterminado)
forkingDaemon que se bifurca al fondo
oneshotScript que se ejecuta una vez y termina

Para embebidos, configura Restart=on-failure con RestartSec=5 (reintentar después de 5 segundos) para habilitar la recuperación automática de caídas.

Permisos

Instala los archivos de servicio con 0644. Añadir permisos de ejecución (0755) puede hacer que systemd emita advertencias.

bash
# Correcto
install -m 0644 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/

# Incorrecto (permiso de ejecución es innecesario)
install -m 0755 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/

Recetas con Timer y Socket Activation

Las fortalezas de systemd van más allá de los servicios. Los timers gestionan tareas periódicas, y la activación por socket habilita el inicio bajo demanda.

Unidades Timer

Usa timers de systemd en lugar de cron para mantener las dependencias y logs integrados con systemd.

ini
# myapp.timer
[Unit]
Description=Run myapp periodically

[Timer]
OnBootSec=1min
OnUnitActiveSec=1h
Persistent=true

[Install]
WantedBy=timers.target
ini
# myapp.service (activado por el timer)
[Unit]
Description=My Application Task

[Service]
Type=oneshot
ExecStart=/usr/bin/myapp --run-task

En la receta, lista ambos archivos en SYSTEMD_SERVICE.

bash
SRC_URI = "file://myapp.sh \
           file://myapp.service \
           file://myapp.timer"

SYSTEMD_SERVICE:${PN} = "myapp.service myapp.timer"

do_install() {
    install -d ${D}${bindir}
    install -m 0755 ${WORKDIR}/myapp.sh ${D}${bindir}/myapp

    install -d ${D}${systemd_system_unitdir}
    install -m 0644 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/
    install -m 0644 ${WORKDIR}/myapp.timer ${D}${systemd_system_unitdir}/
}

FILES:${PN} += "${systemd_system_unitdir}/myapp.service \
                ${systemd_system_unitdir}/myapp.timer"

Activación por socket

La activación por socket retrasa el inicio del servicio hasta que llega una conexión real. Esto es efectivo para dispositivos embebidos con recursos limitados.

ini
# myapp.socket
[Unit]
Description=My Application Socket

[Socket]
ListenStream=8080
Accept=no

[Install]
WantedBy=sockets.target
ini
# myapp.service (iniciado por el socket)
[Unit]
Description=My Application
Requires=myapp.socket

[Service]
Type=simple
ExecStart=/usr/bin/myapp

La sintaxis de la receta es la misma que para los timers. Lista tanto el servicio como el socket en SYSTEMD_SERVICE.

bash
SYSTEMD_SERVICE:${PN} = "myapp.service myapp.socket"

7 errores comunes y soluciones

Estos son los problemas más frecuentes reportados en las listas de correo y foros de Yocto.

1. systemd no está en DISTRO_FEATURES

El error más común. Aunque tengas inherit systemd en tu receta, si la configuración de tu distribución no tiene systemd habilitado, nada funciona.

bash
# Verificar
bitbake-getvar DISTRO_FEATURES | grep systemd

# Solución: añadir a conf/local.conf
INIT_MANAGER = "systemd"

2. Falta WantedBy en la sección [Install]

Si el archivo de servicio no tiene sección [Install] o le falta WantedBy=, systemctl enable no hace nada. No se crea ningún enlace simbólico, y el servicio no se iniciará automáticamente.

ini
# Obligatorio
[Install]
WantedBy=multi-user.target

3. Falta :$ en SYSTEMD_SERVICE

bash
# Incorrecto (sin calificador de paquete)
SYSTEMD_SERVICE = "myapp.service"

# Correcto
SYSTEMD_SERVICE:${PN} = "myapp.service"

SYSTEMD_SERVICE es una variable por paquete. Sin :${PN}, la bbclass systemd no puede generar correctamente el script postinst.

4. Instalar en /etc/systemd/system/

bash
# Incorrecto (/etc/ es para overrides del usuario)
install -d ${D}/etc/systemd/system/

# Correcto
install -d ${D}${systemd_system_unitdir}
# → /usr/lib/systemd/system/

/etc/systemd/system/ es donde los usuarios hacen overrides locales de unidades. Las unidades instaladas por paquetes pertenecen a ${systemd_system_unitdir} (/usr/lib/systemd/system/).

5. Esperar disponibilidad de red con network.target

ini
# network.target: indica que los dispositivos de red están configurados
# ≠ la red está realmente utilizable

# Cuando necesitas conectividad de red real
[Unit]
After=network-online.target
Wants=network-online.target

network.target solo significa que las interfaces han sido configuradas. No garantiza la asignación de IP ni la resolución DNS.

6. Permisos de ejecución en archivos de servicio

bash
# Incorrecto (systemd puede emitir advertencias)
install -m 0755 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/

# Correcto
install -m 0644 ${WORKDIR}/myapp.service ${D}${systemd_system_unitdir}/

Los archivos de servicio son archivos de configuración, no ejecutables. 0644 es el permiso correcto.

7. Logs de journald perdidos al reiniciar

Por defecto, journald almacena los logs en RAM (/run/log/journal/). Los logs desaparecen al reiniciar.

Para persistir los logs, crea el directorio /var/log/journal/ en tu receta.

bash
do_install:append() {
    install -d ${D}${localstatedir}/log/journal
}
FILES:${PN} += "${localstatedir}/log/journal"

Para dispositivos embebidos, considera la frecuencia de escritura en Flash. Las escrituras frecuentes de logs acortan la vida útil del Flash. Establece límites con SystemMaxUse= y MaxRetentionSec= en journald.conf.

Conclusión

Hacer que los servicios systemd funcionen en Yocto requiere tres cosas.

  1. Configuración de distribución: Habilitar con INIT_MANAGER = "systemd"
  2. Receta: inherit systemd y configurar SYSTEMD_SERVICE:${PN}
  3. Archivo de servicio: Incluir WantedBy=multi-user.target en la sección [Install]

La activación por timer y socket te permite integrar tareas periódicas e inicio bajo demanda en systemd también.

La mayoría de los errores se reducen a configuración faltante. Cuando las cosas no funcionan, empieza verificando DISTRO_FEATURES con bitbake-getvar y ejecutando systemctl status myapp en el dispositivo. La guía de depuración de errores de compilación cubre estas herramientas en detalle.

Para una inmersión más profunda en recetas systemd y desarrollo de recetas Yocto, estos libros son referencias sólidas.