Zależności i warunki w systemd: jak poprawnie konfigurować
Zrozum różnice między Requires, Wants, After oraz Condition. Dowiedz się, dlaczego jednostki zawodzą przy starcie systemu i jak debugować błędy typu kod 2 w plikach unit.
Requires nie oznacza After
Zależności i warunki w systemd to cztery odrębne mechanizmy, które w większości plików jednostek są używane tak, jakby stanowiły jedną całość. Requires= oraz Wants= decydują o tym, które inne jednostki zostaną wywołane. After= oraz Before= określają kolejność uruchamiania jednostek. ExecStartPre= wykonuje sprawdzenie, którego niepowodzenie może spowodować błąd jednostki. Rodziny Condition oraz Assert decydują o tym, czy jednostka w ogóle zostanie uruchomiona. Każdy z tych mechanizmów jest niezależny od pozostałych, dlatego jednostka może wymagać innej jednostki i jednocześnie uruchomić się w tym samym momencie co ona.
To ostatnie zdanie jest przyczyną niemal każdego zgłoszenia typu „działa, gdy uruchamiam ręcznie, ale zawodzi przy starcie systemu”.
[Unit]
Description=Inventory API
Requires=postgresql.service
[Service]
ExecStartPre=/usr/bin/pg_isready -h 127.0.0.1 -t 5
ExecStart=/usr/local/bin/inventory-apiRequires=postgresql.service wciąga PostgreSQL do tej samej transakcji startowej. Nie czeka jednak na jego gotowość. systemd uruchamia oba zadania równolegle, więc pg_isready wykonuje się, gdy PostgreSQL wciąż otwiera swój katalog danych. Proces kończy się kodem 2, ponieważ nikt jeszcze nie nasłuchuje, a jednostka zawodzi, zanim ExecStart zostanie osiągnięte. Uruchomienie sudo systemctl start inventory-api godzinę później kończy się sukcesem, ponieważ PostgreSQL jest już w tym czasie aktywny. W pliku jednostki nic się nie zmieniło, dlatego wygląda on na poprawny.
Rozwiązanie wymaga dodania jednej linii.
[Unit]
Requires=postgresql.service
After=postgresql.serviceBardziej szczegółowy problem ukrywa się w tym samym miejscu. Niepowodzenie zależności Requires= powstrzymuje jednostkę przed startem tylko wtedy, gdy ustawiono dla niej również After=. Bez określenia kolejności, systemd uruchomił już jednostkę w momencie, gdy inna zawiodła, więc nie ma już czego anulować. Samo Requires= nie zapewnia ochrony, której oczekują użytkownicy. Należy umieszczać After= obok każdego Requires= oraz każdego Wants=, chyba że istnieją konkretne powody, by tego nie robić.
Co zapewniają dyrektywy Requires, Wants, Requisite oraz BindsTo
Wszystkie te ustawienia definiują zależności. Żadne z nich nie ustala kolejności uruchamiania.
Wants=: wymusza uruchomienie jednostki zależnej. Jeśli jednostka ta zawiedzie lub nie istnieje, jednostka nadrzędna i tak zostanie uruchomiona. Jest to mechanizm tworzony przezsystemctl enable, jako dowiązanie symboliczne wewnątrz katalogu.wants/.Requires=: wymusza uruchomienie jednostki zależnej. Jeśli jednostka ta zawiedzie, a dodatkowo skonfigurowanoAfter=, jednostka nadrzędna nie zostanie uruchomiona. Jeśli jednostka zależna zostanie później jawnie zatrzymana, jednostka nadrzędna również zostanie zatrzymana.Requisite=: nie wymusza uruchomienia jednostki zależnej. Jeśli nie jest ona już aktywna, jednostka nadrzędna natychmiast kończy działanie z błędem.BindsTo=: działa podobnie doRequires=, przy czym jednostka nadrzędna zatrzymuje się zawsze, gdy jednostka zależna przestaje działać z jakiegokolwiek powodu, w tym w przypadku awarii sprzętowej.PartOf=: zdarzenia zatrzymania i restartu są propagowane z jednostki zależnej na jednostkę nadrzędną. Uruchomienie nie jest propagowane.Conflicts=: uruchomienie tej jednostki powoduje zatrzymanie jednostki wskazanej.
W przypadku demona komunikującego się z innym demonem, połączenie Wants= oraz After= jest zazwyczaj właściwym wyborem. Requires= wiąże cykle życia usług: zatrzymanie bazy danych w celu konserwacji powoduje wyłączenie aplikacji, która nie uruchomi się automatycznie po powrocie bazy. Wants= wraz z After= zapewnia kolejność startu bez ścisłego powiązania cykli życia, a polityka restartu obsługuje przypadki, w których zależność przestaje być dostępna w trakcie pracy systemu.
System dziedziczy również zależności, które nie zostały jawnie zdefiniowane. Przy użyciu DefaultDependencies=yes, będącego ustawieniem domyślnym, zwykła usługa automatycznie otrzymuje Requires=sysinit.target, After=sysinit.target basic.target oraz Conflicts=shutdown.target. Dlatego usługa z niemal pustą sekcją [Unit] nadal uruchamia się w późnej fazie bootowania i jest poprawnie zatrzymywana podczas wyłączania systemu.
Kolejność After oraz Before porządkuje transakcję, nic więcej
After= oraz Before= służą wyłącznie do ustalania kolejności. Nie nakładają żadnych innych wymagań. Użycie After=redis.service w jednostce, która nie wywołuje Redis, jest operacją bez znaczenia: jeśli redis.service nie jest częścią transakcji, nie ma na co czekać, więc jednostka uruchamia się natychmiast.
Warto to powtórzyć, ponieważ jest to dokładny opis błędu network-online.target, o którym mowa poniżej. Kolejność oczekuje tylko na jednostki, które są już uruchamiane w ramach tej samej transakcji.
Ta para jest symetryczna. Zapis After=b.service w a.service oznacza to samo, co Before=a.service w b.service, dlatego należy wybrać jeden z nich i umieścić go w zarządzanej jednostce. Kolejność jest automatycznie odwracana podczas zamykania systemu, więc After=b.service oznacza również, że jednostka zostanie zatrzymana przed b.service.
After= oczekuje na "uruchomienie", a Type= definiuje, co to oznacza
After= czeka, aż inna jednostka zakończy proces uruchamiania. To, co oznacza "zakończenie uruchamiania", jest w całości określane przez Type= tej jednostki.
Type=simple: natychmiast po tym, jak systemd utworzy proces (fork). Program może jeszcze nie przetworzyć konfiguracji, a tym bardziej nie otworzyć gniazda (socket).Type=exec: natychmiast po powodzeniuexecve(). Nieco silniejszy wariant. Nadal nie informuje o gotowości do pracy.Type=forking: w momencie zakończenia pierwotnego procesu nadrzędnego.Type=oneshot: w momencie zakończenia procesu. Tutaj "uruchomienie" faktycznie oznacza zakończenie pracy.Type=notify: gdy usługa wyśleREADY=1do swojego gniazda powiadomień. Jest to jedyny typ, który raportuje rzeczywistą gotowość.
Zatem After= w przypadku demona Type=simple jest słabą obietnicą i stanowi drugą połowę wyścigu w pierwszym przykładzie. Jeśli jednostka, od której zależy usługa, działa jako Type=simple, kolejność po niej nie oznacza, że akceptuje ona połączenia. Istnieją dwie poprawne odpowiedzi. Należy ustawić kolejność po jednostce gniazda (socket unit), aby jądro systemu kolejkowało przychodzące połączenia, podczas gdy demon wciąż się uruchamia. Alternatywnie można zaimplementować ponawianie prób w usłudze i pozwolić polityce restartu na obsługę tego stanu. Typ używany przez jednostkę jest widoczny w systemctl cat, a lektura ustawienia Type= i znaczenia poszczególnych wartości dla systemd jest zalecana przed poleganiem na kolejności uruchamiania.
ExecStartPre to bramka, której niepowodzenie przerywa działanie jednostki
ExecStartPre= uruchamia się przed ExecStart=. Jeśli zakończy się kodem innym niż zero, aktywacja zostaje przerwana, a jednostka przechodzi w stan failed. ExecStart= nigdy nie zostanie uruchomione. Jest to mechanizm odpowiedzialny za znaczną część przypadków, w których jednostki kończą się niepowodzeniem bez komunikatu z właściwego programu, ponieważ program ten nigdy nie został uruchomiony.
Fakty, które często umykają:
- To nie jest powłoka (shell). Nie działają potoki, przekierowania, znaki wieloznaczne ani
&&. Pierwszy token musi być ścieżką bezwzględną. W przypadku konieczności użycia składni powłoki, należy zawinąć linię w/bin/sh -c '...'. - Prefiks
-sprawia, że niezerowy kod wyjścia nie jest traktowany jako błąd krytyczny:ExecStartPre=-/usr/bin/optional-check. - Każde
ExecStartPre=musi zakończyć działanie przed uruchomieniem kolejnego. Nie może ono uruchamiać procesów działających w tle. - Wszystkie linie
ExecStartPre=współdzieląTimeoutStartSec=zExecStart=. Wstępna weryfikacja, która oczekuje w pętli na bazę danych, zużywa limit czasu startu, co powoduje błąd jednostkiResult: timeoutpo pojawieniu sięstart operation timed out. Terminating.w dzienniku.
Linia błędu wskazuje proces sterujący, a nie proces główny:
inventory-api.service: Control process exited, code=exited, status=2/INVALIDARGUMENT
inventory-api.service: Failed with result 'exit-code'.Należy uważnie czytać tę nazwę symboliczną. systemd mapuje małe kody wyjścia zgodnie ze stałą tabelą, więc 2 zawsze wyświetli się jako INVALIDARGUMENT, niezależnie od tego, co dany kod oznaczał dla programu. status=203/EXEC to kod niosący rzeczywistą informację: systemd nie był w stanie wykonać pliku binarnego, ponieważ ścieżka jest błędna lub plik nie posiada uprawnień do wykonywania.
Nie należy używać ExecStartPre= do tworzenia katalogów. RuntimeDirectory=, StateDirectory=, LogsDirectory= oraz CacheDirectory= tworzą je z odpowiednim właścicielem i uprawnieniami, a RuntimeDirectory= jest usuwane po zatrzymaniu usługi. Działają one również poprawnie w ramach DynamicUser=, czego nie zapewnia ręcznie napisane mkdir.
Warunek kończy się po cichu. Asercja kończy się głośno.
Rodziny Condition oraz Assert wykonują te same testy. Różnią się jedynie reakcją na niepowodzenie testu.
Nieudany Condition...= pomija jednostkę. Zadanie startowe jest raportowane jako udane. Jednostka pozostaje w stanie inactive (dead), nic nie jest oznaczane jako nieudane, żaden alert nie jest wyzwalany, a dziennik rejestruje jedną linię:
Condition check resulted in Inventory API being skipped.W systemd 250 i nowszych, systemctl status wyświetla przyczynę bezpośrednio:
Active: inactive (dead)
Condition: start condition unmet at Thu 2026-08-20 09:14:02 UTC; 2min agoWcięta linia poniżej wskazuje dokładną dyrektywę, która zawiodła, na przykład ConditionPathExists=/etc/inventory/api.conf was not met.
Nieudany Assert...= powoduje błąd jednostki. Dziennik wskazuje Assertion failed for Inventory API., a jednostka kończy pracę w stanie failed (Result: assert), co jest wystarczająco wyraźne, aby systemy monitoringu zareagowały.
Wybór między nimi zależy od znaczenia nieudanego testu. Condition oznacza: „ta jednostka nie ma zastosowania na tej maszynie”. Assert oznacza: „to musi być prawdą, a jeśli nie jest, powiadom administratora”. Większość jednostek wymaga Condition. Wybieraj Assert tylko wtedy, gdy ciche zaniechanie działania jest gorsze niż błąd jednostki.
Z rodziną Condition wiążą się dwie pułapki.
Po pierwsze, nieudany warunek nie powoduje błędu jednostek, które od niego zależą. Jeśli a.service posiada Requires=b.service, a b.service jest pomijane ze względu na warunek, zadanie startowe dla b.service nadal jest uznawane za wykonane, więc a.service uruchamia się normalnie w środowisku, w którym b nie działa. Warunek chroni tylko tę jednostkę, w której został zapisany.
Po drugie, warunki są sprawdzane przy każdym uruchomieniu jednostki, w momencie wykonywania zadania. Jednostka wyzwalana przez timer systemd na VPS może być pomijana sto razy z rzędu i nigdy nie wyglądać na uszkodzoną. Jest to ten sam rodzaj cichej operacji co zadanie cron, które uruchamia się, ale nic nie robi, a wykrywa się go w ten sam sposób: należy czytać dziennik jednostki zamiast polegać na jej kodzie wyjścia.
Warunki przydatne w administracji serwerem:
ConditionPathExists=/etc/inventory/api.conforaz jego negacjaConditionPathExists=!/etc/inventory/api.conf.ConditionFileNotEmpty=orazConditionDirectoryNotEmpty=, dla plików konfiguracyjnych lub katalogów danych, które zostały utworzone przez pakiet, ale pozostały puste.ConditionVirtualization=, dzięki czemu jednostka wymagająca konkretnego interfejsu jądra może zawieraćConditionVirtualization=!container. Sprawdź, co raportuje system za pomocąsystemd-detect-virt.ConditionHost=dopasowuje nazwę hosta lub identyfikator maszyny (machine ID), co pozwala jednej współdzielonej jednostce zachowywać się różnie na dwóch serwerach.ConditionKernelCommandLine=orazConditionKernelVersion=, dla jednostek powiązanych z parametrem rozruchu lub minimalną wersją jądra.
Puste przypisanie czyści listę; w ten sposób plik typu drop-in usuwa warunek dostarczony przez pakiet:
[Unit]
ConditionPathExists=
ConditionPathExists=/srv/inventory/api.confDlaczego network.target nie oznacza, że sieć jest dostępna
network.target to punkt synchronizacji, a nie stan. Podczas rozruchu kolejność po tym celu oznacza jedynie, że oprogramowanie do zarządzania siecią zostało uruchomione. Nie oznacza to, że interfejs posiada adres IP ani że istnieje trasa do Internetu. Cel ten istnieje głównie dla przeciwnego kierunku: jednostka z dyrektywą After=network.target jest zatrzymywana przed wyłączeniem sieci podczas zamykania systemu.
network-online.target to cel, który faktycznie czeka. Jest on obsługiwany przez usługę typu wait-online, zależną od używanego menedżera sieci:
systemd-networkd-wait-online.service, gdy systemd-networkd zarządza łączami, co jest standardem na serwerach Ubuntu konfigurowanych przez netplan.NetworkManager-wait-online.servicew przypadku NetworkManager.
Starsze konfiguracje ifupdown uzyskują ten sam efekt za pomocą networking.service. Niezależnie od używanego rozwiązania, poprawne użycie tego celu wymaga dwóch linii, a nie jednej.
[Unit]
Wants=network-online.target
After=network-online.targetnetwork-online.target nie jest częścią domyślnej transakcji rozruchowej i nic go automatycznie nie wywołuje. Zapisanie tylko After= powoduje ustawienie kolejności względem jednostki, która nigdy nie została dodana do kolejki, więc kolejność ta nie ma żadnego wpływu. Jest to opisana wcześniej operacja no-op w swojej najbardziej kosztownej formie. Linia Wants= jest tym, co wprowadza cel do transakcji, dzięki czemu linia After= ma na co czekać.
Druga istotna kwestia to fakt, że stan "online" jest definiowany przez implementację wait-online, a nie przez systemd. systemd-networkd-wait-online kończy działanie, gdy zarządzane przez niego łącza osiągną skonfigurowany stan. Nie sprawdza on, czy DNS działa, ani czy jakikolwiek zdalny host jest osiągalny.
Ta definicja powoduje częstą awarię na serwerach VPS. Maszyna z drugim interfejsem dla sieci prywatnej, zadeklarowanym w netplan, ale bez przypisanego adresu, powoduje, że usługa oczekiwania zawiesza się do momentu przekroczenia limitu czasu:
systemd-networkd-wait-online[612]: Timeout occurred while waiting for network connectivity.
systemd-networkd-wait-online.service: Failed with result 'exit-code'.Rozruch trwa o dwie minuty dłużej, ponieważ domyślny limit czasu wynosi 120 sekund. Istnieją dwa rozwiązania. Oznacz nieużywany interfejs jako optional: true w pliku netplan, aby networkd przestał na niego czekać. Alternatywnie dodaj plik typu drop-in do usługi oczekiwania, wskazując istotne łącze za pomocą --interface= lub przekazując --any, aby usługa kończyła działanie, gdy tylko jedno łącze będzie aktywne.
Jeszcze lepiej jest unikać polegania na tym celu. Wiele usług jest ustawianych po network-online.target tylko dlatego, że wiążą się z konkretnym adresem i kończą błędem podczas rozruchu z komunikatem podobnym do tego:
nginx: [emerg] bind() to 203.0.113.10:443 failed (99: Cannot assign requested address)Jądro odrzuca operację bind, ponieważ adres nie jest jeszcze dostępny. Ustawienie net.ipv4.ip_nonlocal_bind=1 pozwala procesowi powiązać się z adresem, którego system jeszcze nie posiada, a polityka restartu zajmuje się resztą. Opóźnianie całego procesu rozruchu w oczekiwaniu na gotowość sieci to zbyt radykalne narzędzie do rozwiązania problemu, który zazwyczaj dotyczy tylko jednego gniazda.
Jak odczytać rzeczywiste zależności systemd na działającym serwerze
Nie należy wyciągać wniosków wyłącznie na podstawie pliku jednostki. Pliki typu drop-in, dowiązania symboliczne .wants/ oraz domyślne zależności niejawne tworzą powiązania, których nie widać w samym pliku.
systemctl cat inventory-api.serviceTo polecenie wyświetla plik jednostki oraz wszystkie pliki typu drop-in w kolejności ich stosowania, wraz ze ścieżką źródłową nad każdym blokiem. Należy uruchomić je w pierwszej kolejności. Pięcioliniowe nadpisanie w /etc/systemd/system/inventory-api.service.d/ ma pierwszeństwo przed plikiem dostarczonym w pakiecie i w innym przypadku pozostaje niewidoczne.
systemctl show inventory-api.service -p Requires -p Wants -p After -p Before -p ConditionResult -p AssertResultTo polecenie wyświetla wartości po rozwiązaniu wszystkich zależności, uwzględniając pliki drop-in oraz zależności niejawne dodane przez systemd. ConditionResult=no stanowi bezpośrednią odpowiedź na sytuację, w której jednostka zgłasza sukces, ale nie wykonuje żadnych działań.
systemctl list-dependencies inventory-api.service
systemctl list-dependencies --reverse inventory-api.service
systemctl list-dependencies --after inventory-api.service
systemctl list-dependencies --before inventory-api.serviceForma podstawowa analizuje Requires= oraz Wants= w dół. --reverse pokazuje, które jednostki wywołują daną usługę; w ten sposób można znaleźć cel (target), który uruchamia ją podczas startu systemu. --after oraz --before pokazują kolejność uruchamiania; należy je sprawdzić, gdy zachodzi wątpliwość, czy jakikolwiek proces faktycznie oczekiwał na zakończenie innego.
journalctl -b -u inventory-api.service --no-pager
journalctl -b -o short-precise -u inventory-api.service -u postgresql.serviceDrugie polecenie zestawia dwie jednostki z dokładnością do milisekund. W ten sposób można udowodnić wystąpienie wyścigu (race condition) zamiast zgadywać. Błąd pg_isready pojawia się przed logami database system is ready to accept connections bazy PostgreSQL, a odstęp czasowy między nimi jest widoczny bezpośrednio w danych wyjściowych.
systemd-analyze verify /etc/systemd/system/inventory-api.service
systemd-analyze critical-chain inventory-api.serviceverify ładuje jednostkę w sposób, w jaki zrobiłby to systemd, i zgłasza nieznane dyrektywy, zależności od nieistniejących jednostek, cykle zależności oraz błędy składni. Polecenie to nie wprowadza żadnych zmian w systemie. critical-chain wyświetla łańcuch zależności, który opóźnił jednostkę, wraz z czasem aktywacji każdego kroku; działa tylko dla jednostek, które zostały uruchomione podczas bieżącej sesji systemu.
Po każdej edycji pliku jednostki należy wykonać sudo systemctl daemon-reload. Aby zmodyfikować jednostkę dostarczoną w pakiecie, należy użyć sudo systemctl edit inventory-api.service, co automatycznie utworzy plik typu drop-in. Edycja pliku dostawcy w /usr/lib/systemd/system/ działa tylko do czasu aktualizacji pakietu, która nadpisze zmiany. Ten sam mechanizm drop-in służy do przypisywania limitów pamięci i procesora do usługi bez modyfikowania plików zarządzanych przez menedżer pakietów.
Cykle zależności i ślad pozostawiany w dzienniku
Dodanie zależności w obu kierunkach powoduje, że systemd przerywa pętlę poprzez usunięcie jednego z zadań:
systemd[1]: Found ordering cycle on inventory-api.service/start
systemd[1]: Job postgresql.service/start deleted to break ordering cycle starting with inventory-api.service/startsystemd samodzielnie wybiera zadanie do usunięcia, co nie zawsze pokrywa się z oczekiwaniami administratora. Skutkuje to usługą, która po niektórych restartach jest dostępna, a po innych nie, co jest niezwykle trudne do zdiagnozowania z zewnątrz. Większość cykli wynika z jednostek, które ustawiają DefaultDependencies=no, a następnie i tak definiują kolejność względem basic.target, lub z dodania Before= do jednostki, która posiada już After= wskazujące z powrotem na daną jednostkę. Polecenie systemd-analyze verify pozwala wykryć takie sytuacje bez konieczności restartu systemu.
Ustalona jednostka
[Unit]
Description=Inventory API
Wants=postgresql.service network-online.target
After=postgresql.service network-online.target
ConditionPathExists=/etc/inventory/api.conf
[Service]
Type=notify
StateDirectory=inventory
ExecStart=/usr/local/bin/inventory-api
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.targetKażda linia wykonuje jedno zadanie. Wants= pobiera obie zależności do transakcji, nie wiążąc czasu życia tej jednostki z ich czasem życia. After= odpowiada za oczekiwanie i musi powtórzyć obie nazwy, ponieważ zależność oraz kolejność to oddzielne ustawienia. ConditionPathExists= oznacza, że maszyna posiadająca pakiet, ale nieposiadająca konfiguracji, pomija jednostkę bez zgłaszania alarmu, co jest właściwym zachowaniem dla usługi sterowanej konfiguracją. Type=notify oznacza, że wszystko, co jest uporządkowane po tej jednostce, czeka na rzeczywistą gotowość, a nie tylko na rozwidlenie procesu. Restart=on-failure obejmuje sytuację, w której baza danych przestaje działać długo po uruchomieniu systemu, ponieważ kolejność dotyczy tylko pierwszego startu. O tym, jak agresywna powinna być ponowna próba, decydują ustawienia Restart= oraz RestartSec=.
Sprawdź to, zanim zaufasz:
sudo systemctl daemon-reload
systemd-analyze verify /etc/systemd/system/inventory-api.service
systemctl list-dependencies --after inventory-api.service
sudo systemctl start inventory-api.service
systemctl show inventory-api.service -p ConditionResult -p ActiveState -p ResultPoprawnie działająca jednostka odczytuje ConditionResult=yes za pomocą ActiveState=active, a Result=success potwierdza, że ostatnie uruchomienie zakończyło się powodzeniem. ConditionResult=no w połączeniu z ActiveState=inactive oznacza, że jednostka została pominięta, a linia w dzienniku wskazująca warunek informuje, który test zakończył się niepowodzeniem.
FAQ
Czy Requires= czeka na uruchomienie drugiej jednostki?
Nie. Requires= i After= to odrębne ustawienia. Requires= włącza drugą jednostkę do tej samej transakcji, a systemd uruchamia oba zadania równolegle. Aby wymusić oczekiwanie, należy dodać After= wskazujące tę samą jednostkę. Istnieje drugi powód, by to zrobić: zależność Requires=, która kończy się niepowodzeniem, uniemożliwia start jednostki tylko wtedy, gdy ustawiono również After=, ponieważ bez określenia kolejności jednostka mogłaby zostać uruchomiona, zanim druga zgłosi błąd.
Czy należy ustawiać kolejność po network.target czy network-online.target?
Podczas startu systemu network.target oznacza jedynie, że oprogramowanie do zarządzania siecią zostało uruchomione, więc nie gwarantuje dostępności adresów ani tras. Należy użyć network-online.target, gdy usługa wymaga działającej sieci w momencie startu, oraz zapisać zarówno Wants=network-online.target, jak i After=network-online.target, ponieważ ten cel nie znajduje się w domyślnej transakcji startowej, a samo After= czeka na jednostkę, której nikt nie zakolejkował. Jeśli usługa zawodzi tylko dlatego, że wiąże się z konkretnym adresem IP, net.ipv4.ip_nonlocal_bind=1 wraz z Restart=on-failure jest rozwiązaniem lżejszym niż opóźnianie startu systemu.
Dlaczego jednostka zgłasza sukces, ale nigdy się nie uruchamia?
Nieudany test Condition...= pomija jednostkę i oznacza zadanie startowe jako zakończone sukcesem, więc nic nie jest oznaczane jako błąd. Należy wykonać systemctl show <unit> -p ConditionResult, a ConditionResult=no potwierdzi ten stan. Następnie należy sprawdzić journalctl -b -u <unit> pod kątem linii Condition check resulted in <description> being skipped. W systemd w wersji 250 i nowszych systemctl status <unit> wskazuje również dokładną dyrektywę, która nie została spełniona.
Jaka jest różnica między Condition a Assert?
Wykonują one identyczne testy. Nieudany test Condition pomija jednostkę po cichu, a zadanie kończy się sukcesem. Nieudany test Assert powoduje błąd jednostki, loguje Assertion failed for <description>. i pozostawia ją w stanie failed (Result: assert). Należy używać Condition w sytuacjach typu „ta jednostka nie ma zastosowania na tej maszynie”, co obejmuje niemal każdy rzeczywisty przypadek. Należy używać Assert tylko wtedy, gdy brak warunku wstępnego musi być widoczny dla osób monitorujących nieudane jednostki.
Dlaczego ExecStartPre kończy się błędem status=203/EXEC?
203/EXEC oznacza, że systemd nie był w stanie wykonać polecenia. Typowe przyczyny to ścieżka, która nie jest bezwzględna, plik binarny nieistniejący na danej maszynie, brak bitu wykonywalności lub skrypt, którego linia #! wskazuje na brakujący interpreter. Inne kody systemd pochodzą ze stałej tabeli, więc status=2/INVALIDARGUMENT oznacza jedynie, że polecenie zakończyło się kodem 2 i nie dostarcza informacji o argumentach. Należy pamiętać, że ExecStartPre= nie jest uruchamiane przez powłokę, więc potoki i znaki wieloznaczne wymagają /bin/sh -c '...'.