SSD Nodes Learn 🎉 VPS $5.50/माह से
गाइड Matt Connorलेखक: Matt Connor

Systemd unit start क्यों नहीं हो रही है: exit code देखें

Systemctl status कमांड का सही उपयोग करना सीखें। 203/EXEC और 226/NAMESPACE एरर कोड का मतलब समझें और जानें कि आपकी यूनिट स्टार्ट होकर तुरंत बंद क्यों हो जाती है।

systemd unit क्यों start नहीं होती है

यदि कोई systemd unit start नहीं हो रही है, तो वह एक field में इसका कारण बताती है। systemctl status <unit> चलाएँ और उस line पर code= और status= देखें जहाँ failure की सूचना दी गई है। 200s की range में status number का मतलब है कि systemd आपके program तक कभी पहुँचा ही नहीं: यह उस environment को बनाने के दौरान विफल रहा जिसे आपकी unit file ने मांगा था। 200 से नीचे का status मतलब है कि आपका program run हुआ और खुद ही exit हो गया, इसलिए unit file संभवतः सही है और application में समस्या है।

यह विभाजन ही निर्णय लेने का मार्ग है। नीचे दी गई हर चीज़ इसी से निकलती है, उसी क्रम में जिसमें numbers दिखाई देते हैं।

प्रश्न का उत्तर देने वाले तीन commands, क्रम में

systemctl status myapp.service
journalctl -u myapp.service -b --no-pager
systemd-analyze verify /etc/systemd/system/myapp.service

systemctl status निर्णय देता है। पहले Loaded: पंक्ति पढ़ें, क्योंकि यह उस फाइल का नाम बताती है जिसे systemd ने वास्तव में पार्स किया है और यह बताती है कि unit enabled है, masked है, या बिल्कुल नहीं मिली। फिर Active: पंक्ति और उसके नीचे दिए गए code= तथा status= जोड़े को पढ़ें।

journalctl -u myapp.service -b --no-pager विवरण देता है। -u उस एक unit तक फिल्टर करता है, -b आउटपुट को वर्तमान बूट तक सीमित करता है ताकि आप पिछले सप्ताह की विफलता न पढ़ रहे हों, और --no-pager सीधे टर्मिनल पर प्रिंट करता है ताकि आप इसे grep में पाइप कर सकें। status केवल लॉग की अंतिम कुछ पंक्तियाँ दिखाता है और लंबी पंक्तियों को छोटा कर देता है। जर्नल वह सब कुछ दिखाता है जो प्रोग्राम ने मरने से पहले प्रिंट किया था, जो आमतौर पर वास्तविक त्रुटि होती है। अधिक इतिहास के लिए -n 100 जोड़ें, या जब आप unit को रीस्टार्ट करें तो इसे दूसरे टर्मिनल में -f के साथ चलाएं।

systemd-analyze verify किसी unit फाइल को चलाए बिना लोड करता है। यह अज्ञात sections और directives के बारे में चेतावनी देता है, और ExecStart= में उन commands को फ्लैग करता है जिन्हें यह निष्पादित नहीं कर सकता। यह गलतियों के दो शांत वर्गों को पकड़ता है: एक गलत वर्तनी वाली key, जिसे systemd लोड समय पर एक चेतावनी के साथ अनदेखा कर देता है जिसे अधिकांश लोग कभी नहीं पढ़ते, और एक ऐसा path जो वहां नहीं है।

किसी भी unit फाइल को एडिट करने के बाद, sudo systemctl daemon-reload चलाएं। जब तक आप ऐसा नहीं करते, systemd उस कॉपी का उपयोग करता रहता है जिसे उसने पहले लोड किया था, और systemctl status एक चेतावनी जोड़ता है कि डिस्क पर फाइल बदल गई है। एक सुधार जिसने "कुछ नहीं किया" वह अक्सर ऐसा सुधार होता है जिसे systemd ने अभी तक पढ़ा ही नहीं है।

