Blog Details

Linux 作業系統 cutexyz > Blog > 伺服器管理 > 伺服器管理必學 100 招 > 伺服器管理必學 100 招|Part 3:系統程序與 Systemd 治理

伺服器管理必學 100 招|Part 3:系統程序與 Systemd 治理

前言
在 Linux 伺服器中,程序的生命週期管理決定了系統的可用性與韌性。許多初學者往往用 nohup、& 或 screen 在背景偷跑程式,一旦伺服器重開或程式崩潰就無聲蒸發。本篇精選 10 個核心程序調度與 Systemd 治理技巧,教你如何編寫標準守護進程(Daemon)、設定自動重啟、精準隔離資源消耗,以及馴服殘酷的 Linux OOM Killer。

技巧 021:告別 nohup!用 Systemd 撰寫正規的 Production Service

  • 痛點場景:
    用 nohup python app.py & 跑背景程式,遇到伺服器重開機、記憶體溢位或未捕捉例外時,程式直接終止且無人知曉。
  • 解決方案:
    建立標準 Unit 檔案 /etc/systemd/system/myapp.service:
[Unit]
Description=My Web Application
After=network.target

[Service]
Type=simple
User=deployer
Group=deployer
WorkingDirectory=/var/www/myapp
ExecStart=/var/www/myapp/venv/bin/python main.py
Restart=always
RestartSec=5s
Environment="PORT=8000"
EnvironmentFile=/var/www/myapp/.env

[Install]
WantedBy=multi-user.target
# 啟用並啟動服務:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
  • 專家提醒:
    ExecStart 內的執行路徑必須是絕對路徑;使用專用非特權帳號(如 deployer)執行,切勿預設以 root 身分跑應用。

技巧 022:防止重啟風暴!配置合理的 RestartSec 與重試門檻

  • 痛點場景:
    程式若是因為「資料庫連線失敗」或「設定檔語法錯誤」而啟動失敗,Restart=always 會在 1 秒內狂閃重開數百次,瞬間把 CPU 打滿並洗爆系統 Log。
  • 解決方案:
    在 Unit 設定中加入冷卻緩衝與重試上限控制:
[Unit]
Description=Worker Service
# 10 秒內若重試超過 5 次,Systemd 將停止重試,避免拖垮系統
StartLimitIntervalSec=10
StartLimitBurst=5

[Service]
ExecStart=/usr/local/bin/worker
Restart=on-failure
# 每次崩潰後強制等待 5 秒再重啟
RestartSec=5s
  • 專家提醒:
    使用 Restart=on-failure 會比 Restart=always 更合理,因為它允許程式正常退出(Exit code 0)時不被強制拉起。

技巧 023:揮別 crontab!改用 Systemd Timer 實現高可靠定時排程

  • 痛點場景:
    傳統 cron 缺乏執行狀態監控、無法直觀調閱失敗日誌,遇到長時間執行的任務更難以追蹤卡在哪個步驟。
  • 解決方案:
    拆分為「Service」與「Timer」兩組檔案。
    1. 定義要跑的任務 /etc/systemd/system/daily-backup.service:
[Unit]
Description=Run Daily Database Backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-script.sh
  • 2. 定義排程計時器 /etc/systemd/system/daily-backup.timer:
[Unit]
Description=Trigger Backup Service Daily

[Timer]
# 每天凌晨 03:30 執行
OnCalendar=*-*-* 03:30:00
# 若關機錯過時間,下次開機補跑
Persistent=true

[Install]
WantedBy=timers.target

啟用並檢視排程狀態:

sudo systemctl daemon-reload
sudo systemctl enable --now daily-backup.timer
systemctl list-timers
  • 專家提醒:
    systemctl list-timers 能一目了然下次觸發時間與上次執行結果,排查日誌只需直接打 journalctl -u daily-backup.service。

技巧 024:單一服務吃爆主機?利用 Cgroups 限制 CPU 與記憶體

  • 痛點場景:
    某個資料處理背景工作或記憶體洩漏(Memory Leak)的程序,無節制地把全主機的 RAM 吃到 100%,導致連 SSH 都卡死登不進去。
  • 解決方案:
    直接在 Systemd Service 的 [Service] 區塊設定 Cgroups 資源上限:
[Service]
# 限制最高只能使用 2 顆 CPU 核心的運算量
CPUQuota=200%

# 限制實體記憶體最多使用 2GB
MemoryMax=2G

# 超過上限時直接拋棄 Swap,觸發該進程專屬的 OOM 處置
MemorySwapMax=0

更新後立即重載:sudo systemctl daemon-reload && sudo systemctl restart myapp

  • 專家提醒:
    利用 systemd-cgtop 指令,可以像查看 htop 一樣即時監看各 Systemd 服務單元的 CPU 與記憶體實際消耗率。

技巧 025:保住關鍵服務!調整 OOM Score 避免核心進程被誤殺

  • 痛點場景:
    當記憶體耗盡時,Linux 核心的 OOM Killer 會根據一套評分標準(OOM Score)隨機殺死大進程,常導致 SSHd 或最重要的 MariaDB / Nginx 被迫犧牲。
  • 解決方案:
    設定關鍵服務的保護分數,降低被殺機率(範圍 -1000 到 1000,負值越高越不容易被殺,-1000 代表完全免疫):
[Service]
# 讓核心在缺記憶體時優先放過這個重要服務
OOMScoreAdjust=-500
  • 若要對非 Systemd 程序即時調整:
