Risoluzione dei Problemi di Fallimento dei Servizi Systemd: Una Guida Passo-Passo

Diagnostica i fallimenti dei servizi systemd con controlli dello stato, log del journal, revisione dei file unit, correzione delle dipendenze e debug dell'ambiente.

Risoluzione dei Problemi di Fallimento dei Servizi Systemd: Una Guida Passo-Passo

I fallimenti dei servizi systemd sono più facili da diagnosticare quando si procede con calma e si seguono le prove. Un'unità fallita di solito lascia tre indizi utili: lo stato registrato da systemd, il comando che ha tentato di eseguire e i log scritti da systemd o dall'applicazione. Se li leggi in ordine, eviti la trappola comune di modificare un file unit prima di sapere se il problema è l'unità, l'applicazione, una dipendenza o l'host.

Gli esempi seguenti utilizzano un fittizio mywebapp.service, ma lo stesso flusso di lavoro si applica a helper di database, consumatori di code, job di backup, esportatori e demoni interni.

La Prima Linea di Difesa: systemctl status

Quando un servizio non si avvia, il primo comando da eseguire è systemctl status <nome_servizio>. Questo comando fornisce un'istantanea dello stato corrente del servizio, inclusi se è attivo, caricato e, cosa cruciale, un estratto dei suoi log recenti. Questo spesso fornisce informazioni sufficienti per identificare rapidamente il problema.

Supponiamo che il tuo servizio applicativo web, mywebapp.service, non si avvii:

systemctl status mywebapp.service

Interpretazione dell'Output di Esempio:

● mywebapp.service - My Web Application
     Loaded: loaded (/etc/systemd/system/mywebapp.service; enabled; vendor preset: disabled)
     Active: failed (Result: exit-code) since Mon 2023-10-26 10:30:05 UTC; 10s ago
    Process: 12345 ExecStart=/usr/local/bin/mywebapp-start.sh (code=exited, status=1/FAILURE)
   Main PID: 12345 (code=exited, status=1/FAILURE)
        CPU: 10ms

Oct 26 10:30:05 hostname systemd[1]: Started My Web Application.
Oct 26 10:30:05 hostname mywebapp-start.sh[12345]: Error: Port 8080 already in use
Oct 26 10:30:05 hostname systemd[1]: mywebapp.service: Main process exited, code=exited, status=1/FAILURE
Oct 26 10:30:05 hostname systemd[1]: mywebapp.service: Failed with result 'exit-code'.

Da questo output, possiamo immediatamente vedere:

  • Il servizio mywebapp.service è failed.
  • È fallito con Result: exit-code, il che significa che il comando ExecStart è uscito con uno stato diverso da zero.
  • La riga Process mostra che il comando mywebapp-start.sh è fallito con status=1/FAILURE.
  • Fondamentalmente, le righe di log indicano: Error: Port 8080 already in use. Questo è un chiaro indicatore del problema.

Questo comando è il tuo primo strumento diagnostico, spesso indica direttamente la causa o restringe il campo su dove cercare successivamente.

Approfondire con journalctl

Mentre systemctl status fornisce un riepilogo rapido, journalctl è il comando di riferimento per una registrazione dettagliata. Interroga il journal di systemd, che raccoglie i log da tutte le parti del sistema, inclusi i servizi.

Revisione di Base dei Log

Per visualizzare tutti i log per un servizio specifico, incluse le voci storiche:

journalctl -u mywebapp.service

Questo mostrerà tutte le voci di log associate a mywebapp.service. Se il servizio fallisce ripetutamente, vedrai le voci di ogni tentativo fallito.

Filtraggio e Query Basate sul Tempo

Per restringere i risultati, specialmente dopo un fallimento recente, puoi usare flag come --since e --priority:

  • Mostra i log da un momento specifico:
    journalctl -u mywebapp.service --since "10 minutes ago"
    journalctl -u mywebapp.service --since "2023-10-26 10:00:00"
    
  • Mostra solo messaggi di livello errore o superiori:
    journalctl -u mywebapp.service -p err
    
  • Combina con -xe per spiegazioni estese e output verboso:
    journalctl -u mywebapp.service -xe --since "5 minutes ago"
    
    -x può aggiungere testo esplicativo per alcuni messaggi di systemd. Tratta queste spiegazioni come suggerimenti, non come sostituti dei log specifici dell'unità.

