Serveis amb systemd

Col·lecció d’exercicis sobre systemd.

Concepte — Recordatori: unitats, tipus i dependències
  • systemctl start activa una unitat ara; systemctl enable només crea els enllaços simbòlics que la faran activar en arrencar (segons [Install]). Són coses independents.
  • Type= és el contracte entre el programa i systemd: defineix quan es considera iniciat el servei. simple: just després del fork(). exec: després de l’execve(). forking: quan el procés pare surt. oneshot: quan el procés acaba. notify: quan el programa envia READY=1.
  • Wants= (feble) i Requires= (fort) diuen què s’activa; no ordenen. L’ordre el donen After= i Before=. Són dimensions independents.
  • Tot el que llança un servei queda al seu grup de control (cgroup). Amb KillMode=control-group (per defecte), en aturar el servei es maten tots els processos del grup.
  • Un temporitzador (.timer) activa un servei.
  • Un socket escolta en nom d’un servei i l’inicia quan arriba trànsit.
Bona Pràctica — Convencions dels enunciats
  • Sistema de referència: Debian 12.
  • Tots els ExecStart= s’inicien a l’instant.
  • La sortida de echo i els missatges de systemd van al journal (journalctl -u NOM).
  • Els fitxers de les unitats d’administrador viuen a /etc/systemd/system/.
Exercici

Un administrador crea la unitat següent:

# /etc/systemd/system/cua-treball.service
[Unit]
Description=Procés de treball de llarga durada

[Service]
Type=simple
ExecStart=/usr/bin/sleep infinity

A continuació executa:

sudo systemctl start cua-treball.service

El servei queda actiu. Després de reiniciar la màquina, però, el servei no s’ha iniciat.

  1. Per què pot estar actiu ara però no iniciar-se automàticament després d’un reinici?
  2. Quina diferència hi ha entre start, enable i enable --now?
  3. Quina ordre utilitzaries per configurar l’inici automàtic i activar el servei immediatament? Què cal canviar a la unitat?
  4. Com comprovaries si està habilitat i si està en execució?

Repte: si s’edita el fitxer d’una unitat ja carregada, quina ordre fa que systemd en torni a llegir la configuració? Què passa si no es fa?

Pots veure el raonament
Solució
  1. systemctl start inicia la unitat en la sessió actual, però no crea cap enllaç d’arrencada. A més, la unitat no té secció [Install]: no diu a quin objectiu s’ha d’enllaçar, i per tant enable no té res a fer.

  2. start activa la unitat ara; enable crea els enllaços definits a [Install] perquè s’activi en arrencar; enable --now fa les dues coses.

  3. Cal afegir a la unitat:

    [Install]
    WantedBy=multi-user.target

    i després habilitar-la i iniciar-la:

    sudo systemctl daemon-reload
    sudo systemctl enable --now cua-treball.service
  4. Per comprovar-ho:

    systemctl is-enabled cua-treball.service
    systemctl is-active cua-treball.service
    systemctl status cua-treball.service

    is-enabled retorna enabled, disabled, static (la unitat no té [Install]; només s’activa per dependència) o masked, entre d’altres.

Repte. sudo systemctl daemon-reload fa que systemd torni a llegir els fitxers de les unitats i reconstrueixi les dependències. Sense aquesta ordre, systemd continua usant la versió en memòria i avisa que el fitxer ha canviat al disc. No reinicia cap servei: un servei en marxa continua executant-se amb la configuració antiga fins que es faci restart.

enable també recarrega la configuració en acabar, però és bona pràctica fer el daemon-reload explícit després d’editar un fitxer i abans del start o restart.


Exercici
# salutacio.service
[Unit]
Description=Salutació

[Service]
Type=oneshot
ExecStart=/bin/echo hola

S’executa sudo systemctl start salutacio.service i després systemctl status salutacio.service.

  1. Quin valor té la línia Active:? Indica un error?
  2. Descriu, en ordre, els esdeveniments que apareixen al journal (no cal el text literal).

Repte: què canvia si s’afegeix RemainAfterExit=yes a [Service]?

