systemd সার্ভিস কেন রিস্টার্ট হচ্ছে না?
Restart= শুধুমাত্র মেইন প্রসেস পর্যবেক্ষণ করে। cgroup-এর ভেতরে কোনো চাইল্ড প্রসেস মারা গেলে systemd তা বুঝতে পারে না। Type= সেটিংস, রিস্টার্ট লিমিট এবং জার্নাল লগিংয়ের বিস্তারিত জানুন।
সংক্ষিপ্ত উত্তর: systemd restart policy শুধুমাত্র একটি প্রসেস পর্যবেক্ষণ করে
systemd restart policy প্রতি unit-এ শুধুমাত্র একটি প্রসেস পর্যবেক্ষণ করে: সেটি হলো main process। Restart= শুধুমাত্র সেই একটি প্রসেসের exit status পড়ে, অন্য কিছুর নয়। একটি unit-এর control group-এ বিশটি প্রসেস থাকতে পারে, যার মধ্যে একটি মারা গেলেও unit-টি active (running) অবস্থায় থাকে কারণ main process তখনও সচল। systemd-এর দৃষ্টিতে কোনো কিছুই ব্যর্থ হয়নি, তাই কোনো কিছু রিস্টার্টও করা হয় না।
systemd অন্যান্য প্রসেসগুলো সম্পর্কে জানে। unit বন্ধ করার সময় এটি সেগুলোকে বন্ধ করে দেয়, unit-এর মেমোরি লিমিটের বিপরীতে সেগুলোর ব্যবহার গণনা করে, সেগুলোর ওপর unit-এর CPU quota প্রয়োগ করে এবং systemctl status-এ সেগুলোকে প্রদর্শন করে। এটি কেবল সেগুলোর exit status পড়ে না। রিস্টার্ট লজিক এবং cgroup দুটি ভিন্ন বিষয়, এবং এই গাইডের বেশিরভাগ অংশই এই দুটির মধ্যবর্তী পার্থক্য নিয়ে।
cgroup কী ধারণ করে এবং রিস্টার্ট লজিক কী পড়ে
একটি cgroup (control group) হলো কার্নেলের একটি অবজেক্ট যা একগুচ্ছ প্রসেস নিয়ন্ত্রণ করে। প্রতিটি সার্ভিস ইউনিটের একটি করে cgroup থাকে, যার নামকরণ করা হয় ইউনিটের নামানুসারে। কোনো প্রসেস এই গ্রুপ থেকে বেরিয়ে যেতে পারে না। চাইল্ড প্রসেসগুলো তাদের প্যারেন্টের cgroup উত্তরাধিকারসূত্রে পায় এবং একটি আনপ্রিভিলেজড প্রসেস নিজেকে অন্য কোথাও সরিয়ে নিতে পারে না। এই কারণেই systemd এমন ডেমোনকেও পরিষ্কার করতে পারে যা দুবার ফর্ক (fork) করে, যা পুরনো init স্ক্রিপ্টগুলো নির্ভরযোগ্যভাবে করতে পারত না।
উভয় তথ্য পাশাপাশি দেখুন:
systemd-cgls --unit myapp.service
systemctl show -p MainPID -p NRestarts -p Restart -p RestartUSec myapp.servicesystemd-cgls ইউনিটের প্রতিটি প্রসেসের তালিকা দেখায়। MainPID হলো সেই একক সংখ্যা যা রিস্টার্ট পলিসি পড়ে। যখন এই দুটি আপনার ধারণার সাথে মেলে না, তখন সেই অমিলটিই হলো বাগ। MainPID=0 ভুল PID-এর চেয়েও খারাপ: এর মানে হলো systemd কোনো কিছুই ট্র্যাক করছে না, তাই কোনো Restart= ভ্যালু কখনোই কার্যকর হতে পারে না।
মেইন-প্রসেস নিয়মের একটি বাস্তব ব্যতিক্রম রয়েছে। যদি কার্নেলের out-of-memory কিলার ইউনিটের cgroup-এর ভেতরের কোনো প্রসেসকে মেরে ফেলে, তবে systemd তা দেখতে পায়, কারণ এটি cgroup-এর memory.events ফাইলটি পর্যবেক্ষণ করে। OOMPolicy= সিদ্ধান্ত নেয় এরপর কী হবে, এবং এর ডিফল্ট হলো stop: পুরো ইউনিটটি বন্ধ হয়ে যায়, ফলাফল oom-kill হিসেবে রেকর্ড করা হয় এবং এটি একটি ব্যর্থতা হিসেবে গণ্য হয়, তাই Restart=on-failure কার্যকর হয়। জার্নালে এটি স্পষ্টভাবে বলা থাকে।
myapp.service: A process of this unit has been killed by the OOM killer.
myapp.service: Failed with result 'oom-kill'.সুতরাং মেমরির কারণে কোনো চাইল্ড প্রসেস মারা গেলে তা পুরো ইউনিটকে বন্ধ করে দেয়, কিন্তু একই চাইল্ড যদি segmentation fault-এর কারণে মারা যায় তবে তা ইউনিটকে বন্ধ করে না। আপনি যদি কোনো ইউনিটে মেমরি লিমিট সেট করেন, তবে রিস্টার্ট পলিসি টিউন করার আগে কীভাবে MemoryMax এবং CPUQuota একটি ইউনিটের cgroup-এ প্রয়োগ হয় তা পড়ে নিন, কারণ এই দুটি ফিচার এখানেই মিলিত হয় এবং অন্য কোথাও নয়।
Type= কীভাবে মূল প্রসেস নির্ধারণ করে
Type=-এর [Service] সেকশনটি শুধুমাত্র স্টার্ট-আপের ক্রম নির্ধারণ করে না। এটি সেই নিয়ম যা ঠিক করে কোন PID (প্রসেস আইডি) MainPID হিসেবে গণ্য হবে, যা মূলত নির্ধারণ করে Restart= কী দেখতে পাবে।
Type=simpleহলো ডিফল্ট। systemd যে প্রসেসটিExecStart=থেকে ফর্ক (fork) করে, সেটিই মূল প্রসেস। systemd ইউনিটটিকে তাৎক্ষণিকভাবে চালু হিসেবে চিহ্নিত করে, এমনকিexecসফল হয়েছে কি না তা জানার আগেই। বাইনারি পাথে কোনো টাইপো থাকলে স্টার্ট জব সফল দেখাবে এবং কিছুক্ষণ পরেইMain process exited, code=exited, status=203/EXECহবে।Type=execঅনেকটাsimple-এর মতোই কাজ করে, তবে এতে স্টার্ট জবexecসফল না হওয়া পর্যন্ত অপেক্ষা করে। এটি উপরের টাইপো জনিত সমস্যাকে সরাসরি স্টার্ট ফেইলর হিসেবে দেখায়। এর জন্য systemd 240 বা তার নতুন সংস্করণ প্রয়োজন, যা বর্তমানে সমর্থিত সব ডিস্ট্রিবিউশনেই আছে।simple-এর পরিবর্তে এটি ব্যবহার করা শ্রেয়।Type=forkingপ্রত্যাশা করে যেExecStart=থেকে আসা প্রসেসটি একটি ব্যাকগ্রাউন্ড ডেমোন ফর্ক করবে এবং তারপর নিজে এক্সিট করবে। systemd প্যারেন্ট প্রসেসটির এক্সিট করার জন্য অপেক্ষা করে এবং তারপর আসল ডেমোনটিকে খুঁজে নেয়। এর জন্যPIDFile=ব্যবহার করুন। এটি ছাড়া,GuessMainPID=(যা ডিফল্টভাবে চালু থাকে) শুধুমাত্র তখনই কাজ করে যখন cgroup-এ ঠিক একটি প্রসেস অবশিষ্ট থাকে। যদি দুটি প্রসেস থেকে যায়, তবেMainPIDঅবস্থায়0থেকে যায়।Type=notifyমানে হলো সার্ভিসটিsd_notify(3)কল করে এবং ট্রাফিক গ্রহণ করতে প্রস্তুত হলেREADY=1পাঠায়। এটি systemd-কে ট্র্যাক করার জন্য ভিন্ন কোনো প্রসেস দিতেMAINPID=-ও পাঠাতে পারে।NotifyAccess=ডিফল্টভাবেmainথাকে, তাই চাইল্ড প্রসেস থেকে পাঠানো নোটিফিকেশন উপেক্ষা করা হয় এবং জার্নালে সেই PID-এর নাম থাকে যেখান থেকে এটি এসেছে।Type=oneshot-এর কোনো স্থায়ী মূল প্রসেস থাকে না।ExecStart=শেষ হওয়ার সাথে সাথেই ইউনিটটি ইনঅ্যাক্টিভ হয়ে যায়, যদি না আপনি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 এরর মনে রাখা জরুরি, কারণ প্রতিটিই এমন একটি ইউনিট তৈরি করে যা কোনো দৃশ্যমান কারণ ছাড়াই অকেজো মনে হয়:
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.প্রথমটির অর্থ হলো ডেমোনটি তার PID ফাইল অন্য কোথাও লিখছে, অথবা systemd খোঁজার পর সেটি লিখছে। দ্বিতীয়টির অর্থ হলো PID ফাইলটি এমন একটি প্রসেসের নাম নির্দেশ করছে যা ইউনিটের cgroup-এর বাইরে, যা systemd গ্রহণ করতে অস্বীকার করে। কারণ একটি রাইটেবল PID ফাইল অন্যথায় systemd-কে মেশিনের যেকোনো প্রসেসে সিগন্যাল পাঠানোর সুযোগ করে দিতে পারে।
কেন একটি র্যাপার স্ক্রিপ্ট তার চাইল্ড প্রসেসের মৃত্যু লুকিয়ে ফেলে
এখানে সেই কাঠামোটি দেওয়া হলো যা শিরোনামের প্রশ্নটি তৈরি করে।
#!/bin/bash
/usr/local/bin/myapp-web &
/usr/local/bin/myapp-worker &
waitইউনিটটি হলো Type=simple, তাই মূল প্রসেসটি হলো শেল। কোনো আর্গুমেন্ট ছাড়া wait প্রতিটি চাইল্ড প্রসেস শেষ না হওয়া পর্যন্ত অপেক্ষা করে। ওয়ার্কার প্রসেসটিকে কিল করলে শেল ওয়েব প্রসেসের জন্য অপেক্ষা করতে থাকে, তাই শেলটি বন্ধ হয় না, ফলে MainPID বন্ধ হয় না এবং Restart= কখনোই কার্যকর হয় না। cgroup-এ এখন একটি প্রসেস কম থাকে, systemctl status ছোট গাছটি প্রিন্ট করে এবং ইউনিটটি তখনও active (running) অবস্থায় থাকে। systemd-এর কোনো কিছুই সেই ট্রিতে কোনো পরিবর্তন পর্যবেক্ষণ করে না।
একই ভুলের দ্বিতীয় সংস্করণটি আরও নীরব:
ExecStart=/bin/sh -c 'export APP_ENV=production; /usr/local/bin/myapp'মূল প্রসেসটি হলো শেল, myapp নয়। systemctl stop-এ, systemd মূল প্রসেসে SIGTERM পাঠায়, কিন্তু ফোরগ্রাউন্ডে কোনো চাইল্ড প্রসেসের জন্য অপেক্ষারত শেল সেই সিগন্যালটি পাস করে না। তখন স্টপ প্রক্রিয়াটি পুরো 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 লিখলে শেলটি প্রোগ্রাম দ্বারা প্রতিস্থাপিত হয়, তাই MainPID হয় সেই প্রোগ্রাম এবং সিগন্যালগুলো সরাসরি সেখানে পৌঁছায়। আরও ভালো হয় যদি শেলটি মুছে ফেলে ইউনিটে Environment= বা EnvironmentFile= ব্যবহার করেন। মনে রাখবেন, এই বাগটি নিজেকে লুকিয়ে ফেলে যখন -c স্ট্রিংটিতে একটি মাত্র কমান্ড থাকে, কারণ bash এবং dash উভয়ই সেই ক্ষেত্রে সরাসরি exec-এ অপ্টিমাইজ করে। স্ট্রিংটিতে দ্বিতীয় একটি কমান্ড যোগ করলেই শেলটি আপনার প্রোগ্রামের সামনে জীবিত থেকে যায়।
দুই মিনিটে একটি টেস্ট VPS-এ এটি পুনরুৎপাদন করুন
উপরের র্যাপারটিকে /usr/local/bin/two-children.sh হিসেবে সেভ করুন, chmod +x দিয়ে সেটিকে এক্সিকিউটেবল করুন এবং দুটি প্রোগ্রামের পাথকে sleep 3600 দিয়ে প্রতিস্থাপন করুন। Type=simple এবং Restart=on-failure দিয়ে একটি ইউনিট সেট করুন, তারপর systemctl daemon-reload করুন এবং এটি স্টার্ট করুন। systemd-cgls --unit two-children.service চালান এবং তিনটি PID লক্ষ্য করুন: শেল এবং তার দুটি চাইল্ড। sudo kill <pid> দিয়ে একটি চাইল্ডকে কিল করুন। ইউনিটটি পুনরায় চেক করুন। ট্রি-তে একটি প্রসেস কম, অবস্থা তখনও active (running) এবং জার্নালে নতুন কিছু নেই। এখন এর পরিবর্তে sudo kill -9 <shell pid> চালান। ইউনিটটি ব্যর্থ হয়, বেঁচে থাকা চাইল্ডটিকে ক্লিনআপ করা হয় কারণ KillMode=control-group হলো ডিফল্ট, এবং জার্নালটি Scheduled restart job, restart counter is at 1. দেখায়।
Restart= এর সম্পূর্ণ শব্দভাণ্ডার এবং কখন on-failure ব্যবহার করা always এর চেয়ে ভালো
Restart= সাতটি মানের যেকোনো একটি গ্রহণ করতে পারে এবং এদের মধ্যে পার্থক্য নির্ধারণকারী বিষয়টি হলো কোনটি 'ক্লিন' বা স্বাভাবিক প্রস্থান হিসেবে গণ্য হবে। systemd প্রস্থান কোড 0, SuccessExitStatus=-এ তালিকাভুক্ত যেকোনো কোড এবং SIGHUP, SIGINT, SIGTERM ও SIGPIPE সিগন্যালগুলোকে ক্লিন এক্সিট হিসেবে বিবেচনা করে। অন্য সবকিছু, যার মধ্যে SIGKILL এবং SIGSEGV অন্তর্ভুক্ত, তা আনক্লিন বা অস্বাভাবিক।
noহলো ডিফল্ট মান। ইউনিটটি নিজে থেকে কখনোই রিস্টার্ট হয় না, যে কারণেRestart=লাইন ছাড়া কোনো ইউনিট প্রথম ক্র্যাশের পরেই বন্ধ হয়ে যায় এবং বন্ধই থাকে।on-successশুধুমাত্র ক্লিন এক্সিটের পরেই রিস্টার্ট হয়।on-failureনন-জিরো এক্সিট কোড, আনক্লিন সিগন্যাল, স্টার্ট বা স্টপ টাইমআউট অথবা ওয়াচডগ এক্সপায়ারি হলে রিস্টার্ট হয়।on-abnormalআনক্লিন সিগন্যাল, টাইমআউট বা ওয়াচডগ এক্সপায়ারি হলে রিস্টার্ট হয়, কিন্তু সাধারণ নন-জিরো এক্সিট কোডের ক্ষেত্রে হয় না।on-abortশুধুমাত্র আনক্লিন সিগন্যালের ক্ষেত্রে রিস্টার্ট হয়, যার অর্থ হলো ক্র্যাশ।on-watchdogশুধুমাত্র যখনWatchdogSec=শেষ হয় তখন রিস্টার্ট হয়।alwaysউপরের প্রতিটি ক্ষেত্রে রিস্টার্ট হয়, যার মধ্যে স্ট্যাটাস 0 সহ ক্লিন এক্সিটও অন্তর্ভুক্ত।
দীর্ঘ সময় ধরে চলা ডেমনের জন্য on-failure সঠিক ডিফল্ট মান। এটি ক্র্যাশের পর সার্ভিস ফিরিয়ে আনে এবং ইচ্ছাকৃতভাবে করা exit 0-কে বাধা দেয় না। always এমন প্রোগ্রামের জন্য উপযুক্ত যা নিয়ন্ত্রণের বাইরের কোনো কারণে ক্লিনলি এক্সিট করে, যেমন একটি টানেল ক্লায়েন্ট যা অপর প্রান্ত বিচ্ছিন্ন হলে 0 রিটার্ন করে। always-এর অসুবিধা হলো এটি বাগ লুকিয়ে ফেলে: একটি সার্ভিস যা চালু হয়, একটি ত্রুটিপূর্ণ কনফিগারেশন ফাইল পড়ে, এরর লগ করে এবং 0 কোড দিয়ে এক্সিট করে, সেটি চিরকাল লুপে চলতে থাকবে এবং এর একমাত্র লক্ষণ হবে রিস্টার্ট কাউন্টার বাড়তে থাকা।
SuccessExitStatus= ক্লিন এবং আনক্লিনের মধ্যকার সীমারেখা পরিবর্তন করে। Borg সতর্কবার্তার জন্য 1 এবং ত্রুটির জন্য 2 কোড দিয়ে এক্সিট করে, তাই SuccessExitStatus=1 ছাড়া একটি ব্যাকআপ ইউনিট প্রতিবার একটি অপাঠ্য ফাইল এড়িয়ে যাওয়ার সময় ব্যর্থ হিসেবে চিহ্নিত হয়। RestartPreventExitStatus= এমন কোডগুলোর তালিকা করে যা always-এর অধীনেও রিস্টার্ট হওয়া বন্ধ করে, যা কোনো প্রোগ্রামের জন্য পুনরায় চালু না হওয়ার ক্লিন উপায়। RestartForceExitStatus= এর বিপরীত কাজ করে। একটি ব্যাকআপ জব রিস্টার্ট লুপের পরিবর্তে টাইমার দ্বারা চালিত Type=oneshot ইউনিটে থাকা উচিত এবং একটি নির্দিষ্ট সময়সূচীতে জব চালানোর জন্য সার্ভিস এবং টাইমার পেয়ার হলো এক্ষেত্রে অনুসরণ করার মতো সঠিক পদ্ধতি।
পরীক্ষার ক্ষেত্রে একটি সতর্কতা। আপনার সার্ভিসকে সাধারণ kill <pid> দিয়ে কিল করলে SIGTERM পাঠানো হয়, যা ক্লিন লিস্টে রয়েছে, তাই Restart=on-failure সঠিকভাবে কিছুই করবে না এবং আপনি মনে করবেন আপনার কনফিগারেশন ত্রুটিপূর্ণ। এর পরিবর্তে kill -9 <pid> বা systemctl kill -s SIGKILL myapp.service ব্যবহার করুন। আরও মনে রাখবেন যে Restart=-এর কোনো মানই systemctl stop-এর পরে কাজ করে না, অথবা যখন কোনো BindsTo= বা PartOf= ডিপেন্ডেন্সি চলে যাওয়ার কারণে ইউনিটটি বন্ধ করা হয়। একটি স্টপ জব ব্যর্থতা নয়।
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 রয়েছে এবং এতে এই সুবিধাগুলো নেই। যখন কোনো ডিপেন্ডেন্সি দীর্ঘ সময়ের জন্য ডাউন থাকতে পারে, তখন ক্রমবর্ধমান বিরতি (growing delay) ব্যবহার করাই সঠিক সমাধান।
"start request repeated too quickly" এর প্রকৃত অর্থ কী
এটি এমন একটি অবস্থা যা দেখে ব্যবহারকারীরা মনে করেন systemd অকারণে হাল ছেড়ে দিয়েছে। এটি মূলত একটি কাউন্টার। নিয়মটি হলো: যদি কোনো unit-কে StartLimitIntervalSec= সময়ের মধ্যে StartLimitBurst= বারের বেশি start করার চেষ্টা করা হয়, তবে systemd সেটিকে আর start করতে অস্বীকার করে এবং failed অবস্থায় পাঠিয়ে দেয়। ডিফল্ট নিয়ম হলো 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 অবস্থাকে রিসেট করে। অন্য কোনো কমান্ড এটি করে না, তাই আপনি যতক্ষণ না এটি চালাচ্ছেন, ততক্ষণ সাধারণ 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 সার্ভার ছাড়া কখনোই চলবে না এবং 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=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.serviceফাইলটি সঠিক থাকলে systemd-analyze verify কোনো আউটপুট দেখাবে না। কোনো আউটপুট আসা মানেই সমস্যা, সাধারণত এটি এমন কোনো কি (key) যা systemd ওই সেকশনে চিনতে পারছে না, অথবা এমন কোনো ইউনিটের ওপর নির্ভরতা যা অস্তিত্বহীন।
দ্বিতীয় সমাধান: Type=notify ব্যবহার করুন, যাতে systemd শুধু PID-এর চেয়ে বেশি কিছু জানতে পারে
যদি প্রোগ্রামটি systemd notification protocol সমর্থন করে, তবে সেটি ব্যবহার করুন। Type=notify-এর মাধ্যমে সার্ভিসটি systemd-কে জানায় যে সেটি প্রস্তুত, যা সার্ভিস শুরুর সময়কে অনুমানের ওপর নির্ভর না করে সুনিশ্চিত করে। এছাড়া এটি MAINPID= ব্যবহার করে systemd-কে মূল প্রসেসটি চিনিয়ে দিতে পারে, যা লঞ্চারের পরিবর্তে সরাসরি সেই প্রসেসকে নির্দেশ করে।
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একটি watchdog trip জার্নালে myapp.service: Watchdog timeout (limit 30s)! হিসেবে দেখা যায়, যার পরেই প্রসেসটি বন্ধ হয়ে যায়। যদি ইউনিটটি TimeoutStartSec শেষ না হওয়া পর্যন্ত activating (start) অবস্থায় আটকে থাকে, তবে বুঝতে হবে READY=1 কখনোই পৌঁছায়নি: হয় প্রোগ্রামটি এই প্রোটোকল সমর্থন করে না, অথবা NotifyAccess=main কোনো চাইল্ড প্রসেস থেকে আসা নোটিফিকেশন প্রত্যাখ্যান করছে, যা জার্নালে উভয় PID-সহ রিপোর্ট করা হয়।
যেসব সফটওয়্যার HTTP health endpoint প্রদান করে কিন্তু সেগুলোতে sd_notify সমর্থন নেই, তাদের জন্য সৎ উপায় হলো একটি ছোট timer unit ব্যবহার করা যা endpoint-টি পরীক্ষা করবে এবং systemctl restart কল করবে, অথবা কন্টেইনার রানটাইমকে এই পরীক্ষার দায়িত্ব দেওয়া, যার জন্য Compose healthchecks and their restart behaviour বিদ্যমান।
তৃতীয় সমাধান: ইউনিটের ভেতরে একটি সুপারভাইজার, যখন অন্য কোনো উপায় নেই
কিছু সফটওয়্যার মূলত এমন সব প্রসেসের সমষ্টি হিসেবে আসে যা একটি লঞ্চারের পেছনে থাকে, যেগুলোকে আলাদা করা সম্ভব নয়। সেক্ষেত্রে আপনি ইউনিটের ভেতরে একটি সুপারভাইজার চালান এবং এর ফলাফল মেনে নেন: systemd সুপারভাইজারকে পর্যবেক্ষণ করে, সুপারভাইজার বাকি সবকিছুকে পর্যবেক্ষণ করে, এবং আপনার রিস্টার্ট পলিসি এখন দুটি ফাইলে বিভক্ত থাকে।
এর সাধারণ রূপ হলো একটি কন্টেইনার রানটাইম। একটি docker compose বা podman ইউনিট ঠিক এই প্যাটার্ন অনুসরণ করে, যেখানে প্রতি-কন্টেইনার রিস্টার্ট পলিসি Compose ফাইলে থাকে এবং systemd ইউনিটটি শুধুমাত্র রানটাইমটিকে সচল রাখে। যদি আপনার সেটআপ এমন হয়, তবে বুট করার সময় যে ইউনিটটি একটি Compose স্ট্যাক চালু করে সেটি কার্যকর সংস্করণটি দেখায়, যার মধ্যে কেন Type=oneshot-এর সাথে RemainAfterExit=yes ব্যবহার করা সাধারণত সঠিক তা ব্যাখ্যা করা হয়েছে।
cgroup এখনও আপনার পক্ষে কাজ করে। সুপারভাইজার যা কিছু শুরু করে তা ইউনিটের cgroup-এর ভেতরেই থাকে, তাই MemoryMax=, CPUQuota= এবং বন্ধ করার সময় ক্লিনআপ প্রক্রিয়া পুরো ট্রি-কে কভার করে। শুধুমাত্র রিস্টার্টের সিদ্ধান্তটি অর্পণ করা হয়।
আপনি যে সুপারভাইজারই বেছে নিন না কেন, বাইরের ইউনিটে Restart=always এবং এর ভেতরে একটি আক্রমণাত্মক রিস্টার্ট পলিসি চিন্তাভাবনা না করে সেট করবেন না। রিস্টার্ট লজিকের দুটি স্তর, যার প্রতিটির নিজস্ব ব্যাকঅফ রয়েছে, এমন একটি সার্ভিস তৈরি করে যা কয়েক মিনিট ধরে ফ্ল্যাপ (flap) করতে থাকে এবং যার জার্নালে এর কারণ ব্যাখ্যা করা থাকে না।
ExitType=cgroup মানে এই নয় যে "যেকোনো প্রসেস মারা গেলে রিস্টার্ট হবে"
ExitType= (systemd 250 এবং তার পরবর্তী ভার্সন, তাই Ubuntu 24.04 এবং Debian 12 উভয়ই এটি সাপোর্ট করে) হলো সেই সেটিং যা মানুষ এই সমস্যার সমাধান খুঁজতে গিয়ে পায়, কিন্তু এটি নামের বিপরীত কাজ করে। ডিফল্ট সেটিং ExitType=main-এর অর্থ হলো, মেইন প্রসেসটি বন্ধ হয়ে গেলে সার্ভিসটিকে বন্ধ হিসেবে গণ্য করা হয়। ExitType=cgroup-এর অর্থ হলো, cgroup-এর শেষ প্রসেসটি বন্ধ না হওয়া পর্যন্ত সার্ভিসটিকে চলমান হিসেবে গণ্য করা হয়।
তাই ExitType=cgroup কোনো ইউনিটকে একটি প্রসেস মারা যাওয়ার বিষয়ে আরও সংবেদনশীল করে না, বরং কম সংবেদনশীল করে। এটি এমন প্রোগ্রামের জন্য সঠিক সেটিং যা তার আসল ওয়ার্কার প্রসেসকে ফর্ক (fork) করে এবং PID ফাইল তৈরি না করেই প্যারেন্ট প্রসেসটি বন্ধ করে দেয়, যেখানে Type=forking ডেমোনটিকে খুঁজে পায় না। এখানে বর্ণিত ব্যর্থতার জন্য এটি ভুল সেটিং।
Restart=-এর এমন কোনো ভ্যালু নেই যার অর্থ "cgroup-এর যেকোনো প্রসেস মারা গেলে ইউনিটটি রিস্টার্ট হবে"। যদি আপনার এই আচরণের প্রয়োজন হয়, তবে প্রতি ইউনিটে একটি করে প্রসেস থাকা প্রয়োজন। যদি আপনি প্রোগ্রামটিকে আলাদা করতে না পারেন এবং আপনার কাছে র্যাপার স্ক্রিপ্টের নিয়ন্ত্রণ থাকে, তবে সবচেয়ে কাছাকাছি সমাধান হলো wait -n, যা প্রথম চাইল্ড প্রসেসটি বন্ধ হওয়ার সাথে সাথেই রিটার্ন করে:
#!/bin/bash
/usr/local/bin/myapp-web &
/usr/local/bin/myapp-worker &
wait -n
exit 1এখন যেকোনো চাইল্ড প্রসেস মারা গেলেই র্যাপারটি নন-জিরো স্ট্যাটাস নিয়ে বন্ধ হয়ে যায়, ফলে Restart=on-failure কার্যকর হয়। এটি একটি আপস, কোনো স্থায়ী সমাধান নয়। আপনি এখনও দুটি প্রোগ্রামের জন্য একটি মাত্র রিস্টার্ট কাউন্টার এবং একটি লগ স্ট্রিম পাবেন, এবং ব্যর্থ হওয়া অংশটিকে আলাদাভাবে রিস্টার্ট করার কোনো উপায় থাকবে না।
কীভাবে প্রকৃত ঘটনা অনুসন্ধান করবেন
এই ক্রমে চারটি কমান্ড ব্যবহার করুন।
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 ট্রি দেখায়। একটি সচল ইউনিটের স্ট্যাটাস Active: active (running) দেখাবে এবং এতে একটি Main PID: লাইন থাকবে যেখানে আপনার প্রত্যাশিত প্রসেসটির নাম থাকবে। যদি নিচের ট্রি-তে এমন কোনো প্রসেস থাকে যা আপনি চিনতে পারছেন না, অথবা কোনো প্রসেস অনুপস্থিত থাকে, তবে আপনি সেখানেই সমস্যার কারণ খুঁজে পাবেন।
systemd-cgls --unit কমান্ডটি কোনো অংশ বাদ না দিয়ে পুরো ট্রি প্রিন্ট করে, যা তখন গুরুত্বপূর্ণ হয়ে ওঠে যখন একটি ইউনিটে অনেকগুলো প্রসেস চলমান থাকে।
systemctl show কমান্ডটি মেশিন-রিডেবল তথ্য প্রদান করে। NRestarts= হলো রিস্টার্ট কাউন্টার, এবং এটিই সবচেয়ে দ্রুত উপায় একটি সার্ভিসকে শনাক্ত করার, যা হয়তো 40 বার রিস্টার্ট হয়েছে অথবা যা বুট হওয়ার পর থেকে সচল আছে। Result= ফিল্ডে সর্বশেষ ব্যর্থতার কারণ থাকে: exit-code, signal, timeout, oom-kill, watchdog অথবা start-limit-hit। ExecMainStatus= হলো সর্বশেষ প্রধান প্রসেসের raw exit status।
জার্নাল বা লগ ফাইলে পুরো ঘটনার ক্রম থাকে। অনুসন্ধানের জন্য নিচের তিনটি লাইন গুরুত্বপূর্ণ:
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 হলো সেই সংখ্যা যা নিয়মিত সংগ্রহ করা উচিত। যে ইউনিটের কাউন্টার প্রতিদিন বাড়ছে, সেটি প্রতিদিন ব্যর্থ হচ্ছে, কেউ তা লক্ষ্য করুক বা না করুক। যখন আপনার সার্ভারের সংখ্যা দুই বা তিনটির বেশি হয়, তখন প্রতিটি সার্ভারে একটি কমান্ড চালানোর ধারাবাহিক উপায় ব্যবহার করে আপনি অনুমান থেকে একটি সঠিক রিপোর্টে পৌঁছাতে পারবেন।
FAQ
কেন systemctl বলে আমার সার্ভিস active, অথচ প্রসেসটি মারা গেছে?
systemd প্রতিটি সার্ভিস ইউনিটের জন্য একটি প্রধান প্রসেস ট্র্যাক করে এবং Restart= শুধুমাত্র সেই প্রসেসের exit status পড়ে। ইউনিটটি যা কিছু শুরু করে তা একই cgroup-এ থাকে এবং ইউনিট বন্ধ হলে systemd সেই প্রসেসগুলোকে বন্ধ করে দেয়, কিন্তু তাদের exit status কখনো পর্যবেক্ষণ করে না। systemctl show -p MainPID myapp.service চালান এবং সেই সংখ্যার সাথে systemd-cgls --unit myapp.service তুলনা করুন। যদি মৃত প্রসেসটি প্রসেস ট্রিতে দেখা যায় কিন্তু সেটি MainPID না হয়, তবে systemd তার নকশা অনুযায়ীই কাজ করেছে। এর সমাধান হলো প্রতি ইউনিটে একটি প্রসেস রাখা এবং ইউনিটগুলোর মধ্যে সম্পর্ক BindsTo= ও Upholds= হিসেবে লেখা।
"start request repeated too quickly" এর অর্থ কী?
এর অর্থ হলো ইউনিটটি StartLimitIntervalSec= সময়ের মধ্যে StartLimitBurst= বারের বেশি চালু করার চেষ্টা করা হয়েছে। ডিফল্টভাবে এটি 10 সেকেন্ডে 5 বার, তাই systemd চেষ্টা করা বন্ধ করে দিয়েছে। এটি একটি rate limit এবং এটি কখনোই বলে না কেন সার্ভিসটি ব্যর্থ হচ্ছে, তাই এর উপরের journal লাইনগুলো পড়ুন। systemctl reset-failed myapp.service দিয়ে স্টেট ক্লিয়ার করুন, তারপর মূল সমস্যাটি সমাধান করুন। যদি সার্ভিসটি কোনো ধীরগতির কিছুর জন্য অপেক্ষা করে, তবে RestartSec= বাড়িয়ে দিন, কারণ ডিফল্ট 100 মিলিসেকেন্ডের বিরতি এক সেকেন্ডের কম সময়েই পাঁচটি প্রচেষ্টাই শেষ করে ফেলে।
আমার কি Restart=always নাকি Restart=on-failure ব্যবহার করা উচিত?
প্রায় সবকিছুর জন্যই on-failure ব্যবহার করুন। এটি ক্র্যাশ, নন-জিরো exit, টাইমআউট এবং watchdog ট্রিপের ক্ষেত্রে সার্ভিস রিস্টার্ট করে এবং ইচ্ছাকৃত exit 0-কে বাধা দেয় না। always শুধুমাত্র তখনই ব্যবহার করুন যখন প্রোগ্রামটি তার নিয়ন্ত্রণের বাইরের কোনো কারণে সফলভাবে (cleanly) বন্ধ হয়ে যায়, যেমন কোনো ক্লায়েন্ট যার পিয়ার ডিসকানেক্ট হলে সেটি 0 রিটার্ন করে। always ব্যবহারের ঝুঁকি হলো, কোনো সার্ভিস যদি ভুল কনফিগারেশন পড়ে, একটি এরর লগ করে 0 কোড দিয়ে বন্ধ হয়ে যায়, তবে সেটি বারবার লুপে চলতে থাকবে এবং এর একমাত্র লক্ষণ হবে systemctl show-এ NRestarts বাড়তে থাকা।
ম্যানুয়ালি প্রসেস কিল করলে কেন রিস্টার্ট ট্রিগার হয় না?
কারণ systemd SIGHUP, SIGINT, SIGTERM এবং SIGPIPE-কে সফল exit হিসেবে গণ্য করে, আর সাধারণ kill <pid> কমান্ডটি SIGTERM পাঠায়। Restart=on-failure-এর অধীনে সফল exit কোনো ব্যর্থতা নয়, তাই কিছুই রিস্টার্ট হয় না এবং কনফিগারেশন ঠিক থাকলেও তা ভুল মনে হতে পারে। kill -9 <pid> বা systemctl kill -s SIGKILL myapp.service দিয়ে পরীক্ষা করুন, যা একটি অসম্পূর্ণ (unclean) টার্মিনেশন এবং এটি রিস্টার্ট পলিসি ট্রিগার করে। একই নিয়ম ব্যাখ্যা করে কেন systemctl stop আপনার রিস্টার্ট পলিসির সাথে কোনো বিরোধ তৈরি করে না।
StartLimitIntervalSec এবং StartLimitBurst কোথায় বসাতে হয়?
এগুলো [Unit] সেকশনে বসাতে হয়। পুরনো নথিপত্র এবং পুরনো systemd ভার্সনে এগুলো [Service]-এ রাখা হতো, তাই কপি করা উদাহরণগুলোতে অমিল দেখা যায়। আপনার ভার্সন কোনটি গ্রহণ করে তা অনুমান করবেন না। systemctl daemon-reload করার পর, systemctl show -p StartLimitBurst -p StartLimitIntervalUSec myapp.service দিয়ে systemd-কে জিজ্ঞাসা করুন সে কী লোড করেছে এবং সেই সংখ্যাগুলোকেই চূড়ান্ত বলে ধরুন। systemd-analyze verify /etc/systemd/system/myapp.service এমন সব কি (key) শনাক্ত করে যা systemd চেনে না, আর ফাইলটি সঠিক থাকলে এটি কিছুই প্রিন্ট করে না।