systemctl 服務管理完全指南

Linux 系統服務管理工具,提供統一的服務控制介面和強大的管理功能。


目錄


systemctl 是什麼?

systemctl 是 Linux 系統服務管理工具,用於控制 systemd 系統和服務管理器。

核心概念

systemd = 現代 Linux 系統的初始化系統(init system)
systemctl = 控制 systemd 的命令列工具

傳統方式(舊):
service nginx start
/etc/init.d/nginx start

現代方式(新):
systemctl start nginx

為什麼要用 systemctl?

✅ 統一的服務管理介面
✅ 並行啟動服務(加快開機)
✅ 依賴關係管理
✅ 自動重啟失敗的服務
✅ 詳細的日誌記錄
✅ 資源限制控制

基本指令速查

服務控制

# 啟動服務
systemctl start nginx

# 停止服務
systemctl stop nginx

# 重啟服務
systemctl restart nginx

# 重新載入設定(不中斷服務)
systemctl reload nginx

# 重新載入或重啟(reload 失敗時 restart)
systemctl reload-or-restart nginx

# 查看服務狀態
systemctl status nginx

# 檢查服務是否啟動
systemctl is-active nginx

# 檢查服務是否啟用(開機自動啟動)
systemctl is-enabled nginx

開機自動啟動

# 啟用開機自動啟動
systemctl enable nginx

# 停用開機自動啟動
systemctl disable nginx

# 啟用並立即啟動
systemctl enable --now nginx

# 停用並立即停止
systemctl disable --now nginx

# 遮蔽服務(完全禁止啟動)
systemctl mask nginx

# 解除遮蔽
systemctl unmask nginx

查詢與列表

# 列出所有服務
systemctl list-units --type=service

# 列出所有執行中的服務
systemctl list-units --type=service --state=running

# 列出所有失敗的服務
systemctl list-units --type=service --state=failed

# 列出所有已啟用的服務
systemctl list-unit-files --type=service --state=enabled

# 列出所有已安裝的 unit 檔案
systemctl list-unit-files

# 顯示服務的依賴關係
systemctl list-dependencies nginx

系統控制

# 重新載入 systemd 設定
systemctl daemon-reload

# 關機
systemctl poweroff

# 重新開機
systemctl reboot

# 暫停(休眠)
systemctl suspend

# 進入救援模式
systemctl rescue

# 進入緊急模式
systemctl emergency

建立 systemd 服務

服務檔案位置

系統服務:
/etc/systemd/system/         ← 自訂服務(優先)
/usr/lib/systemd/system/     ← 套件管理器安裝的服務

使用者服務:
~/.config/systemd/user/      ← 使用者自訂服務

基本服務檔案結構

[Unit]
Description=服務描述
After=network.target         # 在網路啟動後執行

[Service]
Type=simple                  # 服務類型
ExecStart=/path/to/command   # 啟動指令
Restart=on-failure           # 失敗時重啟

[Install]
WantedBy=multi-user.target   # 安裝目標

實際範例

範例 1:簡單的 Python 應用程式

建立服務檔案

sudo nano /etc/systemd/system/myapp.service

服務內容

[Unit]
Description=My Python Application
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/python3 /opt/myapp/app.py
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

啟用服務

# 重新載入 systemd
sudo systemctl daemon-reload

# 啟動服務
sudo systemctl start myapp

# 查看狀態
sudo systemctl status myapp

# 啟用開機自動啟動
sudo systemctl enable myapp

# 查看日誌
sudo journalctl -u myapp -f

範例 2:Node.js 應用程式

服務檔案/etc/systemd/system/nodeapp.service

[Unit]
Description=Node.js Application
After=network.target

[Service]
Type=simple
User=nodeuser
WorkingDirectory=/home/nodeuser/app
ExecStart=/usr/bin/node /home/nodeuser/app/server.js
Restart=on-failure
RestartSec=5

# 環境變數
Environment="NODE_ENV=production"
Environment="PORT=3000"

