systemd ने तुमची service restart का केली नाही?
Restart= फक्त main process पाहते; त्याच cgroup मधील मृत child process दिसत नाही. Type=, restart limits आणि journal प्रत्यक्षात कसे काम करतात ते जाणून घ्या.
थोडक्यात: systemd restart policies एका process वर लक्ष ठेवतात
systemd restart policies प्रत्येक unit मधील एका process वर लक्ष ठेवतात: main process वर. Restart= त्या process चा exit status वाचते आणि इतर कोणत्याही process चा नाही. एखाद्या unit च्या control group मध्ये वीस processes असू शकतात. त्यांपैकी एक process बंद पडला, तरी main process सुरू असल्यामुळे unit active (running) राहते. systemd च्या दृष्टीने काहीही अयशस्वी झालेले नसते, त्यामुळे काहीही restart केले जात नाही.
systemd ला इतर processes बद्दल माहिती असते. unit थांबवताना ते त्यांना kill करते, त्यांची memory unit च्या limits मध्ये मोजते, त्यांना unit ची CPU quota लागू करते आणि त्यांना systemctl status मध्ये दाखवते. पण ते त्यांच्या exit status कधीही वाचत नाही. restart logic आणि cgroup या दोन वेगळ्या गोष्टी आहेत. या guide चा बहुतांश भाग या दोन्हींमधील अंतराविषयी आहे.
cgroup मध्ये काय असते आणि restart logic कोणती माहिती वाचते
cgroup (control group) हा processes चा संच नियंत्रित करणारा kernel object आहे. प्रत्येक service unit ला तिच्या नावावर आधारित एक cgroup मिळतो. एखादा process त्यातून बाहेर पडू शकत नाही. Child processes ना त्यांच्या parent चा cgroup वारशाने मिळतो आणि unprivileged process स्वतःला दुसरीकडे हलवू शकत नाही. म्हणून systemd दोनदा fork होणाऱ्या daemon चीही स्वच्छता करू शकते. जुन्या init scripts ना हे विश्वासार्हपणे कधीच करता येत नव्हते.
ही दोन्ही तथ्ये शेजारी ठेवून पाहा:
systemd-cgls --unit myapp.service
systemctl show -p MainPID -p NRestarts -p Restart -p RestartUSec myapp.servicesystemd-cgls unit मधील प्रत्येक process दाखवते. MainPID ही restart policy वाचत असलेली एकमेव संख्या आहे. या दोन्ही गोष्टींबद्दलच्या तुमच्या मानसिक मॉडेलमध्ये विसंगती असल्यास, तीच bug आहे. MainPID=0 हा चुकीच्या PID पेक्षाही गंभीर प्रकार आहे: याचा अर्थ systemd कोणत्याही गोष्टीचा मागोवा घेत नाही. त्यामुळे कोणतीही Restart= value कधीही लागू होऊ शकत नाही.
मुख्य process च्या नियमाला एक वास्तविक अपवाद आहे. kernel out-of-memory killer ने unit च्या cgroup मधील कोणताही process बंद केला, तर systemd ला ते कळते, कारण ते cgroup ची memory.events file monitor करते. पुढे काय करायचे हे OOMPolicy= ठरवते. तिची default value stop आहे: संपूर्ण unit थांबवली जाते, परिणाम oom-kill म्हणून नोंदवला जातो आणि तो failure मानला जातो. त्यामुळे 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 unit बंद पाडतो, पण segmentation fault मुळे बंद झालेला तोच child तसे करत नाही. एखाद्या unit वर memory limits सेट करत असल्यास, restart policy समायोजित करण्यापूर्वी MemoryMax आणि CPUQuota unit च्या cgroup वर कसे लागू होतात हे पहा, कारण ही दोन्ही वैशिष्ट्ये येथे एकत्र येतात आणि इतरत्र नाही.
Type= कशाप्रकारे मुख्य प्रक्रिया निवडते
Type= विभागातील [Service] हे केवळ प्रारंभाच्या क्रमाबद्दल नाही. कोणता PID (process ID) MainPID बनेल हे ठरवणारा हा नियम आहे. त्यामुळे Restart= ला काय दिसू शकते हेही याच नियमावर ठरते.
Type=simpleहे default आहे. systemdExecStart=मधून ज्या प्रक्रियेचा fork करते, ती मुख्य प्रक्रिया असते.execप्रत्यक्षात यशस्वी झाले आहे का हे कळण्यापूर्वीच systemd unit सुरू झाल्याची नोंद करते. binary path मध्ये typo असल्यास start job यशस्वी ठरतो आणि त्यानंतर क्षणभरानेMain process exited, code=exited, status=203/EXECहोते.Type=execहेsimpleप्रमाणेच कार्य करते. मात्रexecयशस्वी होईपर्यंत start job प्रतीक्षा करते. त्यामुळे वरील typo मुळे स्पष्टपणे start failure मिळतो. यासाठी systemd 240 किंवा त्यानंतरची आवृत्ती आवश्यक आहे. सर्व समर्थित distributions मध्ये ती उपलब्ध आहे.simpleऐवजी हे वापरा.Type=forkingमध्येExecStart=मधील प्रक्रियेने background daemon साठी fork करून नंतर बाहेर पडणे अपेक्षित असते. systemd parent प्रक्रियेच्या बाहेर पडण्याची प्रतीक्षा करते आणि त्यानंतर प्रत्यक्ष daemon शोधते.PIDFile=द्या. ते नसल्यासGuessMainPID=(default ने सक्षम) फक्त cgroup मध्ये नेमकी एक प्रक्रिया उरली असेल तेव्हाच कार्य करते. दोन प्रक्रिया मागे ठेवल्यासMainPIDहे0राहते.Type=notifyम्हणजे सेवाsd_notify(3)कॉल करते आणि traffic स्वीकारण्यास सक्षम झाल्यावरREADY=1पाठवते. systemd ने track करण्यासाठी वेगळी प्रक्रिया देण्यासाठी तीMAINPID=देखील पाठवू शकते.NotifyAccess=चे default मूल्यmainअसते. त्यामुळे child ने पाठवलेली notification दुर्लक्षित केली जाते आणि journal मध्ये notification पाठवणाऱ्या प्रक्रियेचा PID दिसतो.Type=oneshotमध्ये कायमस्वरूपी मुख्य प्रक्रिया नसते.ExecStart=पूर्ण होताच unit inactive होते, जोपर्यंतRemainAfterExit=yesसेट केलेले नसेल. येथेRestart=alwaysआणिRestart=on-successनाकारले जातात आणिService has Restart= set to either always or on-success, which isn't allowed for Type=oneshot services. Refusing.हा संदेश दिला जातो.on-failureसह इतर मूल्ये स्वीकारली जातात.
दोन Type=forking errors लक्षात ठेवणे महत्त्वाचे आहे. प्रत्येक error मुळे कोणतेही स्पष्ट कारण न दिसता unit बिघडल्यासारखे दिसते:
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.पहिल्या error चा अर्थ daemon आपली PID file दुसऱ्या ठिकाणी लिहितो किंवा systemd तपासते त्यापेक्षा उशिरा लिहितो. दुसऱ्या error चा अर्थ PID file मध्ये unit च्या cgroup बाहेरील प्रक्रियेचे नाव आहे. systemd अशी प्रक्रिया स्वीकारत नाही. अन्यथा writable PID file चा वापर करून सर्व्हरवरील कोणत्याही प्रक्रियेला systemd कडून signals पाठवता आले असते.
रॅपर स्क्रिप्टमुळे तिच्या child process चा termination का लपतो
शीर्षकातील प्रश्न निर्माण करणारी रचना पुढीलप्रमाणे आहे.
#!/bin/bash
/usr/local/bin/myapp-web &
/usr/local/bin/myapp-worker &
waitUnit म्हणजे Type=simple, त्यामुळे मुख्य process shell असतो. wait कोणतेही arguments नसताना प्रत्येक child process बंद झाल्यानंतरच परत येतो. Worker process बंद केल्यावर shell web process साठी प्रतीक्षा करत राहतो. त्यामुळे shell बंद होत नाही, MainPID बंद होत नाही आणि Restart= तपासलेच जात नाही. आता cgroup मध्ये एक process कमी असतो, systemctl status लहान झालेली process tree दाखवतो आणि unit अजूनही active (running) असतो. या tree मधील बदलांवर systemd लक्ष ठेवत नाही.
हीच चूक दुसऱ्या प्रकारे अधिक शांतपणे दिसते:
ExecStart=/bin/sh -c 'export APP_ENV=production; /usr/local/bin/myapp'मुख्य process myapp नसून shell असतो. systemctl stop वेळी systemd मुख्य process ला SIGTERM पाठवतो. Foreground child साठी प्रतीक्षा करणारा shell हा signal पुढे पाठवत नाही. त्यामुळे stop प्रक्रिया पूर्ण TimeoutStopSec घेते, जी default ने 90 seconds असते, आणि शेवटी असे दिसते:
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 वर ही समस्या पुन्हा निर्माण करा
वरील wrapper /usr/local/bin/two-children.sh म्हणून save करा, chmod +x वापरून तो executable करा आणि दोन program paths sleep 3600 ने बदला. Type=simple आणि Restart=on-failure वापरून unit ला त्याकडे निर्देशित करा. त्यानंतर systemctl daemon-reload चालवून unit सुरू करा. systemd-cgls --unit two-children.service चालवा आणि तीन PIDs नोंदवा: shell आणि त्याचे दोन child processes. sudo kill <pid> वापरून एक child process बंद करा. Unit पुन्हा तपासा. Tree मध्ये एक process कमी असेल, state अजूनही active (running) असेल आणि journal मध्ये नवीन काहीही नसेल. आता त्याऐवजी sudo kill -9 <shell pid> चालवा. Unit fail होईल, KillMode=control-group default असल्यामुळे जिवंत राहिलेला child process साफ केला जाईल आणि journal मध्ये Scheduled restart job, restart counter is at 1. दिसेल.
Restart= चे संपूर्ण पर्याय आणि always पेक्षा on-failure कधी योग्य ठरते
Restart= ला सातपैकी कोणतेही एक मूल्य देता येते. त्यांच्यातील मुख्य फरक म्हणजे कोणता exit clean मानायचा. systemd exit code 0, SuccessExitStatus= मध्ये नमूद केलेला कोणताही code आणि SIGHUP, SIGINT, SIGTERM व SIGPIPE signals यांना clean exit मानते. SIGKILL आणि SIGSEGV यांसह इतर सर्व बाबी unclean मानल्या जातात.
noहे default आहे. Unit स्वतःहून पुन्हा सुरू होत नाही. त्यामुळेRestart=line नसलेली unit पहिल्या crash नंतर बंद राहते.on-successफक्त clean exit नंतर पुन्हा सुरू करते.on-failurenon-zero exit code, unclean signal, start किंवा stop timeout अथवा watchdog expiry नंतर पुन्हा सुरू करते.on-abnormalunclean signal, timeout किंवा watchdog expiry नंतर पुन्हा सुरू करते; मात्र साध्या non-zero exit code नंतर कधीही सुरू करत नाही.on-abortफक्त unclean signal नंतर पुन्हा सुरू करते. याचा अर्थ crash नंतर.on-watchdogफक्तWatchdogSec=expire झाल्यावर पुन्हा सुरू करते.alwaysवरील प्रत्येक परिस्थितीनंतर पुन्हा सुरू करते. यात status 0 सह झालेला clean exit देखील समाविष्ट आहे.
दीर्घकाळ चालणाऱ्या daemon साठी on-failure हे योग्य default आहे. यामुळे crash नंतर सेवा पुन्हा सुरू होते आणि जाणीवपूर्वक केलेला exit 0 तसाच राहतो. आपल्या नियंत्रणाबाहेरील कारणामुळे clean exit होणाऱ्या program साठी always योग्य आहे. उदाहरणार्थ, दूरच्या endpoint चे connection तुटल्यावर tunnel client 0 return करत असेल तर. always ची किंमत अशी की ते bugs लपवते. एखादी सेवा सुरू होऊन broken config file वाचते, error log करते आणि 0 सह बंद होते, तर ती सतत सुरू होत राहील. याचे एकमेव चिन्ह म्हणजे restart counter वाढत राहणे.
SuccessExitStatus= clean आणि unclean यांच्यातील सीमा बदलते. Borg warnings साठी 1 आणि errors साठी 2 exit करते. त्यामुळे SuccessExitStatus=1 नसलेली backup unit एखादी unreadable file वगळली की प्रत्येक वेळी failed म्हणून चिन्हांकित होते. RestartPreventExitStatus= अशा codes ची यादी देते, जे always असतानाही restart रोखतात. Program ने पुन्हा सुरू होऊ नये असे सांगण्याचा हा clean मार्ग आहे. RestartForceExitStatus= याच्या उलट कार्य करते. Backup job साठी restart loop ऐवजी timer द्वारे चालवली जाणारी Type=oneshot unit योग्य आहे. ठराविक वेळापत्रकानुसार job चालवणारी service आणि timer pair हा त्यासाठी अनुकरण करण्यासारखा नमुना आहे.
Testing करताना एक महत्त्वाची सूचना लक्षात ठेवा. साध्या kill <pid> ने service बंद केल्यास SIGTERM पाठवला जातो. तो clean list मध्ये आहे. त्यामुळे Restart=on-failure योग्यरीत्या काहीही करत नाही आणि तुमची configuration चुकीची आहे असा निष्कर्ष निघू शकतो. त्याऐवजी kill -9 <pid> किंवा systemctl kill -s SIGKILL myapp.service वापरा. तसेच लक्षात ठेवा की Restart= चे कोणतेही मूल्य systemctl stop नंतर लागू होत नाही. तसेच BindsTo= किंवा PartOf= dependency नाहीशी झाल्यामुळे unit थांबवली गेली तरी ते लागू होत नाही. Stop job म्हणजे failure नव्हे.
RestartSec आणि 100 millisecond चे default
RestartSec= म्हणजे unit थांबल्यानंतर systemd ती पुन्हा सुरू करेपर्यंतचा विराम. याचे default 100 milliseconds आहे. तुमच्या unit ने प्रत्यक्षात कोणती value लोड केली आहे ते तपासा:
systemctl show -p RestartUSec -p StartLimitIntervalUSec -p StartLimitBurst myapp.serviceज्या unit ने ही setting केलेली नाही, तिच्यासाठी RestartUSec=100ms असे दिसते. एकदा crash होऊन पुन्हा सुरू होणाऱ्या सेवेसाठी हे default योग्य आहे. परंतु सेवा अजिबात सुरू होऊ शकत नसेल, तर ते चुकीचे आहे. कारण पाच restart अर्ध्या सेकंदात होतात आणि पुढे वर्णन केलेली rate limit लागू होते. Database, mount किंवा network route उपलब्ध होण्याची प्रतीक्षा करणाऱ्या कोणत्याही सेवेसाठी RestartSec=5s किंवा त्याहून अधिक value सेट करा.
August 2026 पासून systemd 254 आणि त्यानंतरच्या आवृत्त्यांमध्ये RestartSteps= आणि RestartMaxDelaySec= देखील उपलब्ध आहेत. ही settings प्रयत्नांची संख्या वाढत असताना RestartSec= पासून सुरू होणारा विलंब एका कमाल मर्यादेपर्यंत वाढवतात. Ubuntu 24.04 मध्ये systemd 255 येते आणि या settings उपलब्ध आहेत. Debian 12 मध्ये systemd 252 येते आणि या settings उपलब्ध नाहीत. Dependency बराच काळ down असण्याची शक्यता असल्यास वाढता विलंब हा योग्य उपाय आहे.
“start request repeated too quickly” याचा खरा अर्थ
systemd मनमानीपणे हार मानते असे वाचकांना वाटण्यामागे ही स्थिती असते. ही एक counter आहे. नियम असा आहे: StartLimitIntervalSec= या कालावधीत एखादी unit StartLimitBurst= पेक्षा जास्त वेळा सुरू केली, तर systemd ती पुन्हा सुरू करण्यास नकार देते आणि तिला failed स्थितीत ठेवते. डीफॉल्टनुसार 10 सेकंदांत 5 starts अनुमत असतात.
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 counter आणि failed स्थिती clear करते. याशिवाय इतर कोणतीही कमांड हे करत नाही. त्यामुळे साधी systemctl start तुम्ही ती कमांड चालवेपर्यंत नाकारली जात राहते. Manual starts देखील या मर्यादेत मोजले जातात. त्यामुळे configuration file संपादित करताना अधीरपणे systemctl restart काही वेळा चालवल्यास कोणताही crash नसतानाही ही मर्यादा लागू होऊ शकते.
लोकांची दिशाभूल करणारा भाग असा आहे: start-limit-hit सेवा का fail होत होती हे कधीही सांगत नाही. ती फक्त सेवा वारंवार आणि कमी वेळात fail झाली असे सांगते. खरे कारण त्यापूर्वीच्या journal lines मध्ये असते.
दोन्ही settings [Unit] section मध्ये असतात. काही उदाहरणांमध्ये त्या [Service] मध्ये लिहिलेल्या आढळतात. जुने systemd हे स्वीकारत असे आणि त्यामुळेच गोंधळ सुरू होतो. त्या [Unit] मध्ये लिहा. त्यानंतर systemd ने कोणती configuration load केली ते systemctl show ने तपासा, कारण load केलेली value हीच ग्राह्य धरली जाते.
[Unit]
Description=My application
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=exec
ExecStart=/usr/local/bin/myapp
Restart=on-failure
RestartSec=10sयामुळे unit हार मानण्यापूर्वी पाच मिनिटांच्या कालावधीत पाच attempts करते. StartLimitIntervalSec=0 मर्यादा पूर्णपणे बंद करते. मात्र तुम्ही काय निवडत आहात हे समजून घ्या: सुरू होऊ न शकणारी सेवा आता कायम retry करत राहील आणि प्रत्येक वेळी journal मध्ये नोंद लिहील. संपूर्ण मशीनसाठीची डीफॉल्ट values /etc/systemd/system.conf मध्ये DefaultStartLimitIntervalSec= आणि DefaultStartLimitBurst= म्हणून असतात.
शेजारील एका setting बद्दल सावध राहा. मर्यादा गाठल्यावर काय घडेल हे StartLimitAction= ठरवते. त्यासाठी reboot, reboot-force आणि poweroff यांसह अनेक values स्वीकारल्या जातात. डीफॉल्ट value none आहे. यामुळे unit fail होते आणि मशीनवर इतर कोणतीही कृती केली जात नाही. Remote VPS वर poweroff म्हणजे provider चे console उघडेपर्यंत बंदच राहणारी मशीन.
प्रत्येक युनिटसाठी एक process
बहुतेक सर्व प्रकरणांत हेच योग्य उत्तर असते. दोन programs चालवायचे असतील, तर दोन units लिहा. त्यामुळे प्रत्येक unit कडे स्वतंत्र मुख्य process, स्वतंत्र exit status आणि स्वतःचे restart policy असते. तसेच स्वतंत्र logs, स्वतंत्र resource limits आणि स्वतंत्र restart counters मिळतात. पहाटे तीन वाजता तुम्हाला हेच हवे असते.
Units मधील संबंध shell script मध्ये नव्हे, तर unit files मध्ये व्यक्त करा.
After=केवळ start-up चा क्रम ठरवते. failures बाबत ते काहीही सांगत नाही.Requires=या unit सोबत दुसरी unit सुरू करते आणि दुसरी unit स्पष्टपणे थांबवली गेल्यास ही unit थांबवते.BindsTo=मध्येRequires=चे कार्य तसेच तुम्हाला आवश्यक असलेले अतिरिक्त कार्य असते: दुसरी unit कोणत्याही कारणामुळे, crash मुळेही, थांबली तर ही unit थांबते. यासोबतAfter=वापरा; अन्यथा क्रम निश्चित नसतो.PartOf=stop आणि restart खालच्या दिशेने प्रसारित करते. त्यामुळेsystemctl restart myapp.targetला पोहोचल्यावरPartOf=असलेल्या प्रत्येक unit पर्यंत ते पोहोचते.Upholds=(systemd 249 आणि त्यानंतरच्या आवृत्त्यांमध्ये, म्हणजे Ubuntu 22.04 आणि त्यानंतर) नामनिर्दिष्ट unit चालू ठेवते: ती थांबली तर systemd तिला पुन्हा सुरू करते. इतर सर्व units प्रमाणेच ती start rate limit च्या अधीन असते.
ज्या worker ने त्याच्या API server शिवाय कधीही चालू नये आणि API सुरू असेपर्यंत systemd ने तो चालू ठेवावा:
# /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=5sworker मध्ये [Install] section नाही आणि तो स्वतःहून कधीही enable केला जात नाही. API unit Upholds= वापरून त्याला समाविष्ट करते. त्यामुळे तुम्ही फक्त systemctl enable --now myapp-api.service command चालवता. Reload करा आणि systemd ने या दोन्ही units ची रचना कशी केली ते तपासा:
sudo systemctl daemon-reload
systemd-analyze verify /etc/systemd/system/myapp-worker.service
systemctl list-dependencies myapp-api.servicefile योग्य असल्यास systemd-analyze verify काहीही output देत नाही. कोणतेही output दिसल्यास समस्या आहे. सामान्यतः तुम्ही लिहिलेल्या section मध्ये systemd ला न ओळखता येणारी key असते किंवा अस्तित्वात नसलेल्या unit वर dependency असते.
दुरुस्ती दोन: Type=notify, त्यामुळे systemd ला PID पेक्षा अधिक माहिती मिळते
प्रोग्राम systemd notification protocol वापरत असेल, तर तो वापरा. Type=notify सह सेवा तयार झाल्यावर systemd ला कळवते. त्यामुळे ordering अंदाजावर आधारित न राहता प्रत्यक्ष होते. तसेच launcher ऐवजी महत्त्वाच्या process कडे systemd चे लक्ष वेधण्यासाठी ती MAINPID= पाठवू शकते.
WatchdogSec= हा प्रयत्न करण्यासारखा भाग आहे. तो सेट केल्यावर सेवेला किमान तेवढ्या वारंवारतेने sd_notify(3) द्वारे WATCHDOG=1 पाठवावे लागते. संदेश येणे थांबल्यावर systemd सेवा SIGABRT ने समाप्त करते आणि तिला failed म्हणून नोंदवते. त्यामुळे Restart=on-failure किंवा Restart=on-watchdog ती पुन्हा सुरू करते. जिवंत पण अडकलेल्या process ला पुन्हा सुरू करण्याचा हा systemd मधील एकमेव अंगभूत मार्ग आहे. कोणतेही exit-status धोरण अशी स्थिती ओळखू शकत नाही.
[Service]
Type=notify
NotifyAccess=main
ExecStart=/usr/local/bin/myapp serve
WatchdogSec=30s
Restart=on-failure
RestartSec=5sWatchdog trip journal मध्ये myapp.service: Watchdog timeout (limit 30s)! म्हणून दिसतो आणि त्यानंतर process kill केली जाते. त्याऐवजी unit activating (start) स्थितीत राहते आणि TimeoutStartSec संपेपर्यंत थांबते, तर READY=1 कधीच आलेले नसते. याची दोन कारणे असू शकतात: प्रोग्राम हा protocol वापरत नाही किंवा child process कडून आलेली notification NotifyAccess=main नाकारत आहे. Journal मध्ये दोन्ही PIDs नोंदवलेले असतात.
HTTP health endpoint उपलब्ध असलेल्या, पण sd_notify support नसलेल्या software साठी दोन स्पष्ट पर्याय आहेत. Endpoint तपासणारे छोटे timer unit तयार करून systemctl restart कॉल करा. किंवा container runtime कडून probing करून घ्या. Compose healthchecks आणि त्यांचे restart behaviour यासाठीच आहेत.
युनिटच्या आत supervisor चालवणे: पर्याय नसतानाच वापरा
काही software प्रत्यक्षात अशा process bundle स्वरूपात releases होते की ते विभाजित करता येत नाही. अशा वेळी युनिटच्या आत supervisor चालवा आणि त्याचे परिणाम स्वीकारा: systemd supervisor वर लक्ष ठेवते, supervisor इतर सर्व गोष्टींवर लक्ष ठेवतो आणि restart policy आता दोन files मध्ये असते.
याचे सामान्य उदाहरण container runtime आहे. docker compose किंवा podman unit ही याच पद्धतीची उदाहरणे आहेत. त्यात प्रत्येक container ची restart policy Compose file मध्ये असते आणि systemd unit फक्त runtime चालू ठेवते. तुमची रचना अशी असल्यास, boot वेळी Compose stack सुरू करणारे unit याची कार्यरत आवृत्ती दाखवते. त्यात Type=oneshot सोबत RemainAfterExit=yes का सामान्यतः योग्य असते हेही स्पष्ट केले आहे.
cgroup अजूनही तुमच्या उपयोगी ठरते. supervisor ने सुरू केलेली प्रत्येक गोष्ट युनिटच्या cgroup मध्येच राहते. त्यामुळे MemoryMax=, CPUQuota= आणि stop वेळी होणारी cleanup प्रक्रिया संपूर्ण process tree वर लागू राहते. फक्त restart करण्याचा निर्णय supervisor कडे सोपवला जातो.
तुम्ही कोणताही supervisor निवडला तरी विचार न करता बाहेरील unit वर Restart=always आणि त्याच्या आत आक्रमक restart policy सेट करू नका. स्वतंत्र backoff असलेल्या restart logic चे दोन स्तर असल्यास service अनेक मिनिटे वारंवार बंद-चालू होते आणि असे का घडते हे journal मधून स्पष्ट होत नाही.
ExitType=cgroup म्हणजे “कोणतीही process बंद झाल्यावर restart करा” असा अर्थ नाही
ExitType= (systemd 250 आणि त्यानंतरच्या आवृत्त्यांमध्ये; त्यामुळे Ubuntu 24.04 आणि Debian 12 या दोन्हींमध्ये ते उपलब्ध आहे) हे setting या समस्येसाठी शोध घेताना आढळते. मात्र, त्याचा परिणाम नावावरून वाटतो त्याच्या उलट असतो. Default ExitType=main मध्ये main process बंद झाल्यावर service थांबलेली मानली जाते. ExitType=cgroup मध्ये cgroup मधील शेवटची process बंद होईपर्यंत service चालू मानली जाते.
त्यामुळे ExitType=cgroup मुळे एखादी process बंद झाल्यावर unit कमी संवेदनशील होते, अधिक नाही. एखादा program त्याचा वास्तविक worker fork करून parent process बंद करतो आणि PID file लिहित नाही, अशा वेळी हे योग्य setting आहे. अशा परिस्थितीत Type=forking ला daemon सापडत नाही. येथे वर्णन केलेल्या failure साठी हे setting चुकीचे आहे.
“cgroup मधील कोणतीही process बंद झाल्यावर unit restart करा” असा अर्थ देणारे कोणतेही Restart= value नाही. असे behaviour हवे असल्यास प्रत्येक unit मध्ये एकच process ठेवावी लागते. Program विभाजित करता येत नसेल आणि wrapper script वर तुमचे नियंत्रण असेल, तर त्याच्या सर्वात जवळचे setting wait -n आहे. पहिली child process बंद होताच ते परत येते:
#!/bin/bash
/usr/local/bin/myapp-web &
/usr/local/bin/myapp-worker &
wait -n
exit 1आता कोणतीही child process बंद झाल्यास wrapper non-zero status सह बंद होईल, त्यामुळे Restart=on-failure लागू होईल. हा तडजोडीचा उपाय आहे; पूर्ण निराकरण नाही. दोन programs साठी तुम्हाला एकच 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 एकाच screen वर स्थिती, मुख्य PID आणि cgroup tree दाखवते. निरोगी unit मध्ये Active: active (running) दिसते आणि Main PID: line मध्ये अपेक्षित process चे नाव असते. खालील tree मध्ये तुम्हाला अपरिचित processes दिसत असतील किंवा अपेक्षित process नसेल, तर कारण समोरच आहे.
systemd-cgls --unit हाच tree truncation न करता दाखवते. एखाद्या unit कडे मोजक्या processes पेक्षा अधिक processes असतील, तेव्हा हे महत्त्वाचे ठरते.
systemctl show machine-readable माहिती देते. NRestarts= हा restart counter आहे. रीबूटपासून सुरू असलेल्या service आणि चाळीस वेळा restart झालेल्या service यांच्यात फरक ओळखण्याचा हा सर्वात जलद मार्ग आहे. Result= मध्ये शेवटचे failure reason असते: exit-code, signal, timeout, oom-kill, watchdog किंवा start-limit-hit. ExecMainStatus= हा शेवटच्या मुख्य process चा raw exit status आहे.
Journal मध्ये घटनाक्रम असतो. शोधण्यासाठी या तीन lines वापरा:
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 म्हणजे program ने N return करण्याचा निर्णय घेतला. त्यामुळे fault program मध्ये किंवा त्याच्या configuration मध्ये आहे. code=killed, signal=SEGV म्हणजे ते crash झाले. code=killed, signal=TERM सहसा दुसऱ्या घटकाने त्याला थांबवण्याची विनंती केली असे दर्शवते. हे failure नसते आणि Restart=on-failure trigger करणार नाही. code=dumped म्हणजे त्याने core file तयार केली. systemd-coredump installed असल्यास coredumpctl list ती दाखवेल.
एकापेक्षा अधिक machines वर NRestarts हा नियमितपणे collect करण्यासारखा number आहे. ज्या unit चा counter दररोज वाढतो, ती unit दररोज fail होत असते, कोणाच्या लक्षात आले असेल किंवा नसेल तरी. दोन किंवा तीन boxes पेक्षा अधिक झाल्यावर प्रत्येक server वर एक command चालवण्याची सुसंगत पद्धत अंदाजाला report मध्ये बदलते.
FAQ
systemctl सेवा active आहे असे सांगते, पण process बंद झाला असल्यास असे का होते?
systemd प्रत्येक service unit साठी एक process track करते. त्याला main process म्हणतात आणि Restart= फक्त त्या process चा exit status वाचते. Unit सुरू करत असलेले इतर सर्व process त्याच cgroup मध्ये असतात. Unit थांबवताना systemd या process ना kill करते, परंतु ते exit झाले आहेत का हे systemd तपासत नाही. 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= पेक्षा अधिक वेळा सुरू झाली. यासाठी default मूल्य 10 seconds मध्ये 5 starts आहे. त्यामुळे systemd ने प्रयत्न करणे थांबवले. हे rate limit आहे. Service का fail होत होती हे ते सांगत नाही. त्यामुळे त्याच्या वरील journal lines वाचा. systemctl reset-failed myapp.service वापरून state clear करा आणि त्यानंतर मूळ failure दुरुस्त करा. Service सुरू होण्यासाठी एखाद्या slow dependency ची वाट पाहत असेल, तर RestartSec= चे मूल्य वाढवा. कारण 100 milliseconds चा default gap पाचही attempts एका second पेक्षा कमी वेळात संपवतो.
Restart=always किंवा Restart=on-failure यापैकी कोणते वापरावे?
बहुतेक सर्व परिस्थितींमध्ये on-failure वापरा. Crash, non-zero exit, timeout आणि watchdog trip झाल्यास ते service पुन्हा सुरू करते. मात्र जाणूनबुजून केलेला exit 0 ते failure मानत नाही. Program आपल्या नियंत्रणाबाहेरील कारणामुळे cleanly exit होत असेल, तेव्हाच always वापरा. उदाहरणार्थ, peer disconnect झाल्यावर client 0 return करत असेल. always वापरण्याचा तोटा असा की broken config वाचून, एक error log करून आणि 0 exit करणारी service सतत loop करत राहील. अशा वेळी दिसणारे एकमेव लक्षण म्हणजे systemctl show मधील NRestarts वाढत जाणे.
process हाताने kill केल्यावर restart का होत नाही?
कारण systemd SIGHUP, SIGINT, SIGTERM आणि SIGPIPE यांना clean exits मानते. साधा kill <pid> SIGTERM पाठवतो. Restart=on-failure अंतर्गत clean exit हे failure मानले जात नाही. त्यामुळे restart होत नाही आणि configuration चुकीची असल्याचा भास होतो, जरी ती चुकीची नसली तरी. kill -9 <pid> किंवा systemctl kill -s SIGKILL myapp.service वापरून चाचणी करा. यामुळे unclean termination होते आणि restart policy लागू होते. याच नियमामुळे systemctl stop तुमच्या restart policy शी कधीही संघर्ष करत नाही.
StartLimitIntervalSec आणि StartLimitBurst कुठे ठेवायचे?
ते [Unit] section मध्ये ठेवा. जुन्या documentation मध्ये आणि जुन्या systemd versions मध्ये ते [Service] मध्ये ठेवलेले आढळतात. त्यामुळे copy केलेली उदाहरणे एकमेकांशी जुळत नाहीत. तुमची version कोणती configuration स्वीकारते याचा अंदाज लावू नका. systemctl daemon-reload नंतर systemd ने काय load केले ते systemctl show -p StartLimitBurst -p StartLimitIntervalUSec myapp.service वापरून तपासा. तीच values अंतिम सत्य माना. systemd ला अजिबात ओळखता न येणाऱ्या keys systemd-analyze verify /etc/systemd/system/myapp.service शोधते. File योग्य असल्यास ती काहीही output देत नाही.