दो और commands अपना स्थान अर्जित करते हैं। systemctl cat myapp.service प्रभावी unit को प्रिंट करता है, जिसका अर्थ है मुख्य फाइल और /etc/systemd/system/myapp.service.d/ के अंतर्गत आने वाले प्रत्येक drop-in का योग। systemctl show myapp.service -p ExecStart -p User -p WorkingDirectory उन मानों को प्रिंट करता है जैसा कि systemd ने उन्हें पार्स किया है, जो वास्तव में वही है जो चलेगा।

status=203/EXEC का क्या अर्थ है?

203/EXEC का अर्थ है कि systemd ने सेटअप पूरा कर लिया है, execve() को कॉल किया है, और kernel ने इसे अस्वीकार कर दिया है। आपके प्रोग्राम ने अपनी एक भी लाइन रन नहीं की है। लगभग सभी मामलों में इसके चार कारण होते हैं।

  1. ExecStart= में दिया गया path गलत है, या वह absolute नहीं है। unit file में मौजूद सटीक string के साथ इसकी तुलना ls -l का उपयोग करके करें।
  2. फाइल में execute bit सेट नहीं है। sudo chmod +x /opt/myapp/run.sh इसे ठीक कर देता है। किसी archive से अनपैक की गई या दूसरी मशीन से कॉपी की गई फाइल अक्सर यह bit खो देती है।
  3. shebang लाइन खराब है। kernel स्क्रिप्ट की पहली लाइन पढ़ता है और वहां नामित interpreter को चलाता है, इसलिए #!/usr/bin/env python3 तब विफल हो जाता है जब service PATH में python3 मौजूद न हो, और Windows लाइन एंडिंग्स के साथ सेव की गई फाइल /bin/bash\r नामक interpreter की मांग करती है, जो मौजूद नहीं है।
  4. फाइल ऐसी नहीं है जिसे यह मशीन चला सके: गलत architecture, या बिना shebang वाली कोई text फाइल।

कुछ भी बदलने से पहले, service user के रूप में इसे मैन्युअल रूप से चलाकर देखें।

sudo -u appuser /opt/myapp/run.sh
file /opt/myapp/run.sh
head -1 /opt/myapp/run.sh | cat -A

file architecture का नाम बताता है और जब लाइन एंडिंग्स की समस्या होती है तो "with CRLF line terminators" रिपोर्ट करता है। cat -A भी वही दिखाता है जो एक trailing ^M के साथ दिखता है। इन्हें sed -i 's/\r$//' /opt/myapp/run.sh का उपयोग करके हटा दें।

इस रेंज के बारे में एक स्पष्ट चेतावनी: 200 और उससे ऊपर की संख्या एक परंपरा है, कोई गारंटी नहीं। आपका अपना प्रोग्राम 203 के साथ exit करने के लिए स्वतंत्र है, और systemd दोनों के बीच अंतर नहीं कर सकता। systemd-analyze exit-status 203 किसी भी कोड का नाम और वर्ग प्रिंट करता है, जो आपको टेबल पढ़ने में मदद करता है, लेकिन यदि आपका application 199 से ऊपर के exit codes चुनता है, तो उन्हें बदल दें।

मुझे 217/USER या 216/GROUP त्रुटि क्यों मिलती है?

217/USER का अर्थ है कि User= में निर्दिष्ट खाता सेवा शुरू होने के समय मौजूद नहीं है। 216/GROUP का अर्थ Group= या SupplementaryGroups= के लिए वही विफलता है। प्रत्येक की पुष्टि एक कमांड से करें।

getent passwd appuser
getent group appgroup

इनमें से प्रत्येक कमांड या तो एक लाइन प्रिंट करता है, या कुछ भी प्रिंट नहीं करता और non-zero exit code देता है। कुछ भी प्रिंट न होने का अर्थ है कि सिस्टम उस नाम को नहीं पहचानता है, इसलिए systemd उस पर स्विच नहीं कर सकता और exec से पहले रुक जाता है। इसका समाधान खाता बनाना है, न कि User=root को सेट करना। किसी सेवा को न्यूनतम विशेषाधिकार वाले समर्पित सिस्टम खाते के तहत चलाना ही उस निर्देश का मुख्य उद्देश्य है।

sudo useradd --system --no-create-home --shell /usr/sbin/nologin appuser