Pots veure el raonament
Solució
  1. inactive (dead). No és un error: el procés ha acabat amb codi 0 i, com que un oneshot no té cap procés en marxa, la unitat torna a quedar inactiva. status mostra també status=0/SUCCESS a la línia del procés.
  2. Ordre dels esdeveniments: (1) systemd anuncia l’inici de la unitat; (2) echo escriu hola; (3) systemd informa que la unitat s’ha desactivat correctament; (4) systemd informa que la unitat ha acabat. Un exemple de sortida:
Starting salutacio.service - Salutació...
echo[1234]: hola
salutacio.service: Deactivated successfully.
Finished salutacio.service - Salutació.

sequenceDiagram
    autonumber
    participant S as systemd
    participant E as echo

    S->>S: Starting salutacio.service
    S->>E: fork + execve
    E->>E: escriu hola al journal
    E-->>S: exit 0
    S->>S: Deactivated successfully
    S->>S: Finished salutacio.service

Repte. Amb RemainAfterExit=yes la unitat es queda activa després que el procés acabi: Active: active (exited), i no apareix la línia Deactivated successfully. Fins que algú l’aturi amb systemctl stop, systemd la considera iniciada.


Exercici
# /etc/systemd/system/web.service
[Unit]
Description=Servidor web

[Service]
ExecStart=/usr/sbin/webd

[Install]
WantedBy=multi-user.target graphical.target
Alias=servidor.service
Also=web.socket
# /etc/systemd/system/web.socket
[Socket]
ListenStream=8080

[Install]
WantedBy=sockets.target

S’executa sudo systemctl enable web.service.

  1. Quins enllaços simbòlics es creen? On apunta cadascun?
  2. S’ha iniciat webd?

Repte: què és servidor.service després de l’enable? Com ho comprovaries?

Pots veure el raonament
Solució
  1. Quatre enllaços: un per cada objectiu de WantedBy= (dos: multi-user.target i graphical.target), un per l’àlies (Alias=) i un pel [Install] del socket, que habilita Also=.
Created symlink /etc/systemd/system/servidor.service → /etc/systemd/system/web.service.
Created symlink /etc/systemd/system/multi-user.target.wants/web.service → /etc/systemd/system/web.service.
Created symlink /etc/systemd/system/graphical.target.wants/web.service → /etc/systemd/system/web.service.
Created symlink /etc/systemd/system/sockets.target.wants/web.socket → /etc/systemd/system/web.socket.

flowchart LR
    MU["multi-user.target.wants/web.service"] --> W["web.service"]
    GR["graphical.target.wants/web.service"] --> W
    AL["servidor.service"] --> W
    SO["sockets.target.wants/web.socket"] --> WS["web.socket"]

  1. No. enable només prepara l’arrencada següent. Per activar-lo ara caldria start (o enable --now).

Repte. servidor.service és un nom alternatiu (àlies) de la mateixa unitat: un enllaç simbòlic cap a web.service. No és una segona unitat. Es pot comprovar amb ls -l /etc/systemd/system/servidor.service i amb systemctl status servidor.service, que mostra la unitat web.service.


Exercici

El programa infod és un dimoni clàssic: en arrencar fa fork(); el fill escriu el seu PID a /run/infod.pid i continua en marxa; el pare surt amb codi 0. Completa la unitat:

[Unit]
Description=Dimoni clàssic

[Service]
Type=______(1)______
PIDFile=______(2)______
ExecStart=/usr/sbin/infod --pidfile /run/infod.pid
ExecReload=/bin/kill -HUP ______(3)______

[Install]
WantedBy=multi-user.target

Pregunta extra: per què Type=simple seria incorrecte aquí?

Pots veure el raonament
Solució
  1. forking
  2. /run/infod.pid
  3. $MAINPID (systemd el substitueix pel PID del procés principal, que ha après del fitxer de PID)

sequenceDiagram
    autonumber
    participant S as systemd
    participant P as infod pare
    participant D as infod fill

    S->>P: fork + execve
    P->>D: fork
    D->>D: escriu el PID a /run/infod.pid
    P-->>S: exit 0
    S->>S: llegeix PIDFile i el declara principal
    Note over D: procés principal del servei

Amb forking, la sortida del pare és el senyal ja estic a punt; PIDFile= li diu a systemd quin és el procés que queda (el fill).

Pregunta extra. Amb Type=simple, el procés principal seria el pare. Quan surt amb codi 0, systemd considera que el servei ha acabat i el desactiva; en fer-ho, neteja el grup de control i el fill també es mata.


