Padroneggiare Systemd: Creare il Tuo Primo File di Unità di Servizio Personalizzato
Impara i fondamenti della gestione dei servizi Systemd creando un file di unità personalizzato. Questo tutorial analizza le sezioni essenziali `[Unit]`, `[Service]` e `[Install]`, fornendo istruzioni passo-passo per definire, abilitare, avviare e verificare un servizio di base in background su Linux utilizzando `systemctl`.
Padroneggiare Systemd: Creare il Tuo Primo File di Unità di Servizio Personalizzato
Un'unità di servizio systemd personalizzata è ciò che si utilizza quando uno script o una piccola applicazione ha superato una sessione terminale, una finestra screen o una soluzione fragile con cron. Forse hai un worker che dovrebbe riavviarsi dopo un crash. Forse una piccola API interna deve avviarsi dopo che la rete è pronta. Forse uno script di backup dovrebbe essere eseguito come servizio controllato in modo che i suoi log risiedano nel journal e gli operatori possano usare gli stessi comandi systemctl che usano per tutto il resto.
La parte utile di systemd non è che il file di unità sia complicato. È che il file di unità rende esplicito il processo: cosa viene eseguito, con chi viene eseguito, quando si avvia, come si ferma, dove vanno i log e cosa dovrebbe fare systemd quando fallisce. Una volta che queste decisioni sono state scritte, il servizio diventa molto più facile da gestire.
Questa guida costruisce un piccolo servizio da zero. L'esempio è volutamente semplice, ma i pattern sono gli stessi che useresti per un worker in background, un consumer di code, un esportatore di metriche o un demone interno.
Inizia con un comando reale, non un file di unità
Una buona unità di servizio inizia con un comando che funziona già manualmente. Prima di scrivere la configurazione systemd, assicurati di poter eseguire il programma direttamente e di capire cosa fa in primo piano.
Per questo esempio, crea un piccolo script reporter:
sudo install -d -o root -g root -m 0755 /opt/my-custom-service
sudo nano /opt/my-custom-service/reporter.sh
Aggiungi questo contenuto:
#!/usr/bin/env bash
set -euo pipefail
while true; do
echo "$(date --iso-8601=seconds) reporter heartbeat"
sleep 10
done
Rendilo eseguibile e testalo:
sudo chmod 0755 /opt/my-custom-service/reporter.sh
/opt/my-custom-service/reporter.sh
Fermalo con Ctrl-C dopo aver visto alcune righe. Nota che lo script scrive sull'output standard invece di aggiungere direttamente a /var/log/reporter.log. Questo è intenzionale. Per la maggior parte dei servizi personalizzati, lasciare che systemd catturi stdout e stderr nel journal è più pulito che far gestire a ogni script i propri permessi di file di log, la rotazione e il comportamento in caso di fallimento.
Crea un utente di servizio dedicato
Evita di eseguire servizi applicativi come root a meno che non necessitino realmente di privilegi di root. Uno script heartbeat non lo fa. Un'app web di solito non lo fa. Un worker che legge da una coda e scrive in un database di solito non lo fa.
Crea un utente di sistema bloccato:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin reporter
Se la tua distribuzione usa un percorso nologin diverso, controllalo con:
command -v nologin || command -v false
L'utente del servizio dovrebbe possedere solo i file che deve scrivere. In questo esempio lo script scrive nel journal tramite systemd, quindi non necessita della proprietà di /opt/my-custom-service.
Scrivi l'unità di servizio
Le unità di sistema personalizzate gestite dall'amministratore risiedono normalmente in /etc/systemd/system/. Le unità dei pacchetti dei fornitori risiedono comunemente in /usr/lib/systemd/system/ o /lib/systemd/system/, a seconda della distribuzione. Non modificare direttamente i file di unità dei fornitori quando puoi evitarlo; usa /etc/systemd/system/ per le tue unità e i drop-in per le sovrascritture.
Crea l'unità:
sudo nano /etc/systemd/system/my-reporter.service
Usa questa come prima versione pratica:
[Unit]
Description=My Custom Reporter Service
Documentation=man:systemd.service(5)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=reporter
Group=reporter
ExecStart=/opt/my-custom-service/reporter.sh
Restart=on-failure
RestartSec=5s
WorkingDirectory=/opt/my-custom-service
StandardOutput=journal
StandardError=journal
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
La sezione [Unit] descrive le relazioni. After=network-online.target controlla l'ordinamento; non attira da solo il target network-online. Wants=network-online.target chiede a systemd di avviare anche quel target. Se il tuo servizio non necessita della rete, rimuovi entrambe le righe e mantieni l'unità più semplice.
La sezione [Service] descrive il processo. Type=simple è giusto per un processo in primo piano che non si mette in background da solo. Questo è il caso comune per i servizi moderni. Se un demone legacy si mette in background, scrive un file PID e restituisce il controllo alla shell, allora potresti aver bisogno di Type=forking, ma non usarlo solo perché la parola suona più simile a un demone.
ExecStart dovrebbe essere un percorso assoluto. Le funzionalità della shell come pipe, redirect e && non vengono interpretate a meno che non si esegua esplicitamente una shell, ad esempio ExecStart=/bin/bash -lc 'comando uno && comando due'. Preferisci uno script quando il comando necessita di logica di shell; è più facile da testare e da leggere.
Restart=on-failure dice a systemd di riavviare il servizio dopo uscite anomale. Non si riavvierà dopo un systemctl stop pulito. RestartSec=5s impedisce a un ciclo di riavvio stretto di martellare la macchina.
Le opzioni di hardening qui sono modeste ma utili. NoNewPrivileges=true impedisce al processo e ai suoi figli di ottenere nuovi privilegi tramite binari setuid o capacità di file. PrivateTmp=true dà al servizio una vista privata di /tmp. Queste sono generalmente sicure per servizi semplici, ma testale con applicazioni reali perché alcuni software si aspettano percorsi temporanei condivisi.
Carica e avvia l'unità
Dopo aver aggiunto o modificato un file di unità, ricarica la configurazione del manager systemd:
sudo systemctl daemon-reload
Avvia il servizio ora:
sudo systemctl start my-reporter.service
Controlla il suo stato:
systemctl status my-reporter.service
Vuoi vedere Active: active (running). Se è fallito, non indovinare. Leggi i log:
journalctl -u my-reporter.service -n 50 --no-pager
Segui i log in tempo reale durante il test:
journalctl -u my-reporter.service -f
Se il percorso dello script è sbagliato, i permessi mancano, l'utente non esiste o il comando esce immediatamente, systemd di solito lo dirà chiaramente nel journal.
Abilita l'avvio all'accensione
Avviare un servizio e abilitare un servizio sono azioni diverse. start lo esegue ora. enable lo collega al target di avvio in modo che si avvii ai prossimi avvii.
sudo systemctl enable my-reporter.service
Puoi fare entrambi in un unico comando dopo che l'unità è stata testata:
sudo systemctl enable --now my-reporter.service
Per vedere se è abilitato:
systemctl is-enabled my-reporter.service
Rendi i fallimenti più facili da diagnosticare
I fallimenti più comuni del primo servizio sono normali problemi Linux travestiti da systemd.
Se vedi status=203/EXEC, systemd non ha potuto eseguire il comando. Controlla il percorso, il bit eseguibile, la riga shebang e le terminazioni di riga. Uno script copiato da Windows con terminazioni CRLF può fallire anche se sembra a posto in un editor.
Se vedi errori di permesso, ricorda che il servizio viene eseguito come reporter, non come il tuo utente shell. Testa con:
sudo -u reporter /opt/my-custom-service/reporter.sh
Se il servizio si avvia e si ferma immediatamente, il processo probabilmente esce. Type=simple si aspetta che il comando continui a essere eseguito. Un comando di configurazione one-shot dovrebbe usare Type=oneshot, non simple.
Se i log mancano, controlla se l'applicazione scrive su file invece di stdout/stderr, o se cambia utente internamente. Per la maggior parte dei piccoli servizi, scrivere su stdout è l'opzione meno sorprendente.
Comandi di gestione utili
Una volta che l'unità è in posizione, l'operatività quotidiana è semplice:
sudo systemctl start my-reporter.service
sudo systemctl stop my-reporter.service
sudo systemctl restart my-reporter.service
sudo systemctl reload my-reporter.service
systemctl status my-reporter.service
journalctl -u my-reporter.service --since "1 hour ago"
systemctl cat my-reporter.service
systemctl show my-reporter.service -p User -p Restart -p ExecStart
systemctl cat è particolarmente utile su macchine con sovrascritture drop-in perché mostra i frammenti di unità effettivi che systemd sta leggendo.
Un file di unità personalizzato non deve essere intelligente. Deve essere noioso, esplicito e testabile. Ottieni il comando funzionante manualmente, eseguilo come utente dedicato, scrivi l'unità più piccola che descrive accuratamente il servizio, ricarica systemd e usa il journal quando qualcosa fallisce. Questo flusso di lavoro scala da uno script reporter giocattolo a demoni di produzione reali.
Aggiungi ambiente e configurazione in modo pulito
Prima o poi il servizio avrà bisogno di configurazione: una porta, un URL di database, un flag di funzionalità o un percorso. Evita di seppellire quei valori all'interno del file di unità quando variano per ambiente. Un pattern comune è un file di ambiente:
sudo nano /etc/my-reporter.env
Esempio:
REPORT_INTERVAL=10
REPORT_LABEL=production
Blocca il file se contiene qualcosa di sensibile:
sudo chown root:reporter /etc/my-reporter.env
sudo chmod 0640 /etc/my-reporter.env
Poi referenzialo dall'unità:
[Service]
EnvironmentFile=/etc/my-reporter.env
ExecStart=/opt/my-custom-service/reporter.sh
Nello script, leggi la variabile con un valore predefinito:
interval="${REPORT_INTERVAL:-10}"
label="${REPORT_LABEL:-default}"
Per i segreti, fai attenzione. Una variabile d'ambiente può essere esposta tramite ispezione del processo o metadati del servizio a seconda della configurazione di sistema e dei permessi. Per valori altamente sensibili, preferisci un gestore di segreti appropriato, un file di credenziali con permessi stretti o le funzionalità di credenziali più recenti di systemd se la tua distribuzione le supporta. L'abitudine importante è decidere deliberatamente invece di spargere password nei file di unità perché è comodo.
Usa sovrascritture drop-in per modifiche locali
Se un pacchetto installa un'unità e devi cambiare un'impostazione, non modificare il file del fornitore. Usa un drop-in:
sudo systemctl edit my-reporter.service
Questo apre un file di sovrascrittura sotto /etc/systemd/system/my-reporter.service.d/. Ad esempio:
[Service]
RestartSec=15s
Ricarica e riavvia dopo aver salvato:
sudo systemctl daemon-reload
sudo systemctl restart my-reporter.service
Controlla il risultato unito:
systemctl cat my-reporter.service
I drop-in sono importanti perché gli aggiornamenti dei pacchetti possono sostituire le unità dei fornitori. Le tue sovrascritture in /etc rimangono visibili e intenzionali.
Pensa al comportamento di spegnimento
L'avvio è solo metà del ciclo di vita. Un servizio dovrebbe anche fermarsi in modo pulito. Per impostazione predefinita, systemd invia SIGTERM, attende e poi può inviare SIGKILL se il processo non esce. Per molti servizi semplici va bene. Per worker di code, processori di upload e scrittori di database, potresti aver bisogno di gestire la terminazione in modo che il processo finisca o abbandoni il lavoro corrente in modo sicuro.
Puoi regolare il timeout:
[Service]
TimeoutStopSec=30s
KillSignal=SIGTERM
Non impostare timeout di stop estremamente lunghi a meno che tu non abbia una ragione. Spegnimenti lunghi rallentano distribuzioni, riavvii e recupero da incidenti. Un worker dovrebbe di solito smettere di accettare nuovo lavoro, finire l'elemento che sta elaborando e uscire entro un tempo limitato.
Previeni cicli di riavvio rumorosi
Restart=on-failure è utile, ma un servizio rotto può comunque riavviarsi ripetutamente. Aggiungi limiti quando la modalità di fallimento potrebbe essere rumorosa:
[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5
Questo dice a systemd di smettere di provare dopo troppi fallimenti nell'intervallo. Quando risolvi il problema, reimposta lo stato fallito:
sudo systemctl reset-failed my-reporter.service
sudo systemctl start my-reporter.service
Questo comando è utile anche durante il test. Un servizio può rimanere in uno stato fallito anche dopo aver corretto lo script o i permessi.
Convalida le unità prima di fare affidamento su di esse
Systemd ha un utile verificatore:
systemd-analyze verify /etc/systemd/system/my-reporter.service
Non catturerà ogni problema dell'applicazione, ma può catturare errori di sintassi, impostazioni sconosciute sulla tua versione di systemd e alcuni problemi di ordinamento. Eseguilo dopo modifiche più grandi o quando copi un'unità tra distribuzioni. Le funzionalità di systemd variano per versione, quindi un'opzione di hardening che funziona su un nuovo server Fedora potrebbe non esistere su una distribuzione enterprise più vecchia.
Controlla anche la vista delle dipendenze dell'unità quando l'ordinamento di avvio diventa confuso:
systemctl list-dependencies my-reporter.service
systemctl list-dependencies --reverse my-reporter.service
Il primo comando mostra cosa l'unità tira dentro o da cui dipende. La vista inversa mostra cosa dipende da essa. Questo è utile quando un servizio si avvia inaspettatamente o quando disabilitare un'unità ne influenza un'altra.
Decidi se il servizio è a livello di sistema o a livello di utente
Questo articolo usa un servizio di sistema sotto /etc/systemd/system/. Questa è la scelta giusta per servizi macchina che dovrebbero avviarsi all'accensione e funzionare indipendentemente da una sessione di login. Systemd supporta anche servizi utente, solitamente gestiti con systemctl --user, per processi in background per utente.
Non usare un servizio utente per demoni di infrastruttura solo per evitare sudo. I servizi utente hanno regole di ciclo di vita, gestione dell'ambiente e comportamento di login diversi. Per worker applicativi, esportatori e agenti a livello di host, un servizio di sistema con un utente dedicato con privilegi minimi è solitamente più facile da ragionare.
Mantieni la prima unità noiosa
È tentante aggiungere ogni direttiva di hardening che trovi online: dispositivi privati, percorsi di sola lettura, filtri syscall, limitazione delle capacità, restrizioni dei namespace e altro ancora. Questi sono strumenti preziosi, ma aggiungili uno alla volta dopo che il servizio funziona. Quando un servizio pesantemente ristretto non riesce ad avviarsi, i principianti spesso non riescono a dire se l'unità è sbagliata, l'applicazione è sbagliata o un'impostazione di sandboxing ha bloccato un file di cui ha bisogno.
Un buon percorso di produzione è incrementale: servizio funzionante, utente dedicato, logging affidabile, politica di riavvio, hardening di base, poi sandboxing più forte dopo aver avuto un test che dimostra che l'applicazione si comporta ancora correttamente.