DynamicUser=yes इस समस्या को हल करता है क्योंकि systemd प्रत्येक स्टार्ट के लिए एक अस्थायी खाता आवंटित करता है। यह ऐसी सेवा के लिए उपयुक्त है जो कोई state नहीं रखती है। जो कुछ भी फाइलें लिखता है, उसे साथ में StateDirectory= की आवश्यकता होती है, क्योंकि प्रत्येक स्टार्ट के बीच user ID बदल जाती है और साधारण पाथ के अंतर्गत फाइलें ऐसे खाते के स्वामित्व में आ जाती हैं जो अब मौजूद नहीं है।

226/NAMESPACE क्या है?

226/NAMESPACE सैंडबॉक्सिंग निर्देशों से आता है। जब कोई unit ProtectSystem=, ProtectHome=, PrivateTmp=, ReadWritePaths= या इसी तरह का कोई भी निर्देश सेट करती है, तो systemd प्रोग्राम को exec करने से पहले उस सर्विस के लिए एक private mount namespace बनाता है। यहाँ namespace का अर्थ एक process के लिए filesystem का एक निजी दृश्य है। यदि उस योजना में कोई भी mount विफल हो जाता है, तो start प्रक्रिया 226 error के साथ विफल हो जाती है और आपका प्रोग्राम कभी run नहीं होता।

इसका सामान्य कारण ReadWritePaths= में दिया गया वह path है जो मौजूद नहीं है। ProtectSystem=strict पूरे filesystem को read-only के रूप में mount करता है, और ReadWritePaths= निर्दिष्ट paths को writing के लिए फिर से खोलता है। systemd ऐसी directory को फिर से नहीं खोल सकता जो वहां मौजूद ही नहीं है। इसके दो अच्छे समाधान हैं। systemd को StateDirectory= के साथ directory बनाने दें, जो हर start पर /var/lib/<name> बनाता है और उसे सर्विस user को सौंप देता है, या path के आगे - लगा दें, जो systemd को यह निर्देश देता है कि यदि source मौजूद न हो तो उस entry को ignore कर दे। गलत समाधान hardening को हटाना है, जो पांच मिनट की समस्या को एक स्थायी समस्या में बदल देता है।

[Service]
ProtectSystem=strict
ProtectHome=yes
StateDirectory=myapp
ReadWritePaths=-/srv/uploads

जब आप यह पता न लगा सकें कि कौन सी line इसके लिए जिम्मेदार है, तो पूरे hardening block को हटा दें, reload करें और start करें। यदि सर्विस start हो जाती है, तो एक-एक करके lines को वापस जोड़ें और हर बार restart करें। इस परिवार में दो अन्य संबंधित निर्देश 233/RUNTIME_DIRECTORY और 238/STATE_DIRECTORY हैं। इनका अर्थ है कि systemd RuntimeDirectory= या StateDirectory= में नामित directory को न तो बना सका और न ही उसका स्वामित्व ले सका, आमतौर पर इसलिए क्योंकि वह path पहले से मौजूद है और किसी अन्य user का है।

WorkingDirectory सही दिखने के बावजूद 200/CHDIR क्यों दिखाई देता है?

200/CHDIR का अर्थ है कि WorkingDirectory= में chdir() विफल हो गया है। या तो वह directory मौजूद नहीं है, या service user उसमें प्रवेश नहीं कर सकता। किसी directory में प्रवेश करने के लिए उस directory और उसके ऊपर के सभी parent directories पर execute permission की आवश्यकता होती है। इसलिए, यदि /home/deploy का mode 700 है और service appuser के रूप में चल रही है, तो पूरी तरह से readable /home/deploy/app तक भी नहीं पहुँचा जा सकता है।

sudo -u appuser test -x /srv/myapp && echo ok
namei -l /srv/myapp

namei -l पाथ के प्रत्येक घटक का owner और mode प्रिंट करता है। यह उस directory को खोजने का सबसे तेज़ तरीका है जो बाकी पाथ को ब्लॉक कर रही है। WorkingDirectory=-/srv/myapp लिखने से एक missing directory non-fatal हो जाती है। यह उन programs के लिए सही है जिन्हें इस बात से फर्क नहीं पड़ता कि वे कहाँ से शुरू होते हैं, लेकिन उन programs के लिए गलत है जो relative path का उपयोग करके फाइलें खोलते हैं।

