SSH სესიიდან გაშვებული აპლიკაცია სესიასთან ერთად კვდება. nohup ... &-ით გაშვებული სესიას გადაურჩება და reboot-ზე კვდება. screen-ის ან tmux-ის შიგნით გაშვებული ორივეს გადაურჩება იქამდე, სანამ ვინმე არასწორ ფანჯარას არ დახურავს, და ამასთან ლოგებს ისე დატოვებს, რომ ვერავინ იპოვის. systemd-ის unit თორმეტი ხაზის ტექსტია, რომელიც სამივე პრობლემას ერთდროულად წყვეტს: პროცესი ჩაირთვება boot-ზე, crash-ის შემდეგ თავად გადაირთვება, გაეშვება სწორი მომხმარებლით და სწორი გარემოთი, ხოლო ყველაფერი, რასაც დაბეჭდავს, მოხვდება ლოგში, რომელშიც დროითა და სიმძიმით შეგიძლია ძებნა.
საკუთარი სერვერის გაძღოლისას ეს ის ნაწილია, რომელიც ყველაზე სწრაფად ანაზღაურდება, და ის მართლა პატარაა. ქვემოთ არის მუშა unit ფაილი, შემდეგ მასში ყველა directive, რომლის ცოდნაც ღირს, მერე ორი რამ, რაც სინამდვილეში ფუჭდება - start limit და environment ფაილი - და ბოლოს ლოგები, hardening, ერთი სერვისის რამდენიმე ეგზემპლარი და timer-ები, როგორც cron-ის უკეთესი ალტერნატივა.
რას გაძლევს systemd, რასაც tmux და nohup ვერ გაძლევს#
- ის ჩაირთვება boot-ზე, სანამ ვინმე შეხვიდოდეს, ისეთი თანმიმდევრობით, როგორიც ქსელთან და იმ ფაილურ სისტემებთან მიმართებაშია საჭირო, რომლებიც სჭირდება.
- ის გადაირთვება მარცხის შემდეგ, შენ მიერ დაყენებული პაუზით და ლიმიტით, რომ სამუდამოდ გატეხილმა სერვისმა უაზროდ არ იტრიალოს.
- მას მთელი პროცესების ხე ეკუთვნის. ყველაფერი, რასაც სერვისი წარმოშობს, ერთ control group-შია, ამიტომ სერვისის გაჩერება შვილობილ პროცესებსაც აჩერებს და პორტზე ჩარჩენილ ობოლ პროცესებს არ ტოვებს.
- ის მუშაობს არჩეული მომხმარებლით, არჩეული სამუშაო დირექტორიით, umask-ითა და გარემოთი, და არცერთი მათგანი არ არის დამოკიდებული იმაზე, ვისი shell-იდან გაეშვა.
- ის აერთიანებს გამოტანილ ტექსტს. ყველაფერი, რაც standard output-ზე ან standard error-ზე იწერება, journal-ში ხვდება, unit-ის ტეგით, დროით საძიებლად და logrotate ფაილის დაწერის გარეშე ბრუნავს.
- მას შეუძლია უფლებების შემცირება და kernel-ზე წვდომის შეზღუდვა ერთხაზიანი ათეული პარამეტრით, რაც ყველგან ყველაზე იაფი hardening-ია.
რასაც ის არ აკეთებს, არის იმის ცოდნა, მუშაობს თუ არა შენი აპლიკაცია. გაჭედილი პროცესი, რომელიც აღარ პასუხობს მოთხოვნებს, systemd-ს სრულიად აკმაყოფილებს. სიცოცხლისუნარიანობა არის health check და რაღაც, რაც მასზე რეაგირებს, და ეს უკვე სხვა საქმეა - graceful shutdown და health check-ები აპლიკაციის მხარეს ჯერ განიხილავს.
შენი პირველი unit ფაილი#
[Unit]Description=Notes web applicationDocumentation=https://example.com/docsAfter=network-online.target postgresql.serviceWants=network-online.target[Service]Type=simpleUser=notesGroup=notesWorkingDirectory=/srv/notesEnvironmentFile=-/etc/notes/notes.envEnvironment=NODE_ENV=productionExecStart=/usr/bin/node /srv/notes/server.jsRestart=on-failureRestartSec=5TimeoutStopSec=30[Install]WantedBy=multi-user.target$ sudo systemctl daemon-reload$ sudo systemctl enable --now notes$ systemctl status notes$ journalctl -u notes -fdaemon-reload ყოველი რედაქტირების შემდეგ, ყოველთვის - systemd unit ფაილებს ქეშირებს და სხვაგვარად ძველს გაუშვებს, სანამ შენ ახალს კითხულობ. enable ქმნის symlink-ს, რომელიც boot-ზე ჩართავს; --now კი მას დაუყოვნებლივ გაუშვებს. ეს ორი განსხვავებული ცნებაა და systemctl start enable-ის გარეშე სწორედ იმის კლასიკური მიზეზია, რომ სერვისი, რომელიც თითქოს "მუშაობს", reboot-ის შემდეგ აღარ არის.
ამ ფაილში სამი დეტალია, რომელშიც ადვილი შესაშლელია. ExecStart აბსოლუტური მისამართი უნდა იყოს - node ვერ მოიძებნება, /usr/bin/node კი მოიძებნება. ის shell-ში არ გადის, ამიტომ pipe-ები, glob-ები, && და ცვლადების გაშლა არ მუშაობს; თუ გჭირდება, ბრძანება შეგნებულად შეფუთე /bin/sh -c '...'-ში. ხოლო /etc/systemd/system/-ში მოთავსებული unit-ები გადაფარავს პაკეტების მიერ /usr/lib/systemd/system/-ში მოწოდებულ ერთსახელიან unit-ებს, სწორედ ამიტომ ეკუთვნის შენი სერვისები პირველ დირექტორიას, პაკეტების unit-ები კი drop-in-ით უნდა შეასწორო და არა პირდაპირ დაარედაქტირო.
ეს drop-in მექანიზმი პირველივე დღეს ღირს შესწავლად:
$ sudo systemctl edit notes # writes /etc/systemd/system/notes.service.d/override.conf$ systemctl cat notes # shows the unit plus every drop-in, mergedDirective-ები, რომლებიც მნიშვნელოვანია#
| Directive | Section | რას აკეთებს |
|---|---|---|
After= / Before= | [Unit] | მხოლოდ თანმიმდევრობა. არაფერს ჩართავს |
Wants= / Requires= | [Unit] | მეორე unit-ს ჩართავს. Requires მასთან ერთად ვარდება |
Type= | [Service] | როგორ წყვეტს systemd, რომ სერვისი გაეშვა |
ExecStart= | [Service] | აბსოლუტური მისამართი და არგუმენტები, shell-ის გარეშე |
ExecStartPre= | [Service] | გაეშვება პირველი; მარცხი გაშვებას აჩერებს |
ExecStop= | [Service] | არასავალდებულო. მის გარეშე სიგნალი იგზავნება |
User= / Group= | [Service] | უფლებების შემცირება. ქსელურ სერვისს root-ად ნუ დატოვებ |
WorkingDirectory= | [Service] | პროცესის საკუთარი დირექტორია |
EnvironmentFile= | [Service] | KEY=value ფაილი. - პრეფიქსით არასავალდებულო ხდება |
Restart= | [Service] | იხილე შემდეგი თავი |
RestartSec= | [Service] | პაუზა გადართვამდე. ნაგულისხმევია 100ms |
TimeoutStopSec= | [Service] | რამდენ ხანს შეიძლება გაგრძელდეს სუფთა გამორთვა SIGKILL-მდე |
KillSignal= | [Service] | ნაგულისხმევია SIGTERM. ზოგ პროგრამას SIGINT უნდა |
MemoryMax= / CPUQuota= | [Service] | control group-ის ლიმიტები, მაგალითად 1G და 150% |
WantedBy= | [Install] | რომელი target ჩართავს. თითქმის ყოველთვის multi-user.target |
თანმიმდევრობის წყვილს ყველაზე ხშირად არასწორად იგებენ. After=postgresql.service ამბობს: "თუ ორივე ირთვება, მე მეორე ჩამრთე". ის PostgreSQL-ს არ რთავს. Wants=postgresql.service რთავს და მარცხის შემთხვევაშიც აგრძელებს; Requires= რთავს და მარცხისას თავადაც არ ირთვება. უმეტესი დამოკიდებულებისთვის გამოიყენე Wants პლუს After, ხოლო Requires შეინახე იმ შემთხვევებისთვის, როცა მეორე unit-ის გარეშე მუშაობა მართლა უაზროა.
After=network-online.target ცალკე შენიშვნას იმსახურებს, რადგან network.target არ არის ის, რაც უმეტესობას სჭირდება. network.target ნიშნავს, რომ ქსელის სტეკი მოქმედებაში მოდის; network-online.target ნიშნავს, რომ მისამართი მართლა მითითებულია, და ის მუშაობს მხოლოდ მაშინ, თუ მას Wants=-შიც ჩამოთვლი და დისტრიბუტივის wait-online სერვისი ჩართულია. თუ შენი სერვისი გაშვებისას კონკრეტულ საჯარო მისამართს იკავებს, მეორე გჭირდება. თუ 0.0.0.0-ს იკავებს, პირველიც საკმარისია.
KillSignal=SIGINT ბუნდოვნად გამოიყურება, სანამ გეიმ სერვერს არ დაჰმასპინძლებ. რამდენიმე გეიმ სერვერი სამყაროს SIGINT-ზე ინახავს და SIGTERM-ზე უბრალოდ კვდება, ამიტომ ნაგულისხმევი kill signal-ის მქონე unit ყოველ გადატვირთვაზე კარგავს ყველაფერს ბოლო autosave-ის შემდეგ. სანამ restart ღილაკს ენდობი, შეამოწმე, რას ელოდება შენი პროგრამა.
Type, Restart და start limit#
Type= systemd-ს ეუბნება, როდის ჩაითვალოს სერვისი გაშვებულად, რაც განსაზღვრავს, როდის შეუძლიათ დამოკიდებულ unit-ებს დაწყება.
| Type | გამოიყენე, როცა |
|---|---|
simple | პროცესი foreground-ში მუშაობს. ნაგულისხმევია და უმეტესი აპისთვის სწორია |
exec | როგორც simple, მაგრამ გაშვება ვარდება, თუ ბინარული ფაილი ვერ შესრულდა |
forking | პროგრამა თავად ხდება daemon. სჭირდება PIDFile= |
oneshot | სკრიპტი, რომელიც გაეშვება და მთავრდება. დააწყვილე RemainAfterExit=yes-თან |
notify | პროგრამა sd_notify-ს იძახებს მაშინ, როცა მართლა მზადაა |
თუ აპლიკაცია შენ დაწერე, გაუშვი foreground-ში და გამოიყენე simple ან exec. daemon-ის ფლაგს ნუ დაამატებ იმისთვის, რომ forking გამოიყენო; ეს მეტი მოძრავი ნაწილია სარგებლის გარეშე, და თანამედროვე process manager-ები ყველა foreground-ს ელოდება.
Restart=-ს იმაზე მეტი მნიშვნელობა აქვს, ვიდრე ხალხი იყენებს, და ორი მათგანი თითქმის ყველაფერს ფარავს:
| მნიშვნელობა | ქცევა |
|---|---|
no | არასოდეს გადაირთვება. ნაგულისხმევია |
on-failure | გადაირთვება არანულოვანი exit-ის, სიგნალის ან timeout-ის შემდეგ. სუფთა exit-ზე არა |
always | გადაირთვება ნებისმიერ შემთხვევაში, სუფთა exit-ის ჩათვლით |
on-abnormal | მხოლოდ სიგნალებზე, timeout-ებზე და watchdog-ის მარცხზე |
on-failure იმისთვის, რაც კანონიერად შეიძლება დასრულდეს; always სერვერისთვის, რომელიც საერთოდ არასოდეს უნდა გამოვიდეს. მასთან ერთად დააყენე RestartSec=5, რადგან 100 მილიწამის ნაგულისხმევი მნიშვნელობა crash-ციკლს ცარიელ ციკლად აქცევს.
ახლა ხაფანგი. systemd უარს ამბობს სერვისის გადართვაზე StartLimitBurst-ზე მეტჯერ StartLimitIntervalSec დროის განმავლობაში - ნაგულისხმევად ათ წამში ხუთჯერ - და ამ ლიმიტს რომ მიაღწევს, საერთოდ წყვეტს ცდას და აცხადებს:
notes.service: Start request repeated too quickly.notes.service: Failed with result 'start-limit-hit'.აქ systemd სწორად იქცევა: სერვისი, რომელიც ზედიზედ ხუთჯერ მაშინვე კვდება, გატეხილია და არა უიღბლო. რაც ხალხს უკვირს, ისაა, რომ სერვისი ამის შემდეგ მკვდარი რჩება, სანამ ადამიანი არ ჩაერევა, იმ შემთხვევაშიც კი, თუ ძირითადი პრობლემა უკვე გასწორდა. გამოსავალია sudo systemctl reset-failed notes და შემდეგ გაშვება, ხოლო პარამეტრები [Unit]-შია:
[Unit]StartLimitIntervalSec=60StartLimitBurst=5burst-ის გაზრდის ნაცვლად ფანჯრის გაფართოება ჩვეულებრივ სწორია. თუ შენს სერვისს მარცხისთვის ათი წამი სჭირდება, ხუთი მარცხი უკვე ორმოცდაათ წამს მოიცავს და ნაგულისხმევი ინტერვალი არასოდეს ჩაირთვება - სწორედ ამიტომ ჩნდება ეს შეტყობინება ძირითადად იმ სერვისებთან, რომლებიც არარსებული ფაილის გამო წამზე ნაკლებ დროში ვარდებიან. იგივე შაბლონი მართულ გარემოში და ის, რასაც ჰოსტი მასზე აკეთებს, აღწერილია პოსტში რატომ გადაირთვება შენი გეიმ სერვერი განუწყვეტლივ.
Environment ცვლადები და საიდუმლოებები#
unit-ში კონფიგურაციის შეყვანის სამი გზა არსებობს და ისინი ერთმანეთს ედება.
Environment=NODE_ENV=productionEnvironment="GREETING=hello there"EnvironmentFile=-/etc/notes/notes.envEnvironment= განკუთვნილია არასაიდუმლო მნიშვნელობებისთვის, რომლებიც არ გიშლის ხელს ყველასთვის წასაკითხ unit ფაილში. EnvironmentFile= მიუთითებს KEY=value ხაზების ფაილზე, და სწორედ იქ ეკუთვნის საიდუმლოებებს, რადგან ფაილის მფლობელი შეიძლება root იყოს და უფლებები 0600, სანამ unit წაკითხვადი რჩება.
$ sudo install -d -m 0750 -o root -g notes /etc/notes$ sudo install -m 0640 -o root -g notes /dev/null /etc/notes/notes.env$ sudo nano /etc/notes/notes.envDATABASE_URL=postgres://notes:secret@127.0.0.1:5432/notesSESSION_SECRET=a-long-random-stringPORT=3000ამ ფაილის ოთხი წესი, რომელსაც ყველა ერთხელ მაინც არღვევს:
- `export`-ის გარეშე. ეს shell სკრიპტი არ არის.
export FOO=barადგენს ცვლადს, რომლის სახელია სიტყვასიტყვითexport FOO. - shell-ის გაშლის გარეშე.
PATH=$PATH:/opt/binინახავს ოთხ სიმბოლოს$PATHდა არა შენს path-ს. - ბრჭყალები იჭრება, ამიტომ
FOO="bar"იძლევაbar-ს. ეს ნიშნავს, რომ მნიშვნელობას, რომელსაც ბრჭყალები მართლა სჭირდება, სიფრთხილე უნდა. - წინა `-` ხაზში
EnvironmentFile=-/etc/notes/notes.envარარსებულ ფაილს არაფატალურს ხდის. მის გარეშე path-ში შეცდომა სერვისს საერთოდ არ გაუშვებს - რაც ზოგჯერ ზუსტად ის არის, რაც გინდა ფაილისთვის, რომელშიც ბაზის პაროლი დევს.
გადაამოწმე, რა მიიღო სერვისმა სინამდვილეში და ნუ დაუშვებ:
$ systemctl show notes -p Environment$ sudo cat /proc/$(systemctl show notes -p MainPID --value)/environ | tr '\0' '\n'ეს მეორე ბრძანება იმის მიზეზიცაა, რომ იფიქრო, ვის შეუძლია გაზიარებულ მანქანაზე პროცესის გარემოს წაკითხვა. Environment ცვლადები და საიდუმლოებები უფრო ფართო საკითხს განიხილავს, იმის ჩათვლით, რა არ უნდა იყოს საერთოდ environment ცვლადი.
journalctl: სად წავიდა გამოტანილი ტექსტი#
ყველაფერი, რასაც სერვისი standard output-ზე ან standard error-ზე წერს, journal-ში ხვდება unit-ის სახელით მონიშნული. არც გადამისამართება, არც ლოგ ფაილი, არც logrotate-ის კონფიგურაცია.
$ journalctl -u notes # everything, oldest first$ journalctl -u notes -f # follow, like tail -f$ journalctl -u notes -n 200 --no-pager # last 200 lines, printable$ journalctl -u notes --since "1 hour ago"$ journalctl -u notes --since "2026-09-20 18:00" --until "2026-09-20 19:00"$ journalctl -u notes -p err # errors and worse only$ journalctl -u notes -b # this boot only$ journalctl -u notes -o cat # message text, no timestamps-p ფილტრი syslog-ის სიმძიმეებს იღებს, ამიტომ -p warning აჩვენებს გაფრთხილებებს და მასზე მძიმეს. -b -1 აჩვენებს წინა boot-ს, და სწორედ ასე გაიგებ, რა მოხდა დაუგეგმავ reboot-მდე მყისვე.
ერთი რამ, რაც ახალ სერვერზე უნდა შეამოწმო: გადაურჩება თუ არა journal reboot-ს საერთოდ. ზოგ დისტრიბუტივში ის /run-შია და მანქანის გადატვირთვისას ქრება, რაც post-mortem დებაგს შეუძლებელს ხდის.
$ journalctl --disk-usage$ sudo mkdir -p /var/log/journal$ sudo systemd-tmpfiles --create --prefix /var/log/journal$ sudo systemctl restart systemd-journaldშემდეგ შეზღუდე, რადგან უსაზღვრო journal ხმაურიან სერვისზე დისკის შევსების ნელი გზაა:
Storage=persistentSystemMaxUse=500MMaxRetentionSec=1monthsudo journalctl --vacuum-time=14d არსებულ journal-ს დაუყოვნებლივ ასუფთავებს. და თუ ლოგში ხედავ Suppressed N messages from notes.service-ს, ეს journald-ის rate limiter-ია - ნაგულისხმევად რამდენიმე ათასი შეტყობინება მოკლე ფანჯარაში - რაც ნიშნავს, რომ შენი სერვისი ზედმეტად ხმაურიანია და არა იმას, რომ journald გატეხილია. ლოგები, რომლებიც ღირს შენახვად იმაზეა, როგორ გადაწყვიტო, რა უნდა იყოს იქ საერთოდ.
სერვისის ჩაკეტვა#
ესენი თითოეული ერთი ხაზია და უმეტესობა არაფერი ღირს. დაამატე ცოტ-ცოტა და გამოსცადე, რადგან რამდენიმე მათგანი რეალურ პროგრამებს მართლა ამტვრევს.
NoNewPrivileges=yesPrivateTmp=yesProtectSystem=strictProtectHome=yesReadWritePaths=/srv/notes/uploadsProtectKernelTunables=yesProtectKernelModules=yesProtectControlGroups=yesRestrictAddressFamilies=AF_INET AF_INET6 AF_UNIXLockPersonality=yesProtectSystem=strict მთელ ფაილურ სისტემას ამ სერვისისთვის მხოლოდ წასაკითხად მიამონტაჟებს, /dev-ის, /proc-ისა და /sys-ის გარდა, ამიტომ ყველა დირექტორია, რომელშიც ის კანონიერად წერს, ReadWritePaths=-ში უნდა ჩამოთვალო. ეს აქ ყველაზე ღირებული ხაზია და ის, რომელიც პირველად ყველაზე ხშირად იძლევა გაშვების შეცდომას, და ეს სწორედ მისი აზრია: ის გაიძულებს, იცოდე, რას წერს შენი აპლიკაცია.
კიდევ ორი, რაც ღირს ცოდნად. DynamicUser=yes სერვისის გარშემო ქმნის და შლის სისტემურ მომხმარებელს, რომ თავად არასოდეს მართო, და ის წყვილდება StateDirectory=notes-თან, რომელიც /var/lib/notes-ს სწორი მფლობელობით ავტომატურად ქმნის. ხოლო AmbientCapabilities=CAP_NET_BIND_SERVICE საშუალებას აძლევს უუფლებო სერვისს, დაიკავოს 80 ან 443 პორტი root-ად გაშვების გარეშე - თუმცა nginx reverse proxy-ის უკან ის საერთოდ არ გჭირდება.
ერთი, რომელთანაც სიფრთხილეა საჭირო, არის MemoryDenyWriteExecute=yes, რომელიც ამტვრევს ნებისმიერ runtime-ს just-in-time კომპილატორით. Node, თანამედროვე Java და რამდენიმე Python გაფართოება გაშვებაზე უარს იტყვის. რასაც აირჩევ, შედეგი გაზომე:
$ systemd-analyze security notes.serviceეს ბეჭდავს თითო ხაზს თითო პარამეტრზე და საერთო exposure ქულას. მიუდექი მას როგორც checklist-ს და არა როგორც მიზანს - "unsafe"-დან "medium"-მდე გადასვლა შესაძლო სარგებლის უმეტესობაა, ბოლო რამდენიმე ქულა კი ჩვეულებრივ უფრო მეტი ჯდება, ვიდრე ბრუნდება.
Template unit-ები და timer-ები#
template unit ერთი კონფიგურაციის ბევრ ასლს უშვებს. ფაილის სახელი @-ით მთავრდება, ხოლო %i იცვლება იმით, რაც @-ის შემდეგ მიუწერე გაშვებისას.
[Unit]Description=Valheim server (%i)After=network-online.targetWants=network-online.target[Service]Type=simpleUser=gamesWorkingDirectory=/srv/valheim/%iEnvironmentFile=/etc/valheim/%i.envExecStart=/srv/valheim/%i/valheim_server.x86_64 -nographics -batchmodeKillSignal=SIGINTTimeoutStopSec=90Restart=on-failureRestartSec=10[Install]WantedBy=multi-user.target$ sudo systemctl enable --now valheim@midgard$ sudo systemctl enable --now valheim@testworld$ journalctl -u valheim@midgard -fორი სერვერი, ერთი unit ფაილი, ცალკე environment ფაილები, ცალკე დირექტორიები, ცალკე ლოგები. KillSignal=SIGINT და გრძელი TimeoutStopSec იმიტომაა, რომ ეს გეიმ სერვერია, რომელიც გამორთვისას ინახავს და უნდა მისცე დასრულების საშუალება. რამდენიმე გეიმ სერვერი ერთ VDS-ზე ამ შაბლონის სრული ვერსიაა, პორტებისა და მომხმარებლების ჩათვლით.
timer-ები cron-ს ცვლიან, და გადასვლის არგუმენტი ესთეტიკური არ არის. timer ორი ფაილია - oneshot სერვისი და timer, რომელიც მას იძახებს - და სერვისი იღებს ყველაფერს ზემოთ ნახსენებს: მომხმარებელს, environment ფაილს, რესურსების ლიმიტებს, hardening-ს და გამოტანილ ტექსტს journal-ში იმ ელფოსტის ნაცვლად, რომელსაც არავინ კითხულობს.
[Unit]Description=Nightly database dump[Service]Type=oneshotUser=postgresExecStart=/usr/local/bin/db-backup.sh[Unit]Description=Run the database dump at 04:30[Timer]OnCalendar=*-*-* 04:30:00Persistent=trueRandomizedDelaySec=300[Install]WantedBy=timers.target$ sudo systemctl enable --now db-backup.timer$ systemctl list-timers --all$ systemd-analyze calendar "*-*-* 04:30:00"$ sudo systemctl start db-backup.service # run it now, on demandPersistent=true ის თვისებაა, რომელიც cron-ს არ აქვს: თუ მანქანა 04:30-ზე გამორთული იყო, სამუშაო ერთხელ გაეშვება ჩართვისთანავე და ჩუმად გამოტოვებული არ იქნება. RandomizedDelaySec რამდენიმე მანქანას ერთმანეთისგან აშორებს, რომ ყველამ ერთდროულად არ დაარტყას ერთ backup-ის სამიზნეს. ხოლო systemd-analyze calendar გეუბნება, როდის ამოქმედდება გამოსახულება შემდეგჯერ, რაც ხვალამდე ლოდინს სჯობს, რათა გაიგო, რომ არასწორად დაწერე. თუ cron-ის სინტაქსი გირჩევნია, cron გამოსახულებები ახსნილი ხუთივე ველს აღწერს - და გაითვალისწინე, რომ timer იმდენად კარგია, რამდენადაც კარგია სკრიპტი, რომელსაც უშვებს, ამიტომ წაიკითხე backup-ები, რომლებიც მართლა აღდგება, სანამ რომელიმეს ენდობი.
როცა არ ირთვება#
$ systemctl status notes # state, last lines, exit code$ journalctl -u notes -n 50 # the actual error$ systemctl cat notes # the unit as systemd sees it, plus drop-ins$ systemd-analyze verify /etc/systemd/system/notes.service$ systemctl list-units --failed # everything broken on this box`status=203/EXEC`. systemd-მ ExecStart ვერ შეასრულა. არასწორი path, არაშესრულებადი ფაილი, ან სკრიპტი დაკარგული ან არასწორი shebang-ით. გაუშვი ზუსტად ეს ბრძანება ხელით, სერვისის მომხმარებლით.
`status=200/CHDIR`. WorkingDirectory არ არსებობს, ან სერვისის მომხმარებელს მასში შესვლა არ შეუძლია.
`Start request repeated too quickly`. start limit. გაასწორე ნამდვილი პრობლემა და მერე systemctl reset-failed.
ხელით ირთვება და boot-ზე ვარდება. დამოკიდებულება მზად არ იყო. დაამატე After= იმისთვის, რაც სჭირდება, და Wants=network-online.target, თუ ის კონკრეტულ მისამართს იკავებს.
მუშაობს, მაგრამ ვერაფერს წერს. ProtectSystem=strict შესაბამისი ReadWritePaths=-ის გარეშე, ან უბრალოდ ფაილის მფლობელობა. journal დაასახელებს path-ს.
Environment ცვლადები ცარიელია. ფაილის path არასწორია და - პრეფიქსი გამოიყენე, ამიტომ მარცხი ჩუმია. შეამოწმე systemctl show notes -p Environment-ით.
shell-ში მუშაობს და სერვისად არა. თითქმის ყოველთვის გარემოა: შენს login shell-ს აქვს PATH, HOME, ენის პარამეტრი და შესაძლოა ვერსიების მენეჯერი, რაც სერვისს არ აქვს. საჭირო ყველაფერი unit-ში პირდაპირ მიუთითე და იღბალზე დამოკიდებულ მემკვიდრეობას ნუ დაეყრდნობი.
unit-ის ცვლილებები არაფერს აკეთებს. daemon-reload დაგავიწყდა.
RE:NODE-ის მართულ გეგმებზე ამისგან არაფერი შენი დასაწერი არ არის: პანელი პროცესს რთავს და აჩერებს, watcher ყოველ ორ წუთში ამოწმებს სერვერს, რომელიც გაითიშა ან რომლის uptime უკან დაბრუნდა, ხოლო საათში სამი არაგამოთხოვილი გადატვირთვა ბადებს გაფრთხილებას და ავტომატურად ხსნის ticket-ს. Schedules ტაბი იღებს cron გამოსახულებას და უშვებს დალაგებულ ამოცანებს - console ბრძანებას, backup-ს, power ქმედებას - რაც ზემოთ აღწერილი timer-ის მართული ეკვივალენტია. VDS-ზე root გაქვს და იგივე ქცევას ამ პოსტის ნაწილებისგან თავად აგებ, რაც მეტი მუშაობაა და გაცილებით მეტი კონტროლი. VDS თუ გეიმ პანელი, როგორ აირჩიო გულწრფელი შედარებაა, ხოლო პირველი საათი ახალ VDS-ზე არის ის, რაც ამ ყველაფრამდე უნდა გააკეთო.
FAQ#
მჭირდება თუ არა კვლავ pm2 ან supervisor?
პროცესის ცოცხლად შესანარჩუნებლად არა - systemd ამას უკეთ აკეთებს, უფრო დაბალ დონეზე, ლოგებითა და დამოკიდებულებებით ერთად. process manager-ებს მაინც აქვთ ადგილი იმ შესაძლებლობებისთვის, რაც systemd-ს არ აქვს, მაგალითად Node აპის რამდენიმე ეგზემპლარის გაშვება ბირთვებზე downtime-ის გარეშე reload-ით. ორივეს ერთად გაშვება გავრცელებული და თავიდან ასაცილებელი შეცდომაა; pm2 თუ hosting panel გადაფარვას განიხილავს.
უნდა გავუშვა თუ არა სერვისები root-ად?
არა. შექმენი სისტემური მომხმარებელი login shell-ის გარეშე, unit-ში მიუთითე User= და წერის უფლება მიეცი მხოლოდ იმ დირექტორიებზე, რომლებიც სჭირდება. თუ სერვისს დაბალი პორტის დაკავება სჭირდება, გამოიყენე AmbientCapabilities=CAP_NET_BIND_SERVICE ან წინ proxy დააყენე და root-ისკენ ნუ წახვალ.
რა განსხვავებაა systemctl start-სა და systemctl enable-ს შორის?
start მას ახლავე უშვებს. enable ხდის, რომ boot-ზე ჩაირთოს. ისინი დამოუკიდებელია, და სერვისი, რომელიც გაუშვი, მაგრამ არასოდეს ჩაგირთავს, მომდევნო reboot-ის შემდეგ აღარ არის. enable --now ორივეს აკეთებს.
შემიძლია თუ არა systemd-ით Docker კონტეინერების გაშვება?
დიახ, და ეს გონივრული შაბლონია, როცა კონტეინერს უნდა ჩაირთოს მონტირებული ფაილური სისტემის შემდეგ ან განსაზღვრული თანმიმდევრობით. დაწერე unit, რომელიც docker compose up -d-ს უშვებს Type=oneshot-ით და RemainAfterExit=yes-ით, ან მარტივ შემთხვევაში გამოიყენე Docker-ის საკუთარი restart policy-ები - Docker VDS-ზე მათ აღწერს.
რატომ ქრება ჩემი ლოგები reboot-ის შემდეგ?
journal ზოგ დისტრიბუტივში არამდგრადია და /run-ში ინახება. შექმენი /var/log/journal, journald.conf-ში დააყენე Storage=persistent, გადატვირთე systemd-journald და ზომა შეზღუდე, რომ დისკი ვერ შეავსოს.
როგორ გავუშვა სერვისი ჩემი მომხმარებლით root-ის გარეშე?
systemctl --user მართავს unit-ებს ~/.config/systemd/user/-ში. ნაგულისხმევად ისინი გასვლისას ჩერდება, ამიტომ ერთხელ გაუშვი loginctl enable-linger yourname, რომ მუშაობა გააგრძელონ. ეს პირადი ხელსაწყოებისთვის მოსახერხებელია და არ არის სწორი ადგილი იმისთვის, რაც მანქანის uptime-ზეა დამოკიდებული.




კომენტარები
სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.