Comprendere i Messaggi di Log

Cerca parole chiave come Error, Failed, Warning o messaggi specifici dell'applicazione che indicano cosa è andato storto. Presta attenzione ai timestamp per comprendere la sequenza degli eventi che hanno portato al fallimento.

Suggerimento: Se lo script ExecStart del tuo servizio stampa sull'output standard o sull'errore standard, quei messaggi vengono solitamente catturati da journalctl. Assicurati che i tuoi script registrino messaggi di errore descrittivi.

Ispezionare il File Unit: Il Progetto del Tuo Servizio

Ogni servizio systemd è definito da un file unit (es., mywebapp.service). Le configurazioni errate in questo file sono una fonte comune di fallimenti all'avvio. Devi capire cosa il servizio sta cercando di fare.

Recuperare il File Unit

Per visualizzare il file unit attivo per il tuo servizio:

systemctl cat mywebapp.service

Questo comando mostra il file unit esatto che systemd sta utilizzando, incluse eventuali sostituzioni.

Direttive Chiave da Controllare

Concentrati sulla sezione [Service] per problemi relativi all'esecuzione e su [Unit] per le dipendenze.

  • ExecStart: Questo è il comando che systemd esegue per avviare il tuo servizio. Verifica che il percorso sia corretto e che il comando stesso sia eseguibile e venga eseguito con successo quando invocato manualmente (es., come User specificato).
    ExecStart=/usr/local/bin/mywebapp-start.sh
    
  • Type: Definisce il tipo di avvio del processo. I tipi comuni includono:
    • simple (predefinito): ExecStart è il processo principale.
    • forking: ExecStart crea un processo figlio e il processo padre esce. Systemd attende che il processo padre esca.
    • oneshot: ExecStart viene eseguito ed esce; systemd considera il servizio attivo finché il comando è in esecuzione.
    • notify: Il servizio invia una notifica a systemd quando è pronto.
    • Un Type errato può portare systemd a pensare che un servizio sia fallito quando in realtà è stato avviato, o viceversa.
  • User / Group: L'utente e il gruppo sotto cui verrà eseguito il servizio. I problemi di autorizzazione spesso derivano dal tentativo del servizio di accedere a file o risorse per cui non ha i diritti sotto questo utente.
    User=mywebappuser
    Group=mywebappgroup
    
  • WorkingDirectory: La directory da cui verrà eseguito il servizio. I percorsi relativi in ExecStart o altri comandi dipendono da questo.
  • Restart: Definisce quando il servizio deve essere riavviato. Se impostato su on-failure o always, un servizio che fallisce potrebbe riavviarsi costantemente, rendendo più difficile catturare il fallimento iniziale.
  • TimeoutStartSec / TimeoutStopSec: Quanto tempo systemd attende che il servizio si avvii o si arresti. Se un servizio impiega più tempo per inizializzare di TimeoutStartSec, systemd lo ucciderà e segnalerà un fallimento.

Problemi Comuni del File Unit

  • Percorsi errati: Errore di battitura in ExecStart o in altri percorsi di file.
  • Variabili Environment mancanti: I servizi spesso richiedono variabili d'ambiente specifiche (es., PATH) che potrebbero non essere presenti nell'ambiente pulito di systemd (vedi sotto).
  • Autorizzazioni: L'User specificato non ha i permessi di esecuzione per lo script o i permessi di lettura/scrittura per i file di dati necessari.
  • Errori di sintassi: Semplici errori di battitura nel file unit stesso.

Per testare ExecStart manualmente:

Passa all'utente del servizio e prova a eseguire il comando direttamente:

sudo -u mywebappuser /usr/local/bin/mywebapp-start.sh

Questo spesso riproduce l'errore visto in journalctl direttamente nel tuo terminale, rendendo il debug più facile.

Gestione delle Dipendenze: Quando i Servizi Non Possono Avviarsi da Soli