Service start होने के बाद एक सेकंड में क्यों बंद हो जाती है?

यहाँ कोई 200-series code नहीं मिलता है, और अक्सर कोई error text भी नहीं होता है। Unit start होने के तुरंत बाद inactive (dead) दिखाती है, या यह activating (auto-restart) के बीच cycle करती रहती है। systemd ने environment को सही ढंग से build किया है। विसंगति आपके program के कार्य और Type= द्वारा किए गए वादे के बीच होती है।

Type=simple, जो default है, कहता है कि program foreground में रहेगा। यदि आप इसे ऐसा daemon देते हैं जो background में fork होकर exit हो जाता है, तो systemd मुख्य process को समाप्त मान लेता है और service को पूर्ण घोषित कर देता है। अधिकांश daemons में foreground में बने रहने के लिए एक flag होता है, जैसे कि nginx -g 'daemon off;'

Type=forking कहता है कि पहली process अपने child के तैयार होते ही exit हो जाएगी। यदि आप इसे foreground program देते हैं, तो start job तब तक प्रतीक्षा करती है जब तक TimeoutStartSec= समाप्त नहीं हो जाता (default रूप से 90 सेकंड), जिसके बाद systemd उसे kill कर देता है और timeout log करता है।

Type=notify कहता है कि program readiness की घोषणा करने के लिए sd_notify() को call करता है। जिस program में यह support नहीं है, वह कुछ भी announce नहीं करता है, इसलिए start process timeout हो जाती है और journal में परिणाम protocol failure के रूप में दर्ज होता है।

program वास्तव में जो करता है, उसके आधार पर type चुनें। simple, forking, oneshot और notify में क्या अंतर है यह वह निर्णय है जो इस प्रकार की सभी विफलताओं को हल करता है।

जब कोई service बार-बार exit होती है, तो systemd प्रयास करना बंद कर देता है और सूचित करता है कि start request बहुत तेजी से दोहराया गया है। इसके बाद unit तब तक failed रहती है जब तक rate limit window समाप्त नहीं हो जाती या आप sudo systemctl reset-failed myapp.service नहीं चलाते। limit को बढ़ाना केवल लक्षण को छिपाता है। अंतिम विफलता के बजाय पहली विफलता से journal पढ़ें, और बदलाव करने से पहले देखें Restart=on-failure वास्तव में क्या retry करता है

यूनिट बिना किसी त्रुटि के inactive क्यों है?

एक यूनिट को start करने के बजाय skip किया जा सकता है। Condition* directives डिफ़ॉल्ट रूप से silent होते हैं: जब check विफल हो जाता है, तो systemd job को सफल मान लेता है और कुछ नहीं करता। ConditionPathExists=/etc/myapp/config.yml वाली यूनिट तब तक कभी start नहीं होगी जब तक वह फ़ाइल मौजूद न हो, और यह कोई त्रुटि भी रिपोर्ट नहीं करेगी।

systemctl show myapp.service -p ConditionResult -p ConditionTimestamp
journalctl -u myapp.service -b --no-pager | grep -i condition

ConditionResult=no skip की पुष्टि करता है, और journal उस check का नाम बताता है जो पूरा नहीं हुआ। जब किसी आवश्यक prerequisite के न होने पर loud failure की आवश्यकता हो, तो इसके बजाय Assert* directive का उपयोग करें। Conditions, asserts and unit ordering में बताया गया है कि कौन सा check कहाँ उपयोग करना चाहिए।

कुछ अन्य शांत मामले भी होते हैं। "could not be found" त्रुटि का सामान्य अर्थ है कि फ़ाइल गलत directory में है या आपने reload नहीं किया है: आपके द्वारा लिखी गई unit files /etc/systemd/system/ में होनी चाहिए। एक masked यूनिट तब तक start नहीं होती जब तक sudo systemctl unmask myapp.service उसे clear न कर दे। और systemctl enable उस यूनिट पर विफल हो जाता है जिसमें [Install] section नहीं होता, इसलिए इसमें WantedBy=multi-user.target जोड़ें।

