איך להריץ תוכנית כשירות systemd בשרת Linux
למדו איך להגדיר שירות systemd כדי להריץ תוכנית ברקע בשרת VPS. המדריך מסביר איך להבטיח הפעלה אוטומטית ב-boot, ניהול לוגים ב-journalctl והרצה מאובטחת עם הרשאות מוגבלות.
מהו שירות systemd ומדוע כדאי להשתמש בו
שירות systemd הוא קובץ טקסט קטן המנחה את השרת כיצד להריץ תוכנית: להפעיל אותה בעת עליית המערכת, להפעיל אותה מחדש במקרה של קריסה, ולהעביר את הפלט שלה ליומן המערכת. זהו תפקידו המלא. תוכנית שאתם מפעילים ידנית בתוך סשן SSH מסתיימת ברגע שאתם מתנתקים או שהשרת מבצע אתחול. תוכנית שעטופה בתוך שירות systemd ממשיכה לרוץ, כיוון שהשרת עצמו הוא הבעלים שלה במקום ה-shell שלכם.
systemd היא מערכת ה-init ב-Ubuntu, Debian, Fedora וברוב שרתי ה-Linux המודרניים. זהו התהליך הראשון שמתחיל והוא זה שמפקח על כל השאר. זה לא תמיד היה המצב, ומומלץ לקרוא על האופן שבו systemd החליפה את סקריפטי ה-init שקדמו לה לאחר שתבינו מה עושה קובץ unit. כאשר אתם כותבים קובץ שירות, אתם מוסרים את התוכנית שלכם לאותו מפקח. מדריך זה מציג את ה-unit הקטן ביותר שעובד, שלוש השדות שיש לכל unit, כיצד להפעיל אותו ולקרוא את הלוגים שלו, כיצד להריץ אותו לפי לוח זמנים באמצעות timer, וכיצד להגביל אותו כך שירוץ עם מינימום הרשאות אפשרי.
השירות המינימלי שפועל
קובץ שירות נמצא ב-/etc/systemd/system/, מסתיים ב-.service, וזקוק למספר שורות בלבד. צרו קובץ כזה עבור תוכנית שנמצאת ב-/usr/local/bin/myapp:
sudo nano /etc/systemd/system/myapp.service[Unit]
Description=My application
[Service]
ExecStart=/usr/local/bin/myapp
[Install]
WantedBy=multi-user.targetזוהי יחידה מלאה ופועלת. ExecStart הוא הפקודה להרצה. WantedBy=multi-user.target משמעותו הפעלה ברגע שהשרת מגיע למצב עבודה רב-משתמשים תקין, וזה מה שגורם לו לעלות בעת האתחול. כל השאר הוא שיפורים בלבד.
שלושת המקטעים, והתפקיד של כל אחד מהם
כל קובץ unit מחולק למקטעים בסוגריים מרובעים. שירות משתמש בשלושה מהם.
[Unit] מתאר את השירות ואת הקשרים שלו. שתי השורות שבהן תשתמש הכי הרבה:
[Unit]
Description=My application
After=network-online.target
Wants=network-online.targetDescription הוא התווית האנושית שאתה רואה ב-systemctl status. After=network-online.target מורה ל-systemd לא להפעיל את התוכנית שלך עד שהרשת זמינה, מה שחשוב לכל מה שמאזין לפורט או יוצר חיבור יוצא.
[Service] מגדיר כיצד התוכנית רצה. כאן נמצאות רוב ההגדרות שלך:
[Service]
ExecStart=/usr/local/bin/myapp --config /etc/myapp/config.toml
WorkingDirectory=/opt/myapp
User=myapp
Restart=on-failure
RestartSec=5
Environment=LOG_LEVEL=infoUser=myapp מריץ את התוכנית כמשתמש ללא הרשאות מיוחדות במקום כ-root, וזו השורה החשובה ביותר לאבטחה. אין כאן שורת Type=, לכן systemd חוזר ל-simple ומניח שתהליך ה-ExecStart נשאר בחזית; תוכנית שמתפצלת לרקע זקוקה ל-Type= המתאים לאופן שבו היא עולה, אחרת ה-unit ידווח על מצב active בזמן שה-daemon האמיתי כבר איננו. Restart=on-failure ו-RestartSec=5 מקבלים מקטע משלהם בהמשך, כיוון שהם הסיבה העיקרית לכך שאנשים כותבים שירות מלכתחילה.
[Install] מגדיר מה קורה כשאתה מפעיל (enable) את השירות:
[Install]
WantedBy=multi-user.targetWantedBy=multi-user.target הוא מה שמקשר את השירות לעליית המערכת כשאתה מריץ את systemctl enable. ללא מקטע [Install], ניתן להפעיל שירות ידנית, אך הוא לא יעלה מעצמו לאחר reboot.
הפעלה ומעקב
לאחר כתיבה או עריכה של קובץ unit, יש לבצע טעינה מחדש (reload) ל-systemd כדי שיקרא את השינויים, ולאחר מכן להפעיל את השירות ולהגדיר אותו לעלייה אוטומטית בפעולה אחת:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.servicedaemon-reload הוא השלב שמשתמשים נוטים לשכוח: systemd שומר את קובצי ה-unit בזיכרון המטמון, לכן עריכה לא תניב תוצאות ללא טעינה מחדש. enable --now גם מגדיר את השירות לעלייה באתחול וגם מפעיל אותו מיד. בדקו את המצב:
sudo systemctl status myapp.service* myapp.service - My application
Loaded: loaded (/etc/systemd/system/myapp.service; enabled)
Active: active (running) since Wed 2026-07-15 22:40:11 UTC; 3s ago
Main PID: 4123 (myapp)Active: active (running) ו-enabled הם המצבים הרצויים. כדי לקרוא את הפלט של התוכנית, בקשו מה-journal את הלוגים עבור יחידה זו בלבד:
sudo journalctl -u myapp.service -fהדגל -f עוקב אחר שורות חדשות כפי שהן מגיעות, בדומה ל-tail -f. כל מה שהתוכנית כותבת ל-standard output או ל-standard error מופיע כאן, ללא צורך בהגדרת לוגים מצדכם.
הפעלה מחדש בעת כשל, הסיבה לכך שאתם כאן
היתרון המרכזי של שירות הוא ש-systemd מפעיל מחדש את התוכנית שלכם כשהיא קורסת. שתי שורות מבצעות זאת:
[Service]
Restart=on-failure
RestartSec=5Restart=on-failure מפעיל מחדש את התוכנית כאשר היא מסתיימת עם קוד שגיאה שאינו אפס או קורסת עקב אות (signal) כמו SIGKILL או SIGSEGV. יציאה תקינה, או עצירה באמצעות SIGTERM, SIGINT, SIGHUP או SIGPIPE, לא יפעילו זאת. RestartSec=5 ממתין חמש שניות בין ניסיונות, כך שתוכנית שקורסת באופן מיידי לא תיתקע בלולאה אינסופית מהירה. אשרו זאת על ידי הריגת התהליך וצפייה ב-systemd מחזיר אותו לפעולה. השתמשו ב-SIGKILL: ה-SIGTERM המוגדר כברירת מחדל נחשב לעצירה תקינה, ולכן on-failure לא יפעיל את השירות מחדש:
sudo systemctl kill -s SIGKILL myapp.service
sudo systemctl status myapp.serviceבתוך חמש שניות הסטטוס יציג Main PID חדש ו-active (running) שוב. זהו כל הפיצ'ר, וזו הסיבה ששירות עדיף על השארת תוכנית רצה ב-tmux או ב-screen.
הרצת השירות כמשתמש ללא הרשאות מיוחדות והקשחת האבטחה
שירות שרץ כ-root עלול להעניק לתוקף שליטה מלאה בשרת במקרה של ניצול פרצת אבטחה בתוכנה. יש להריץ את השירות תחת משתמש ייעודי ולהוסיף ל-systemd הנחיות המגבילות את פעילותו. תחילה, צרו חשבון מערכת ללא אפשרות התחברות וללא תיקיית בית:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin myappלאחר מכן, הגדירו את User=myapp והוסיפו שורות הקשחה לקובץ [Service]:
[Service]
User=myapp
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=trueכל שורה מסירה הרשאה שאינה נחוצה לתוכנה. NoNewPrivileges=true מונע מהתהליך לקבל הרשאות חדשות, גם דרך קובץ בינארי מסוג setuid. PrivateTmp=true מעניק לשירות מרחב /tmp פרטי ששום תהליך אחר אינו יכול לראות. ProtectSystem=strict הופך את מערכת הקבצים כולה לקריאה בלבד, למעט נתיבים ספציפיים שתגדירו באמצעות ReadWritePaths=. ProtectHome=true מסתיר ממנו לחלוטין את /home. זוהי אותה תפיסת "הרשאות מינימליות" המיושמת בהצבת שירות מאחורי firewall: העניקו לו רק את מה שהוא חייב. אם קראתם את המדריך בנושא סגירת פרצת ה-IPv6 ב-firewall של שרת VPS, דעו שזוהי המחצית המקומית של אותו רעיון. עבור שירות החשוף לאינטרנט, שלבו הקשחה זו עם Fail2ban לפני SSH ומדיניות firewall של חסימה כברירת מחדל.
במקום להקליד את כל ההגדרות ידנית ולהסתכן בשגיאה, צרו יחידת systemd מלאה ומוקשחת והעתיקו אותה:
Timers: התחליף המודרני ל-cron
יחידת timer ב-systemd מריצה שירות לפי לוח זמנים, והיא מהווה את התחליף המודרני ל-cron job. טיימר מורכב משני קבצים: קובץ .service שמבצע את העבודה, וקובץ .timer שמגדיר מתי. נניח שברצונכם לבצע גיבוי בכל יום בשעה 3 לפנות בוקר. השירות מבצע את המשימה פעם אחת ומסתיים:
# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.shהערך Type=oneshot מורה ל-systemd שהתוכנית רצה, מסיימת את פעולתה ונסגרת, במקום להישאר תהליך רץ ברקע. הטיימר מתזמן את הפעולה:
# /etc/systemd/system/backup.timer
[Unit]
Description=Run the nightly backup
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.targetהערך OnCalendar=*-*-* 03:00:00 מייצג את השעה 3 לפנות בוקר בכל יום. ניתן לבדוק כל ביטוי לוח שנה באמצעות systemd-analyze calendar "*-*-* 03:00:00", המאשר את תקינות הניתוח ומציג את מועדי ההרצה הבאים. הערך Persistent=true מריץ משימה שהוחמצה ברגע שהשרת חוזר לפעילות אם הוא היה כבוי בשעה 3, יכולת שאינה קיימת ב-cron. שימו לב שטיימר מופעל באמצעות timers.target, ולא באמצעות multi-user.target. הפעילו את הטיימר, לא את השירות:
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timersהפקודה list-timers מציגה את כל הטיימרים עם מועד ההרצה הבא והאחרון שלהם, כך שניתן לראות במבט חטוף מתי המשימה תרוץ שוב. המחולל לעיל בונה עבורכם את הזוג .service ו-.timer כאשר אתם מפעילים את מצב הטיימר. בהשוואה לשורת cron, טיימר מספק לוגים אמיתיים ב-journal, את אותן הנחיות אבטחה (hardening) של כל שירות, ואת היכולת להשלים הרצות שהוחמצו באמצעות Persistent=true. Cron עדיין מתאים למשימות פשוטות; טיימר הוא הכלי העדיף כאשר למשימה יש חשיבות.
FAQ
מה ההבדל בין שירות systemd לבין משימת cron?
שירות מריץ תוכנית ארוכת טווח: הוא עולה עם האתחול, מופעל מחדש במקרה של כשל, ומתעד לוגים ל-journal. משימת cron מריצה פקודה קצרה לפי לוח זמנים ומסתיימת. כאשר נדרש תזמון לצד לוגים, הקשחת אבטחה והשלמת הרצות שהוחמצו, יש להשתמש ב-systemd timer. הוא משלב לוח זמנים מסוג .timer עם שירות מסוג oneshot ומחליף את cron ברוב משימות השרת.
היכן עליי לשמור את קובץ ה-service של systemd?
יש לשמור יחידות מותאמות אישית ב-/etc/systemd/system/, עם שם שמסתיים ב-.service. ספרייה זו מיועדת ליחידות שהמנהל מוסיף, והיא מקבלת עדיפות על פני יחידות שמגיעות עם חבילות ב-/lib/systemd/system/. לאחר יצירה או עריכה של קובץ שם, יש להריץ את sudo systemctl daemon-reload כדי ש-systemd יזהה את השינוי.
כיצד אוכל לגרום לשירות להפעיל את עצמו מחדש אם הוא קורס?
יש להוסיף את Restart=on-failure ואת RestartSec=5 למקטע [Service], ולאחר מכן להריץ את sudo systemctl daemon-reload ולהפעיל מחדש את השירות. המערכת תפעיל את התוכנית מחדש כאשר היא מסתיימת עם קוד שגיאה או קורסת עקב אות (signal), תוך המתנה של חמש שניות בין ניסיון לניסיון. ניתן לבדוק זאת באמצעות sudo systemctl kill -s SIGKILL myapp.service; אות SIGTERM, שהוא ברירת המחדל, נחשב לעצירה תקינה ולא יפעיל את on-failure. ניתן לעקוב אחר systemctl status כדי לראות PID חדש תוך מספר שניות.
כיצד אוכל להריץ שירות systemd כמשתמש שאינו root?
יש ליצור חשבון מערכת באמצעות sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp, ולאחר מכן להוסיף את User=myapp למקטע [Service]. יש להוסיף את NoNewPrivileges=true, PrivateTmp=true ו-ProtectSystem=strict כדי שהתהליך ירוץ עם מינימום הרשאות נדרשות. הרצה כמשתמש ללא הרשאות מיוחדות היא הצעד החשוב ביותר שניתן לבצע לשיפור אבטחת השירות.
מדוע השירות שלי לא עלה?
יש להריץ את systemctl status myapp.service לקבלת סיכום ואת journalctl -u myapp.service לקבלת הפלט המלא. הסיבות הנפוצות ביותר הן נתיב שגוי ב-ExecStart, קובץ WorkingDirectory חסר, שגיאת הרשאות שבה User= אינו יכול לקרוא קובץ, או שכחה של sudo systemctl daemon-reload לאחר עריכה. ה-journal מציג את הודעת השגיאה של התוכנית עצמה, שבדרך כלל מצביעה על הבעיה באופן ישיר.