# 資源限制
LimitNOFILE=4096
MemoryLimit=512M

[Install]
WantedBy=multi-user.target

管理指令

sudo systemctl daemon-reload
sudo systemctl start nodeapp
sudo systemctl enable nodeapp
sudo systemctl status nodeapp

範例 3:使用環境變數檔案

服務檔案/etc/systemd/system/webapp.service

[Unit]
Description=Web Application
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/app
EnvironmentFile=/etc/myapp/app.env
ExecStart=/var/www/app/start.sh
Restart=always

[Install]
WantedBy=multi-user.target

環境變數檔案/etc/myapp/app.env

NODE_ENV=production
DATABASE_URL=postgresql://localhost/mydb
API_KEY=your-secret-key
PORT=8080

範例 4:多個啟動/停止指令

[Unit]
Description=Complex Application
After=network.target postgresql.service

[Service]
Type=forking
User=appuser
WorkingDirectory=/opt/app

# 啟動前執行
ExecStartPre=/opt/app/scripts/pre-start.sh

# 啟動指令
ExecStart=/opt/app/bin/start.sh

# 啟動後執行
ExecStartPost=/opt/app/scripts/post-start.sh

# 重新載入設定
ExecReload=/bin/kill -HUP $MAINPID

# 停止指令
ExecStop=/opt/app/bin/stop.sh

# 停止後執行
ExecStopPost=/opt/app/scripts/cleanup.sh

# 重啟設定
Restart=on-failure
RestartSec=10

# PID 檔案
PIDFile=/var/run/myapp.pid

[Install]
WantedBy=multi-user.target

範例 5:定時任務(Timer)

服務檔案/etc/systemd/system/backup.service

[Unit]
Description=Backup Service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=root

Timer 檔案/etc/systemd/system/backup.timer

[Unit]
Description=Backup Timer

[Timer]
# 每天凌晨 2 點執行
OnCalendar=daily
OnCalendar=*-*-* 02:00:00

# 如果錯過執行時間,開機後立即執行
Persistent=true

[Install]
WantedBy=timers.target

啟用 Timer

sudo systemctl daemon-reload
sudo systemctl enable backup.timer
sudo systemctl start backup.timer

# 查看所有 timer
systemctl list-timers

# 查看 timer 狀態
systemctl status backup.timer

# 手動觸發執行
sudo systemctl start backup.service

Service Type 詳解

常用類型

[Service]
Type=simple    # 預設,ExecStart 就是主程序
Type=forking   # 程序會 fork,需要 PIDFile
Type=oneshot   # 執行一次就結束(適合腳本)
Type=notify    # 程序會通知 systemd 啟動完成
Type=dbus      # 等待 D-Bus 名稱出現
Type=idle      # 延遲到所有工作完成才執行

Type=simple

[Service]
Type=simple
ExecStart=/usr/bin/python3 app.py

# 特點:
# - ExecStart 程序就是主程序
# - 不會 fork
# - systemd 認為程序啟動後服務就是 active

Type=forking

[Service]
Type=forking
PIDFile=/var/run/myapp.pid
ExecStart=/usr/bin/myapp --daemon

# 特點:
# - 程序會 fork 到背景
# - 需要 PIDFile 讓 systemd 追蹤
# - 適合傳統的 daemon

Type=oneshot

[Service]
Type=oneshot
ExecStart=/usr/local/bin/setup.sh
RemainAfterExit=yes

# 特點:
# - 執行完就結束
# - 適合一次性任務或腳本
# - RemainAfterExit=yes 讓服務保持 active 狀態

Type=notify

[Service]
Type=notify
ExecStart=/usr/bin/myapp
NotifyAccess=main

# 特點:
# - 程序需要呼叫 sd_notify() 通知就緒
# - systemd 等待通知才認為服務啟動完成
# - 適合需要初始化時間的應用

Restart 策略

重啟選項