Exercici
# a.service           # b.service           # c.service           # d.service
[Unit]                [Unit]                [Unit]                [Service]
Wants=b.service       Wants=c.service       Requires=d.service    Type=oneshot
                                            After=d.service       ExecStart=/bin/false

Les unitats a, b i c són Type=simple i executen /usr/bin/sleep infinity. S’executa sudo systemctl start a.service.

  1. Quines unitats participen en la transacció?
  2. Quin és l’estat final de cadascuna?
  3. systemctl start acaba amb error?

Repte: canvia Wants= per Requires= + After= a a (cap a b) i a b (cap a c). Torna a respondre les tres preguntes.

Pots veure el raonament
Solució
  1. Les quatre: a arrossega b (Wants=), b arrossega c, i c arrossega d (Requires=).
  2. d falla (ExecStart=/bin/false surt amb codi 1): failed. c té Requires=d + After=d: espera que d acabi i, com que falla, no s’arriba a iniciar (Dependency failed for c.service); systemctl status c el mostra com failed (Result: dependency). En canvi, b i a només tenen Wants=: una fallada no es propaga cap amunt a través d’un requeriment feble, i s’inicien normalment (active (running)).
  3. No. El treball de a ha tingut èxit: les fallades de les unitats desitjades amb Wants= no compten.

flowchart TD
    A["a.service · active"] -->|"Wants"| B["b.service · active"]
    B -->|"Wants"| C["c.service · failed (dependency)"]
    C -->|"Requires + After"| D["d.service · failed"]

Repte. Ara la fallada puja per tota la cadena: d falla → c no s’inicia → b (té Requires=c + After=c) tampoc → a tampoc. Les tres queden failed (Result: dependency), i systemctl start acaba amb error: A dependency job for a.service failed. See ‘journalctl -xe’ for details.

Amb After=, la unitat dependent espera que la requerida acabi d’iniciar-se i, si falla, el seu inici es cancel·la. Sense After=, les dues unitats s’inicien en paral·lel i el resultat és menys previsible. Per això, a la pràctica, Requires= es combina gairebé sempre amb After=.


Exercici

Un programa pot acabar de cinc maneres. Per a cada causa i per a cada valor de Restart=, digues si systemd reinicia el servei (sí / no). El servei és Type=simple.

Causa always on-success on-failure on-abnormal
El programa fa exit(0)
El programa fa exit(1)
kill -9 (SIGKILL) des de fora
kill -TERM (SIGTERM) des de fora
systemctl stop

Repte: un programa amb un error surt amb codi 0 just en arrencar. Quin és el resultat amb Restart=always i els valors per defecte (RestartSec=100ms, 5 inicis cada 10 s)?

Pots veure el raonament
Solució
Causa always on-success on-failure on-abnormal
exit(0) sí sí no no
exit(1) sí no sí no
SIGKILL sí no sí sí
SIGTERM sí sí no no
systemctl stop no no no no
  • Per a systemd, una sortida neta és el codi 0 o un dels senyals SIGHUP, SIGINT, SIGTERM i SIGPIPE. Per això SIGTERM es comporta com exit(0), i no com un error.
  • Una aturada explícita amb systemctl stop mai provoca un reinici, sigui quin sigui el valor.

Repte. Bucle: el servei s’inicia, surt amb 0, always el torna a iniciar, i així successivament. Amb 0,1 s de retard, es superen els 5 inicis en 10 s en menys d’un segon. La unitat queda failed amb el resultat start-limit-hit. Per recuperar-la, cal corregir la causa i executar systemctl reset-failed.

Cal tenir en compte que Restart=always no és vàlid per a Type=oneshot.


Exercici
# /lib/systemd/system/web.service (instal·lat pel paquet)
[Service]
ExecStart=/usr/sbin/webd --port 80
Environment=MODE=prod
# /etc/systemd/system/web.service.d/10-mode.conf
[Service]
Environment=MODE=debug
# /etc/systemd/system/web.service.d/20-port.conf
[Service]
ExecStart=
ExecStart=/usr/sbin/webd --port 8080
  1. Quina ordre s’executa, i amb quin valor de MODE?
  2. S’afegeix 30-log.conf amb ExecStart=/usr/sbin/webd --port 8080 --verbose (sense línia buida abans). Què passa?
  3. Amb quina ordre es veu la unitat efectiva? Amb quina ordre es crearia un fragment com 20-port.conf sense escriure el camí a mà?