# 查閱進程目前的評分
cat /proc/<PID>/oom_score
# 手動調降優先權(需 root)
echo -500 | sudo tee /proc/<PID>/oom_score_adj
  • 專家提醒:
    不要將所有程式都設為 -1000;只對 SSHd、資料庫或核心反向代理做降分保護,次要的 Worker 則應保持預設或調高分數讓它優先犧牲。

技巧 026:追殺打不死的殭屍程序(Zombie Processes)

  • 痛點場景:
    在 top 或 htop 中看到 Zombie 數量居高不下,程序狀態顯示為 Z 或 <defunct>,無法使用 kill -9 強制刪除。
  • 解決方案:
    殭屍程序本身已經死亡,殺不掉是因為父程序(Parent Process)未執行 wait() 回收子程序的退出狀態。解決方法是找到父程序並將其重啟或終止:
# 1. 找出所有狀態為 Z 的殭屍進程及其父進程 PID (PPID)
ps -A -o stat,ppid,pid,cmd | grep -e '^[Zz]'

# 2. 檢視該父程序的詳細身分
ps -fp <PPID>

# 3. 終止或優雅重啟該父程序,讓 init/Systemd (PID 1) 認領並收割殭屍
kill -15 <PPID>
  • 專家提醒:
    殭屍程序雖然不消耗 CPU 與記憶體,但會佔用系統有限的 PID 名額,若 PID 號碼耗盡將導致整台伺服器無法建立任何新行程。

技巧 027:平滑重載(Zero-Downtime Reload)與環境變數注入

  • 痛點場景:
    每次修改設定檔就下 systemctl restart nginx,會粗暴中斷正在傳輸中的使用者連線與長請求。
  • 解決方案:
    正確利用 ExecReload 實現零停機更新。以 Nginx 為例,它支援透過 HUP 訊號重載:
[Service]
# 啟動新 Worker 接收新流量,舊 Worker 處理完既有連線後自行退出
ExecReload=/usr/sbin/nginx -s reload
  • 日常操作養成使用平滑重載習慣:
# 先檢查語法,確認無誤再平滑 reload
sudo nginx -t && sudo systemctl reload nginx
  • 專家提醒:
    如果是自己寫的 Node.js 或 Python 程式,需在程式碼中監聽 process.on('SIGHUP') 或 SIGTERM 實作優雅退出(Graceful Shutdown),才能支援真正的熱重載。

技巧 028:多實例模版服務!一個設定檔開 10 個 Worker(@ 語法)

  • 痛點場景:
    需要跑 4 個不同 Port 的 API 節點(如 8001、8002、8003、8004)做本機負載平衡,若複製 4 份 Service 檔案日後極難維護。
  • 解決方案:
    使用 Systemd 的 Template 機制,檔名末尾加上 @,例如 /etc/systemd/system/api-worker@.service:
[Unit]
Description=API Worker on port %I
After=network.target

[Service]
User=deployer
WorkingDirectory=/var/www/myapp
# %I 會動態替換為 @ 後面的參數
ExecStart=/var/www/myapp/venv/bin/uvicorn main:app --port %I
Restart=always

[Install]
WantedBy=multi-user.target
  • 一鍵平行啟動不同 Port 的實例:
sudo systemctl enable --now api-worker@8001
sudo systemctl enable --now api-worker@8002
sudo systemctl enable --now api-worker@8003
  • 專家提醒:
    %I 代表傳入的參數,也可以傳入使用者名稱、租戶 ID 或專案資料夾名稱,極適合多 Tenant 架構。

技巧 029:監控終極儀表!比 top 好用十倍的 btop 與 htop 必殺技

  • 痛點場景:
    預設的 top 介面黑白單調、缺乏硬碟 I/O 與網路狀態即時圖表,除錯時難以直觀定位系統瓶頸。
  • 解決方案:
    安裝現代化監控神器 btop(或 htop):
sudo apt install btop -y
# 執行 btop
btop
  • 高頻快捷鍵操作指南:
    • 按 P:依 CPU 消耗排序。
    • 按 M:依記憶體消耗排序。
    • 按 T:切換為樹狀圖檢視(Tree View),輕鬆找出哪個子程序是由哪個 Master 父行程長出來的。
    • 按 K:直接在介面選取程序發送 Terminate / Kill 訊號,不用退回終端機手打 PID。
  • 專家提醒:
    如果伺服器沒有安裝第三方工具的權限,傳統 top 進入後直接按 Shift + M 依記憶體排序、按 Shift + P 依 CPU 排序,是最基礎的生存指令。

技巧 030:利用 /proc 檔案系統透視執行中程序的真實面貌

  • 痛點場景:
    遇到可疑或未知來源的程序在背景狂吃 CPU,但 ps 指令只顯示模糊的指令名稱,不知道它的實際執行路徑、環境變數與工作目錄在哪裡。
  • 解決方案:
    Linux 萬物皆檔案,進程所有細節全部攤在 /proc/<PID>/:
# 1. 查出該程序實際對應的執行檔真實路徑
ls -l /proc/<PID>/exe

# 2. 查出該程序啟動時的 Working Directory
ls -l /proc/<PID>/cwd

# 3. 查出該程序啟動時載入的所有環境變數(以 Null 字符分隔)
cat /proc/<PID>/environ | tr '\0' '\n'

# 4. 查出該程序目前開啟了哪些檔案與網路連線
ls -l /proc/<PID>/fd/
  • 專家提醒:
    即便攻擊者把硬碟上的原始惡意腳本刪除了,只要進程還在跑,你甚至能直接用 cp /proc/<PID>/exe /tmp/recovered_malware 把該二進制檔完整 dump 出來進行逆向取證!