[Service]
# 重啟策略
Restart=no              # 不重啟(預設)
Restart=always          # 總是重啟
Restart=on-success      # 成功退出才重啟
Restart=on-failure      # 失敗才重啟(推薦)
Restart=on-abnormal     # 異常終止才重啟
Restart=on-abort        # 收到未處理的信號才重啟
Restart=on-watchdog     # watchdog 逾時才重啟

# 重啟間隔
RestartSec=10           # 重啟前等待 10 秒

# 最大重啟次數(防止無限重啟)
StartLimitBurst=5       # 5 次
StartLimitIntervalSec=60  # 60 秒內

實際範例

[Service]
Type=simple
ExecStart=/usr/bin/myapp
Restart=on-failure
RestartSec=5
StartLimitBurst=3
StartLimitIntervalSec=300

# 意思:
# - 失敗時重啟
# - 重啟前等 5 秒
# - 5 分鐘內最多重啟 3 次
# - 超過就放棄

依賴關係管理

依賴指令

[Unit]
# 在某服務之後啟動
After=network.target postgresql.service

# 在某服務之前啟動
Before=nginx.service

# 要求某服務必須啟動
Requires=postgresql.service

# 建議某服務啟動(但非必要)
Wants=redis.service

# 與某服務衝突(不能同時執行)
Conflicts=apache2.service

實際範例:Web 應用

[Unit]
Description=Web Application
After=network.target postgresql.service redis.service
Requires=postgresql.service
Wants=redis.service

[Service]
Type=simple
ExecStart=/usr/bin/webapp

[Install]
WantedBy=multi-user.target

啟動順序

1. network.target
2. postgresql.service(必須成功)
3. redis.service(嘗試啟動,但失敗也繼續)
4. webapp.service

資源限制

CPU 限制

[Service]
# CPU 使用率限制(百分比)
CPUQuota=50%            # 最多使用 50% CPU

# CPU 權重(相對值)
CPUWeight=500           # 預設 100,越高優先權越高

# CPU 親和性
CPUAffinity=0 1         # 只使用 CPU 0 和 1

記憶體限制

[Service]
# 記憶體限制
MemoryLimit=512M        # 最多使用 512MB
MemoryMax=1G            # 硬限制 1GB
MemoryHigh=800M         # 軟限制 800MB(超過會被限流)

檔案與程序限制

[Service]
# 開啟檔案數量限制
LimitNOFILE=4096        # 最多 4096 個檔案描述符

# 程序數量限制
LimitNPROC=512          # 最多 512 個程序

# Core dump 大小
LimitCORE=infinity      # 允許 core dump

完整範例

[Unit]
Description=Resource Limited Application

[Service]
Type=simple
ExecStart=/usr/bin/myapp

# CPU 限制
CPUQuota=50%
CPUWeight=500

# 記憶體限制
MemoryMax=1G
MemoryHigh=800M

# 檔案限制
LimitNOFILE=4096
LimitNPROC=512

# I/O 優先權
IOSchedulingClass=best-effort
IOSchedulingPriority=4

# Nice 值(CPU 排程優先權)
Nice=10

[Install]
WantedBy=multi-user.target

日誌管理

查看日誌

# 查看服務日誌
journalctl -u nginx

# 即時查看日誌(類似 tail -f)
journalctl -u nginx -f

# 查看最近 100 行
journalctl -u nginx -n 100

# 查看今天的日誌
journalctl -u nginx --since today

# 查看特定時間範圍
journalctl -u nginx --since "2024-01-01" --until "2024-01-02"

# 查看最近 1 小時
journalctl -u nginx --since "1 hour ago"

# 只顯示錯誤
journalctl -u nginx -p err

# 反向顯示(最新在前)
journalctl -u nginx -r

# 不分頁顯示
journalctl -u nginx --no-pager

日誌優先權

journalctl -p <level>

Level:
0 - emerg    緊急
1 - alert    警報
2 - crit     嚴重
3 - err      錯誤
4 - warning  警告
5 - notice   注意
6 - info     資訊
7 - debug    除錯