यदि process fail होने के बजाय kill कर दी गई हो तो क्या करें?

code=killed का मामला code=exited से बिल्कुल अलग है। किसी बाहरी कारक ने process को समाप्त किया है। status=9/KILL आउट ऑफ मेमोरी (OOM) killer की ओर संकेत करता है, और journal उस process का नाम बताता है जिसे उसने चुना है। cgroup (control group) के भीतर आपके द्वारा निर्धारित सीमा भी यही काम करती है, इसलिए free -m के साथ host पर उपलब्ध memory की जाँच करें और unit के लिए MemoryMax= देखें। MemoryMax, CPUQuota और अन्य cgroup सीमाएं यह स्पष्ट करता है कि कौन सी सीमा process को kill करती है और कौन सी उसे केवल धीमा करती है।

start करने के प्रयास के तुरंत बाद status=15/TERM का अर्थ आमतौर पर यह होता है कि systemd ने start होने की प्रक्रिया को time out कर दिया और process को terminate कर दिया, जो आपको वापस Type= पर ले जाता है।

इन विफलताओं को रोकने वाली दो आदतें

हर जगह absolute paths का उपयोग करें। systemd आपके login shell को नहीं चलाता है, इसलिए यहाँ न तो .bashrc होता है, न .profile, और न ही कोई सक्रिय virtual environment। system service के लिए $PATH एक छोटी सी built-in सूची होती है जिसमें /opt या किसी language version manager के shims शामिल नहीं होते हैं। /usr/bin/python3 या /opt/myapp/venv/bin/python को पूरा लिखें। अपने shell में command -v myapp चलाने पर वह path प्रिंट हो जाता है जिसे आप copy-paste कर सकते हैं। यही नियम WorkingDirectory=, EnvironmentFile= और ReadWritePaths= के हर path पर लागू होता है।

ExecStart= एक shell नहीं है। systemd लाइन को शब्दों में विभाजित करता है और स्वयं execve() को कॉल करता है। Pipes, redirections, globs, &&, backticks और ~ का यहाँ कोई अर्थ नहीं है: ये आपके प्रोग्राम तक literal arguments के रूप में पहुँचते हैं। ExecStart=/usr/bin/myapp --flag > /tmp/out.log, > और /tmp/out.log को myapp के पास भेजता है, जो फिर एक usage error के साथ exit हो जाता है, जो देखने में systemd की समस्या जैसा बिल्कुल नहीं लगता। जब आपको shell features की आवश्यकता हो, तो shell का उपयोग करें।

ExecStart=/bin/sh -c '/usr/bin/myapp --flag | /usr/bin/tee -a /var/log/myapp.log'

केवल output के लिए आपको इसकी आवश्यकता नहीं है। Service का output डिफ़ॉल्ट रूप से journal में जाता है, और StandardOutput=append:/var/log/myapp.log बिना किसी shell के सीधे फ़ाइल में लिखता है।

Variable expansion भी इसी तरह सीमित है। $MYVAR और ${MYVAR} को Environment= और EnvironmentFile= से बदल दिया जाता है, और इसके अलावा कुछ भी expand नहीं होता है। system service के लिए $HOME तब तक set नहीं होता जब तक आप इसे स्वयं set न करें। एक EnvironmentFile= भी shell script नहीं है: export इसमें काम नहीं करता, इसके quoting नियम bash से अलग हैं, और यदि फ़ाइल मौजूद न हो तो यह fatal error देता है, जब तक कि आप path के आगे - न लगा दें।

लाइव सर्वर पर काम करना

कोड को पढ़ें, कारण सिद्ध करें, एक बदलाव करें और फिर restart करें। यह क्रम हर संख्या को जानने से अधिक महत्वपूर्ण है, क्योंकि यह आपको तीन अनुमानित बदलाव एक साथ करने और यह भूलने से रोकता है कि किस बदलाव ने समस्या हल की। यही तरीका उन units के लिए भी काम करता है जिन्हें आपने नहीं लिखा है। जो timer कभी trigger नहीं होता, वह एक ऐसी service है जो कभी start ही नहीं हुई, इसलिए पहले service को debug करें: a systemd timer and the service it triggers बिल्कुल ऊपर बताए गए तरीकों से विफल होता है, जहाँ timer तब तक output छिपाए रखता है जब तक आप journal से उसे न पूछें।

