systemd dependencies और conditions कैसे काम करती हैं
Requires, Wants, After और Condition के बीच का अंतर समझें। जानें कि कैसे गलत निर्भरता के कारण boot के समय unit fail होती है और इसे ठीक करने के लिए सही निर्देश क्या हैं।
Requires का मतलब After नहीं होता है
systemd dependencies और conditions चार अलग-अलग तंत्र हैं, जिनका उपयोग अधिकांश unit files ऐसे करती हैं जैसे वे एक ही हों। Requires= और Wants= यह तय करते हैं कि कौन सी अन्य units को pull-in किया जाएगा। After= और Before= यह तय करते हैं कि units किस क्रम में start होंगी। ExecStartPre= एक ऐसी जाँच चलाता है जो unit को fail कर सकती है। Condition और Assert परिवार यह तय करते हैं कि unit चलेगी या नहीं। प्रत्येक तंत्र दूसरों से स्वतंत्र है, इसलिए एक unit दूसरी unit को require कर सकती है और फिर भी उसके साथ ही start हो सकती है।
वह आखिरी वाक्य उस बग के पीछे का कारण है जो लगभग हर "यह हाथ से start करने पर काम करता है, लेकिन boot पर fail हो जाता है" वाली रिपोर्ट का आधार है।
[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, PostgreSQL को उसी start transaction में pull करता है। यह उसका इंतज़ार नहीं करता है। systemd दोनों jobs को समानांतर (parallel) रूप से start करता है, इसलिए pg_isready तब चलता है जब PostgreSQL अभी भी अपनी data directory खोल रहा होता है। यह 2 के साथ exit हो जाता है क्योंकि तब तक कोई भी port listening पर नहीं होता है, और unit, ExecStart तक पहुँचने से पहले ही fail हो जाती है। एक घंटे बाद sudo systemctl start inventory-api चलाने पर यह काम करता है, क्योंकि तब तक PostgreSQL पहले ही up हो चुका होता है। unit file में कुछ भी नहीं बदला होता है, यही कारण है कि file निर्दोष (innocent) दिखती है।
इसका समाधान एक लाइन का है।
[Unit]
Requires=postgresql.service
After=postgresql.serviceइसी जगह एक और बारीक विवरण छिपा है। एक विफल Requires= dependency आपकी unit को केवल तभी start होने से रोकती है जब आप उस पर After= भी set करते हैं। ordering के बिना, systemd आपके द्वारा unit start करने तक उसे पहले ही शुरू कर चुका होता है, इसलिए रद्द करने के लिए कुछ भी नहीं बचता है। Requires= अपने आप में वह सुरक्षा नहीं देता है जो लोग सोचते हैं कि उन्हें मिल रही है। हर Requires= और हर Wants= के साथ After= लिखें, जब तक कि आपके पास ऐसा न करने का कोई विशिष्ट कारण न हो।
Requires, Wants, Requisite और BindsTo के वादे
ये सभी dependency सेटिंग्स हैं। इनमें से कोई भी किसी चीज़ को क्रमबद्ध (order) नहीं करता है।
Wants=: दूसरी unit को साथ में लाता है। यदि वह विफल हो जाती है या मौजूद नहीं है, तब भी यह unit start हो जाती है।systemctl enableयही बनाता है, जो.wants/डायरेक्टरी के अंदर एक symlink के रूप में होता है।Requires=: दूसरी unit को साथ में लाता है। यदि वह विफल हो जाती है और आपनेAfter=का भी आदेश दिया है, तो यह unit start नहीं होती है। यदि दूसरी unit को बाद में स्पष्ट रूप से stop किया जाता है, तो यह unit भी उसके साथ stop हो जाती है।Requisite=: दूसरी unit को साथ में नहीं लाता है। यदि वह पहले से active नहीं है, तो यह unit तुरंत विफल हो जाती है।BindsTo=:Requires=की तरह, और यह unit तब भी stop हो जाती है जब दूसरी unit किसी भी कारण से stop होती है, जिसमें हार्डवेयर का गायब होना भी शामिल है।PartOf=: stop और restart दूसरी unit से इस unit तक प्रसारित (propagate) होते हैं। Start प्रसारित नहीं होता है।Conflicts=: इस unit को start करने से दूसरी unit stop हो जाती है।
एक daemon जो दूसरे daemon से बात करता है, उसके लिए Wants= के साथ After= आमतौर पर सही जोड़ी है। Requires= जीवनकाल (lifetimes) को जोड़ता है: रखरखाव के लिए database को stop करें और आपका application भी उसके साथ बंद हो जाएगा, और database के वापस आने पर यह अपने आप वापस नहीं आता है। Wants= के साथ After= आपको उस जुड़ाव के बिना boot क्रम प्रदान करता है, और एक restart policy उस स्थिति को संभालती है जहाँ dependency बाद में गायब हो जाती है।
आपको वे dependencies भी विरासत में मिलती हैं जिन्हें आपने कभी नहीं लिखा। DefaultDependencies=yes के साथ, जो कि default है, एक सामान्य service को मुफ्त में Requires=sysinit.target, After=sysinit.target basic.target और Conflicts=shutdown.target मिल जाते हैं। यही कारण है कि लगभग खाली [Unit] सेक्शन वाली service भी boot के अंत में start होती है और shutdown के समय ठीक से stop हो जाती है।
Transaction के बाद और पहले का क्रम, और कुछ नहीं
After= और Before= केवल क्रम निर्धारित करते हैं। इनकी अपनी कोई अन्य आवश्यकता नहीं होती है। यदि किसी ऐसी unit में After=redis.service का उपयोग किया जाता है जिसे कोई अन्य प्रक्रिया Redis से नहीं जोड़ती, तो यह निष्प्रभावी (no-op) है: यदि redis.service transaction का हिस्सा नहीं है, तो प्रतीक्षा करने के लिए कुछ भी नहीं है, इसलिए आपकी unit तुरंत शुरू हो जाती है।
यह बात दो बार दोहराने योग्य है, क्योंकि यह नीचे दी गई network-online.target वाली गलती का सटीक स्वरूप है। Ordering केवल उन units की प्रतीक्षा करती है जिन्हें उसी transaction में पहले से शुरू किया जा रहा है।
यह जोड़ी सममित (symmetric) है। a.service में लिखा गया After=b.service वही अर्थ रखता है जो b.service में लिखा गया Before=a.service, इसलिए इनमें से किसी एक का उपयोग करें और उसे उस unit में रखें जिसे आप नियंत्रित करते हैं। Shutdown के समय क्रम अपने आप उल्टा हो जाता है, इसलिए After=b.service का अर्थ यह भी है कि आपकी unit को b.service से पहले रोक दिया जाएगा।
After= "started" होने का इंतजार करता है, और Type= यह परिभाषित करता है कि इसका क्या अर्थ है
After= तब तक इंतजार करता है जब तक दूसरी unit शुरू होना समाप्त न कर ले। "शुरू होना समाप्त" होने का क्या अर्थ है, यह पूरी तरह से उस unit के Type= द्वारा तय किया जाता है।
Type=simple: जैसे ही systemd ने process को fork किया। हो सकता है कि program ने अभी तक अपनी config को parse भी न किया हो, socket खोलना तो दूर की बात है।Type=exec: जैसे हीexecve()सफल हुआ। यह थोड़ा अधिक मजबूत है। फिर भी यह readiness (तैयारी) के बारे में कुछ नहीं कहता।Type=forking: जब मूल parent process exit हो जाती है।Type=oneshot: जब process exit हो जाती है। यहाँ "started" का वास्तव में अर्थ है कि काम पूरा हो गया है।Type=notify: जब service अपने notification socket परREADY=1भेजती है। यह एकमात्र type है जो वास्तविक readiness की रिपोर्ट करता है।
इसलिए Type=simple daemon पर After= एक कमजोर वादा है, और यह पहले उदाहरण में race की दूसरी छमाही है। यदि जिस unit पर आप निर्भर हैं वह Type=simple के रूप में ship होती है, तो उसके बाद ordering करने का मतलब यह नहीं है कि वह connections स्वीकार कर रही है। इसके दो ईमानदार समाधान हैं। इसके बजाय इसकी socket unit के बाद order करें, ताकि जब daemon अभी भी शुरू हो रहा हो, तो kernel incoming connections को queue में रख सके। या अपनी service को retry करने के लिए बनाएँ और restart policy को अपना काम करने दें। एक unit कौन सा type उपयोग करती है, यह systemctl cat में देखा जा सकता है, और Type= setting और प्रत्येक value systemd को क्या बताती है को पढ़ना तब उपयोगी होता है जब आप ordering पर निर्भर होने वाले हों।
ExecStartPre एक गेट है जो यूनिट को विफल कर सकता है
ExecStartPre=, ExecStart= से पहले चलता है। यदि यह non-zero exit code देता है, तो activation रद्द हो जाती है और यूनिट failed स्थिति में चली जाती है। ExecStart= कभी नहीं चलता। यही कारण है कि बड़ी संख्या में यूनिट्स बिना किसी वास्तविक प्रोग्राम संदेश के विफल हो जाती हैं, क्योंकि प्रोग्राम कभी शुरू ही नहीं हुआ था।
तथ्य जो अक्सर लोगों को भ्रमित करते हैं:
- यह एक shell नहीं है। इसमें pipes, redirection, globs या
&&का उपयोग नहीं होता। पहला token एक absolute path होना चाहिए। जब आपको shell syntax की आवश्यकता हो, तो लाइन को/bin/sh -c '...'में लपेटें। -prefix लगाने से non-zero exit घातक नहीं रहता:ExecStartPre=-/usr/bin/optional-check।- प्रत्येक
ExecStartPre=को अगले के चलने से पहले exit करना होगा। यह किसी long-running process को शुरू नहीं कर सकता। - सभी
ExecStartPre=लाइनेंExecStart=के साथTimeoutStartSec=साझा करती हैं। डेटाबेस की प्रतीक्षा करने वाला एक pre-check start timeout को समाप्त कर देता है, और फिर यूनिटstart operation timed out. Terminating.के journal में दिखने के बादResult: timeoutके साथ विफल हो जाती है।
विफलता की लाइन मुख्य process के बजाय control process का नाम बताती है:
inventory-api.service: Control process exited, code=exited, status=2/INVALIDARGUMENT
inventory-api.service: Failed with result 'exit-code'.उस symbolic नाम को ध्यान से पढ़ें। systemd छोटे exit codes को एक निश्चित table के माध्यम से map करता है, इसलिए 2 हमेशा INVALIDARGUMENT के रूप में प्रिंट होता है, चाहे प्रोग्राम का उससे कुछ भी अर्थ हो। status=203/EXEC वह है जो वास्तविक जानकारी देता है: systemd binary को execute ही नहीं कर सका, क्योंकि path गलत है या file executable नहीं है।
Directories बनाने के लिए ExecStartPre= का उपयोग न करें। RuntimeDirectory=, StateDirectory=, LogsDirectory= और CacheDirectory= उन्हें सही owner और mode के साथ बनाते हैं, और service रुकने पर RuntimeDirectory= को साफ कर दिया जाता है। वे DynamicUser= के तहत भी सही ढंग से व्यवहार करते हैं, जो कि हाथ से लिखा गया mkdir नहीं करता है।
Condition चुपचाप विफल होता है। Assert जोर से विफल होता है।
Condition और Assert परिवार एक ही टेस्ट चलाते हैं। वे केवल इस बात में भिन्न हैं कि टेस्ट विफल होने पर क्या होता है।
एक विफल Condition...= यूनिट को छोड़ देता है। स्टार्ट जॉब को सफल बताया जाता है। यूनिट inactive (dead) बनी रहती है, कुछ भी विफल चिह्नित नहीं होता, कोई अलर्ट नहीं बजता, और जर्नल में एक लाइन दर्ज होती है:
Condition check resulted in Inventory API being skipped.systemd 250 और नए वर्ज़न पर, systemctl status कारण को सीधे प्रिंट करता है:
Active: inactive (dead)
Condition: start condition unmet at Thu 2026-08-20 09:14:02 UTC; 2min agoइसके नीचे इंडेंट की गई लाइन उस सटीक डायरेक्टिव का नाम बताती है जो विफल हुआ, उदाहरण के लिए ConditionPathExists=/etc/inventory/api.conf was not met।
एक विफल Assert...= यूनिट को विफल कर देता है। जर्नल में Assertion failed for Inventory API. लिखा आता है और यूनिट failed (Result: assert) में समाप्त होती है, जो मॉनिटरिंग के लिए पर्याप्त स्पष्ट है।
इनके बीच चुनाव करने के लिए खुद से पूछें कि एक विफल टेस्ट का क्या अर्थ है। Condition का अर्थ है "यह यूनिट इस मशीन पर लागू नहीं होती है"। Assert का अर्थ है "यह सत्य होना चाहिए, और यदि नहीं है, तो किसी को सूचित करें"। अधिकांश यूनिट्स को Condition की आवश्यकता होती है। Assert का उपयोग केवल तब करें जब चुपचाप कुछ न करना, यूनिट के विफल होने से भी बदतर हो।
Condition परिवार के साथ दो खतरे हैं।
पहला, एक विफल कंडीशन उन यूनिट्स को विफल नहीं करती जो उस पर निर्भर हैं। यदि a.service में Requires=b.service है और b.service किसी कंडीशन पर स्किप हो जाता है, तो b.service के लिए स्टार्ट जॉब अभी भी पूर्ण मानी जाती है, इसलिए a.service सामान्य रूप से ऐसी स्थिति में शुरू हो जाता है जहाँ b नहीं चल रहा है। एक कंडीशन केवल उसी यूनिट की सुरक्षा करती है जिसमें वह लिखी गई है।
दूसरा, कंडीशंस का मूल्यांकन हर बार यूनिट शुरू होने पर, उस क्षण किया जाता है जब जॉब चलती है। a systemd timer on a VPS द्वारा ट्रिगर की गई यूनिट को लगातार सौ बार स्किप किया जा सकता है और वह कभी भी विफल नहीं दिखेगी। यह a cron job that runs but does nothing की तरह ही एक साइलेंट नो-ऑप है, और आप इसे उसी तरह खोजते हैं: यूनिट की एग्जिट स्टेट पर भरोसा करने के बजाय उसके जर्नल को पढ़ें।
सर्वर पर जानने योग्य कंडीशंस:
ConditionPathExists=/etc/inventory/api.conf, और इसका निषेधConditionPathExists=!/etc/inventory/api.conf।ConditionFileNotEmpty=औरConditionDirectoryNotEmpty=, किसी कॉन्फ़िगरेशन फ़ाइल या डेटा डायरेक्टरी के लिए जिसे किसी पैकेज ने बनाया है लेकिन खाली छोड़ दिया है।ConditionVirtualization=, ताकि वास्तविक कर्नल इंटरफ़ेस की आवश्यकता वाली यूनिटConditionVirtualization=!containerले जा सके। जांचें कि आपका बॉक्सsystemd-detect-virtके साथ क्या रिपोर्ट करता है।ConditionHost=होस्टनेम या मशीन आईडी से मेल खाता है, जो यह निर्धारित करता है कि एक साझा यूनिट फ़ाइल दो सर्वरों पर अलग तरह से कैसे व्यवहार करती है।ConditionKernelCommandLine=औरConditionKernelVersion=, बूट पैरामीटर या न्यूनतम कर्नल से जुड़ी यूनिट्स के लिए।
एक खाली असाइनमेंट लिस्ट को साफ़ कर देता है, जो कि वह तरीका है जिससे ड्रॉप-इन उस कंडीशन को हटाता है जिसे पैकेज ने शिप किया था:
[Unit]
ConditionPathExists=
ConditionPathExists=/srv/inventory/api.confnetwork.target का मतलब यह क्यों नहीं है कि नेटवर्क चालू है
network.target एक सिंक्रोनाइज़ेशन पॉइंट है, न कि कोई स्टेट। बूट के समय, इसके बाद ऑर्डर करने का मतलब केवल यह है कि नेटवर्क मैनेजमेंट सॉफ़्टवेयर शुरू हो गया है। इसका मतलब यह नहीं है कि किसी इंटरफ़ेस को IP एड्रेस मिल गया है, या इंटरनेट के लिए कोई रूट मौजूद है। यह टारगेट मुख्य रूप से दूसरी दिशा के लिए मौजूद है: शटडाउन के समय नेटवर्क बंद होने से पहले After=network.target के साथ ऑर्डर की गई यूनिट को रोक दिया जाता है।
network-online.target वह है जो प्रतीक्षा करता है। यह आपके द्वारा चलाए जा रहे नेटवर्क मैनेजर से संबंधित एक wait-online सर्विस द्वारा समर्थित होता है:
systemd-networkd-wait-online.serviceजब systemd-networkd लिंक को मैनेज करता है, जो netplan के माध्यम से कॉन्फ़िगर किए गए Ubuntu सर्वर पर सामान्य स्थिति है।NetworkManager-wait-online.serviceNetworkManager के अंतर्गत।
पुराने ifupdown सेटअप में इसके बजाय networking.service से वही प्रभाव मिलता है। आपके पास जो भी हो, टारगेट का सही उपयोग करने के लिए दो लाइनों की आवश्यकता होती है, एक की नहीं।
[Unit]
Wants=network-online.target
After=network-online.targetnetwork-online.target डिफ़ॉल्ट बूट ट्रांजेक्शन का हिस्सा नहीं है, और कोई भी इसे आपके लिए पुल नहीं करता है। यदि आप केवल After= लिखते हैं, तो आप एक ऐसी यूनिट के खिलाफ ऑर्डर कर रहे हैं जिसे कभी कतार में नहीं रखा गया था, इसलिए ऑर्डरिंग का कोई प्रभाव नहीं पड़ता है। यह वही no-op है जिसका वर्णन पहले किया गया था, अपने सबसे महंगे रूप में। Wants= लाइन वह है जो टारगेट को ट्रांजेक्शन में डालती है ताकि After= लाइन के पास प्रतीक्षा करने के लिए कुछ हो।
दूसरी बात यह जानना है कि "online" को systemd द्वारा नहीं, बल्कि wait-online कार्यान्वयन द्वारा परिभाषित किया गया है। systemd-networkd-wait-online तब वापस आता है जब उसके द्वारा प्रबंधित लिंक एक कॉन्फ़िगर की गई स्थिति तक पहुँच जाते हैं। यह यह जांच नहीं करता है कि DNS रिज़ॉल्व हो रहा है या नहीं, और यह यह जांच नहीं करता है कि कोई रिमोट होस्ट पहुंच योग्य है या नहीं।
यह परिभाषा एक सामान्य VPS विफलता का कारण बनती है। एक मशीन जिसमें प्राइवेट नेटवर्क के लिए दूसरा इंटरफ़ेस है, जिसे netplan में घोषित तो किया गया है लेकिन कभी एड्रेस नहीं दिया गया, वह wait सर्विस को तब तक वहीं रोक कर रखता है जब तक वह हार नहीं मान लेती:
systemd-networkd-wait-online[612]: Timeout occurred while waiting for network connectivity.
systemd-networkd-wait-online.service: Failed with result 'exit-code'.बूट में दो अतिरिक्त मिनट लगते हैं क्योंकि डिफ़ॉल्ट टाइमआउट 120 सेकंड है। इसके दो समाधान हैं। netplan फ़ाइल में अप्रयुक्त इंटरफ़ेस को optional: true के रूप में चिह्नित करें, ताकि networkd उसके लिए प्रतीक्षा करना बंद कर दे। या wait सर्विस पर एक ड्रॉप-इन जोड़ें जो आपके काम के लिंक को --interface= के साथ नाम दे, या जो --any पास करे ताकि एक लिंक के चालू होते ही वह वापस आ जाए।
इससे भी बेहतर, टारगेट की आवश्यकता से बचें। कई सर्विस को केवल इसलिए network-online.target के बाद ऑर्डर किया जाता है क्योंकि वे एक विशिष्ट एड्रेस को बाइंड करती हैं और बूट के समय इस तरह की लाइन के साथ विफल हो जाती हैं:
nginx: [emerg] bind() to 203.0.113.10:443 failed (99: Cannot assign requested address)कर्नेल बाइंड करने से इनकार कर देता है क्योंकि वह एड्रेस अभी तक चालू नहीं हुआ है। net.ipv4.ip_nonlocal_bind=1 सेट करने से एक प्रोसेस उस एड्रेस को बाइंड कर सकती है जिसे बॉक्स ने अभी तक होल्ड नहीं किया है, और एक रीस्टार्ट पॉलिसी बाकी काम संभाल लेती है। नेटवर्क की तैयारी पर पूरे बूट को विलंबित करना एक ऐसी समस्या के लिए भारी उपकरण है जो आमतौर पर केवल एक सॉकेट की होती है।
चलते हुए सिस्टम पर वास्तविक systemd dependencies को कैसे पढ़ें
कभी भी केवल unit file के आधार पर निष्कर्ष न निकालें। Drop-ins, .wants/ symlinks और implicit default dependencies ऐसी कड़ियाँ जोड़ते हैं जो file में दिखाई नहीं देतीं।
systemctl cat inventory-api.serviceयह command unit file और हर drop-in को उस क्रम में print करती है जिस क्रम में वे लागू होते हैं, और हर block के ऊपर source path दिखाती है। इसे सबसे पहले चलाएं। /etc/systemd/system/inventory-api.service.d/ में पांच लाइन का override, packaged file पर भारी पड़ता है और अन्यथा दिखाई नहीं देता।
systemctl show inventory-api.service -p Requires -p Wants -p After -p Before -p ConditionResult -p AssertResultयह command drop-ins और systemd द्वारा जोड़ी गई implicit dependencies के बाद, resolved values को print करती है। ConditionResult=no उस स्थिति का सीधा उत्तर है जहाँ "unit ने सफलता की सूचना दी लेकिन कुछ नहीं किया"।
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.serviceसाधारण रूप से यह command Requires= और Wants= को नीचे की ओर देखती है। --reverse दिखाता है कि कौन सी units आपकी unit को pull करती हैं, जिससे आप उस target को ढूंढ सकते हैं जो boot के समय इसे start करता है। --after और --before ordering दिखाते हैं, और जब यह सवाल हो कि क्या किसी चीज़ ने वास्तव में प्रतीक्षा की, तो इन्हें ही पढ़ें।
journalctl -b -u inventory-api.service --no-pager
journalctl -b -o short-precise -u inventory-api.service -u postgresql.serviceदूसरी command दो units को millisecond timestamps के साथ मिलाती है। इसी तरह आप अनुमान लगाने के बजाय ordering race को साबित करते हैं। pg_isready की विफलता PostgreSQL के database system is ready to accept connections logs से पहले आती है, और उनके बीच का अंतर output में स्पष्ट दिखाई देता है।
systemd-analyze verify /etc/systemd/system/inventory-api.service
systemd-analyze critical-chain inventory-api.serviceverify unit को उस तरह load करता है जैसे systemd करेगा और अज्ञात directives, मौजूद न होने वाली units पर dependencies, ordering cycles और ऐसी syntax की रिपोर्ट करता है जिसे वह parse नहीं कर सकता। यह सिस्टम में कुछ भी नहीं बदलता। critical-chain उस ordering chain को print करता है जिसने unit को delay किया, साथ ही वह समय भी जब हर step active हुआ, और यह केवल उसी unit के लिए काम करता है जो वर्तमान boot के दौरान start हुई हो।
किसी भी unit file को edit करने के बाद, sudo systemctl daemon-reload चलाएं। Packaged unit को बदलने के लिए, sudo systemctl edit inventory-api.service का उपयोग करें, जो आपके लिए एक drop-in बनाता है। /usr/lib/systemd/system/ के अंतर्गत vendor file को edit करना तब तक काम करता है जब तक कि अगला package upgrade उसे बदल न दे। यही drop-in mechanism वह तरीका है जिससे आप service पर memory और CPU limits लगा सकते हैं, बिना उस file को छुए जो package के स्वामित्व में है।
ऑर्डरिंग साइकिल और जर्नल में उनकी लाइन
दोनों दिशाओं में ऑर्डरिंग जोड़ने पर systemd एक जॉब को हटाकर लूप को तोड़ देता है:
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 यह चुनता है कि किस जॉब को हटाना है, और हो सकता है कि वह उसे न चुने जिसे आप चुनना चाहेंगे। इसका परिणाम एक ऐसी सर्विस के रूप में सामने आता है जो कुछ रीबूट के बाद गायब हो जाती है और दूसरों के बाद मौजूद रहती है, जिसे बाहर से डीबग करना बहुत कठिन होता है। अधिकांश साइकिल उन यूनिट्स से आती हैं जो DefaultDependencies=no सेट करती हैं और फिर भी खुद को basic.target के विरुद्ध ऑर्डर करती हैं, या किसी ऐसी यूनिट में Before= जोड़ने से आती हैं जिसमें पहले से ही आपकी ओर इशारा करने वाला After= मौजूद हो। systemd-analyze verify उन्हें बिना रीबूट किए ढूंढ लेता है।
निश्चित यूनिट
[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.targetप्रत्येक लाइन एक कार्य करती है। Wants= इस यूनिट के जीवनकाल को उनसे जोड़े बिना दोनों dependencies को transaction में खींचता है। After= प्रतीक्षा करने का कार्य करता है, और इसे दोनों नामों को दोहराना पड़ता है क्योंकि dependency और ordering अलग-अलग सेटिंग्स हैं। ConditionPathExists= का अर्थ है कि जिस मशीन में पैकेज तो है लेकिन कॉन्फ़िगरेशन नहीं है, वह यूनिट को अलार्म बजाने के बजाय चुपचाप छोड़ देगी, जो कि कॉन्फ़िगरेशन-संचालित सर्विस के लिए सही व्यवहार है। Type=notify का अर्थ है कि इस यूनिट के बाद ordered कोई भी चीज़ fork के बजाय वास्तविक readiness की प्रतीक्षा करती है। Restart=on-failure बूट के काफी समय बाद डेटाबेस के चले जाने की स्थिति को कवर करता है, क्योंकि ordering केवल पहली बार शुरू होने पर लागू होती है। वह retry कितनी आक्रामक होनी चाहिए, यह Restart= और RestartSec= सेटिंग्स द्वारा नियंत्रित होता है।
इस पर भरोसा करने से पहले इसकी जाँच करें:
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 Resultएक स्वस्थ यूनिट ConditionResult=yes को ActiveState=active के साथ पढ़ती है, और Result=success पुष्टि करता है कि अंतिम रन पर कुछ भी विफल नहीं हुआ। ActiveState=inactive के साथ ConditionResult=no का अर्थ है कि यूनिट को छोड़ दिया गया था, और जिस condition का नाम बताने वाली journal लाइन आपको बताती है कि कौन सा परीक्षण विफल रहा।
FAQ
क्या Requires= दूसरी unit के start होने का इंतज़ार करता है?
नहीं। Requires= और After= अलग-अलग सेटिंग्स हैं। Requires= दूसरी unit को उसी transaction में शामिल करता है, और फिर systemd दोनों jobs को समानांतर (parallel) रूप से शुरू करता है। इंतज़ार करने के लिए, उसी unit का नाम देते हुए After= जोड़ें। इसे जोड़ने का एक दूसरा कारण भी है: यदि Requires= dependency विफल हो जाती है, तो यह आपकी unit को शुरू होने से तभी रोकता है जब After= भी सेट हो, क्योंकि ordering के बिना आपकी unit उस समय तक शुरू हो चुकी होती है जब दूसरी unit विफल होती है।
क्या मुझे network.target या network-online.target के बाद order करना चाहिए?
बूट के समय, network.target का केवल यह अर्थ है कि नेटवर्क प्रबंधन सॉफ्टवेयर शुरू हो गया है, इसलिए यह IP addresses या routes के बारे में कोई गारंटी नहीं देता है। जब आपकी service को शुरू होने के समय एक कार्यशील IP address की आवश्यकता हो, तो network-online.target का उपयोग करें, और Wants=network-online.target तथा After=network-online.target दोनों लिखें, क्योंकि यह target डिफ़ॉल्ट बूट transaction में नहीं होता है और केवल After= का उपयोग करने पर वह ऐसी unit का इंतज़ार करता है जिसे किसी ने queue नहीं किया है। यदि service केवल इसलिए विफल होती है क्योंकि वह एक विशिष्ट IP पर bind होती है, तो Restart=on-failure के साथ net.ipv4.ip_nonlocal_bind=1 का उपयोग करना बूट को धीमा करने की तुलना में बेहतर विकल्प है।
मेरी unit सफलता की रिपोर्ट क्यों देती है लेकिन कभी चलती नहीं है?
एक विफल Condition...= परीक्षण unit को छोड़ देता है और start job को सफल बताता है, इसलिए कुछ भी विफल (failed) के रूप में चिह्नित नहीं होता है। systemctl show <unit> -p ConditionResult चलाएं, और ConditionResult=no इसकी पुष्टि करेगा। फिर Condition check resulted in <description> being skipped लाइन के लिए journalctl -b -u <unit> पढ़ें। systemd 250 और उसके बाद के संस्करणों में, systemctl status <unit> उस सटीक directive का नाम भी बताता है जो पूरा नहीं हुआ था।
Condition और Assert के बीच क्या अंतर है?
वे समान परीक्षण चलाते हैं। एक विफल Condition unit को चुपचाप छोड़ देता है और job फिर भी सफल हो जाती है। एक विफल Assert unit को विफल कर देता है, Assertion failed for <description>. लॉग करता है और इसे failed (Result: assert) स्थिति में छोड़ देता है। "यह unit इस मशीन पर लागू नहीं होती है" के लिए Condition का उपयोग करें, जो लगभग हर वास्तविक स्थिति को कवर करता है। Assert का उपयोग केवल तब करें जब किसी missing precondition का विफल unit पर नज़र रखने वाले को दिखना आवश्यक हो।
ExecStartPre, status=203/EXEC के साथ विफल क्यों होता है?
203/EXEC का अर्थ है कि systemd उस command को निष्पादित ही नहीं कर सका। इसके सामान्य कारण हैं: path का absolute न होना, binary का उस मशीन पर मौजूद न होना, file में execute bit का न होना, या ऐसी script जिसका #! लाइन किसी missing interpreter की ओर इशारा करता हो। systemd के अन्य छोटे codes एक निश्चित तालिका से आते हैं, इसलिए status=2/INVALIDARGUMENT का केवल यह अर्थ है कि command 2 के साथ exit हुई और यह arguments के बारे में कुछ नहीं बताता है। याद रखें कि ExecStartPre= को shell के माध्यम से नहीं चलाया जाता है, इसलिए pipes और globs के लिए /bin/sh -c '...' की आवश्यकता होती है।