systemd service restart क्यों नहीं हो रही है?
Restart= केवल मुख्य process पर नज़र रखता है। यदि cgroup में कोई child process मरती है तो systemd उसे नहीं देख पाता। Type= और restart limits के साथ यह समस्या कैसे हल करें।
संक्षिप्त उत्तर: systemd restart policies केवल एक process पर नज़र रखती हैं
systemd restart policies प्रति unit केवल एक process की निगरानी करती हैं: मुख्य process। Restart= केवल उस एक process के exit status को पढ़ता है, किसी और को नहीं। एक unit के control group में बीस processes हो सकती हैं, उनमें से एक मर सकती है, और unit active (running) बनी रहती है क्योंकि मुख्य process अभी भी चल रही होती है। systemd के अनुसार कुछ भी विफल नहीं हुआ है, इसलिए कुछ भी restart नहीं होता है।
systemd अन्य processes के बारे में जानता है। जब unit रुकती है तो यह उन्हें kill कर देता है, यह unit की limits के विरुद्ध उनकी memory की गणना करता है, यह उन पर unit का CPU quota लागू करता है, और यह उन्हें systemctl status में print करता है। यह बस कभी भी उनके exit status को नहीं पढ़ता है। restart logic और cgroup दो अलग-अलग चीजें हैं, और इस गाइड का अधिकांश हिस्सा उनके बीच के अंतर के बारे में है।
cgroup में क्या होता है, और restart logic क्या पढ़ता है
cgroup (control group) एक kernel object है जो प्रक्रियाओं (processes) के एक समूह का स्वामी होता है। प्रत्येक service unit को एक cgroup मिलता है, जिसका नाम unit के नाम पर रखा जाता है। कोई भी प्रक्रिया इसे छोड़ नहीं सकती। child processes अपने parent का cgroup विरासत में प्राप्त करती हैं, और एक unprivileged प्रक्रिया खुद को कहीं और स्थानांतरित नहीं कर सकती। यही कारण है कि systemd उस daemon को साफ कर सकता है जो दो बार fork होता है, जिसे पुराने init scripts कभी भी भरोसेमंद तरीके से नहीं कर पाते थे।
दोनों तथ्यों को एक साथ देखें:
systemd-cgls --unit myapp.service
systemctl show -p MainPID -p NRestarts -p Restart -p RestartUSec myapp.servicesystemd-cgls unit की प्रत्येक प्रक्रिया को सूचीबद्ध करता है। MainPID वह एकल संख्या है जिसे restart policy पढ़ती है। जब ये दोनों आपके मानसिक मॉडल से मेल नहीं खाते, तो वह विसंगति ही bug है। MainPID=0 गलत PID से भी बदतर है: इसका मतलब है कि systemd किसी भी चीज़ को ट्रैक नहीं कर रहा है, इसलिए कोई भी Restart= मान कभी सक्रिय नहीं हो सकता।
main-process नियम का एक वास्तविक अपवाद है। यदि kernel का out-of-memory killer unit के cgroup के भीतर किसी भी प्रक्रिया को मार देता है, तो systemd उसे देख लेता है, क्योंकि वह cgroup की memory.events फ़ाइल की निगरानी करता है। OOMPolicy= यह तय करता है कि आगे क्या होगा, और इसका default stop है: पूरी unit को रोक दिया जाता है, परिणाम को oom-kill के रूप में दर्ज किया जाता है, और इसे एक विफलता माना जाता है, इसलिए Restart=on-failure सक्रिय हो जाता है। journal में यह स्पष्ट रूप से लिखा होता है।
myapp.service: A process of this unit has been killed by the OOM killer.
myapp.service: Failed with result 'oom-kill'.इसलिए memory के कारण मारी गई child process unit को बंद कर देती है, जबकि segmentation fault से मरने वाली वही child process ऐसा नहीं करती। यदि आप किसी unit पर memory limits सेट करते हैं, तो restart policy को ट्यून करने से पहले MemoryMax और CPUQuota एक unit के cgroup पर कैसे लागू होते हैं पढ़ें, क्योंकि ये दोनों सुविधाएँ यहीं मिलती हैं और कहीं नहीं।
How Type= picks the main process
Type= in the [Service] section is not only about start-up ordering. It is the rule that decides which PID (process ID) becomes MainPID, which is the same as deciding what Restart= is able to see.
Type=simpleis the default. The process systemd forks fromExecStart=is the main process. systemd marks the unit as started immediately, before it knows whether theexeceven worked. A typo in the binary path gives you a start job that succeeds, and thenMain process exited, code=exited, status=203/EXECa moment later.Type=execbehaves likesimple, except the start job waits until theexechas succeeded. That turns the typo above into an honest start failure. It needs systemd 240 or newer, which every supported distribution has. Prefer it tosimple.Type=forkingexpects the process fromExecStart=to fork a background daemon and then exit. systemd waits for the parent to exit, then looks for the real daemon. Give itPIDFile=. Without one,GuessMainPID=(on by default) works only when exactly one process is left in the cgroup. Leave two behind andMainPIDstays0.Type=notifymeans the service callssd_notify(3)and sendsREADY=1when it can serve traffic. It may also sendMAINPID=to hand systemd a different process to track.NotifyAccess=defaults tomain, so a notification sent by a child is ignored and the journal names the PID it came from.Type=oneshothas no lasting main process. The unit goes inactive as soon asExecStart=finishes, unless you setRemainAfterExit=yes.Restart=alwaysandRestart=on-successare refused here, with the messageService has Restart= set to either always or on-success, which isn't allowed for Type=oneshot services. Refusing.The other values, includingon-failure, are accepted.
Two Type=forking errors are worth memorising, because each leaves you with a unit that looks broken for no visible reason:
myapp.service: Can't open PID file /run/myapp.pid (yet?) after start: No such file or directory
myapp.service: New main PID 4711 does not belong to service, and PID file is not owned by root. Refusing.The first means the daemon writes its PID file somewhere else, or writes it later than systemd looks. The second means the PID file names a process outside the unit's cgroup, which systemd refuses to adopt, because a writable PID file would otherwise become a way to make systemd send signals to any process on the box.
Wrapper script अपने child processes की मृत्यु को क्यों छिपाता है
यहाँ वह संरचना दी गई है जो शीर्षक में दिए गए प्रश्न को जन्म देती है।
#!/bin/bash
/usr/local/bin/myapp-web &
/usr/local/bin/myapp-worker &
waitयह unit Type=simple है, इसलिए मुख्य process shell है। बिना किसी argument के wait केवल तभी return होता है जब हर child process exit हो जाती है। यदि आप worker को kill करते हैं, तो shell web process के लिए प्रतीक्षा करता रहता है। इस कारण shell exit नहीं होता, MainPID exit नहीं होता, और Restart= को कभी नहीं देखा जाता। अब cgroup में एक process कम है, systemctl status छोटा tree print करता है, और unit अभी भी active (running) है। systemd में कोई भी उस tree में बदलावों की निगरानी नहीं करता है।
इसी गलती का दूसरा संस्करण अधिक शांत है:
ExecStart=/bin/sh -c 'export APP_ENV=production; /usr/local/bin/myapp'मुख्य process shell है, myapp नहीं। systemctl stop पर, systemd मुख्य process को SIGTERM भेजता है, और foreground child की प्रतीक्षा कर रहा shell उस signal को आगे नहीं बढ़ाता है। इसके बाद stop प्रक्रिया पूरा TimeoutStopSec लेती है, जो डिफ़ॉल्ट रूप से 90 सेकंड है, और इस तरह समाप्त होती है:
myapp.service: State 'stop-sigterm' timed out. Killing.
myapp.service: Killing process 4711 (myapp) with signal SIGKILL.इसका समाधान exec है। exec /usr/local/bin/myapp लिखें और shell को program द्वारा प्रतिस्थापित कर दिया जाएगा, जिससे MainPID वह program बन जाएगा और signals उस तक पहुँच सकेंगे। इससे भी बेहतर यह है कि shell को हटा दें और unit में Environment= या EnvironmentFile= का उपयोग करें। ध्यान दें कि यह बग तब छिप जाता है जब -c string में केवल एक command होती है, क्योंकि bash और dash दोनों ही उस स्थिति को सीधे exec में optimize कर देते हैं। string में दूसरी command जोड़ें और shell आपके program के सामने जीवित रहेगा।
इसे दो मिनट में test VPS पर reproduce करें
ऊपर दिए गए wrapper को /usr/local/bin/two-children.sh के रूप में save करें, chmod +x के साथ इसे executable बनाएँ, और दोनों program paths को sleep 3600 से बदलें। Type=simple और Restart=on-failure के साथ एक unit को इसकी ओर इंगित करें, फिर systemctl daemon-reload करें और इसे start करें। systemd-cgls --unit two-children.service चलाएँ और तीन PIDs नोट करें: shell और उसके दो children। एक child को sudo kill <pid> के साथ kill करें। unit को फिर से जाँचें। tree में एक process कम है, state अभी भी active (running) है, और journal में कुछ भी नया नहीं है। अब इसके बजाय sudo kill -9 <shell pid> चलाएँ। unit fail हो जाती है, जीवित child को साफ कर दिया जाता है क्योंकि KillMode=control-group डिफ़ॉल्ट है, और journal Scheduled restart job, restart counter is at 1. दिखाता है।
Restart= का पूरा vocabulary, और on-failure कब always से बेहतर होता है
Restart= सात मानों (values) में से एक लेता है, और इनके बीच का अंतर इस बात पर निर्भर करता है कि किसे clean माना जाता है। systemd, exit code 0, SuccessExitStatus= में सूचीबद्ध किसी भी कोड, और SIGHUP, SIGINT, SIGTERM तथा SIGPIPE signals को clean exit मानता है। बाकी सब कुछ, जिसमें SIGKILL और SIGSEGV शामिल हैं, unclean है।
noडिफ़ॉल्ट है। unit कभी खुद को restart नहीं करती, यही कारण है कि बिनाRestart=लाइन वाली unit पहली बार crash होने पर बंद हो जाती है और बंद ही रहती है।on-successकेवल clean exit के बाद ही restart करती है।on-failurenon-zero exit code, unclean signal, start या stop timeout, या watchdog expiry पर restart करती है।on-abnormalunclean signal, timeout या watchdog expiry पर restart करती है, लेकिन साधारण non-zero exit code पर कभी नहीं।on-abortकेवल unclean signal पर restart करती है, जिसका अर्थ है crash।on-watchdogकेवल तब restart करती है जबWatchdogSec=समाप्त (expire) हो जाता है।alwaysऊपर दिए गए हर मामले के बाद restart करती है, जिसमें status 0 के साथ clean exit भी शामिल है।
on-failure लंबे समय तक चलने वाले daemon के लिए सही डिफ़ॉल्ट है। यह crash होने पर service को वापस लाता है और जानबूझकर किए गए exit 0 को वैसे ही रहने देता है। always ऐसे प्रोग्राम के लिए उपयुक्त है जो अपने नियंत्रण से बाहर के कारणों से clean exit करता है, जैसे कि एक tunnel client जो far end के disconnect होने पर 0 return करता है। always का नुकसान यह है कि यह bugs को छिपा देता है: एक service जो start होती है, एक खराब config file पढ़ती है, error log करती है और 0 के साथ exit हो जाती है, वह हमेशा loop में चलती रहेगी, और इसका एकमात्र संकेत restart counter का बढ़ना होगा।
SuccessExitStatus= clean और unclean के बीच की रेखा को बदल देता है। Borg चेतावनियों के लिए 1 और errors के लिए 2 exit code देता है, इसलिए SuccessExitStatus=1 के बिना एक backup unit हर बार failed मार्क हो जाती है जब वह एक unreadable file को छोड़ती है। RestartPreventExitStatus= उन कोड्स को सूचीबद्ध करता है जो always के तहत भी restart को रोकते हैं, जो कि किसी प्रोग्राम के लिए यह बताने का clean तरीका है कि उसे वापस नहीं आना चाहिए। RestartForceExitStatus= इसका उल्टा करता है। एक backup job को restart loop के बजाय timer द्वारा संचालित Type=oneshot unit में होना चाहिए, और schedule पर job चलाने वाला service और timer pair ही वह तरीका है जिसे यहाँ copy करना चाहिए।
testing के बारे में एक चेतावनी। अपनी service को साधारण kill <pid> से kill करने पर SIGTERM भेजा जाता है, जो clean सूची में है, इसलिए Restart=on-failure सही ढंग से कुछ नहीं करता है और आप यह निष्कर्ष निकालते हैं कि आपकी config खराब है। इसके बजाय kill -9 <pid> या systemctl kill -s SIGKILL myapp.service का उपयोग करें। यह भी याद रखें कि Restart= का कोई भी मान systemctl stop के बाद, या तब काम नहीं करता जब unit को इसलिए रोका गया क्योंकि कोई BindsTo= या PartOf= dependency हट गई थी। एक stop job विफलता (failure) नहीं है।
RestartSec, और 100 मिलीसेकंड का डिफ़ॉल्ट
RestartSec= यूनिट के रुकने और systemd द्वारा उसे दोबारा शुरू करने के बीच का विराम है, और इसका डिफ़ॉल्ट मान 100 मिलीसेकंड है। जाँचें कि आपकी यूनिट ने वास्तव में क्या लोड किया है:
systemctl show -p RestartUSec -p StartLimitIntervalUSec -p StartLimitBurst myapp.serviceजो यूनिट इसे सेट नहीं करती, वह RestartUSec=100ms प्रिंट करती है। यह डिफ़ॉल्ट मान उस सर्विस के लिए ठीक है जो एक बार क्रैश होकर वापस आ जाती है। यह उस सर्विस के लिए गलत है जो बिल्कुल भी शुरू नहीं हो सकती, क्योंकि ऐसी स्थिति में आधे सेकंड के भीतर पाँच बार रीस्टार्ट का प्रयास होता है, जो ठीक वही स्थिति है जिससे आगे वर्णित रेट लिमिट ट्रिगर हो जाती है। किसी भी ऐसी सर्विस के लिए जो डेटाबेस, माउंट या नेटवर्क रूट की प्रतीक्षा करती है, RestartSec=5s या उससे अधिक सेट करें।
अगस्त 2026 तक, systemd 254 और उसके नए वर्ज़न RestartSteps= और RestartMaxDelaySec= भी प्रदान करते हैं, जो इतने प्रयासों के दौरान डिले को RestartSec= से बढ़ाकर एक अधिकतम सीमा तक ले जाते हैं। Ubuntu 24.04 में systemd 255 आता है और इसमें ये विकल्प मौजूद हैं। Debian 12 में systemd 252 आता है और इसमें ये विकल्प नहीं हैं। जब निर्भरता (dependency) लंबे समय तक डाउन रह सकती है, तो बढ़ता हुआ डिले (growing delay) सही समाधान है।
"start request repeated too quickly" का वास्तविक अर्थ
यह वह स्थिति है जिसे देखकर उपयोगकर्ता यह मान लेते हैं कि systemd ने मनमाने ढंग से प्रयास करना छोड़ दिया है। यह वास्तव में एक काउंटर है। नियम यह है: यदि कोई unit StartLimitIntervalSec= के भीतर StartLimitBurst= बार से अधिक start की जाती है, तो systemd उसे दोबारा start करने से मना कर देता है और उसे failed state में डाल देता है। डिफ़ॉल्ट रूप से यह सीमा 10 सेकंड में 5 बार start करने की है।
Journal में यह क्रम दिखाई देता है:
myapp.service: Scheduled restart job, restart counter is at 5.
myapp.service: Start request repeated too quickly.
myapp.service: Failed with result 'start-limit-hit'.
Failed to start myapp.service - My application.और systemctl start समाधान के साथ उत्तर देता है जो पहले से ही लिखा हुआ है:
Job for myapp.service failed because start of the service was attempted too often. See "systemctl status myapp.service" and "journalctl -xeu myapp.service" for details. To force a start use "systemctl reset-failed myapp.service" followed by "systemctl start myapp.service" again.systemctl reset-failed myapp.service काउंटर और failed state को साफ़ कर देता है। इसके अलावा कोई और कमांड ऐसा नहीं करती, इसलिए जब तक आप इसे नहीं चलाते, तब तक एक साधारण systemctl start को बार-बार अस्वीकार किया जाता रहेगा। मैन्युअल रूप से start करने के प्रयास भी इस सीमा में गिने जाते हैं, इसलिए कॉन्फ़िगरेशन फ़ाइल को एडिट करते समय कुछ बार जल्दबाजी में systemctl restart चलाने से बिना किसी क्रैश के भी यह स्थिति उत्पन्न हो सकती है।
जो बात लोगों को भ्रमित करती है वह यह है: start-limit-hit कभी यह नहीं बताता कि service विफल क्यों हो रही थी। यह केवल यह बताता है कि यह बार-बार और तेजी से विफल हुई है। वास्तविक कारण इसके ऊपर दी गई journal लाइनों में होता है।
ये दोनों सेटिंग्स [Unit] सेक्शन में होनी चाहिए। आपको ऐसे उदाहरण मिलेंगे जो इन्हें [Service] में रखते हैं, जिसे पुराने systemd ने स्वीकार किया था, और यहीं से भ्रम शुरू होता है। इन्हें [Unit] में लिखें, फिर systemctl show के साथ systemd से पूछें कि उसने क्या लोड किया है, क्योंकि केवल लोड की गई वैल्यू ही मायने रखती है।
[Unit]
Description=My application
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=exec
ExecStart=/usr/local/bin/myapp
Restart=on-failure
RestartSec=10sयह unit को हार मानने से पहले पांच मिनट की अवधि के भीतर पांच प्रयास करने की अनुमति देता है। StartLimitIntervalSec=0 इस सीमा को पूरी तरह से बंद कर देता है, और आपको पता होना चाहिए कि आप क्या चुन रहे हैं: एक service जो कभी start नहीं हो सकती, वह अब हमेशा retry करती रहेगी और हर बार journal में लिखती रहेगी। मशीन-व्यापी डिफ़ॉल्ट सेटिंग्स /etc/systemd/system.conf में DefaultStartLimitIntervalSec= और DefaultStartLimitBurst= के रूप में रहती हैं।
एक संबंधित सेटिंग चेतावनी की हकदार है। StartLimitAction= यह तय करता है कि सीमा पूरी होने पर क्या होगा, और यह reboot, reboot-force और poweroff सहित कई वैल्यू स्वीकार करता है। डिफ़ॉल्ट none है, जो unit को विफल कर देता है और मशीन को वैसे ही छोड़ देता है। एक रिमोट VPS पर, poweroff का मतलब एक ऐसी मशीन है जो तब तक बंद रहेगी जब तक आप प्रदाता का कंसोल नहीं खोलते।
समाधान एक: प्रति यूनिट एक प्रोसेस
लगभग हर मामले में यही सही उत्तर है। यदि दो प्रोग्राम चलाने आवश्यक हैं, तो दो यूनिट लिखें। प्रत्येक का अपना वास्तविक मुख्य प्रोसेस, वास्तविक एग्जिट स्टेटस और अपनी रीस्टार्ट पॉलिसी होती है। आपको अलग-अलग लॉग, अलग-अलग रिसोर्स लिमिट और अलग-अलग रीस्टार्ट काउंटर भी मिलते हैं, जो कि रात के तीन बजे आपको चाहिए होते हैं।
यूनिट्स के बीच के संबंध को शेल स्क्रिप्ट में नहीं, बल्कि यूनिट फाइलों में व्यक्त करें।
After=केवल स्टार्ट-अप को व्यवस्थित करता है। यह विफलताओं के बारे में कुछ नहीं कहता।Requires=दूसरी यूनिट को इसके साथ शुरू करता है, और यदि दूसरी यूनिट को स्पष्ट रूप से रोका जाता है, तो यह इसे भी रोक देता है।BindsTo=,Requires=के साथ वह स्थिति भी है जिसकी आपको परवाह है: यह यूनिट तब रुक जाती है जब दूसरी यूनिट किसी भी कारण से रुकती है, जिसमें क्रैश होना भी शामिल है। इसेAfter=के साथ जोड़ें, अन्यथा ऑर्डरिंग अनिश्चित रहती है।PartOf=स्टॉप और रीस्टार्ट को नीचे की ओर प्रसारित करता है, इसलिएsystemctl restart myapp.targetहर उस यूनिट तक पहुँचता है जो इसकेPartOf=है।Upholds=(systemd 249 और नए संस्करण, यानी Ubuntu 22.04 और बाद के संस्करण) नामित यूनिट को चालू रखता है: यदि यह रुकती है, तो systemd इसे फिर से शुरू कर देता है। यह बाकी सभी चीजों की तरह ही स्टार्ट रेट लिमिट के अधीन है।
एक वर्कर जिसे अपने API सर्वर के बिना कभी नहीं चलना चाहिए, और जिसे systemd तब तक जीवित रखता है जब तक API चालू है:
# /etc/systemd/system/myapp-api.service
[Unit]
Description=myapp API server
Wants=network-online.target
After=network-online.target
Upholds=myapp-worker.service
[Service]
Type=exec
User=myapp
ExecStart=/usr/local/bin/myapp serve
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target# /etc/systemd/system/myapp-worker.service
[Unit]
Description=myapp background worker
BindsTo=myapp-api.service
After=myapp-api.service
StartLimitIntervalSec=120
StartLimitBurst=5
[Service]
Type=exec
User=myapp
ExecStart=/usr/local/bin/myapp worker
Restart=on-failure
RestartSec=5sवर्कर में कोई [Install] सेक्शन नहीं है और इसे कभी भी मैन्युअल रूप से इनेबल नहीं किया जाता है। API यूनिट इसे Upholds= के साथ खींचती है, इसलिए systemctl enable --now myapp-api.service ही एकमात्र कमांड है जिसे आप चलाते हैं। रिलोड करें और देखें कि systemd ने इस जोड़ी के बारे में क्या किया है:
sudo systemctl daemon-reload
systemd-analyze verify /etc/systemd/system/myapp-worker.service
systemctl list-dependencies myapp-api.servicesystemd-analyze verify फाइल साफ होने पर कुछ भी प्रिंट नहीं करता है। कोई भी आउटपुट एक समस्या है, आमतौर पर ऐसी की (key) जिसे systemd उस सेक्शन में नहीं पहचानता जहाँ आपने इसे लिखा है, या ऐसी यूनिट पर निर्भरता जो मौजूद नहीं है।
दूसरा समाधान: Type=notify, ताकि systemd को PID से अधिक जानकारी हो
यदि प्रोग्राम systemd नोटिफिकेशन प्रोटोकॉल का उपयोग करता है, तो इसका लाभ उठाएं। Type=notify के साथ, सर्विस systemd को बताती है कि वह कब तैयार है। इससे सर्विस की शुरुआत केवल उम्मीद पर नहीं, बल्कि वास्तविक स्थिति पर आधारित होती है। साथ ही, यह एक लॉन्चर के बजाय उस प्रोसेस को इंगित करने के लिए MAINPID= भेज सकती है जो वास्तव में महत्वपूर्ण है।
WatchdogSec= वह हिस्सा है जिसके लिए यह प्रयास सार्थक है। इसे सेट करें, और सर्विस को कम से कम उतनी बार sd_notify(3) के माध्यम से WATCHDOG=1 भेजना होगा। जब ये संदेश आने बंद हो जाते हैं, तो systemd सर्विस को SIGABRT के साथ समाप्त कर देता है और उसे failed के रूप में चिह्नित करता है, ताकि Restart=on-failure या Restart=on-watchdog उसे पुनः शुरू कर सके। किसी ऐसी प्रोसेस को रीस्टार्ट करने का यह एकमात्र इन-बिल्ट तरीका है जो चल तो रही है लेकिन अटक गई है; ऐसी स्थिति को कोई भी exit-status पॉलिसी नहीं पकड़ सकती।
[Service]
Type=notify
NotifyAccess=main
ExecStart=/usr/local/bin/myapp serve
WatchdogSec=30s
Restart=on-failure
RestartSec=5sवॉचडॉग ट्रिप जर्नल में myapp.service: Watchdog timeout (limit 30s)! के रूप में दिखाई देता है, जिसके बाद प्रोसेस को किल कर दिया जाता है। यदि इसके बजाय यूनिट TimeoutStartSec समाप्त होने तक activating (start) स्थिति में रहती है, तो इसका मतलब है कि READY=1 कभी प्राप्त नहीं हुआ: या तो प्रोग्राम इस प्रोटोकॉल का समर्थन नहीं करता है, या NotifyAccess=main किसी चाइल्ड प्रोसेस से आए नोटिफिकेशन को अस्वीकार कर रहा है, जिसे जर्नल दोनों PIDs के साथ रिपोर्ट करता है।
ऐसे सॉफ्टवेयर के लिए जो HTTP हेल्थ एंडपॉइंट तो प्रदान करता है लेकिन जिसमें sd_notify का समर्थन नहीं है, सही विकल्प यह है कि या तो एक छोटी टाइमर यूनिट का उपयोग करें जो एंडपॉइंट को प्रोब करे और systemctl restart को कॉल करे, या फिर कंटेनर रनटाइम को प्रोबिंग करने दें, जिसके लिए Compose healthchecks and their restart behaviour मौजूद हैं।
तीसरा समाधान: यूनिट के भीतर एक सुपरवाइजर, केवल तब जब कोई विकल्प न हो
कुछ सॉफ्टवेयर वास्तव में ऐसे प्रोसेस के बंडल के रूप में आते हैं जो एक लॉन्चर के पीछे होते हैं जिन्हें आप अलग नहीं कर सकते। ऐसी स्थिति में आप यूनिट के भीतर एक सुपरवाइजर चलाते हैं और इसके परिणामों को स्वीकार करते हैं: systemd सुपरवाइजर की निगरानी करता है, सुपरवाइजर बाकी सब चीजों की निगरानी करता है, और आपकी रीस्टार्ट पॉलिसी अब दो फाइलों में रहती है।
इसका सामान्य रूप एक कंटेनर रनटाइम है। एक docker compose या podman यूनिट बिल्कुल इसी पैटर्न पर आधारित है, जिसमें प्रति-कंटेनर रीस्टार्ट पॉलिसी Compose फाइल में व्यक्त की जाती है और systemd यूनिट केवल रनटाइम को चालू रखती है। यदि आपका सेटअप ऐसा है, तो बूट के समय Compose स्टैक को चालू करने वाली यूनिट कार्यशील संस्करण दिखाती है, जिसमें यह भी शामिल है कि वहां Type=oneshot के साथ RemainAfterExit=yes का उपयोग आमतौर पर सही क्यों होता है।
cgroup अभी भी आपके पक्ष में काम करता है। सुपरवाइजर जो कुछ भी शुरू करता है वह यूनिट के cgroup के भीतर रहता है, इसलिए MemoryMax=, CPUQuota= और स्टॉप के समय सफाई की प्रक्रिया अभी भी पूरे ट्री को कवर करती है। केवल रीस्टार्ट का निर्णय प्रत्यायोजित (delegate) किया जाता है।
आप चाहे जो भी सुपरवाइजर चुनें, बाहरी यूनिट पर Restart=always और उसके भीतर एक आक्रामक रीस्टार्ट पॉलिसी को बिना सोचे-समझे सेट न करें। रीस्टार्ट लॉजिक की दो परतें, जिनमें से प्रत्येक का अपना बैकऑफ़ होता है, एक ऐसी सर्विस बनाती हैं जो मिनटों तक फ्लैप (flap) करती रहती है और जिसका जर्नल यह नहीं बताता कि ऐसा क्यों हो रहा है।
ExitType=cgroup का अर्थ यह नहीं है कि "किसी भी process के बंद होने पर restart करें"
ExitType= (systemd 250 और उसके बाद के संस्करण, इसलिए Ubuntu 24.04 और Debian 12 दोनों में यह उपलब्ध है) वह सेटिंग है जिसे लोग इस समस्या को खोजने पर पाते हैं, और यह नाम के सुझाव से बिल्कुल विपरीत काम करती है। डिफ़ॉल्ट, ExitType=main, का अर्थ है कि मुख्य process के exit होने पर service को बंद माना जाता है। ExitType=cgroup का अर्थ है कि service को तब तक चालू माना जाता है जब तक कि cgroup की अंतिम process exit न हो जाए।
इसलिए ExitType=cgroup एक unit को किसी एक process के बंद होने के प्रति कम संवेदनशील बनाता है, न कि अधिक। यह उस प्रोग्राम के लिए सही सेटिंग है जो अपने वास्तविक worker को fork करता है और PID file लिखे बिना parent को exit कर देता है, जहाँ Type=forking daemon को नहीं ढूंढ पाता है। यहाँ वर्णित विफलता के लिए यह गलत सेटिंग है।
Restart= का ऐसा कोई मान नहीं है जिसका अर्थ "cgroup में किसी भी process के बंद होने पर unit को restart करें" हो। यदि आपको उस व्यवहार की आवश्यकता है, तो आपको प्रति unit एक process की आवश्यकता होगी। यदि आप प्रोग्राम को विभाजित नहीं कर सकते हैं और आप wrapper script को नियंत्रित करते हैं, तो सबसे निकटतम विकल्प wait -n है, जो पहली child के exit होते ही वापस आ जाता है:
#!/bin/bash
/usr/local/bin/myapp-web &
/usr/local/bin/myapp-worker &
wait -n
exit 1अब किसी भी child के बंद होने पर wrapper एक non-zero status के साथ बंद हो जाता है, इसलिए Restart=on-failure सक्रिय हो जाता है। यह एक समझौता है, समाधान नहीं। आपको अभी भी दो प्रोग्रामों के लिए एक ही restart counter और एक ही log stream मिलती है, और विफल होने वाले आधे हिस्से को अलग से restart करने का कोई तरीका नहीं होता है।
वास्तव में क्या हुआ, इसकी जाँच कैसे करें
इस क्रम में चार commands का उपयोग करें।
systemctl status myapp.service
systemd-cgls --unit myapp.service
systemctl show -p MainPID -p NRestarts -p Result -p ExecMainStatus myapp.service
journalctl -u myapp.service -b -o short-precisesystemctl status एक ही स्क्रीन पर स्थिति, मुख्य PID और cgroup ट्री दिखाता है। एक सही ढंग से चल रही unit Active: active (running) दिखाती है, जिसमें Main PID: लाइन उस process का नाम बताती है जिसकी आप अपेक्षा करते हैं। यदि नीचे दिए गए ट्री में ऐसी processes हैं जिन्हें आप नहीं पहचानते, या कोई ऐसी process गायब है जो होनी चाहिए थी, तो आपको अपना उत्तर मिल गया है।
systemd-cgls --unit बिना किसी truncation के वही ट्री प्रिंट करता है, जो तब महत्वपूर्ण हो जाता है जब एक unit में कई processes चल रही हों।
systemctl show मशीन-पठनीय (machine-readable) तथ्य देता है। NRestarts= रीस्टार्ट काउंटर है, और यह किसी ऐसी service को पहचानने का सबसे तेज़ तरीका है जो चालीस बार रीस्टार्ट हुई है, बजाय उस service के जो बूट के समय से चल रही है। Result= में अंतिम विफलता का कारण होता है: exit-code, signal, timeout, oom-kill, watchdog या start-limit-hit। ExecMainStatus= अंतिम मुख्य process का raw exit status है।
जर्नल में पूरी sequence होती है। खोजने के लिए ये तीन लाइनें हैं:
myapp.service: Main process exited, code=exited, status=1/FAILURE
myapp.service: Failed with result 'exit-code'.
myapp.service: Scheduled restart job, restart counter is at 1.code=exited, status=N का अर्थ है कि प्रोग्राम ने N रिटर्न करने का विकल्प चुना, इसलिए दोष प्रोग्राम या उसके कॉन्फ़िगरेशन में है। code=killed, signal=SEGV का अर्थ है कि वह क्रैश हो गया। code=killed, signal=TERM का आमतौर पर अर्थ है कि किसी अन्य चीज़ ने इसे रुकने के लिए कहा, जो कि विफलता नहीं है और इससे Restart=on-failure ट्रिगर नहीं होगा। code=dumped का अर्थ है कि इसने एक core file छोड़ी है, जिसे coredumpctl list तब दिखाएगा जब systemd-coredump इंस्टॉल हो।
एक से अधिक मशीनों पर, NRestarts वह संख्या है जिसे शेड्यूल पर एकत्र करना चाहिए। जिस unit का काउंटर हर दिन बढ़ता है, वह हर दिन विफल हो रही है, चाहे किसी ने ध्यान दिया हो या नहीं। जब आप दो या तीन मशीनों से आगे बढ़ जाते हैं, तो हर सर्वर पर एक कमांड चलाने का एक सुसंगत तरीका इसे एक अनुमान से बदलकर एक रिपोर्ट बना देता है।
FAQ
जब process बंद हो जाता है, तो systemctl मेरी service को active क्यों दिखाता है?
systemd प्रति service unit केवल एक मुख्य process को ट्रैक करता है, और Restart= केवल उसी process के exit status को पढ़ता है। unit द्वारा शुरू की गई अन्य सभी चीजें एक ही cgroup में रहती हैं, और unit बंद होने पर systemd उन processes को kill कर देता है, लेकिन वह कभी भी उनके exit status की निगरानी नहीं करता है। systemctl show -p MainPID myapp.service चलाएं और उस संख्या की तुलना systemd-cgls --unit myapp.service से करें। यदि जो process बंद हुआ है वह tree में दिखाई देता है लेकिन MainPID नहीं है, तो systemd ने बिल्कुल वैसा ही व्यवहार किया है जैसा उसे करना चाहिए था। इसका समाधान प्रति unit एक process रखना है, और units के बीच संबंध को BindsTo= और Upholds= के रूप में लिखना है।
"start request repeated too quickly" का क्या अर्थ है?
इसका अर्थ है कि unit को StartLimitIntervalSec= के भीतर StartLimitBurst= से अधिक बार शुरू किया गया था, जो डिफ़ॉल्ट रूप से 10 सेकंड में 5 बार होता है, इसलिए systemd ने प्रयास करना बंद कर दिया। यह एक rate limit है और यह कभी नहीं बताता कि service विफल क्यों हो रही थी, इसलिए इसके ऊपर की journal लाइनों को पढ़ें। systemctl reset-failed myapp.service के साथ state को साफ़ करें, फिर मूल विफलता को ठीक करें। यदि service किसी धीमी चीज़ के आने का इंतज़ार करती है, तो RestartSec= को बढ़ाएं, क्योंकि 100 मिलीसेकंड का डिफ़ॉल्ट अंतराल एक सेकंड से भी कम समय में सभी पांच प्रयासों को समाप्त कर देता है।
क्या मुझे Restart=always का उपयोग करना चाहिए या Restart=on-failure का?
लगभग हर चीज़ के लिए on-failure का उपयोग करें। यह crash, non-zero exit, timeout और watchdog trip के बाद restart करता है, और एक जानबूझकर किए गए exit 0 को छोड़ देता है। always का उपयोग केवल तब करें जब program अपने नियंत्रण से बाहर के कारणों से cleanly exit होता है, जैसे कि एक client जो peer के disconnect होने पर 0 return करता है। always की लागत यह है कि जो service एक broken config को पढ़ती है, एक error log करती है और 0 के साथ exit होती है, वह हमेशा के लिए loop में फंस जाएगी, और एकमात्र दृश्य लक्षण systemctl show में NRestarts का बढ़ना होगा।
मेरे द्वारा process को manually kill करने पर restart ट्रिगर क्यों नहीं होता?
क्योंकि systemd SIGHUP, SIGINT, SIGTERM और SIGPIPE को clean exits मानता है, और एक साधारण kill <pid>, SIGTERM भेजता है। Restart=on-failure के तहत एक clean exit विफलता नहीं है, इसलिए कुछ भी restart नहीं होता है और configuration broken दिखाई देता है जबकि वह ऐसा नहीं होता है। kill -9 <pid> या systemctl kill -s SIGKILL myapp.service के साथ परीक्षण करें, जो एक unclean termination है और policy को ट्रिगर करता है। यही नियम बताता है कि systemctl stop आपकी restart policy से कभी नहीं टकराता है।
StartLimitIntervalSec और StartLimitBurst कहाँ रखे जाते हैं?
[Unit] section में। पुरानी सामग्री और पुराने systemd versions इन्हें [Service] में रखते हैं, इसलिए copy किए गए उदाहरण एक-दूसरे से मेल नहीं खाते। यह अनुमान न लगाएं कि आपका version किसे मानता है। systemctl daemon-reload के बाद, systemd से पूछें कि उसने systemctl show -p StartLimitBurst -p StartLimitIntervalUSec myapp.service के साथ क्या load किया है, और उन संख्याओं को ही सत्य मानें। systemd-analyze verify /etc/systemd/system/myapp.service उन keys को पकड़ता है जिन्हें systemd बिल्कुल नहीं पहचानता है, और जब file clean होती है तो कुछ भी print नहीं करता है।