FAQ

systemctl status में status=203/EXEC का क्या अर्थ है?

systemd ने unit द्वारा मांगी गई सभी चीजें सेट कर दीं, लेकिन execve() कॉल विफल हो गई, इसलिए आपका प्रोग्राम कभी शुरू नहीं हुआ। क्रमवार इन चार चीजों की जाँच करें: ExecStart= में दिया गया पाथ मौजूद है और absolute है, फाइल में execute बिट सेट है, shebang में दिया गया इंटरप्रेटर service PATH पर मौजूद है, और फाइल Unix लाइन एंडिंग्स का उपयोग करती है। अंतिम बिंदु के लिए, file "with CRLF line terminators" रिपोर्ट करता है, जो इंटरप्रेटर के नाम को /bin/bash\r में बदल देता है और कर्नल इसे चलाने से मना कर देता है।

मेरी सर्विस शुरू होकर तुरंत बंद क्यों हो जाती है?

unit फाइल ऐसे व्यवहार का वादा करती है जो प्रोग्राम में नहीं है। Type=simple के साथ systemd उम्मीद करता है कि प्रोग्राम foreground में रहेगा, इसलिए जो डेमन बैकग्राउंड में fork हो जाता है, वह fork होते ही समाप्त माना जाता है। Type=forking के साथ systemd पहले प्रोसेस के बाहर निकलने का इंतजार करता है, इसलिए foreground प्रोग्राम start जॉब को तब तक लटकाए रखता है जब तक TimeoutStartSec= समाप्त नहीं हो जाता। Type= को प्रोग्राम के अनुसार मैच करें, और जहाँ प्रोग्राम foreground फ्लैग प्रदान करता है, वहां उस फ्लैग को डिफ़ॉल्ट Type=simple के साथ उपयोग करें।

मैं संक्षिप्त status आउटपुट के बजाय वास्तविक त्रुटि कैसे देख सकता हूँ?

systemctl status केवल अंतिम कुछ जर्नल लाइनें प्रिंट करता है और लंबी लाइनों को काट देता है। इस बूट के दौरान unit द्वारा लॉग की गई हर चीज को प्राप्त करने के लिए journalctl -u myapp.service -b --no-pager चलाएं, बड़ी विंडो के लिए -n 200 जोड़ें, या इसे grep में पाइप करें। यदि एप्लिकेशन अपनी खुद की लॉग फाइल लिखता है, तो उसे भी पढ़ें, क्योंकि systemd केवल वही कैप्चर करता है जो प्रोग्राम standard output और standard error पर भेजता है।

मेरी unit बिना किसी त्रुटि संदेश के inactive क्यों है?

अक्सर किसी Condition* निर्देश ने इसे छोड़ दिया होता है। वे जाँचें साइलेंट होती हैं: एक विफल condition start जॉब को सफल के रूप में चिह्नित करती है। systemctl show myapp.service -p ConditionResult चलाएं और ConditionResult=no खोजें, फिर उस जर्नल लाइन को पढ़ें जो जाँच का नाम बताती है। दूसरा सामान्य कारण masked unit है, जो तब तक शुरू होने से मना कर देती है जब तक sudo systemctl unmask उसे क्लियर न कर दे।

क्या मुझे हर unit फाइल बदलाव के बाद daemon-reload की आवश्यकता है?

हाँ, unit फाइल या ड्रॉप-इन में किसी भी बदलाव के लिए। sudo systemctl daemon-reload systemd को डिस्क से फाइलें फिर से पढ़ने के लिए मजबूर करता है, फिर sudo systemctl restart myapp.service उन्हें चल रही सर्विस पर लागू करता है। आपको systemctl edit के बाद इसकी आवश्यकता नहीं है, जो आपके लिए रिलोड कर देता है, और आपको ऐसी कॉन्फ़िगरेशन फाइल बदलने के बाद इसकी आवश्यकता नहीं है जो systemd के बजाय एप्लिकेशन से संबंधित है।

#systemd#troubleshooting#journalctl#exit-codes#linux-fundamentals