Systemd unit start niet: exit codes analyseren
Bekijk de status van uw systemd unit met systemctl status. Ontdek wat de foutcodes 203/EXEC en 226/NAMESPACE betekenen en waarom een unit direct na het starten weer stopt.
Waarom een systemd unit niet start
Een systemd unit die niet start, geeft de reden aan in één veld. Voer systemctl status <unit> uit en zoek naar code= en status= op de regel die de fout rapporteert. Een statusnummer in de 200-reeks betekent dat systemd uw programma nooit heeft bereikt: het proces faalde tijdens het opbouwen van de omgeving waar uw unit-bestand om vroeg. Een status onder de 200 betekent dat uw programma wel is uitgevoerd en zelf is afgesloten; het unit-bestand is dan waarschijnlijk correct en het probleem ligt bij de applicatie.
Deze splitsing vormt het beslissingspad. Alles hieronder volgt daaruit, in de volgorde waarin de nummers verschijnen.
Welke drie commando's beantwoorden de vraag, in volgorde
systemctl status myapp.service
journalctl -u myapp.service -b --no-pager
systemd-analyze verify /etc/systemd/system/myapp.servicesystemctl status geeft het oordeel. Lees eerst de regel Loaded:, omdat deze het bestand noemt dat systemd daadwerkelijk heeft geparseerd en aangeeft of de unit is ingeschakeld, gemaskeerd of helemaal niet is gevonden. Lees daarna de regel Active: en het paar code= en status= daaronder.
journalctl -u myapp.service -b --no-pager geeft de details. -u filtert op die ene unit, -b beperkt de uitvoer tot de huidige boot zodat u geen foutmelding van vorige week leest, en --no-pager print direct naar de terminal zodat u het kunt doorsturen naar grep. status toont alleen de laatste paar logregels en kort lange regels in. De journal toont alles wat het programma printte voordat het crashte; dit is meestal de werkelijke fout. Voeg -n 100 toe voor meer geschiedenis, of voer het uit met -f in een tweede terminal terwijl u de unit herstart.
systemd-analyze verify laadt een unit-bestand zonder het uit te voeren. Het waarschuwt voor onbekende secties en richtlijnen, en het markeert commando's in ExecStart= die het niet kan uitvoeren. Dit vangt de twee stille klassen van fouten op: een verkeerd gespelde sleutel, die systemd bij het laden negeert met een waarschuwing die de meeste mensen nooit lezen, en een pad dat niet bestaat.
Voer na het bewerken van een unit-bestand sudo systemctl daemon-reload uit. Totdat u dit doet, blijft systemd de kopie gebruiken die het eerder heeft geladen, en systemctl status voegt een waarschuwing toe dat het bestand op de schijf is gewijzigd. Een oplossing die "niets deed" is vaak een oplossing die systemd nog niet heeft gelezen.
Nog twee commando's verdienen hun plek. systemctl cat myapp.service print de effectieve unit, wat betekent het hoofdbestand plus elke drop-in onder /etc/systemd/system/myapp.service.d/. systemctl show myapp.service -p ExecStart -p User -p WorkingDirectory print die waarden zoals systemd ze heeft geparseerd; dit is wat daadwerkelijk zal draaien.
Wat betekent status=203/EXEC?
203/EXEC betekent dat systemd de configuratie heeft voltooid, execve() heeft aangeroepen en de kernel dit heeft geweigerd. Uw programma heeft nooit een regel van de eigen code uitgevoerd. Vier oorzaken dekken bijna elk geval.
- Het pad in
ExecStart=is onjuist of niet absoluut. Controleer dit metls -laan de hand van de exacte tekenreeks in het unit-bestand. - Het bestand heeft geen uitvoeringsrechten (execute bit).
sudo chmod +x /opt/myapp/run.shlost dit op. Een bestand dat uit een archief is uitgepakt of van een andere machine is gekopieerd, verliest vaak deze rechten. - De shebang-regel is defect. De kernel leest de eerste regel van een script en voert de daarin genoemde interpreter uit. Daarom faalt
#!/usr/bin/env python3wanneer het service-PATH geenpython3bevat, en een bestand dat is opgeslagen met Windows-regeleinden vraagt om een interpreter genaamd/bin/bash\r, die niet bestaat. - Het bestand is niet geschikt voor deze machine: de verkeerde architectuur of een tekstbestand zonder shebang.
Reproduceer het handmatig, als de servicegebruiker, voordat u wijzigingen aanbrengt.
sudo -u appuser /opt/myapp/run.sh
file /opt/myapp/run.sh
head -1 /opt/myapp/run.sh | cat -Afile benoemt de architectuur en rapporteert "with CRLF line terminators" wanneer de regeleinden het probleem zijn. cat -A toont hetzelfde als een afsluitende ^M. Verwijder deze met sed -i 's/\r$//' /opt/myapp/run.sh.
Eén belangrijke kanttekening bij dit bereik: 200 en hoger is een conventie, geen garantie. Uw eigen programma mag afsluiten met 203, en systemd kan het verschil niet zien. systemd-analyze exit-status 203 drukt de naam en de klasse van elke code af, wat helpt bij het lezen van de tabel, maar als uw applicatie exitcodes boven 199 gebruikt, wijzig deze dan.
Waarom krijg ik 217/USER of 216/GROUP?
217/USER betekent dat het account dat in User= wordt genoemd niet bestaat op het moment dat de service start. 216/GROUP is dezelfde fout voor Group= of SupplementaryGroups=. Bevestig dit met elk één commando.
getent passwd appuser
getent group appgroupElk commando print ofwel een regel, of print niets en geeft een non-zero exitcode terug. Niets printen betekent dat de naam onbekend is voor het systeem, waardoor systemd niet naar dit account kan overschakelen en stopt vóór de exec. De oplossing is om het account aan te maken, niet om User=root in te stellen. Het draaien van een service onder een specifiek systeemaccount met minimale rechten is het hele doel van die directive.
sudo useradd --system --no-create-home --shell /usr/sbin/nologin appuserDynamicUser=yes omzeilt het probleem door systemd bij elke start een tijdelijk account te laten toewijzen. Dit is geschikt voor een service die geen status bijhoudt. Alles wat bestanden schrijft, heeft daarnaast StateDirectory= nodig, omdat het user ID tussen starts verandert en bestanden op een standaardpad uiteindelijk eigendom zijn van een account dat niet meer bestaat.
Wat is 226/NAMESPACE?
226/NAMESPACE komt voort uit de sandboxing-richtlijnen. Wanneer een unit ProtectSystem=, ProtectHome=, PrivateTmp=, ReadWritePaths= of iets dergelijks instelt, bouwt systemd een private mount namespace voor die service voordat het programma wordt uitgevoerd. Een namespace is hier een private weergave van het bestandssysteem voor één proces. Als een mount in dat plan mislukt, faalt de start met 226 en wordt uw programma nooit uitgevoerd.
De gebruikelijke oorzaak is een pad in ReadWritePaths= dat niet bestaat. ProtectSystem=strict mount het volledige bestandssysteem als read-only, en ReadWritePaths= heropent benoemde paden voor schrijven. systemd kan geen map heropenen die er niet is. Er zijn twee goede oplossingen. Laat systemd de map aanmaken met StateDirectory=, wat bij elke start /var/lib/<name> aanmaakt en toewijst aan de servicegebruiker, of voeg het voorvoegsel - toe aan het pad, wat systemd vertelt om die regel te negeren wanneer de bron ontbreekt. De slechte oplossing is het verwijderen van de hardening; dit ruilt een probleem van vijf minuten in voor een permanent probleem.
[Service]
ProtectSystem=strict
ProtectHome=yes
StateDirectory=myapp
ReadWritePaths=-/srv/uploadsWanneer u niet kunt achterhalen welke regel verantwoordelijk is, verwijder dan het volledige hardening-blok, herlaad de configuratie en start de service. Als de service opstart, voeg de regels één voor één toe en herstart na elke toevoeging. Twee verwante instellingen in deze familie zijn 233/RUNTIME_DIRECTORY en 238/STATE_DIRECTORY. Deze betekenen dat systemd de map die in RuntimeDirectory= of StateDirectory= is benoemd niet kon aanmaken of het eigendom ervan niet kon overnemen, meestal omdat dat pad al bestaat en toebehoort aan een andere gebruiker.
Waarom verschijnt 200/CHDIR terwijl WorkingDirectory correct lijkt?
200/CHDIR betekent dat de chdir() naar WorkingDirectory= is mislukt. De map ontbreekt of de servicegebruiker heeft geen toegang om deze te betreden. Om een map te betreden is uitvoerrecht (execute) vereist op die map en op elke bovenliggende map. Een perfect leesbare /home/deploy/app is daarom onbereikbaar wanneer /home/deploy de modus 700 heeft en de service draait als appuser.
sudo -u appuser test -x /srv/myapp && echo ok
namei -l /srv/myappnamei -l toont de eigenaar en de modus van elk onderdeel van het pad. Dit is de snelste manier om de map te vinden die de toegang blokkeert. Het instellen van WorkingDirectory=-/srv/myapp maakt een ontbrekende map niet-fataal. Dit is correct voor een programma dat niet afhankelijk is van de startlocatie, maar onjuist voor een programma dat bestanden opent via een relatief pad.
Waarom start de service en stopt deze een seconde later weer?
Hier is geen 200-serie code aanwezig en vaak ontbreekt fouttekst volledig. De unit toont inactive (dead) direct na een start, of de status wisselt continu via activating (auto-restart). systemd heeft de omgeving correct opgebouwd. De discrepantie zit tussen wat uw programma doet en wat Type= beloofde dat het zou doen.
Type=simple, de standaardinstelling, geeft aan dat het programma op de voorgrond blijft draaien. Als u een daemon gebruikt die naar de achtergrond fork-t en afsluit, ziet systemd het hoofdproces eindigen en markeert de service als voltooid. De meeste daemons hebben een vlag om op de voorgrond te blijven, zoals nginx -g 'daemon off;'.
Type=forking geeft aan dat het eerste proces afsluit zodra het kindproces gereed is. Als u dit gebruikt voor een voorgrondprogramma, wacht de starttaak totdat TimeoutStartSec= is verstreken (standaard 90 seconden), waarna systemd het proces beëindigt en een time-out logt.
Type=notify geeft aan dat het programma sd_notify() aanroept om gereedheid te melden. Een programma zonder die ondersteuning meldt niets, waardoor de start in een time-out loopt en het journal het resultaat registreert als een protocolfout.
Kies het type op basis van wat het programma daadwerkelijk doet. Het verschil tussen simple, forking, oneshot en notify is de beslissing die deze hele klasse van fouten oplost.
Wanneer een service herhaaldelijk afsluit, stopt systemd met proberen en rapporteert dat het startverzoek te snel achter elkaar werd uitgevoerd. De unit blijft daarna in de status failed totdat het rate limit-venster is verstreken of u sudo systemctl reset-failed myapp.service uitvoert. Het verhogen van de limiet verbergt alleen het symptoom. Lees het journal vanaf de eerste fout in plaats van de laatste, en bekijk wat Restart=on-failure daadwerkelijk opnieuw probeert voordat u dit aanpast.
Waarom is de unit inactief zonder enige foutmelding?
Een unit kan worden overgeslagen in plaats van gestart. Condition*-richtlijnen zijn stil van ontwerp: wanneer de controle faalt, markeert systemd de taak als succesvol en onderneemt het geen actie. Een unit met ConditionPathExists=/etc/myapp/config.yml zal nooit starten zolang dat bestand ontbreekt, en zal ook nooit een foutmelding rapporteren.
systemctl show myapp.service -p ConditionResult -p ConditionTimestamp
journalctl -u myapp.service -b --no-pager | grep -i conditionConditionResult=no bevestigt het overslaan, en de journal vermeldt de controle die niet voldeed. Gebruik in plaats daarvan een Assert*-richtlijn wanneer een ontbrekende voorwaarde tot een duidelijke foutmelding moet leiden. Condities, asserts en unit-volgorde behandelt welke controle op welke plek thuishoort.
Er zijn nog enkele andere situaties die stil blijven. Een "could not be found"-fout betekent meestal dat het bestand in de verkeerde map staat of dat u de configuratie niet heeft herladen: unit-bestanden die u zelf schrijft, horen thuis in /etc/systemd/system/. Een gemaskeerde unit weigert elke start totdat sudo systemctl unmask myapp.service deze vrijgeeft. Bovendien faalt systemctl enable op een unit zonder [Install]-sectie, dus voeg een WantedBy=multi-user.target toe.
Wat als het proces is beëindigd in plaats van gefaald?
code=killed is een ander verhaal dan code=exited. Iets heeft het proces van buitenaf beëindigd. status=9/KILL wijst op de out of memory (OOM) killer, en de journal vermeldt het proces dat werd geselecteerd. Een limiet die u zelf heeft ingesteld doet hetzelfde binnen de cgroup (control group), dus controleer het vrije geheugen op de host met free -m en controleer de unit op een MemoryMax=. MemoryMax, CPUQuota en de overige cgroup-limieten legt uit welke limiet een proces beëindigt en welke het proces slechts vertraagt.
status=15/TERM direct na een startpoging betekent meestal dat systemd de starttijd heeft overschreden en het proces heeft beëindigd, wat u terugbrengt naar Type=.
Twee gewoonten die de meeste van deze fouten voorkomen
Gebruik overal absolute paden. systemd voert uw login-shell niet uit, dus er is geen .bashrc, geen .profile en geen geactiveerde virtuele omgeving. $PATH voor een systeemservice is een korte ingebouwde lijst die geen /opt of de shims van een taalversiebeheerder bevat. Schrijf /usr/bin/python3 of /opt/myapp/venv/bin/python volledig uit. command -v myapp in uw shell print het pad dat u kunt kopiëren. Dezelfde regel geldt voor WorkingDirectory=, EnvironmentFile= en elk pad in ReadWritePaths=.
ExecStart= is geen shell. systemd splitst de regel in woorden en roept execve() zelf aan. Pipes, redirects, globs, &&, backticks en ~ hebben geen betekenis: ze bereiken uw programma als letterlijke argumenten. ExecStart=/usr/bin/myapp --flag > /tmp/out.log geeft > en /tmp/out.log door aan myapp, die vervolgens afsluit met een gebruiks-foutmelding die in niets lijkt op een systemd-probleem. Wanneer u shell-functies nodig heeft, vraag dan om een shell.
ExecStart=/bin/sh -c '/usr/bin/myapp --flag | /usr/bin/tee -a /var/log/myapp.log'Voor alleen uitvoer heeft u dat niet nodig. Service-uitvoer gaat standaard naar de journal en StandardOutput=append:/var/log/myapp.log schrijft naar een bestand zonder dat daar een shell bij betrokken is.
Variabele-expansie is op dezelfde manier beperkt. $MYVAR en ${MYVAR} worden vervangen vanuit Environment= en EnvironmentFile=, en niets anders wordt geëxpandeerd. $HOME is niet ingesteld voor een systeemservice, tenzij u dit zelf instelt. Een EnvironmentFile= is ook geen shell-script: export hoort er niet in thuis, de quoting-regels verschillen van bash en een ontbrekend bestand is fataal, tenzij u het pad vooraf laat gaan door -.
Werken op een live server
Lees de code, bewijs de oorzaak, wijzig één ding en herstart. Die volgorde is belangrijker dan elk getal uit je hoofd kennen, omdat het voorkomt dat u drie speculatieve wijzigingen tegelijk doorvoert en niet meer weet welke actie het probleem heeft opgelost. Dezelfde werkwijze geldt voor units die u niet zelf heeft geschreven. Een timer die nooit wordt geactiveerd, is een service die nooit is gestart; debug daarom eerst de service: een systemd timer en de service die deze aanstuurt faalt op precies dezelfde manieren als hierboven beschreven, waarbij de timer de output verbergt totdat u deze opvraagt via de journal.
FAQ
Wat betekent status=203/EXEC in systemctl status?
systemd heeft alles voorbereid wat de unit vereiste, maar de execve()-aanroep mislukte, waardoor uw programma nooit is gestart. Controleer in deze volgorde vier zaken: het pad in ExecStart= bestaat en is absoluut, het bestand heeft het uitvoeringsrecht (execute bit), de shebang verwijst naar een interpreter die in het service PATH bestaat, en het bestand gebruikt Unix-regeleinden. file rapporteert "with CRLF line terminators" voor het laatste punt; dit verandert de naam van de interpreter in /bin/bash\r, waardoor de kernel het uitvoeren weigert.
Waarom start mijn service en stopt deze direct weer?
Het unit-bestand belooft gedrag dat het programma niet vertoont. Met Type=simple verwacht systemd dat het programma op de voorgrond blijft draaien; een daemon die naar de achtergrond fork-t, lijkt direct na het forken voltooid. Met Type=forking wacht systemd tot het eerste proces is afgesloten, waardoor een voorgrondprogramma de starttaak laat hangen totdat TimeoutStartSec= verloopt. Stem Type= af op het programma en gebruik, indien het programma een voorgrond-vlag aanbiedt, die vlag in combinatie met de standaard Type=simple.
Hoe zie ik de werkelijke fout in plaats van de korte statusuitvoer?
systemctl status toont slechts de laatste paar regels uit het logboek en kort lange regels af. Voer journalctl -u myapp.service -b --no-pager uit om alles te zien wat de unit tijdens deze boot heeft gelogd, voeg -n 200 toe voor een groter venster, of pipe de uitvoer naar grep. Als de applicatie een eigen logbestand schrijft, lees dat dan ook, aangezien systemd alleen vastlegt wat het programma naar de standaarduitvoer en standaardfout stuurt.
Waarom is mijn unit inactief zonder foutmelding?
Meestal is de unit overgeslagen door een Condition*-richtlijn. Deze controles zijn stil: een mislukte voorwaarde markeert de starttaak als succesvol. Voer systemctl show myapp.service -p ConditionResult uit en zoek naar ConditionResult=no, en lees vervolgens de logregel die de controle benoemt. De andere veelvoorkomende oorzaak is een gemaskeerde unit, die elke start weigert totdat sudo systemctl unmask deze vrijgeeft.
Heb ik daemon-reload nodig na elke wijziging in een unit-bestand?
Ja, voor elke aanpassing aan een unit-bestand of een drop-in. sudo systemctl daemon-reload zorgt ervoor dat systemd de bestanden opnieuw van schijf leest, waarna sudo systemctl restart myapp.service de wijzigingen toepast op de draaiende service. U heeft dit niet nodig na systemctl edit, wat de herlaadactie voor u uitvoert, en u heeft het niet nodig na het wijzigen van een configuratiebestand dat bij de applicatie hoort in plaats van bij systemd.