Repte: i si web.service fos Type=oneshot?

Pots veure el raonament
Solució

flowchart LR
    O["web.service · paquet"] --> D1["10-mode.conf"]
    D1 --> D2["20-port.conf"]
    D2 --> E["unitat efectiva"]

  1. /usr/sbin/webd --port 8080 amb MODE=debug. Els fragments es llegeixen en ordre alfabètic i se superposen a l’original. Per a Environment=, si una variable s’assigna dues vegades, preval l’última. Per a ExecStart= (que normalment només admet una ordre), la línia buida esborra la llista anterior; sense aquesta línia, la nova ordre s’afegiria a la de l’original.
  2. Ara hi ha dues línies ExecStart= (la de 20-port.conf i la de 30-log.conf), i el servei no és oneshot. La unitat es rebutja:
web.service: Service has more than one ExecStart= setting, which is only allowed for Type=oneshot services. Refusing.
  1. La unitat efectiva (fitxer original i fragments) es veu amb systemctl cat web.service. L’ordre sudo systemctl edit web.service obre un editor i desa el fragment com /etc/systemd/system/web.service.d/override.conf; en acabar fa el daemon-reload automàticament. Així no es modifica mai el fitxer del paquet, que una actualització sobreescriuria.

Repte. Un oneshot admet diverses línies ExecStart= i les executa en seqüència, en l’ordre en què apareixen. Si una falla, les següents no s’executen.


Exercici

El servei informes.service no s’inicia. La unitat és:

# /etc/systemd/system/informes.service
[Unit]
Description=Generació d'informes

[Service]
Type=exec
User=informes
WorkingDirectory=/srv/informes
ExecStart=/opt/informes/bin/informesd

[Install]
WantedBy=multi-user.target

Aquests són tres registres de fallades diferents (cada un correspon a un escenari diferent):

── Registre A ──
informes.service: Failed to determine user credentials: No such process
informes.service: Failed at step USER spawning /opt/informes/bin/informesd: No such process
informes.service: Main process exited, code=exited, status=217/USER
informes.service: Failed with result 'exit-code'.

── Registre B ──
informes.service: Changing to the requested working directory failed: No such file or directory
informes.service: Failed at step CHDIR spawning /opt/informes/bin/informesd: No such file or directory
informes.service: Main process exited, code=exited, status=200/CHDIR
informes.service: Failed with result 'exit-code'.

── Registre C ──
informes.service: Failed to locate executable /opt/informes/bin/informesd: No such file or directory
informes.service: Failed at step EXEC spawning /opt/informes/bin/informesd: No such file or directory
informes.service: Main process exited, code=exited, status=203/EXEC
informes.service: Failed with result 'exit-code'.
  1. Per a cada registre, quina línia de la unitat (o quin element del sistema) és la causa?
  2. Qui genera els codis 217/USER, 200/CHDIR i 203/EXEC: el programa o systemd? S’ha arribat a executar informesd en algun cas?
  3. Amb Type=simple, systemctl start retorna error en algun dels tres casos? I amb Type=exec?
  4. Per al registre A: quines ordres utilitzaries per comprovar si l’usuari existeix, validar la unitat, corregir el problema i tornar a provar el servei?

Repte: el registre C també apareix si el fitxer existeix però no té permís d’execució. Quin canvi hi ha al missatge i com ho corregiries?

