اجرای پایدار با systemd؛ تعریف سرویس، شروع، توقف و پیدا کردن علت شکست
در این درس ۴ بخش
در پایان درس قبل، برنامه را در ترمینال اجرا و سپس متوقف کردید. اکنون میخواهیم مسئولیت آغاز و نظارت بر فرایند را به مدیر سرویس اوبونتو بدهیم. systemd در این سناریو بخشی از سیستم عامل است که واحدهای تعریفشده را با ترتیب و شرایط مشخص اجرا میکند. service یا سرویس در اینجا برنامهای است که باید مستقل از پنجرهٔ SSH کار کند؛ نه حساب کاربر است، نه خود VPS و نه نام دامنه. فایل unit توصیف میکند کدام فرمان، در کدام پوشه، با چه هویت و محیطی اجرا شود و هنگام شکست چه سیاستی اعمال شود. اگر اجرای دستی هنوز سالم نیست، ساختن این فایل قدم بعدی درستی نیست. ابتدا همان خطا را برطرف کنید؛ لایهٔ نظارت قرار نیست نام فایل اشتباه یا PORT نامعتبر را درست کند.
دو مرور هدفمند به شما کمک میکند تفاوتها را در عمل ببینید. درس systemd دورهٔ لینوکس پیشرفته، نشست کاربر و عملیات واحد را توضیح میدهد: https://www.darsavand.com/courses/linux-advanced-administration?lesson=0a675015-d758-4018-aa4e-a6f9baf96230
درس عیبیابی Node نیز خواندن خطا را از حدس و نصب دوباره جدا میکند: https://www.darsavand.com/courses/nodejs-windows-backend-start?lesson=7f46ca6f-c14d-45dd-b9b9-2ef88ae773aa
این بار یک واحد سیستمی با User=darsnode میسازیم؛ استفاده از sudo systemctl در فرمانهای مدیریت، به معنی اجرای برنامه با root نیست. #debugging-console فقط بخشی از مشاهدهٔ برنامه است؛ با systemd، خروجی استاندارد در journal جمع میشود و باید آن را همراه وضعیت واحد و پاسخ شبکه تفسیر کرد. اگر تنها پیام «فعال» را ببینیم ولی نتیجهٔ HTTP را نسنجیم، ممکن است فرایندی را سالم بدانیم که هنوز پورت را آماده نکرده است.
واحد سیستمی، فرایند و محیط اجرا را جدا بشناسید
در اجرای دستی، شل تعاملی شما PATH، پوشهٔ جاری و متغیرهای خودش را دارد. واحد سیستمی لزوماً همان محیط را به ارث نمیبرد و فایل تنظیم شل مدیر را نمیخواند. برای همین ExecStart مسیر کامل /opt/node-current/bin/node را دارد و WorkingDirectory نیز صریح نوشته شده است. گذاشتن nvm use یا npm start بدون شناخت PATH و نوع شل در یک واحد، میتواند کاری را که در ترمینال موفق است در بوت شکست دهد. در این دوره Node قبلاً در مسیر ریشهمالک نصب شده و وابستگی خارجی هم نداریم، پس نقطهٔ ورود مستقیم و روشن است. #modules-import-export همچنان ماژولهای کنار server.mjs را از روی فایلها میخواند؛ systemd آنها را به قالب دیگری تبدیل نمیکند و نیاز به نام و دسترسی درست همچنان برقرار است.
گزینهٔ Type=simple به مدیر میگوید فرمان اصلی فرایند سرویس است. این انتخاب پروتکل اعلام آمادگی اختصاصی به برنامه اضافه نمیکند؛ فعالشدن واحد لزوماً اثبات پاسخگویی API نیست. Restart=on-failure برای خروج ناموفق یا شرایط شکست تعریفشده تلاش دوباره میکند، اما stop عمدی مدیر را خنثی نمیکند. همچنین برنامهای که با خروج موفق و مطابق سیاست سیگنالها پایان مییابد لزوماً دوباره شروع نمیشود. RestartSec فاصلهٔ تلاش و StartLimitIntervalSec و StartLimitBurst سقف شروعهای مکرر را تعیین میکنند. اینها برای مهار حلقهٔ شکستاند، نه برای پنهانکردن آن. اگر هر سه ثانیه خطای یکسان تکرار شد، راهحل بالابردن شمار تلاشها نیست؛ باید نخستین علت و پیکربندی واقعی را پیدا کرد.
محدودیتهای واحد، بخشی از کمکردن اختیار برنامهاند. NoNewPrivileges مانع بهدستآوردن امتیاز تازه از سازوکارهای مربوط در فرایند و فرزندانش میشود؛ جای بررسی کد و مجوزها را نمیگیرد. ProtectSystem=strict دید فایلسیستم را برای این سرویس عمدتاً فقطخواندنی میکند و ProtectHome خانههای کاربران را پنهان میکند. به همین دلیل کد را در خانهٔ مدیر اجرا نکردیم. PrivateTmp فضای موقت خصوصی سرویس میسازد؛ با ترکیب آن، نوشتن در فضای موقت خود سرویس ممکن است، اما مسیر دادهٔ دائمی ایجاد نشده است. UMask روی مجوز پیشفرض فایلهای تازه اثر دارد. این پروژه نیازی به نوشتن فایل ندارد؛ اگر بعداً پایگاه داده یا آپلود اضافه کردید، بهجای خاموشکردن همهٔ حفاظتها باید مسیر نوشتن محدود و مالکیت آن را مشخص کنید.
فایل کامل unit را بسازید و پیش از شروع بررسی کنید
همهٔ مراحل بعد در Bash اوبونتو و روی همان آزمایشگاه تازه انجام میشوند. مطمئن شوید اجرای دستی درس پنج متوقف است و فایل /etc/systemd/system/darsavand-node.service از قبل وجود ندارد. اگر وجود دارد، آن را کورکورانه جایگزین نکنید؛ ابتدا با sudo systemctl cat darsavand-node.service تعریف و overrideهای احتمالی را ببینید و از مسیر بهروزرسانی استفاده کنید. برای نخستین نصب، sudoedit /etc/systemd/system/darsavand-node.service را اجرا کنید، متن کامل زیر را ذخیره کنید و از ویرایشگر خارج شوید. sudoedit اجازه میدهد ویرایش را با روش معمول ویرایشگر انجام دهید؛ در nano معمولاً Ctrl+O و Enter برای ذخیره و Ctrl+X برای خروج است. فایل باید متن ساده باشد؛ شمارهٔ خط یا نشانهٔ بلوک کد را داخل آن نگذارید.
[Unit]
Description=Darsavand Node.js task training API
Wants=network-online.target
After=network-online.target
StartLimitIntervalSec=60
StartLimitBurst=5
[Service]
Type=simple
User=darsnode
Group=darsnode
WorkingDirectory=/opt/darsavand-node/current
EnvironmentFile=/etc/darsavand-node/app.env
ExecStart=/opt/node-current/bin/node /opt/darsavand-node/current/server.mjs
Restart=on-failure
RestartSec=3
TimeoutStopSec=5
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
UMask=0077
StandardOutput=journal
StandardError=journal
SyslogIdentifier=darsavand-node
[Install]
WantedBy=multi-user.targetبخش Unit توضیح، ترتیب و محدودیت شروع را نگه میدارد. Wants و After با network-online.target یک وابستگی و ترتیب در شروع ایجاد میکنند، اما تضمین دسترسی اینترنت یا آمادهبودن پایگاه دادهٔ فرضی نیستند. این برنامه فقط روی loopback میشنود و به سرویس بیرونی وابسته نیست؛ اگر وابستگی واقعی اضافه شد، منطق اتصال مجدد و بررسی سلامت خودش لازم است. بخش Service هویت، محیط، فرمان و سیاست اجرا را دارد و Install مشخص میکند هنگام enable چه پیوند وابستگی برای شروع عادی ساخته شود. EnvironmentFile بدون علامت منفی نوشته شده، پس نبود فایل باید خطا باشد و با مقدار پیشفرض پنهان نشود. مدیر سیستمی این فایل را پیش از اجرای darsnode میخواند. این توضیح دربارهٔ همین دو تنظیم غیرمحرمانه است و مجوز ذخیرهٔ رمز در خروجی journal یا فایل unit نیست.
اکنون مالکیت فایل را ریشهمالک و مجوزش را ۶۴۴ تثبیت کنید و systemd-analyze verify را اجرا کنید. این ابزار بخشی از خطاهای نحوی، نام گزینه و ارجاعها را پیش از شروع نشان میدهد. سکوت و خروج صفر یعنی بررسی ایستا از دید همین ابزار موفق بوده است؛ هنوز برنامه اجرا نشده و آزمون HTTP هم انجام نشده است. هشدار یا خطای ExecStart را با بررسی مسیر Node و مجوز اجرا دنبال کنید، نه با تغییر تصادفی نام سرویس. اگر محیط شما WSL بدون systemd یا کانتینری با PID1 متفاوت است، ممکن است کنترل سرویس عادی کار نکند؛ این فصل بر Ubuntu Server دارای systemd متمرکز است. دیدن پیام System has not been booted with systemd نشانهٔ خطای فایل جاوااسکریپت نیست و باید نوع محیط را بررسی کنید.
sudo chown root:root /etc/systemd/system/darsavand-node.service
sudo chmod 0644 /etc/systemd/system/darsavand-node.service
sudo systemd-analyze verify /etc/systemd/system/darsavand-node.service
test -x /opt/node-current/bin/node
readlink -f /opt/darsavand-node/current
sudo stat -c '%U:%G %a %n' /etc/darsavand-node/app.envچهار عملیات متفاوت: خواندن تعریف، شروع، فعالسازی و شروع دوباره
daemon-reload از مدیر سرویس میخواهد تعریف واحدها و وابستگیها را دوباره بخواند. فایل کد برنامه را دوباره بارگذاری نمیکند و فرایند فعلی را خودکار متوقف یا شروع نمیکند. start تلاش میکند واحدی را که اجرا نیست آغاز کند. enable برای شروع خودکار در هدف تعریفشده پیوند میسازد و بدون --now اجرای همین لحظه نیست. restart فرایند جاری را متوقف و نمونهٔ تازه را آغاز میکند؛ برای این برنامه دادهٔ حافظهای هم همراه آن از بین میرود. این واژهها را با reload عادی یک سرویس نیز مخلوط نکنید؛ واحد ما ExecReload ندارد و روش بارگذاری بیوقفهٔ کد تعریف نکردهایم. همین تفکیک به شما کمک میکند بعد از تغییر app.env بدانید صرف daemon-reload مقدار را وارد فرایند از قبل اجراشده نمیکند؛ باید اثر تغییر و زمان restart را برنامهریزی کنید.
نخست تعریف را بخوانید و سرویس را آغاز کنید، سپس وضعیت و سلامت را جدا بسنجید. خروجی is-active باید active باشد و status باید فرمان و کاربر مورد انتظار را نشان دهد؛ PID عدد متغیری است و نباید دقیقاً با یک تصویر برابر باشد. curl باید پاسخ ۲۰۰ و JSON سلامت را برگرداند. ss شنوندهٔ ۱۲۷.۰.۰.۱:۳۰۰۰ را نشان میدهد؛ اگر صفرِ همهٔ رابطها یا نشانی عمومی دیدید، با قرارداد این دوره هماهنگ نیست و پیش از ادامه باید علت تغییر کد را پیدا کنید. موفقیت این مرحله هنوز انتشار عمومی نیست؛ Nginx و HTTPS در فصل بعد ساخته میشوند. اگر start شکست خورد، همانجا بمانید و status و journal را بخوانید؛ بازکردن پورت فایروال، خطای آغاز فرایند را اصلاح نمیکند.
sudo systemctl daemon-reload
sudo systemctl start darsavand-node.service
sudo systemctl status darsavand-node.service --no-pager --full
systemctl is-active darsavand-node.service
curl --fail-with-body --silent --show-error -i http://127.0.0.1:3000/health
sudo ss -ltnp 'sport = :3000'پس از سلامت موفق، enable را اجرا و is-enabled را بررسی کنید. enabled شاهد تنظیم شروع خودکار است، نه اثبات اینکه همین حالا reboot انجام شده و سرویس پس از آن بالا آمده است. برای آزمون این ادعا، در پایان دوره و فقط در ماشین آزمایشی با راه دسترسی کنسول و پنجرهٔ مناسب، راهاندازی دوبارهٔ سیستم را جدا برنامهریزی میکنیم. فعلاً میتوانید نشست SSH را ببندید و دوباره وصل شوید و سلامت را بررسی کنید؛ چون systemd مالک فرایند است، بستن شل مدیر نباید آن را مانند اجرای پیشزمینه از بین ببرد. این آزمون را با قطع عمدی برق یا کشتن همهٔ فرایندهای node جایگزین نکنید. اگر برنامهای روی پورت دیگری در سرور دارید، نام دقیق واحد مانع دستکاری ناخواستهٔ آن میشود.
sudo systemctl enable darsavand-node.service
systemctl is-enabled darsavand-node.service
sudo journalctl -u darsavand-node.service -n 40 --no-pager -o short-isoشکست را از اولین علت بخوانید، نه از آخرین پیام خلاصه
journal گزارش زماندار خروجی و رویدادهای واحد است. -u دامنه را به همین سرویس محدود میکند، -n شمار خطهای آخر را میدهد و --since برای بازهٔ زمانی مفید است. پیامهای قدیمی را با تلاش تازه مخلوط نکنید؛ زمان فرمان و نخستین خطای مرتبط را یادداشت کنید. status ممکن است در انتها فقط failed یا start request repeated too quickly نشان دهد، در حالی که چند خط قبل Cannot start: EADDRINUSE یا PORT must be an integer آمده است. #try-catch در کد درخواستها خطای قابل انتظار را به پاسخ کنترلشده تبدیل میکند، اما همهٔ شکستهای شروع داخل handler درخواست رخ نمیدهند. #throw-error و کد خروج هم باید با لایهٔ آغاز فرایند خوانده شوند. بازنشر کامل لاگ عمومی ممکن است مسیر، نام کاربر یا دادهٔ ناخواسته را افشا کند؛ فقط بخش لازم و پاکسازیشده را برای پشتیبانی آماده کنید.
چند کد اجرایی systemd سرنخهای مشخصاند. ۲۰۳/EXEC معمولاً یعنی مدیر نتوانسته فرمان را اجرا کند؛ وجود فایل اجرایی، مجوز، معماری و مسیر کامل را بررسی کنید. ۲۰۰/CHDIR به آمادهکردن پوشهٔ کاری مربوط است؛ مقصد current و عبور از پوشههای والد را با namei بسنجید. ۲۱۷/USER به آمادهکردن هویت کاربر مربوط است؛ getent passwd darsnode و تعریف گروه را بررسی کنید. این کدها جای خواندن پیام همراه را نمیگیرند و علت یگانهٔ جهانی نیستند. اگر Node شروع شده و سپس خطای ماژول داده، دیگر مسئله صرفاً اجرای باینری نیست. اگر active و loopback سالماند ولی دامنه خطا دارد، شاخهٔ عیبیابی به سمت Nginx، DNS یا TLS میرود. این درخت تصمیم باعث میشود برای خطای یک لایه، حفاظت لایهٔ دیگر را خاموش نکنید.
sudo systemctl cat darsavand-node.service
sudo systemctl show darsavand-node.service -p User -p Group -p WorkingDirectory -p ExecStart -p MainPID -p Result -p ExecMainStatus
sudo journalctl -u darsavand-node.service --since '10 minutes ago' --no-pager -o short-iso
getent passwd darsnode
getent group darsnode
namei -l /opt/node-current/bin/node
namei -l /opt/darsavand-node/current/server.mjsاگر سقف شروعهای مکرر فعال شده، نخست علت اصلی را اصلاح کنید؛ برای مثال فایل ناقص را از نسخهٔ تأییدشده بازسازی یا PORT نادرست را با sudoedit اصلاح کنید. اگر خود unit تغییر کرده daemon-reload لازم است؛ اگر فقط env یا کد تغییر کرده، نمونهٔ بعدی اجرا آن را میخواند و باید طبق روند کنترلشده restart شود. reset-failed شمارنده و وضعیت شکست را پاک میکند، اما علت را درمان نمیکند. بعد از اصلاح میتوان reset-failed و start را اجرا و دوباره سه شاهد وضعیت، شنونده و HTTP را گرفت. از تغییر چند گزینه بهطور همزمان پرهیز کنید؛ در غیر این صورت نمیدانید کدام تغییر مؤثر بوده است. اگر سرویس را عمداً با stop متوقف کنید، on-failure نباید تصمیم مدیر را با اجرای دوباره خنثی کند؛ تفاوت توقف اداری و crash بخشی از همین قرارداد است.
تمرین اول: یک نفر فایل unit را ذخیره کرده، daemon-reload زده و انتظار دارد کد server.mjs فعلی عوض شده باشد. پاسخ: daemon-reload فقط تعریف مدیر را تازه میکند؛ برای خواندن کد جدید نمونهٔ فرایند تازه لازم است و restart برنامهٔ حافظهای دادهها را پاک میکند. تمرین دوم: enable موفق است ولی is-active میگوید failed؛ آیا سرویس پس از بوت حتماً سالم خواهد بود؟ خیر؛ enable پیوند شروع را تنظیم میکند و علت شکست باید اکنون حل شود. تمرین سوم: پس از restart سلامت سبز است اما فهرست خالی شده؛ برای این مدل انتظار درست است و افزودن پایگاه داده موضوع طراحی جداگانه است. معیار تحویل، فایل unit ثبتشده، verify موفق، active، enabled، شنوندهٔ loopback و پاسخ سلامت است. با این شواهد، فصل دامنه و Nginx را شروع کنید.
منابع رسمی انگلیسی:
Ubuntu 24.04 — systemd.service: https://manpages.ubuntu.com/manpages/noble/man5/systemd.service.5.html
systemd 255 — محیط اجرا و کدهای خروج: https://www.freedesktop.org/software/systemd/man/255/systemd.exec.html
systemd 255 — systemctl: https://www.freedesktop.org/software/systemd/man/255/systemctl.html
systemd 255 — journalctl: https://www.freedesktop.org/software/systemd/man/255/journalctl.html
systemd 255 — systemd-analyze: https://www.freedesktop.org/software/systemd/man/255/systemd-analyze.html
Node.js 24 — Process signals: https://nodejs.org/docs/latest-v24.x/api/process.html
مرور لاگخوانی درسآوند: https://www.darsavand.com/courses/linux-advanced-administration?lesson=32265068-21de-4ef1-86c7-6ae5d7f00827
تألیف و مثالهای اختصاصی: تحریریه درسآوند. منابع رسمی انگلیسی برای مطالعهٔ تکمیلی معرفی شدهاند.
متن درسهای عمومی را بدون ورود بخوانید.