systemd i usługi
Po tej lekcji: kursant uruchamia, zatrzymuje, włącza na stałe i blokuje usługi przez systemctl, czyta ich stan i zamienia własny skrypt w usługę, która sama wstaje po awarii.
Wstęp
terminal, skrypt zegarek.sh działa w pętli, ktoś zamyka okno terminala, skrypt znika
Napisałeś świetny skrypt, który co dziesięć sekund coś sprawdza. Uruchamiasz go w terminalu i… zamykasz laptopa. Skrypt nie żyje. Odpalasz z nohup w tle. Serwer się restartuje w nocy. Skrypt nie żyje. Skrypt wywala się na błędzie o trzeciej w nocy. Skrypt nie żyje, a Ty o tym nie wiesz.
A teraz pomyśl o sshd albo serwerze WWW. One jakoś działają latami, wstają po restarcie same, a ich logi da się przeczytać. To nie magia, to kierownik zmiany. Dziś nauczymy się z nim rozmawiać, a na koniec zatrudnimy u niego nasz własny skrypt.
Jednostki: karty pracowników
systemd zarządza jednostkami, po angielsku units. Jednostka to karta w segregatorze kierownika: kto to jest, co robi, kiedy ma przyjść do pracy i co zrobić, jeśli zasłabł. Typ jednostki poznasz po końcówce nazwy:
- .service — usługa, czyli program działający w tle:
sshd.service,cron.service. - .target — grupa jednostek, tryb pracy budynku. Znamy z poprzedniej lekcji.
- .timer — budzik, czyli nowocześniejszy cron. Wróci w module o automatyzacji.
- .socket, .mount, .path, .device — gniazdo sieciowe, punkt montowania, obserwowany plik, urządzenie.
Karty leżą w dwóch miejscach. Te z pakietów w /usr/lib/systemd/system/. Tam nie grzebiesz, bo aktualizacja nadpisze. Twoje własne i Twoje poprawki w /etc/systemd/system/. Jeśli plik o tej samej nazwie jest w obu, wygrywa /etc. Jak w TuneD: wersja admina bije wersję producenta.
systemctl status: karta pacjenta
systemctl status cron● cron.service - Regular background program processing daemon
Loaded: loaded (/usr/lib/systemd/system/cron.service; enabled; preset: enabled)
Active: active (running) since Tue 2026-11-03 21:15:58 UTC; 14s ago
Docs: man:cron(8)
Main PID: 43 (cron)
CGroup: /system.slice/cron.service
└─43 /usr/sbin/cron -f -P
Nov 03 21:15:58 serwer systemd[1]: Started cron.service - Regular background program processing daemon.
Nov 03 21:15:58 serwer cron[43]: (CRON) INFO (pidfile fd = 3)Czytamy od góry:
- kropka — zielona to działa, białe kółko to nie działa, czerwony krzyżyk to awaria.
- Loaded — gdzie leży karta i czy usługa jest
enabled, czyli włączona przy starcie, czydisabled. - Active — co robi teraz i od kiedy.
- Main PID — numer głównego procesu.
- CGroup — wszystkie procesy usługi, czyli jej grupa z lekcji o limitach.
- Na dole ostatnie linijki z jej logów. Dla wielu awarii to wystarczy, żeby wiedzieć, co się stało.
Końcówkę .service możesz pomijać, systemctl sam ją dopisze.
[RHEL] [Ubuntu] Uwaga na nazwy, bo dystrybucje się różnią: serwer SSH to na RHEL sshd, na Ubuntu ssh. Cron to na RHEL crond, na Ubuntu cron. Apache to na RHEL httpd, na Ubuntu apache2. Nie wiesz, jak się nazywa? systemctl list-unit-files --type=service | grep -i ssh.
start, stop, restart, reload
Czynności na teraz, do najbliższego restartu:
sudo systemctl stop cron
systemctl is-active cron
sudo systemctl start cron
sudo systemctl restart croninactiveis-active odpowiada jednym słowem i ustawia kod wyjścia: zero, gdy działa, inny, gdy nie. U mnie po stop wyszło 3. To idealne do skryptów: if systemctl is-active --quiet nginx; then .... Pamiętasz kciuk w górę i w dół? To właśnie on.
A co z reload? Restart to wyłączenie lokalu i otwarcie od nowa: wszyscy goście wychodzą. Reload to szepnięcie kelnerom „od teraz nowe menu”, a goście dalej jedzą. Serwer WWW po reload czyta nową konfigurację bez zrywania połączeń. Ale nie każdy program to umie:
sudo systemctl reload cronFailed to reload cron.service: Job type reload is not applicable for unit cron.service.Cron nie ma przeładowania, więc systemd odmawia. Jeśli nie wiesz, co umie usługa, jest systemctl reload-or-restart: przeładuje, jeśli się da, a jak nie, zrestartuje.
enable, disable, enable --now
start to „przyjdź dziś do pracy”. enable to „wpisuję cię do grafiku na każdy dzień”. To dwie zupełnie różne rzeczy i jedna z najczęstszych pomyłek początkujących: usługa uruchomiona, ale nie włączona, więc po restarcie serwera jej nie ma.
sudo systemctl disable --now cronRemoved "/etc/systemd/system/multi-user.target.wants/cron.service".I znów: tylko dowiązanie. enable wkłada skrót do folderu multi-user.target.wants, czyli na listę „te jednostki chce tryb serwerowy”. disable go wyjmuje. A --now robi przy okazji start albo stop, żeby nie pisać dwóch komend:
sudo systemctl enable --now cron
systemctl is-enabled cronCreated symlink /etc/systemd/system/multi-user.target.wants/cron.service → /usr/lib/systemd/system/cron.service.
enabled[Ubuntu] Przy niektórych usługach zobaczysz też „Synchronizing state of cron.service with SysV service script”. To systemd uprzejmie aktualizuje stare skrypty z czasów SysV, które pakiet wciąż ze sobą nosi. Nic z tym nie robisz.
mask: zamurowane drzwi
disable znaczy „nie przychodź sam z siebie”. Ale usługę wyłączoną dalej może uruchomić ręcznie ktoś inny albo inna usługa, która jej potrzebuje. Czasem chcesz mieć pewność, że coś nie wstanie nigdy, na przykład dwie zapory sieciowe, które się gryzą. Wtedy zamurowujesz drzwi:
sudo systemctl mask cron
sudo systemctl start cronCreated symlink /etc/systemd/system/cron.service → /dev/null.
Failed to start cron.service: Unit cron.service is masked.Maskowanie to dowiązanie karty do /dev/null, czyli czarnej dziury. Karta w /etc wygrywa z kartą z pakietu, a jest pusta. Nikt nie uruchomi. Odmurowanie: sudo systemctl unmask cron.
Gdzie się pali: list-units --failed
Po restarcie serwera pierwsze pytanie brzmi: czy wszystko wstało?
systemctl list-units --failed UNIT LOAD ACTIVE SUB DESCRIPTION
● zepsuty.service loaded failed failed Zepsuta usluga
1 loaded units listed.Pusta lista to dobry dzień. Jest coś? systemctl status tej usługi i czytasz. Po naprawie licznik awarii czyści systemctl reset-failed.
Własna usługa ze skryptu
Czas zatrudnić nasz skrypt. Najpierw sam skrypt, /usr/local/bin/zegarek.sh:
#!/bin/bash
# zegarek.sh - co 10 sekund zapisuje do dziennika, ile jest wolnej pamieci
while true; do
echo "Dostepna pamiec: $(free -m | awk '/^Mem:/ {print $7}') MB"
sleep 10
doneZauważ: żadnego zapisywania do pliku. Skrypt po prostu pisze na ekran, a systemd sam złapie to do dziennika. /usr/local/bin to w planie biurowca miejsce na programy dopisane przez admina. Nie zapomnij o sudo chmod +x.
Usługa będzie działać na własnym, osobnym koncie, bez prawa logowania. Root do liczenia pamięci to jak zatrudnienie dyrektora do parzenia kawy:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin zegarekTeraz karta pracownika, /etc/systemd/system/zegarek.service:
[Unit]
Description=Zegarek - co 10 sekund raportuje wolna pamiec
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/zegarek.sh
Restart=on-failure
RestartSec=5
User=zegarek
LimitNOFILE=4096
[Install]
WantedBy=multi-user.targetTrzy sekcje:
- [Unit] — kim jest: opis i kolejność.
After=network.targetznaczy „przyjdź po tym, jak sieć już jest”. To tylko kolejność, nie wymaganie. - [Service] — jak pracuje.
ExecStartto pełna ścieżka do programu.Type=simplemówi, że program działa na pierwszym planie i nie ucieka w tło.Restart=on-failureto podnoszenie po awarii, aRestartSec=5to pięć sekund przerwy na oddech.Userto konto. ALimitNOFILEto limit otwartych plików, który obiecałem w lekcji o limitach. Pamiętasz, żelimits.confusług nie dotyczy? Właśnie tak się to robi. - [Install] — do jakiego grafiku wpisuje ją
enable.WantedBy=multi-user.targetznaczy: włącz w normalnym trybie serwera.
Zanim włączysz, sprawdź składnię:
systemd-analyze verify /etc/systemd/system/zegarek.serviceCisza znaczy, że jest dobrze. Gdybym wpisał Restart=czasami, dostałbym:
zly.service:5: Failed to parse service restart specifier, ignoring: czasamiA gdybym zamiast własnego konta wpisał User=nobody, verify ostrzegłby: „Special user nobody configured, this is not safe!”. Konto nobody jest współdzielone przez różne programy, więc to zły pomysł. Uruchamiamy:
sudo systemctl daemon-reload
sudo systemctl enable --now zegarek
systemctl status zegarek● zegarek.service - Zegarek - co 10 sekund raportuje wolna pamiec
Loaded: loaded (/etc/systemd/system/zegarek.service; enabled; preset: enabled)
Active: active (running) since Tue 2026-11-03 21:16:33 UTC; 22s ago
Main PID: 189 (zegarek.sh)
CGroup: /system.slice/zegarek.service
├─189 /bin/bash /usr/local/bin/zegarek.sh
└─202 sleep 10
Nov 03 21:16:33 serwer zegarek.sh[189]: Dostepna pamiec: 6962 MB
Nov 03 21:16:43 serwer zegarek.sh[189]: Dostepna pamiec: 6965 MBdaemon-reload to „kierowniku, przeczytaj segregator jeszcze raz”. Bez tego systemd nie wie o nowej karcie ani o zmianach w starej. Jeśli zapomnisz, sam Ci przypomni: „The unit file, source configuration file or drop-ins of zegarek.service changed on disk. Run 'systemctl daemon-reload' to reload units.”
Teraz test najważniejszy. Zabijamy główny proces bez litości, sygnałem 9:
sudo kill -9 $(systemctl show -p MainPID --value zegarek)
sleep 6
journalctl -u zegarek | tail -3Nov 03 21:16:59 serwer systemd[1]: zegarek.service: Main process exited, code=killed, status=9/KILL
Nov 03 21:16:59 serwer systemd[1]: zegarek.service: Failed with result 'signal'.
Nov 03 21:17:04 serwer systemd[1]: zegarek.service: Scheduled restart job, restart counter is at 4.Pięć sekund i kierownik postawił kucharza z powrotem przy kuchence. Licznik restartów mówi, ile razy już musiał. U mnie cztery, bo wcześniej trochę eksperymentowałem. A limit plików? grep 'open files' /proc/PID/limits pokazuje 4096. Działa.
Jak to działa pod spodem
systemctl sam niczego nie uruchamia. To tylko telefon do kierownika: wysyła wiadomość przez szynę D-Bus do procesu numer 1, a ten robi robotę. Dlatego w kontenerze bez systemd systemctl odpowiada błędem, bo nie ma kto odebrać.
Kiedy systemd uruchamia usługę, tworzy dla niej własną grupę cgroups, ustawia konto, limity i środowisko, a potem odpala ExecStart. Wszystko, co proces wypisze na ekran, trafia nie na ekran, tylko do systemd-journald, czyli dziennika. Kiedy proces umiera, jądro powiadamia rodzica, a rodzicem jest właśnie PID 1. Kierownik od razu wie, że kucharz padł, z jakim kodem i od jakiego sygnału, i decyduje według Restart=. Dzięki grupie wie też, że przy stop ma zamknąć wszystkie procesy usługi, nawet te, które skrypt odpalił po drodze, jak nasz sleep. Żaden nie ucieknie przez tylne drzwi.
Ściąga flag
| Komenda | Flaga | Co robi |
|---|---|---|
| systemctl | status USŁUGA | stan, PID, ostatnie logi |
| systemctl | start / stop / restart | uruchamia / zatrzymuje / restartuje teraz |
| systemctl | reload | przeładowuje konfigurację bez restartu |
| systemctl | reload-or-restart | reload, a jak się nie da, restart |
| systemctl | enable / disable | włącza / wyłącza start przy rozruchu |
| systemctl | enable --now | włącza i od razu uruchamia |
| systemctl | mask / unmask | blokuje całkowicie / odblokowuje |
| systemctl | is-active / is-enabled | jedno słowo i kod wyjścia, do skryptów |
| systemctl | list-units --failed | jednostki w stanie awarii |
| systemctl | list-unit-files --type=service | wszystkie usługi i ich stan włączenia |
| systemctl | daemon-reload | ponownie wczytuje pliki jednostek |
| systemctl | cat / edit | pokazuje kartę / tworzy nakładkę |
| systemctl | reset-failed | czyści stan awarii |
| systemctl | show -p WŁAŚCIWOŚĆ --value | jedna wartość, np. MainPID |
| systemd-analyze | verify PLIK | sprawdza składnię jednostki |
⚠ Typowy błąd
Piszesz szybki skrypt, kartkę usługi, daemon-reload, start. I:
× zepsuty.service - Zepsuta usluga
Loaded: loaded (/etc/systemd/system/zepsuty.service; static)
Active: failed (Result: exit-code) since Tue 2026-11-03 21:17:10 UTC; 7ms ago
Process: 256 ExecStart=/usr/local/bin/zepsuty.sh (code=exited, status=203/EXEC)203/EXEC to kod systemd, który znaczy: „nie udało mi się nawet uruchomić programu”. Skrypt nie zdążył wykonać ani jednej linijki. Dwie najczęstsze przyczyny. Pierwsza: plik nie ma prawa wykonania, czyli zapomniałeś chmod +x. Druga, podstępniejsza: zrobiłeś chmod +x, a w dzienniku dalej:
zepsuty.service: Failed to execute /usr/local/bin/zepsuty.sh: Exec format errorBrakuje pierwszej linijki #!/bin/bash. W terminalu skrypt działał, bo Bash był tak miły, że uruchomił go u siebie. Ale systemd to nie Bash. On pyta jądro, a jądro bez tej linijki nie ma pojęcia, czym ten plik otworzyć. To jak wysłanie przepisu do kuchni bez informacji, czy to po polsku, czy po węgiersku.
Naprawa: #!/bin/bash na początku, chmod +x, systemctl start, a po wszystkim systemctl reset-failed.
✎ Zadanie
Napisz usługę licznik.service, która uruchamia skrypt /usr/local/bin/licznik.sh. Skrypt co 5 sekund wypisuje kolejną liczbę: 1, 2, 3 i tak dalej. Usługa ma wstawać po każdym zakończeniu, nie tylko po awarii (podpowiedź: inna wartość Restart=). Sprawdź ją systemd-analyze verify, włącz jedną komendą na stałe i od razu, zabij jej główny proces i pokaż, że wstała i liczy od nowa. Na koniec wyłącz ją i zamaskuj.
Pokaż rozwiązanie
Skrypt /usr/local/bin/licznik.sh:
#!/bin/bash
# licznik.sh - co 5 sekund wypisuje kolejna liczbe
n=0
while true; do
n=$((n + 1))
echo "$n"
sleep 5
doneKarta /etc/systemd/system/licznik.service:
[Unit]
Description=Licznik - co 5 sekund kolejna liczba
[Service]
ExecStart=/usr/local/bin/licznik.sh
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.targetRestart=always podnosi usługę po każdym zakończeniu, nawet grzecznym. Dalej chmod +x, systemd-analyze verify, daemon-reload, enable --now i zabójstwo:
sudo kill -9 $(systemctl show -p MainPID --value licznik)
journalctl -u licznik | tail -4Nov 03 21:19:36 serwer systemd[1]: licznik.service: Failed with result 'signal'.
Nov 03 21:19:39 serwer systemd[1]: licznik.service: Scheduled restart job, restart counter is at 1.
Nov 03 21:19:39 serwer systemd[1]: Started licznik.service - Licznik - co 5 sekund kolejna liczba.
Nov 03 21:19:39 serwer licznik.sh[636]: 1Wstał i liczy od jedynki. Nowy proces, nowa pamięć. A teraz pułapka, w którą pewnie wpadłeś:
sudo systemctl disable --now licznik
sudo systemctl mask licznikFailed to mask unit: File /etc/systemd/system/licznik.service already exists.Pamiętasz, jak działa maskowanie? Wkłada do /etc/systemd/system dowiązanie do /dev/null. A tam już leży nasza karta. Nie da się zamurować drzwi, które są ścianą nośną. Rozwiązanie: własne jednostki, które chcesz kiedyś maskować, trzymaj w /usr/local/lib/systemd/system/. To też oficjalne miejsce na karty od admina. Przenosisz, daemon-reload i mask działa:
Created symlink /etc/systemd/system/licznik.service → /dev/null.
Failed to start licznik.service: Unit licznik.service is masked.Jeśli wymyśliłeś to sam, zasłużyłeś na kawę. Jeśli nie — też, bo właśnie się tego nauczyłeś.
Co dalej
Przez cała lekcja zaglądaliśmy do dziennika tylnymi drzwiami. W następnej lekcji wchodzimy frontowymi: journalctl, klasyczne pliki w /var/log, rsyslog, logrotate, czyli kto pilnuje, żeby logi nie zjadły dysku, i jak samemu coś do dziennika napisać.