systemd-এর Requires, After ও Condition সহজ ব্যাখ্যা
Requires, Wants, After, Before, ExecStartPre এবং Condition এক কাজ করে না। কোনটি কী নিশ্চয়তা দেয় এবং unit না চললে কীভাবে debug করবেন, তা জানুন।
Requires মানেই After নয়
systemd-এর dependency এবং condition মোট 4টি আলাদা mechanism, যদিও অধিকাংশ unit file-এ এগুলোকে একটি mechanism হিসেবেই ব্যবহার করা হয়। Requires= এবং Wants= নির্ধারণ করে, অন্য কোন unit-গুলো start transaction-এ অন্তর্ভুক্ত হবে। After= এবং Before= নির্ধারণ করে, unit-গুলো কোন ক্রমে start হবে। ExecStartPre= একটি check চালায়, যা ব্যর্থ হলে unit-ও ব্যর্থ হতে পারে। Condition এবং Assert family নির্ধারণ করে, unit-টি আদৌ চলবে কি না। প্রতিটি mechanism অন্যগুলোর থেকে স্বাধীন। তাই একটি unit অন্য একটি unit-কে require করলেও উভয়টি একই সময়ে start হতে পারে।
শেষ বাক্যটিই প্রায় সব “হাতে start করলে কাজ করে, কিন্তু boot-এর সময় ব্যর্থ হয়” সমস্যার মূল কারণ।
[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 একই start transaction-এ PostgreSQL-কে অন্তর্ভুক্ত করে। এটি PostgreSQL-এর জন্য অপেক্ষা করে না। systemd উভয় job parallelভাবে start করে। ফলে PostgreSQL এখনও তার data directory খুলছে, সেই সময়ে pg_isready চলে। তখন কোনো process listening না থাকায় এটি 2 exit code দিয়ে বন্ধ হয়, এবং ExecStart-এ পৌঁছানোর আগেই unit ব্যর্থ হয়। এক ঘণ্টা পরে sudo systemctl start inventory-api চালালে এটি কাজ করে, কারণ ততক্ষণে PostgreSQL চালু হয়ে গেছে। unit file-এ কিছুই পরিবর্তন হয়নি। তাই file-টি নির্দোষ মনে হয়।
সমাধান হলো একটি line যোগ করা।
[Unit]
Requires=postgresql.service
After=postgresql.serviceএকই জায়গায় আরও গুরুত্বপূর্ণ একটি বিষয় আছে। ব্যর্থ হওয়া Requires= dependency আপনার unit-এর start বন্ধ করবে কেবল তখনই, যখন আপনি সেটির সঙ্গে After=-ও সেট করবেন। ordering না থাকলে অন্য unit ব্যর্থ হওয়ার আগেই systemd আপনার unit start করে ফেলে। তখন cancel করার মতো আর কিছু থাকে না। শুধু Requires= ব্যবহার করলে যে সুরক্ষা পাওয়া যাবে বলে অনেকে মনে করেন, তা পাওয়া যায় না। কোনো নির্দিষ্ট কারণ না থাকলে প্রতিটি Requires= এবং প্রতিটি Wants=-এর পাশে After= লিখুন।
Requires, Wants, Requisite এবং BindsTo কী নিশ্চিত করে
এগুলো সব dependency setting। কোনোটিই execution order নির্ধারণ করে না।
Wants=: অন্য unit-টিকে dependency হিসেবে যুক্ত করে। এটি ব্যর্থ হলেও বা অস্তিত্ব না থাকলেও এই unit start হয়। একটি.wants/directory-র মধ্যে symlink হিসেবেsystemctl enableএটিই তৈরি করে।Requires=: অন্য unit-টিকে dependency হিসেবে যুক্ত করে। এটি ব্যর্থ হলে এবং আপনি যদি এর সঙ্গেAfter=-এর ordering-ও নির্ধারণ করে থাকেন, তাহলে এই unit start হয় না। অন্য unit-টি পরে explicitভাবে stop হলে, এই unit-টিও তার সঙ্গে stop হয়।Requisite=: অন্য unit-টিকে dependency হিসেবে যুক্ত করে না। এটি আগে থেকেই active না থাকলে, এই unit সঙ্গে সঙ্গে ব্যর্থ হয়।BindsTo=:Requires=-এর মতো কাজ করে। এ ছাড়া অন্য unit-টি যেকোনো কারণে stop হলে, এই unit-টিও stop হয়। এর মধ্যে হারিয়ে যাওয়া hardware-জনিত stop-ও অন্তর্ভুক্ত।PartOf=: অন্য unit থেকে এই unit-এ stop এবং restart propagate হয়। Start propagate হয় না।Conflicts=: এই unit start করলে অন্য unit-টি stop হয়।
একটি daemon যখন অন্য একটি daemon-এর সঙ্গে যোগাযোগ করে, তখন Wants= এবং After= সাধারণত সঠিক জোড়া। Requires= দুই unit-এর lifetime একসঙ্গে যুক্ত করে। Maintenance-এর জন্য database stop করলে application-ও তার সঙ্গে বন্ধ হয়ে যায়। Database ফিরে এলেও application নিজে থেকে ফিরে আসে না। Wants= এবং After= coupling ছাড়াই boot ordering দেয়। পরে dependency হারিয়ে গেলে restart policy সেই পরিস্থিতি সামলায়।
আপনি নিজে না লিখলেও কিছু dependency উত্তরাধিকারসূত্রে যুক্ত হয়। ডিফল্ট DefaultDependencies=yes ব্যবহৃত হলে, একটি সাধারণ service স্বয়ংক্রিয়ভাবে Requires=sysinit.target, After=sysinit.target basic.target এবং Conflicts=shutdown.target পায়। তাই প্রায় খালি [Unit] section থাকা service-ও boot-এর শেষদিকে start হয় এবং shutdown-এর সময় সুশৃঙ্খলভাবে stop হয়।
After এবং Before শুধুমাত্র transaction-এর ক্রম নির্ধারণ করে, অন্য কিছু নয়
After= এবং Before= শুধুই ক্রম নির্ধারণ করে। এগুলো কোনো requirement তৈরি করে না। এমন একটি unit-এ After=redis.service ব্যবহার করলে, যেটি অন্য কোনো কিছু Redis-কে transaction-এ আনে না, সেটি কার্যত কোনো কাজ করে না: redis.service transaction-এর অংশ না হলে অপেক্ষা করার মতো কিছু থাকে না। তাই আপনার unit সঙ্গে সঙ্গে শুরু হয়।
এটি দুবার বলা দরকার, কারণ নিচের network-online.target ভুলটির গঠন ঠিক এমনই। Ordering শুধুমাত্র সেই unit-গুলোর জন্য অপেক্ষা করে, যেগুলো একই transaction-এ ইতিমধ্যে start হওয়ার জন্য নির্ধারিত।
এই জোড়াটি symmetric। a.service-এ লেখা After=b.service-এর অর্থ b.service-এ লেখা Before=a.service-এর মতোই। তাই যেকোনো একটি ব্যবহার করুন এবং সেটি আপনার নিয়ন্ত্রিত unit-এ রাখুন। Shutdown-এর সময় ordering স্বয়ংক্রিয়ভাবে বিপরীত হয়। তাই After=b.service-এর অর্থ আপনার unit-টি b.service-এর আগে stop হয়।
After= “started” হওয়া পর্যন্ত অপেক্ষা করে, আর Type= নির্ধারণ করে এর অর্থ কী
After= অন্য unit-এর start শেষ হওয়া পর্যন্ত অপেক্ষা করে। “Start শেষ হওয়া” বলতে কী বোঝাবে, তা সম্পূর্ণভাবে ওই unit-এর Type= নির্ধারণ করে।
Type=simple: systemd process-টি fork করার সঙ্গে সঙ্গে। প্রোগ্রামটি হয়তো এখনও configuration parse করেনি, socket খোলার কথা তো বাদই দিলাম।Type=exec:execve()সফল হওয়ার সঙ্গে সঙ্গে। এটি কিছুটা শক্তিশালী নিশ্চয়তা। তবু service প্রস্তুত কি না, সে বিষয়ে কিছু বলে না।Type=forking: মূল parent process শেষ হলে।Type=oneshot: process শেষ হলে। এখানে “started” সত্যিই কাজ শেষ হওয়া বোঝায়।Type=notify: service তার notification socket-এREADY=1পাঠালে। প্রকৃত readiness জানায় একমাত্র এই type-ই।
তাই Type=simple daemon-এর ক্ষেত্রে After= একটি দুর্বল নিশ্চয়তা। প্রথম উদাহরণের race-এর দ্বিতীয় অংশও এটাই। আপনি যে unit-এর ওপর নির্ভর করছেন, সেটি যদি Type=simple হিসেবে shipped হয়, তার পরে ordering করলেই যে সেটি connection গ্রহণ করছে, এমন নয়। এর দুটি নির্ভরযোগ্য সমাধান আছে। তার socket unit-এর পরে ordering করুন, যাতে daemon start হওয়ার সময় kernel incoming connection queue করে রাখতে পারে। অথবা নিজের service-এ retry যুক্ত করুন এবং restart policy-কে তা পরিচালনা করতে দিন। কোনো unit কোন type ব্যবহার করছে, তা systemctl cat-এ দেখা যায়। Ordering-এর ওপর নির্ভর করার আগে Type= setting এবং প্রতিটি value systemd-কে কী জানায় পড়ে নেওয়া উপকারী।
ExecStartPre একটি gate, যা unit ব্যর্থ করতে পারে
ExecStartPre=, ExecStart=-এর আগে চলে। এটি non-zero exit code দিয়ে শেষ হলে activation বাতিল হয় এবং unit failed অবস্থায় যায়। ExecStart= কখনো চলে না। কোনো প্রকৃত program-এর কাছ থেকে কোনো message না এসেও অনেক unit ব্যর্থ হওয়ার পেছনে এই mechanism কাজ করে, কারণ program-টি আদৌ start হয়নি।
যে বিষয়গুলো প্রায়ই সমস্যা তৈরি করে:
- এটি shell নয়। Pipe, redirection, glob বা
&&ব্যবহার করা যায় না। প্রথম token অবশ্যই একটি absolute path হতে হবে। Shell syntax প্রয়োজন হলে line-টি/bin/sh -c '...'-এর মধ্যে wrap করুন। -prefix non-zero exit-কে non-fatal করে:ExecStartPre=-/usr/bin/optional-check।- প্রতিটি
ExecStartPre=পরেরটি চলার আগে শেষ হতে হবে। এটি দীর্ঘ সময় চলা কোনো process start করতে পারে না। - সব
ExecStartPre=lineTimeoutStartSec=-কেExecStart=-এর সঙ্গে share করে। Database-এর জন্য অপেক্ষা করতে থাকা কোনো pre-check start timeout শেষ করে ফেলতে পারে। এরপর journal-এstart operation timed out. Terminating.দেখা গেলে unitResult: timeoutঅবস্থায় ব্যর্থ হয়।
ব্যর্থতার line-এ main process নয়, control process-এর নাম থাকে:
inventory-api.service: Control process exited, code=exited, status=2/INVALIDARGUMENT
inventory-api.service: Failed with result 'exit-code'.এই symbolic name-টি সতর্কভাবে পড়ুন। systemd একটি fixed table ব্যবহার করে ছোট exit code map করে। তাই program এটি দিয়ে যা বোঝাতে চেয়েছে তা যাই হোক, 2 সব সময় INVALIDARGUMENT print করে। প্রকৃত তথ্য বহন করে status=203/EXEC। এর অর্থ systemd binary-টি একেবারেই execute করতে পারেনি, কারণ path ভুল অথবা file-টি executable নয়।
Directory তৈরি করতে ExecStartPre= ব্যবহার করবেন না। RuntimeDirectory=, StateDirectory=, LogsDirectory= এবং CacheDirectory= সঠিক owner ও mode সহ directory তৈরি করে। Service বন্ধ হলে RuntimeDirectory= সেগুলো পরিষ্কার করে। DynamicUser=-এর অধীনেও এগুলো সঠিকভাবে কাজ করে, যা নিজে লেখা mkdir করে না।
Condition ব্যর্থ হলে নীরবে এড়িয়ে যায়। Assert ব্যর্থ হলে স্পষ্টভাবে ব্যর্থতা জানায়।
Condition এবং Assert family একই পরীক্ষা চালায়। পরীক্ষাটি ব্যর্থ হলে কী ঘটে, শুধু সেই পার্থক্য রয়েছে।
Condition...= ব্যর্থ হলে unit-টি এড়িয়ে যায়। Start job-কে successful হিসেবে জানানো হয়। Unit-টি inactive (dead) অবস্থায় থাকে, কোনো কিছু failed হিসেবে চিহ্নিত হয় না, কোনো alert তৈরি হয় না, এবং journal-এ একটি লাইন লেখা হয়:
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এর নিচের indented line-এ ব্যর্থ হওয়া নির্দিষ্ট directive-এর নাম থাকে, যেমন ConditionPathExists=/etc/inventory/api.conf was not met।
Assert...= ব্যর্থ হলে unit-টি failed হয়। Journal-এ Assertion failed for Inventory API. লেখা হয় এবং unit-টি failed (Result: assert) অবস্থায় শেষ হয়। Monitoring এটি শনাক্ত করার জন্য যথেষ্ট স্পষ্ট।
কোনটি ব্যবহার করবেন, তা নির্ধারণ করতে ব্যর্থ পরীক্ষার অর্থ বিবেচনা করুন। Condition অর্থ, "এই মেশিনে এই unit প্রযোজ্য নয়"। Assert অর্থ, "এটি অবশ্যই সত্য হতে হবে; সত্য না হলে কাউকে জানাতে হবে"। অধিকাংশ unit-এর জন্য Condition উপযুক্ত। Assert কেবল তখন ব্যবহার করুন, যখন নীরবে কিছু না করার চেয়ে failed unit থাকা বেশি উপযুক্ত।
Condition family-এর সঙ্গে দুটি বিষয় মনে রাখতে হবে।
প্রথমত, ব্যর্থ condition-এর কারণে তার ওপর নির্ভরশীল unit ব্যর্থ হয় না। a.service-এ Requires=b.service থাকলে এবং b.service কোনো condition-এর কারণে start এড়িয়ে গেলে, b.service-এর start job তবুও সম্পন্ন হিসেবে গণ্য হয়। ফলে a.service স্বাভাবিকভাবে শুরু হয়, যদিও b চালু নেই। Condition শুধু যে unit-এ লেখা থাকে, সেই unit-টিকেই সুরক্ষিত করে।
দ্বিতীয়ত, unit প্রতিবার শুরু হওয়ার সময় job চালানোর মুহূর্তে condition মূল্যায়ন করা হয়। একটি VPS-এ systemd timer দ্বারা চালিত unit পরপর একশো বার এড়িয়ে যেতে পারে, কিন্তু একবারও failed হিসেবে দেখা নাও যেতে পারে। এটি চলা সত্ত্বেও কিছু না করা cron job-এর মতোই নীরব no-op। একইভাবে এটি শনাক্ত করতে unit-এর exit state-এর ওপর নির্ভর না করে তার journal পড়ুন।
সার্ভারে যেসব condition জানা দরকার:
ConditionPathExists=/etc/inventory/api.confএবং এর negationConditionPathExists=!/etc/inventory/api.conf।ConditionFileNotEmpty=এবংConditionDirectoryNotEmpty=, কোনো package তৈরি করলেও খালি রেখে যাওয়া config file বা data directory-এর জন্য।ConditionVirtualization=, যাতে বাস্তব kernel interface প্রয়োজন এমন unit-এConditionVirtualization=!containerব্যবহার করা যায়। আপনার মেশিন কী রিপোর্ট করে, তাsystemd-detect-virtদিয়ে পরীক্ষা করুন।ConditionHost=hostname বা machine ID-এর সঙ্গে মেলে। এর মাধ্যমে একই shared unit file দুই সার্ভারে ভিন্নভাবে কাজ করতে পারে।ConditionKernelCommandLine=এবংConditionKernelVersion=, boot parameter বা ন্যূনতম kernel version-এর সঙ্গে যুক্ত unit-এর জন্য।
খালি assignment তালিকাটি মুছে দেয়। এভাবেই drop-in কোনো package-এর সরবরাহ করা condition সরিয়ে দেয়:
[Unit]
ConditionPathExists=
ConditionPathExists=/srv/inventory/api.confnetwork.target কেন network up হওয়ার অর্থ নয়
network.target একটি synchronization point, state নয়। Boot-এর সময় এর পরে ordering করার অর্থ হলো network management software start হয়েছে। এর অর্থ নয় যে কোনো interface-এ address আছে, অথবা Internet-এ যাওয়ার route বিদ্যমান। এই target মূলত বিপরীত দিকের কাজের জন্য থাকে: After=network.target-এর পরে ordered কোনো unit shutdown-এর সময় network teardown শুরু হওয়ার আগে stop হয়।
network-online.target অপেক্ষা করে। এটি আপনি যে network manager ব্যবহার করেন, তার wait-online service দ্বারা সমর্থিত:
- links systemd-networkd পরিচালনা করলে
systemd-networkd-wait-online.service, যা netplan দিয়ে configured Ubuntu server-এ স্বাভাবিক অবস্থা। - NetworkManager-এর ক্ষেত্রে
NetworkManager-wait-online.service।
পুরোনো ifupdown setup-এ একই কাজের জন্য networking.service ব্যবহার করা হয়। যেটিই আপনার ক্ষেত্রে প্রযোজ্য হোক, target সঠিকভাবে ব্যবহার করতে একটির বদলে দুইটি line লাগে।
[Unit]
Wants=network-online.target
After=network-online.targetnetwork-online.target default boot transaction-এর অংশ নয়, এবং কোনো কিছু নিজে থেকে এটিকে transaction-এ যুক্ত করে না। শুধু After= লিখলে এমন একটি unit-এর পরে ordering করা হয়, যেটি কখনো queue-তেই যুক্ত হয়নি। তাই ordering একেবারেই কোনো কাজ করে না। আগের অংশে বর্ণিত no-op-এর এটাই সবচেয়ে ব্যয়বহুল রূপ। Wants= line-টি target-কে transaction-এ যুক্ত করে, যাতে After= line-টির অপেক্ষা করার মতো কিছু থাকে।
দ্বিতীয় গুরুত্বপূর্ণ বিষয় হলো, “online” বলতে কী বোঝাবে তা wait-online implementation নির্ধারণ করে, systemd নয়। systemd-networkd-wait-online তার পরিচালিত links configured state-এ পৌঁছালে return করে। এটি DNS resolve হচ্ছে কি না পরীক্ষা করে না। কোনো remote host reachable কি না, তাও পরীক্ষা করে না।
এই সংজ্ঞা VPS-এ একটি সাধারণ সমস্যা তৈরি করে। Private network-এর জন্য ব্যবহৃত দ্বিতীয় একটি interface netplan-এ ঘোষিত আছে, কিন্তু তাকে কোনো address দেওয়া হয়নি। ফলে wait service timeout হওয়া পর্যন্ত অপেক্ষা করে:
systemd-networkd-wait-online[612]: Timeout occurred while waiting for network connectivity.
systemd-networkd-wait-online.service: Failed with result 'exit-code'.Default timeout 120 seconds হওয়ায় boot হতে আরও দুই মিনিট লাগে। এর দুইটি সমাধান আছে। netplan file-এ অব্যবহৃত interface-টিকে optional: true হিসেবে চিহ্নিত করুন, যাতে networkd সেটির জন্য অপেক্ষা বন্ধ করে। অথবা wait service-এ একটি drop-in যোগ করুন, যেখানে --interface= দিয়ে প্রয়োজনীয় link-এর নাম উল্লেখ করবেন। বিকল্পভাবে --any pass করুন, যাতে একটি link up হলেই service return করে।
আরও ভালো সমাধান হলো target-এর ওপর নির্ভর না করা। অনেক service শুধু একটি নির্দিষ্ট address-এ bind করার কারণে network-online.target-এর পরে ordered হয় এবং boot-এর সময় এ ধরনের line দিয়ে ব্যর্থ হয়:
nginx: [emerg] bind() to 203.0.113.10:443 failed (99: Cannot assign requested address)ওই address তখনও up না থাকায় kernel bind প্রত্যাখ্যান করে। net.ipv4.ip_nonlocal_bind=1 সেট করলে process এমন একটি address-এ bind করতে পারে, যা machine-এ এখনও নেই। বাকি পরিস্থিতি restart policy সামলে নেবে। সাধারণত সমস্যাটি একটি socket-সংক্রান্ত হওয়ায় network readiness-এর জন্য পুরো boot বিলম্বিত করা অতিরিক্ত ভারী পদ্ধতি।
চলমান সিস্টেমে প্রকৃত systemd dependency কীভাবে পড়বেন
শুধু unit file দেখে কখনো সিদ্ধান্ত নেবেন না। Drop-in, .wants/ symlink এবং implicit default dependency এমন edge যোগ করে, যা unit file-এ দেখা যায় না।
systemctl cat inventory-api.serviceএটি unit file এবং প্রতিটি drop-in প্রয়োগের ক্রমে দেখায়। প্রতিটি 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এটি drop-in প্রয়োগের পরে এবং systemd implicit dependency যোগ করার পরে resolved value দেখায়। ConditionResult=no-এর মাধ্যমে “unit success জানিয়েছে, কিন্তু কোনো কাজ করেনি”—এই প্রশ্নের সরাসরি উত্তর পাওয়া যায়।
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সাধারণ form-টি Requires= এবং Wants=-এর dependency tree নিচের দিকে অনুসরণ করে। --reverse দেখায় কোন unit আপনার unit-কে dependency হিসেবে টেনে আনে। এভাবেই boot-এর সময় কোন target এটি 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-টি millisecond timestamp-সহ দুটি unit-এর event interleave করে দেখায়। অনুমান না করে ordering race প্রমাণ করার এটাই উপায়। pg_isready failure, PostgreSQL database system is ready to accept connections log লেখার আগে ঘটে। দুটির মধ্যকার ব্যবধানও output-এ সরাসরি দেখা যায়।
systemd-analyze verify /etc/systemd/system/inventory-api.service
systemd-analyze critical-chain inventory-api.serviceverify unit-টি systemd যেভাবে load করত সেভাবেই load করে এবং অজানা directive, অস্তিত্বহীন unit-এর dependency, ordering cycle এবং parse করা যায় না এমন syntax জানায়। এটি সিস্টেমে কোনো পরিবর্তন করে না। critical-chain যে ordering chain unit-টিকে বিলম্বিত করেছে, তা দেখায় এবং প্রতিটি ধাপ কখন active হয়েছে সেই সময়ও দেয়। এটি শুধু বর্তমান boot-এর সময় start হওয়া unit-এর ক্ষেত্রে কাজ করে।
যেকোনো unit file সম্পাদনা করার পরে sudo systemctl daemon-reload চালান। Packaged unit পরিবর্তন করতে sudo systemctl edit inventory-api.service ব্যবহার করুন। এটি আপনার জন্য একটি drop-in তৈরি করে। /usr/lib/systemd/system/-এর অধীনে vendor file সম্পাদনা করলে পরবর্তী package upgrade-এ file-টি প্রতিস্থাপিত হওয়া পর্যন্তই পরিবর্তনটি কার্যকর থাকবে। Package-এর মালিকানাধীন file না ছুঁয়ে service-এ memory এবং CPU limit যুক্ত করার জন্যও একই drop-in mechanism ব্যবহার করা হয়।
Ordering cycle এবং journal-এ থাকা তার চিহ্ন
উভয় দিকেই ordering যোগ করলে systemd loop ভাঙতে একটি job মুছে দেয়:
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 কোন job মুছবে তা নিজেই নির্ধারণ করে। আপনি যে job-টি মুছতে চাইতেন, সেটি নাও হতে পারে। এর ফলে কিছু reboot-এর পরে service অনুপস্থিত থাকে, আবার অন্য reboot-এর পরে উপস্থিত থাকে। বাইরে থেকে এটি debug করা অত্যন্ত কঠিন। বেশিরভাগ cycle তৈরি হয় এমন unit থেকে, যেগুলো DefaultDependencies=no সেট করার পরও নিজেদের basic.target-এর আগে বা পরে order করে। আরেকটি সাধারণ কারণ হলো, এমন unit-এ Before= যোগ করা, যার মধ্যে আগে থেকেই আপনার দিকে নির্দেশ করা After= ছিল। reboot ছাড়াই systemd-analyze verify এগুলো খুঁজে বের করে।
নির্দিষ্ট unit
[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= উভয় dependency-কে transaction-এ অন্তর্ভুক্ত করে, তবে এই unit-এর lifetime-কে সেগুলোর সঙ্গে যুক্ত করে না। After= অপেক্ষা করার কাজটি করে, এবং এখানে উভয় নামই আবার লিখতে হয়, কারণ dependency ও ordering আলাদা setting। ConditionPathExists=-এর অর্থ হলো package থাকলেও configuration না থাকলে unit-টি নীরবে এড়িয়ে যাবে এবং alarm তৈরি করবে না। config-driven service-এর জন্য এটিই সঠিক আচরণ। Type=notify-এর অর্থ হলো এই unit-এর পরে ordered কোনো কিছু fork-এর জন্য নয়, প্রকৃত readiness-এর জন্য অপেক্ষা করবে। Restart=on-failure boot-এর অনেক পরে database অপ্রাপ্য হয়ে গেলেও তা সামলায়, কারণ ordering শুধু প্রথম start-এর ক্ষেত্রে প্রযোজ্য। retry কতটা aggressive হবে, তা Restart= এবং RestartSec= setting নিয়ন্ত্রণ করে।
বিশ্বাস করার আগে এটি পরীক্ষা করুন:
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একটি সুস্থ unit ConditionResult=yes-কে ActiveState=active দিয়ে পড়ে, এবং Result=success নিশ্চিত করে যে শেষ run-এ কোনো কিছু ব্যর্থ হয়নি। ConditionResult=no-এর সঙ্গে ActiveState=inactive থাকলে বোঝায় যে unit-টি এড়িয়ে যাওয়া হয়েছে, আর condition-এর নাম উল্লেখ করা journal line দেখে কোন test ব্যর্থ হয়েছে তা জানা যায়।
FAQ
Requires= কি অন্য unit চালু হওয়া পর্যন্ত অপেক্ষা করে?
না। Requires= এবং After= পৃথক সেটিং। Requires= অন্য unit-টিকে একই transaction-এ যুক্ত করে, তারপর systemd উভয় job সমান্তরালে চালু করে। অপেক্ষা করাতে একই unit-এর নাম দিয়ে After= যোগ করুন। এটি যোগ করার আরেকটি কারণ আছে: Requires= dependency ব্যর্থ হলেও আপনার unit চালু হওয়া বন্ধ হবে কেবল After= সেট করা থাকলে। কারণ ordering না থাকলে অন্য unit ব্যর্থ হওয়ার আগেই আপনার unit চালু হয়ে যায়।
network.target নাকি network-online.target-এর পরে ordering করা উচিত?
boot-এর সময় network.target শুধু বোঝায় যে network management software চালু হয়েছে। এটি address বা route প্রস্তুত থাকার কোনো নিশ্চয়তা দেয় না। service চালুর সময় কার্যকর address প্রয়োজন হলে network-online.target ব্যবহার করুন এবং Wants=network-online.target ও After=network-online.target দুটিই লিখুন। কারণ target-টি default boot transaction-এ থাকে না, আর After= একা এমন একটি unit-এর জন্য অপেক্ষা করে যেটিকে কেউ queue করেনি। service শুধু একটি নির্দিষ্ট IP-তে bind করার সময় ব্যর্থ হলে, Restart=on-failure-এর সঙ্গে net.ipv4.ip_nonlocal_bind=1 ব্যবহার করা boot বিলম্বিত করার চেয়ে হালকা সমাধান।
আমার unit সফলতা জানায়, কিন্তু কখনো চালু হয় না কেন?
Condition...= test ব্যর্থ হলে unit-টি বাদ পড়ে এবং start job সফল হিসেবে রিপোর্ট হয়। তাই কোনো কিছু ব্যর্থ হিসেবে চিহ্নিত হয় না। systemctl show <unit> -p ConditionResult চালান, এবং ConditionResult=no এটি নিশ্চিত করবে। এরপর journalctl -b -u <unit>-এ Condition check resulted in <description> being skipped লাইনটি দেখুন। systemd 250 এবং পরবর্তী সংস্করণে systemctl status <unit> পূরণ না হওয়া নির্দিষ্ট directive-টির নামও দেখায়।
Condition এবং Assert-এর মধ্যে পার্থক্য কী?
উভয়ই একই test চালায়। Condition ব্যর্থ হলে unit-টি নীরবে বাদ পড়ে এবং job সফল থাকে। Assert ব্যর্থ হলে unit ব্যর্থ হয়, Assertion failed for <description>. log করা হয় এবং এটি failed (Result: assert) অবস্থায় থাকে। “এই unit এই machine-এ প্রযোজ্য নয়” বোঝাতে Condition ব্যবহার করুন। প্রায় সব বাস্তব ক্ষেত্রেই এটি যথেষ্ট। কোনো precondition অনুপস্থিত থাকলে failed unit পর্যবেক্ষণকারী ব্যক্তির কাছে তা দৃশ্যমান হওয়া জরুরি হলে শুধু Assert ব্যবহার করুন।
status=203/EXEC সহ ExecStartPre ব্যর্থ হয় কেন?
203/EXEC বোঝায় যে systemd command-টি একেবারেই execute করতে পারেনি। সাধারণ কারণ হলো absolute নয় এমন path, machine-এ অনুপস্থিত binary, execute bit ছাড়া file, অথবা এমন script যার #! line একটি অনুপস্থিত interpreter নির্দেশ করে। systemd-এর অন্যান্য ছোট code একটি নির্দিষ্ট table থেকে আসে। তাই status=2/INVALIDARGUMENT শুধু বোঝায় যে command 2 exit code দিয়ে শেষ হয়েছে; এটি arguments সম্পর্কে কিছু জানায় না। মনে রাখুন, ExecStartPre= shell-এর মাধ্যমে চালানো হয় না। তাই pipe এবং glob ব্যবহার করতে /bin/sh -c '...' প্রয়োজন।