服務中的日誌設定

[Service]
# 標準輸出到 journal
StandardOutput=journal

# 標準錯誤到 journal
StandardError=journal

# 或是寫到 syslog
StandardOutput=syslog
StandardError=syslog

# 或是寫到檔案
StandardOutput=file:/var/log/myapp.log
StandardError=file:/var/log/myapp.error.log

除錯與故障排除

檢查服務狀態

# 詳細狀態
systemctl status nginx -l

# 顯示屬性
systemctl show nginx

# 顯示特定屬性
systemctl show nginx -p MainPID -p LoadState -p ActiveState

# 檢查設定檔語法
systemd-analyze verify /etc/systemd/system/myapp.service

常見問題排查

問題 1:服務啟動失敗

# 查看詳細狀態
systemctl status myapp

# 查看完整日誌
journalctl -u myapp -n 50 --no-pager

# 檢查設定檔
systemctl cat myapp

# 驗證設定檔
systemd-analyze verify /etc/systemd/system/myapp.service

問題 2:服務一直重啟

# 查看重啟次數
systemctl show myapp -p NRestarts

# 查看日誌找原因
journalctl -u myapp -f

# 暫時停止重啟
systemctl stop myapp

# 檢查是否達到重啟限制
systemctl show myapp -p Result

問題 3:開機時未自動啟動

# 檢查是否啟用
systemctl is-enabled myapp

# 查看安裝狀態
systemctl show myapp -p UnitFileState

# 檢查依賴
systemctl list-dependencies myapp

# 查看開機時發生的事
journalctl -b -u myapp

問題 4:權限問題

# 檢查服務執行的使用者
systemctl show myapp -p User

# 檢查工作目錄權限
ls -la /path/to/workdir

# 測試指令能否執行
sudo -u www-data /path/to/command

# 檢查 SELinux(如果啟用)
getenforce
ausearch -m avc -ts recent

除錯模式

# 啟用除錯日誌
systemd-analyze set-log-level debug

# 執行服務並顯示詳細輸出
systemctl --user daemon-reload
systemctl --user start myapp

# 恢復正常日誌級別
systemd-analyze set-log-level info

進階技巧

使用 Template(範本)

範本檔案/etc/systemd/system/worker@.service

[Unit]
Description=Worker %i
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/worker --id=%i
Restart=always

[Install]
WantedBy=multi-user.target

使用範本

# 啟動多個 worker
systemctl start worker@1
systemctl start worker@2
systemctl start worker@3

# %i 會被替換成 1, 2, 3

# 停止特定 worker
systemctl stop worker@2

# 查看所有 worker
systemctl list-units 'worker@*'

Socket Activation

Socket 檔案/etc/systemd/system/myapp.socket

[Unit]
Description=My App Socket

[Socket]
ListenStream=127.0.0.1:8080
Accept=no

[Install]
WantedBy=sockets.target

服務檔案/etc/systemd/system/myapp.service

[Unit]
Description=My App
Requires=myapp.socket

[Service]
Type=simple
ExecStart=/usr/bin/myapp
StandardInput=socket

[Install]
WantedBy=multi-user.target

啟用

systemctl enable myapp.socket
systemctl start myapp.socket

# 當有連線時,myapp.service 會自動啟動

Path Activation(檔案監控)

Path 檔案/etc/systemd/system/watch-folder.path

[Unit]
Description=Watch Folder

[Path]
PathChanged=/var/www/uploads
Unit=process-upload.service

[Install]
WantedBy=multi-user.target

服務檔案/etc/systemd/system/process-upload.service

[Unit]
Description=Process Upload

[Service]
Type=oneshot
ExecStart=/usr/local/bin/process-uploads.sh

啟用

systemctl enable watch-folder.path
systemctl start watch-folder.path

# 當 /var/www/uploads 有變化時,會觸發 process-upload.service

macOS 注意事項