Pots veure el raonament
Solució
  1. A: User=informes: l’usuari no existeix (el missatge No such process és un ESRCH poc intuïtiu). B: WorkingDirectory=/srv/informes: el directori no existeix. C: ExecStart=: el binari no existeix.

  2. Els genera systemd: formen una família de codis a partir del 200 (200/CHDIR, 203/EXEC, 217/USER…) documentada a systemd.exec(5). El nom indica el pas que ha fallat: el procés fill de systemd ha fallat mentre es preparava el context d’execució. informesd no s’ha arribat a executar en cap cas: el missatge no ve del programa.

    El criteri fiable no és el número, sinó la línia Failed at step X spawning, perquè un programa pot sortir amb qualsevol codi, també amb valors alts.

  3. Amb simple, no: start només espera que es faci el fork(), i l’error només es veu al journal o a systemctl status. Amb exec, sí: systemd espera que l’execve() tingui èxit, i en els tres casos la fallada ve abans.

  4. Per al registre A:

    getent passwd informes                      # no retorna res (codi de sortida 2) si no existeix
    sudo systemd-analyze verify /etc/systemd/system/informes.service
    sudo useradd --system --no-create-home --shell /usr/sbin/nologin informes
    sudo systemctl start informes.service
    systemctl status informes.service
    sudo journalctl -u informes.service -b --no-pager

    systemd-analyze verify detecta errors de la unitat (sintaxi, directives desconegudes, fitxers que falten), però no substitueix la lectura del journal, perquè no comprova l’entorn real d’execució. No cal daemon-reload perquè el fitxer de la unitat no s’ha modificat; només caldria si s’hagués editat. Una alternativa a crear l’usuari és DynamicUser=yes, amb implicacions sobre els permisos dels directoris.

Repte. Si el fitxer existeix però no és executable, el pas continua sent EXEC, però l’error passa a ser Permission denied. La correcció és sudo chmod +x /opt/informes/bin/informesd (i comprovar també que els directoris del camí són accessibles per a l’usuari del servei).


Exercici

Volem que el servei copia.service es comprovi abans d’executar-se i que, si falla, s’enregistri un avís mitjançant un segon servei.

# /etc/systemd/system/copia.service
[Unit]
Description=Còpia de seguretat
OnFailure=avis@%n.service

[Service]
Type=oneshot
ExecStartPre=/usr/bin/test -d /srv/dades
ExecStart=/usr/local/bin/copia
# /etc/systemd/system/avis@.service
[Unit]
Description=Avís de fallada de %i

[Service]
Type=oneshot
ExecStart=/usr/bin/logger -t avis -p user.err "Ha fallat %i"

El directori /srv/dades no existeix. S’executa sudo systemctl start copia.service.

  1. S’executa ExecStart=? Quin és l’estat final de copia.service?
  2. Què fa OnFailure=? Quina unitat s’activa exactament i què valen %n i %i?
  3. Com es consulta l’avís?

Repte: què canviaria si la línia fos ExecStartPre=-/usr/bin/test -d /srv/dades?

Pots veure el raonament
Solució
  1. No. test -d surt amb codi 1; una fallada d’ExecStartPre= avorta l’inici: no s’executa ExecStart= i la unitat queda failed (Result: exit-code).
  2. OnFailure= activa les unitats indicades quan la unitat entra a l’estat failed. %n és el nom complet de la unitat (copia.service), de manera que s’activa la instància avis@copia.service.service de la plantilla avis@.service, on %i val copia.service.
  3. Amb journalctl -t avis (el -t del logger és l’identificador) o journalctl -u 'avis@*'.

