目錄
- systemctl 是什麼?
- 基本指令速查
- 建立 systemd 服務
- 實際範例
- Service Type 詳解
- Restart 策略
- 依賴關係管理
- 資源限制
- 日誌管理
- 除錯與故障排除
- 進階技巧
- macOS 注意事項
- 最佳實踐
- 快速檢查清單
- 參考資源
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 - 指定
User和Group(不用 root) - 設定
WorkingDirectory - 設定適當的
Restart策略 - 加入必要的依賴(
After、Requires) - 執行
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