I servizi spesso si basano su altri servizi o componenti di sistema per essere attivi prima di poter avviarsi. Systemd utilizza le direttive Wants, Requires, After e Before per gestire queste dipendenze.

Identificare le Dipendenze

Usa systemctl list-dependencies <nome_servizio> per vedere cosa un servizio richiede o vuole esplicitamente per funzionare.

systemctl list-dependencies mywebapp.service

Direttive comuni nella sezione [Unit]:

  • After=: Specifica che questo servizio dovrebbe avviarsi dopo le unità elencate. Se l'unità elencata fallisce, questo servizio tenterà comunque di avviarsi (a meno che non venga utilizzato anche Requires=).
  • Requires=: Specifica che questo servizio richiede le unità elencate. Se una qualsiasi delle unità richieste non si avvia, questo servizio non si avvierà.
  • Wants=: Una forma più debole di Requires=. Se un'unità desiderata fallisce, questo servizio tenterà comunque di avviarsi.

Esempio:

[Unit]
Description=My Web Application
After=network.target mysql.service
Requires=mysql.service

Qui, mywebapp.service è ordinato dopo network.target e mysql.service, e richiede che mysql.service sia avviato con successo. Se mysql.service fallisce, mywebapp.service non si avvierà.

Risolvere i Conflitti di Dipendenza

Se un servizio fallisce a causa di un problema di dipendenza, journalctl di solito indicherà quale dipendenza non è stata soddisfatta. Ad esempio, potrebbe indicare Dependency failed for My Web Application seguito da dettagli sul fallimento di mysql.service.

Passaggi per risolvere:

  1. Controlla il servizio dipendente: Esegui systemctl status <servizio_dipendente> (es., systemctl status mysql.service) e journalctl -u <servizio_dipendente> per diagnosticare prima il suo fallimento.
  2. Verifica le direttive After= e Requires=: Assicurati che riflettano correttamente l'ordine di avvio desiderato e la rigidità. A volte, un servizio deve attendere che una porta specifica sia aperta, non solo che il job di avvio di un'altra unità sia terminato. Per controlli mirati, ExecStartPre= può aiutare. Per i demoni di rete, l'attivazione del socket o la logica di ripetizione a livello di applicazione sono spesso più affidabili.

Variabili d'Ambiente e Percorsi: Le Insidie Nascoste

I servizi systemd vengono eseguiti in un ambiente molto pulito e minimale. Questo spesso porta a problemi in cui i comandi che funzionano perfettamente nella shell di un utente falliscono quando vengono eseguiti da systemd perché mancano variabili d'ambiente cruciali (come PATH).

L'Ambiente Pulito di Systemd

Quando systemd avvia un servizio, non eredita l'ambiente completo dell'utente che ha avviato systemctl start. La variabile PATH, ad esempio, è spesso ridotta, il che significa che comandi come python o node potrebbero non essere trovati se non si trovano in posizioni standard come /usr/bin o /bin.

Sintomo: ExecStart=/usr/local/bin/myscript.sh fallisce con python: command not found, node: command not found, un errore di libreria mancante o un messaggio dell'applicazione che dice che un'impostazione richiesta è vuota.

Correzione: Rendi esplicito l'ambiente del servizio.

[Service]
WorkingDirectory=/opt/mywebapp
Environment="APP_ENV=production"
Environment="PATH=/opt/mywebapp/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
ExecStart=/opt/mywebapp/venv/bin/gunicorn app:app

Per molte variabili, usa un file di ambiente:

[Service]
EnvironmentFile=/etc/mywebapp/mywebapp.env
ExecStart=/opt/mywebapp/bin/server

Mantieni quel file semplice. EnvironmentFile= non è uno script Bash. Usa righe KEY=value, non export KEY=value, sostituzione di comandi o condizionali di shell. Imposta anche permessi restrittivi se il file contiene segreti:

sudo chown root:mywebapp /etc/mywebapp/mywebapp.env
sudo chmod 0640 /etc/mywebapp/mywebapp.env

Autorizzazioni: Riprodurre il Fallimento come Utente del Servizio

I problemi di autorizzazione sono comuni perché i test manuali spesso avvengono come root o come utente di login, mentre l'unità viene eseguita come account di servizio dedicato.