sequenceDiagram
    autonumber
    participant S as systemd
    participant C as copia.service
    participant A as avis@copia.service

    S->>C: ExecStartPre: test -d /srv/dades
    C-->>S: codi 1
    S->>C: marca failed (ExecStart no s'executa)
    S->>A: OnFailure=
    A->>A: logger "Ha fallat copia.service"

Repte. El prefix - fa que systemd ignori el resultat de l’ordre: la comprovació deixa de ser una condició, ExecStart= s’executaria igualment i OnFailure= no s’activaria per aquesta causa. Si el que es vol és saltar-se el servei sense considerar-ho un error, la directiva adequada és ConditionPathIsDirectory=/srv/dades.


Exercici

Volem que informe.service s’executi cada dilluns a les 04:00 i que, si la màquina estava apagada a aquella hora, s’executi tan bon punt arrenqui. Completa:

# informe.timer
[Unit]
Description=Informe setmanal

[Timer]
OnCalendar=______(1)______
______(2)______=true

[Install]
WantedBy=______(3)______

Per activar-lo ara i en arrencar: sudo systemctl ______(4)______ informe.timer.

Pregunta extra: per què informe.service no necessita secció [Install]? Amb quina ordre comprovaries l’expressió abans d’activar-la?

Pots veure el raonament
Solució
  1. Mon *-*-* 04:00:00 (també s’accepta Mon 04:00)
  2. Persistent
  3. timers.target
  4. enable --now

sequenceDiagram
    autonumber
    participant T as informe.timer
    participant S as informe.service

    Note over T: dilluns 04:00, màquina apagada
    Note over T: dilluns 09:00, arrencada
    T->>S: Persistent=true, s'executa ara

Amb Persistent=true, systemd desa a disc l’instant de l’última execució (a /var/lib/systemd/timers/); quan el temporitzador s’activa, si n’ha perdut alguna, dispara el servei immediatament. Només té efecte amb OnCalendar=.

Pregunta extra. Perquè el servei no s’inicia en arrencar: l’activa el temporitzador. S’habilita el .timer, no el .service. L’expressió es comprova amb systemd-analyze calendar 'Mon *-*-* 04:00:00', que mostra la forma normalitzada i la propera execució. Els temporitzadors actius i la propera execució es veuen amb systemctl list-timers.


Exercici

Dos serveis independents, treball-a.service i treball-b.service, són Type=oneshot i executen /usr/bin/sleep 180 (triguen 3 minuts). Cadascun té el seu temporitzador, activat en arrencar (minut 0):

# treball-a.timer                    # treball-b.timer
[Timer]                              [Timer]
OnBootSec=2min                       OnBootSec=2min
OnUnitActiveSec=10min                OnUnitInactiveSec=10min
AccuracySec=1s                       AccuracySec=1s

En quin minut comença cadascuna de les quatre primeres execucions de A i de B?

Pots veure el raonament
Solució

OnUnitActiveSec= compta des del moment en què el servei comença a activar-se (surt de l’estat inactiu); OnUnitInactiveSec= des que es va desactivar (és a dir, des que va acabar). Pots veure el resultat amb systemctl list-timers.

Execució A (ActiveSec) B (InactiveSec)
1 minut 2 minut 2 (acaba al 5)
2 minut 12 minut 15 (acaba al 18)
3 minut 22 minut 28 (acaba al 31)
4 minut 32 minut 41

gantt
    dateFormat HH:mm
    axisFormat %M
    section A
    execució 1 :a1, 00:02, 3m
    execució 2 :a2, 00:12, 3m
    execució 3 :a3, 00:22, 3m
    execució 4 :a4, 00:32, 3m
    section B
    execució 1 :b1, 00:02, 3m
    execució 2 :b2, 00:15, 3m
    execució 3 :b3, 00:28, 3m
    execució 4 :b4, 00:41, 3m

A dóna un període fix de 10 minuts entre inicis. B dóna un descans de 10 minuts entre el final d’una execució i l’inici de la següent, de manera que el període real és de 13 minuts. Si el que importa és no solapar-se amb l’execució anterior, B és el que es vol.


Exercici
# /etc/systemd/system/eco.socket
[Socket]
ListenStream=127.0.0.1:9000

[Install]
WantedBy=sockets.target
# /etc/systemd/system/eco.service
[Service]
ExecStart=/usr/local/bin/eco

eco és un servidor que, si ha estat iniciat per un socket, usa el socket que li passa systemd. S’executa sudo systemctl enable --now eco.socket.

  1. Quin és l’estat de eco.service just després? Qui escolta al port 9000? Amb quina ordre ho comprovaries?
  2. Un client es connecta amb nc 127.0.0.1 9000. Què passa?
  3. Per què s’habilita eco.socket i no eco.service? Per què eco.service no té [Install]?

Repte: què canvia amb Accept=yes al .socket?

Pots veure el raonament
Solució
  1. eco.service és inactive (dead): encara no s’ha iniciat. El que escolta al port 9000 és systemd (PID 1), en nom del servei. Es comprova amb sudo ss -ltnp 'sport = :9000' i systemctl status eco.socket (Active: active (listening)).
  2. En arribar la primera connexió, systemd inicia eco.service (per defecte, el servei amb el mateix nom que el socket) i li passa el socket que escolta; el programa agafa la connexió pendent.
  3. Perquè qui ha d’estar actiu des de l’arrencada és el socket; el servei s’activa sota demanda. No necessita [Install] perquè l’activa el socket.

sequenceDiagram
    autonumber
    participant C as client
    participant S as systemd (eco.socket)
    participant E as eco.service

    S->>S: escolta a 127.0.0.1:9000
    C->>S: connexió
    S->>E: inicia el servei i li passa el socket
    E->>C: respon

Si eco.service s’atura, el socket continua escoltant i la propera connexió el torna a iniciar. Les connexions que arriben mentre no és viu queden en cua, de manera que no es perden.

Repte. Amb Accept=yes, systemd accepta ell mateix cada connexió i inicia una instància nova per connexió, que rep la connexió ja acceptada. Això obliga que el servei sigui una plantilla (eco@.service) i que el programa llegeixi de l’entrada estàndard (StandardInput=socket).


Exercici

El servei processa-dades.service llegeix i escriu fitxers a /srv/dades. Aquest directori correspon a un volum separat (per exemple, un disc extern) que es munta des de /etc/fstab amb l’opció nofail:

UUID=<uuid>  /srv/dades  ext4  defaults,nofail  0  2
# /etc/systemd/system/processa-dades.service
[Unit]
Description=Processament de dades

[Service]
Type=oneshot
User=processador
Group=processador
ExecStart=/usr/local/bin/processa-dades --entrada /srv/dades/entrada --sortida /srv/dades/sortida

[Install]
WantedBy=multi-user.target
  1. Quin problema pot aparèixer si el servei s’inicia quan el volum no està muntat?
  2. Quina directiva de systemd permet expressar que el servei necessita els muntatges requerits per accedir a /srv/dades?
  3. Completa la secció [Unit] amb aquesta directiva.
  4. Per què no és equivalent afegir només After=local-fs.target?
  5. Com comprovaries si el sistema de fitxers està muntat i si el servei ha fallat per aquesta causa?

Repte: si el volum no està muntat però /srv/dades existeix al sistema de fitxers arrel, què passa? Quines mesures el protegeixen? Canviaria alguna cosa si s’afegís WorkingDirectory=/srv/dades a la unitat?

Pots veure el raonament
Solució
  1. El programa podria no trobar les dades esperades o, si el punt de muntatge buit és accessible i escrivible, podria escriure al sistema de fitxers arrel en lloc del volum previst, sense cap error visible.

  2. RequiresMountsFor=.

  3. La secció queda així:

    [Unit]
    Description=Processament de dades
    RequiresMountsFor=/srv/dades

    systemd afegeix Requires= i After= sobre totes les unitats de muntatge necessàries per accedir a aquest camí.

  4. Amb nofail, el muntatge no és un requisit de local-fs.target: aquest objectiu no exigeix que tingui èxit. After=local-fs.target és, per tant, només una relació d’ordre i no declara que el muntatge sigui un requisit del servei: si el muntatge falla, el servei s’iniciaria igualment. RequiresMountsFor= el converteix en un requisit: si el muntatge no es pot fer, el servei no s’inicia (dependency failed).

    Amb muntatges d’arrencada sense nofail, el sistema ja depèn de local-fs.target i l’After= seria sovint redundant. RequiresMountsFor= continua sent útil per a muntatges tardans (x-systemd.automount, _netdev) i per expressar la intenció.

  5. Per exemple:

    findmnt /srv/dades
    systemd-escape -p --suffix=mount /srv/dades     # srv-dades.mount
    systemctl status srv-dades.mount
    systemctl status processa-dades.service
    sudo journalctl -u processa-dades.service -b --no-pager

Repte. El servei llegiria dades incorrectes o escriuria al lloc equivocat. Mesures de protecció:

  • RequiresMountsFor=/srv/dades (el servei no s’inicia si el volum no es pot muntar);
  • ConditionPathIsMountPoint=/srv/dades (el servei s’omet sense error si no és un punt de muntatge);
  • sudo chattr +i /srv/dades amb el volum desmuntat (el directori buit del sistema arrel deixa de ser escrivible).

Segons systemd.exec(5), WorkingDirectory= també afegeix automàticament dependències sobre els muntatges d’aquest camí, però no ho fa per camins que només apareixen com a arguments de l’ordre, com en aquest enunciat. Per això aquí RequiresMountsFor= és necessari.


Exercici

Volem que el servei informe-segur.service generi un fitxer de resultat a /var/lib/informes/ sense executar-se com a root i sense poder modificar lliurement la resta del sistema. L’usuari i el grup informes existeixen.

# /etc/systemd/system/informe-segur.service
[Unit]
Description=Generació segura d'un informe

[Service]
Type=oneshot
User=informes
Group=informes
StateDirectory=informes
ProtectSystem=strict
PrivateTmp=yes
NoNewPrivileges=yes
ExecStart=/bin/sh -c 'date > /var/lib/informes/resultat.txt'

[Install]
WantedBy=multi-user.target
  1. Quin usuari executa el procés?
  2. Quin directori crea i prepara StateDirectory=informes?
  3. Per què l’escriptura a /var/lib/informes/resultat.txt pot funcionar malgrat ProtectSystem=strict?
  4. Quina funció tenen PrivateTmp=yes i NoNewPrivileges=yes?
  5. Com comprovaries que el servei ha acabat correctament i que el fitxer existeix? Quina ordre avalua el nivell d’aïllament de la unitat?

Repte: es canvia la sortida a /etc/resultat.txt. Per aïllar l’efecte de ProtectSystem=strict, es proven tres variants: (a) la unitat tal com està; (b) sense User= ni Group= (com a root), amb ProtectSystem=strict; (c) com a root i sense ProtectSystem. Quin resultat esperes a cada una?

Pots veure el raonament
Solució
  1. El procés s’executa com a usuari informes i grup informes, no com a root.

  2. StateDirectory=informes demana a systemd que creï i prepari el directori d’estat /var/lib/informes, amb propietat de l’usuari i el grup configurats.

  3. ProtectSystem=strict fa que tota la jerarquia del sistema de fitxers sigui de només lectura per al servei, però systemd deixa escrivible el directori declarat amb StateDirectory=.

  4. PrivateTmp=yes dóna al servei un /tmp i un /var/tmp privats, invisibles per a la resta del sistema. NoNewPrivileges=yes impedeix que el procés guanyi privilegis addicionals en executar altres programes (per exemple, els que tenen el bit set-user-ID).

  5. Es pot fer servir:

    sudo systemctl start informe-segur.service
    systemctl status informe-segur.service
    sudo journalctl -u informe-segur.service -b --no-pager
    sudo cat /var/lib/informes/resultat.txt
    systemd-analyze security informe-segur.service

    Com que és Type=oneshot i no hi ha RemainAfterExit=yes, després d’una execució correcta la unitat queda inactive (dead); això no és una fallada. systemd-analyze security mostra una puntuació d’exposició i les directives que encara es poden endurir.

Repte.

Variant Resultat esperat
(a) informes + strict Falla. El sistema de fitxers és de només lectura abans que es comprovin els permisos: Read-only file system.
(b) root + strict Falla amb Read-only file system: ni root pot escriure.
(c) root, sense ProtectSystem Funciona.
(a’) informes, sense ProtectSystem Falla per permisos: Permission denied.

Comparant (b) amb (c) s’aïlla l’efecte de ProtectSystem=strict. Comparant (a’) amb (c), l’efecte de User=. La diferència entre els dos missatges (Read-only file system davant Permission denied) mostra que són dues barreres independents: el muntatge de només lectura i els permisos de fitxer.


Exercici

Escriu l’ordre (o ordres) adequada per a cada situació. Totes es refereixen a web.service.

  1. Canviar un paràmetre sense tocar el fitxer del paquet.
  2. Veure la configuració efectiva, incloent-hi els fragments.
  3. Veure només els errors del servei des de l’últim arrencada.
  4. Seguir el journal del servei en temps real.
  5. Veure els missatges dels últims 10 minuts.
  6. Mostrar l’estat actiu, el subestat i el resultat com a propietats.
  7. Trobar la propera execució dels temporitzadors.

Repte: per què systemctl status no es suficient per diagnosticar una fallada ocorreguda fa tres dies?

Pots veure el raonament
Solució
  1. sudo systemctl edit web.service (crea override.conf i recarrega).
  2. systemctl cat web.service
  3. journalctl -u web.service -b -p err (-p err inclou err i més greus).
  4. journalctl -u web.service -f
  5. journalctl -u web.service --since "10 min ago"
  6. systemctl show -p ActiveState,SubState,Result web.service
  7. systemctl list-timers

Repte. status només mostra les últimes línies del journal i l’estat actual de la unitat. Per a un historial cal journalctl, amb filtres de temps (--since, --until) o d’arrencada (-b -1 per a l’arrencada anterior). Aquest últim exigeix que el journal sigui persistent (/var/log/journal/ existent o Storage=persistent a journald.conf).