macOS 不使用 systemd!

macOS 使用 launchd 作為服務管理系統。

macOS 等價指令

# systemctl start → launchctl
launchctl start com.example.myapp

# systemctl stop
launchctl stop com.example.myapp

# systemctl enable
launchctl load ~/Library/LaunchAgents/com.example.myapp.plist

# systemctl disable
launchctl unload ~/Library/LaunchAgents/com.example.myapp.plist

# systemctl status(沒有直接等價)
launchctl list | grep myapp

macOS launchd 設定檔

位置

系統服務:
/Library/LaunchDaemons/          # root 執行
/Library/LaunchAgents/           # 每個使用者登入時執行

使用者服務:
~/Library/LaunchAgents/          # 使用者自己的服務

範例~/Library/LaunchAgents/com.example.myapp.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.example.myapp</string>

    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/myapp</string>
        <string>--config</string>
        <string>/etc/myapp/config.json</string>
    </array>

    <key>WorkingDirectory</key>
    <string>/usr/local/myapp</string>

    <key>RunAtLoad</key>
    <true/>

    <key>KeepAlive</key>
    <true/>

    <key>StandardOutPath</key>
    <string>/var/log/myapp.log</string>

    <key>StandardErrorPath</key>
    <string>/var/log/myapp.error.log</string>
</dict>
</plist>

載入服務

# 載入並啟動
launchctl load ~/Library/LaunchAgents/com.example.myapp.plist

# 卸載
launchctl unload ~/Library/LaunchAgents/com.example.myapp.plist

# 查看已載入的服務
launchctl list

# 查看特定服務
launchctl list | grep myapp

最佳實踐

1. 使用專用使用者

[Service]
User=www-data
Group=www-data

# 不要用 root 執行應用程式(安全風險)

2. 設定工作目錄

[Service]
WorkingDirectory=/opt/myapp

# 確保相對路徑正確

3. 環境變數管理

[Service]
# 方法 1:直接設定
Environment="NODE_ENV=production"
Environment="PORT=3000"

# 方法 2:使用檔案(推薦)
EnvironmentFile=/etc/myapp/config.env

# 不要在服務檔案中放敏感資訊!

4. 適當的重啟策略

[Service]
Restart=on-failure      # 只在失敗時重啟
RestartSec=5            # 等待 5 秒
StartLimitBurst=3       # 最多 3 次
StartLimitIntervalSec=300  # 5 分鐘內

5. 資源限制

[Service]
MemoryMax=1G            # 防止記憶體洩漏
CPUQuota=50%            # 防止 CPU 過載
LimitNOFILE=4096        # 足夠的檔案描述符

6. 日誌管理

[Service]
StandardOutput=journal
StandardError=journal

# 讓 journald 統一管理日誌

7. 依賴關係

[Unit]
After=network.target    # 確保網路就緒
Requires=database.service  # 依賴的必要服務

8. 安全性

[Service]
# 限制權限
PrivateTmp=true         # 使用私有 /tmp
NoNewPrivileges=true    # 禁止提升權限
ProtectSystem=strict    # 保護系統目錄
ProtectHome=true        # 保護家目錄

快速檢查清單

建立新服務時檢查:

  • 服務檔案放在 /etc/systemd/system/
  • 設定正確的 Type
  • 指定 UserGroup(不用 root)
  • 設定 WorkingDirectory
  • 設定適當的 Restart 策略
  • 加入必要的依賴(AfterRequires
  • 執行 systemctl daemon-reload
  • 測試啟動:systemctl start
  • 檢查狀態:systemctl status
  • 查看日誌:journalctl -u
  • 啟用開機自動啟動:systemctl enable
  • 測試重新開機後是否正常啟動

參考資源

官方文件

  • systemd 官網:https://systemd.io/
  • systemd.service man page:man systemd.service
  • systemd.unit man page:man systemd.unit

建立日期:2025-11-07 最後更新:2025-11-07

🔗相關文章