Controlla l'utente configurato:

systemctl show mywebapp.service -p User -p Group

Quindi esegui lo stesso comando come quell'utente:

sudo -u mywebappuser /usr/local/bin/mywebapp-start.sh

Se l'app ha bisogno di una directory di lavoro, includila:

sudo -u mywebappuser bash -lc 'cd /opt/mywebapp && /usr/local/bin/mywebapp-start.sh'

Guarda oltre l'eseguibile. L'utente del servizio potrebbe aver bisogno di accesso in lettura a /etc/mywebapp/config.yml, accesso in scrittura a /var/lib/mywebapp, accesso in esecuzione su ogni directory padre o il permesso di creare un socket Unix sotto /run/mywebapp. Un controllo rapido può risparmiare molte supposizioni:

sudo -u mywebappuser test -r /etc/mywebapp/config.yml
sudo -u mywebappuser test -w /var/lib/mywebapp
namei -l /var/lib/mywebapp/uploads

Se il servizio fallisce solo quando si lega a una porta bassa come 80 o 443, non eseguirlo immediatamente come root. Un proxy inverso, l'attivazione del socket o una capacità mirata potrebbero essere più sicuri a seconda del servizio.

Limiti di Avvio e Cicli di Riavvio

Un servizio che si blocca ripetutamente potrebbe fermarsi con un messaggio come start request repeated too quickly. Ciò significa che il limite di velocità di systemd è intervenuto. Il fallimento originale è avvenuto prima, quindi non concentrarti solo sul messaggio del limite di velocità.

Usa:

journalctl -u mywebapp.service --since "30 minutes ago"
systemctl show mywebapp.service -p NRestarts -p Restart -p StartLimitBurst -p StartLimitIntervalUSec

Dopo aver risolto la causa principale, cancella lo stato di fallimento:

sudo systemctl reset-failed mywebapp.service
sudo systemctl start mywebapp.service

Fai attenzione con Restart=always. È utile per demoni resilienti, ma durante il debug può inondare il journal e nascondere il primo errore chiaro. Puoi fermare temporaneamente l'unità, rivedere i log e avviarla manualmente una volta che hai cambiato una cosa.

Convalidare l'Unità Prima di Ricaricare

Prima di riavviare un servizio dopo aver modificato un file unit, convalida il file e ricarica systemd:

sudo systemd-analyze verify /etc/systemd/system/mywebapp.service
sudo systemctl daemon-reload
sudo systemctl restart mywebapp.service

Se il servizio ha sostituzioni drop-in, ispeziona la versione unita:

systemctl cat mywebapp.service
systemctl show mywebapp.service -p FragmentPath -p DropInPaths -p ExecStart

Questo cattura i casi imbarazzanti: hai modificato un file sotto /usr/lib/systemd/system, ma un drop-in sotto /etc/systemd/system/mywebapp.service.d/override.conf cambia ancora ExecStart; o hai corretto un file unit copiato che non è quello caricato da systemd.

Un Ordine Pratico delle Operazioni

Quando un servizio di produzione è giù, usa un ciclo breve e ripetibile:

  1. Esegui systemctl status mywebapp.service --no-pager.
  2. Leggi journalctl -u mywebapp.service --since "15 minutes ago".
  3. Ispeziona systemctl cat mywebapp.service.
  4. Controlla il comando, l'utente, la directory di lavoro, l'ambiente e le dipendenze.
  5. Riproduci il comando come utente del servizio.
  6. Apporta una modifica.
  7. Esegui systemctl daemon-reload se l'unità è cambiata.
  8. Riavvia e controlla di nuovo il journal.

Quest'ordine mantiene l'indagine concreta. Se il journal dice Permission denied, correggi le autorizzazioni. Se dice No such file or directory, controlla i percorsi dal punto di vista di systemd. Se dice Dependency failed, esegui il debug della dipendenza per primo. Se dice che il processo è uscito con stato 0/SUCCESS ma il servizio è fallito, controlla Type= e se l'applicazione si demonizza o esce immediatamente.

L'obiettivo non è memorizzare ogni direttiva di systemd. È continuare a far corrispondere il messaggio di fallimento al livello che lo ha prodotto.