🐧 Linux systemd + logrotate 實戰:從開機服務到日誌輪替的一條龍管理
在現代 Linux 伺服器上,我們多半使用 systemd 來管理服務啟動、監控狀態與自動重啟,
同時用 logrotate 來處理 /var/log/*.log 的輪替與壓縮。
如果兩者沒有設計好,就會遇到「服務跑得好好的,但日誌無限長大、吃滿磁碟」的情況。
本篇從 範例服務 開始,一路做到 開機自動啟動 + 標準化輸出 + logrotate 日誌輪替 + systemd timer 自動排程,
給你一套可以直接複製的「一條龍」管理流程。
📑 目錄
- 一、使用情境與設計目標
- 二、基礎觀念:systemd 日誌 vs 傳統 log 檔
- 三、實作範例服務:從 systemd 開機啟動開始
- 四、讓服務輸出到專屬 log 檔
- 五、撰寫 logrotate 規則:保留天數、壓縮與權限
- 六、與 systemd 串接:postrotate 重新開啟日誌
- 七、用 systemd timer 管理 logrotate(取代 cron)
- 八、常見錯誤與排錯方法
- 九、實戰情境範例:Web App / Nginx / Squid
- 🧭 行動清單:在自己主機上落地這套流程
- 🔗 延伸閱讀
一、使用情境與設計目標
先定義我們要解決的典型情境:
- 你有一個自製的 Web / API / Worker 服務,使用
systemd管理開機啟動與自動重啟。 - 服務會輸出大量
stdout/stderr日誌或寫入某個/var/log/myapp.log。 - 希望:
- 📌 服務開機自動啟動,異常自動重啟。
- 📌 日誌按天或按檔案大小輪替、壓縮,只保留最近 N 份。
- 📌 日誌輪替後,服務能「接在新檔案上繼續寫」,不中斷服務。
接下來的示範會用一個名為 myapp 的服務來說明,但你可以很容易改成自己的專案名稱。
二、基礎觀念:systemd 日誌 vs 傳統 log 檔
1️⃣ systemd journal:預設就有的日誌
- 大部分服務在 systemd 之下,預設 標準輸出/錯誤 都會進入 journald。
- 可用
journalctl -u myapp.service -f即時查看。 - journal 自己有輪替機制,透過
/etc/systemd/journald.conf設定SystemMaxUse等限制。
# 只看 myapp 服務的最近 100 行日誌
journalctl -u myapp.service -n 100
# 追蹤 myapp 即時輸出
journalctl -u myapp.service -f
2️⃣ 傳統 log 檔:/var/log/*.log
- 很多程式(Nginx、Squid、自製 App)會自己寫到
/var/log/xxx.log。 - 這類檔案通常用 logrotate 做輪替與壓縮。
- 輪替方式常見:
- 以 大小 為主:
size 100M。 - 以 時間 為主:
daily/weekly。
- 以 大小 為主:
# logrotate 主設定檔
/etc/logrotate.conf
# 各服務的單獨設定
/etc/logrotate.d/
本文的策略會是:
開發中 / 除錯 → 以 journalctl 為主;
正式環境 / 長期保留 → 讓服務寫到特定 log 檔,再交給 logrotate 管理容量。
三、實作範例服務:從 systemd 開機啟動開始
先建立一個簡單的服務,持續輸出時間戳記與訊息到標準輸出:
# /usr/local/bin/myapp.sh
#!/usr/bin/env bash
LOG_TAG="myapp"
INTERVAL=5
while true; do
echo "$(date '+%F %T') [$LOG_TAG] heartbeat: service is running"
sleep "$INTERVAL"
done
sudo chmod +x /usr/local/bin/myapp.sh
接著建立 systemd service:
# /etc/systemd/system/myapp.service
[Unit]
Description=MyApp Demo Service (journal only)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/myapp.sh
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
sudo systemctl status myapp.service
journalctl -u myapp.service -f
到這裡,我們已經完成:
- ✅ 開機自動啟動與異常自動重啟(靠 systemd)。
- ✅ 日誌進入 journal,便於除錯與即時追蹤。
四、讓服務輸出到專屬 log 檔
若你想讓日誌交給 logrotate 管理,可以:
- 在 service 裡直接把輸出導向檔案。
- 或在程式內自行打開檔案(範例採用第 1 種)。
1️⃣ 建立日誌目錄與權限
sudo mkdir -p /var/log/myapp
sudo chown root:root /var/log/myapp
sudo chmod 750 /var/log/myapp
2️⃣ 更新 service:把輸出導向檔案
做法一:直接在 ExecStart 內使用 bash -c 轉向(簡單好理解):
# /etc/systemd/system/myapp.service
[Unit]
Description=MyApp Demo Service (file logging)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/bin/bash -c 'exec /usr/local/bin/myapp.sh >> /var/log/myapp/myapp.log 2>> /var/log/myapp/myapp.err.log'
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
tail -n 20 /var/log/myapp/myapp.log
到這裡為止,我們已經有一個:
- ✅ 開機自動啟動的 systemd 服務。
- ✅ 會寫入
/var/log/myapp/myapp.log與myapp.err.log的日誌檔。
五、撰寫 logrotate 規則:保留天數、壓縮與權限
接下來讓 logrotate 管理這兩個檔案的大小與保留份數。
1️⃣ 建立 /etc/logrotate.d/myapp
sudo vi /etc/logrotate.d/myapp
/var/log/myapp/myapp.log
/var/log/myapp/myapp.err.log {
daily # 每天輪一次
rotate 14 # 保留 14 份(約兩週)
compress # 轉成 .gz
delaycompress # 下一次輪替時再壓縮上一份
missingok # 不在時忽略,不視為錯誤
notifempty # 空檔案不輪替
create 640 root root
dateext # 檔名加日期,方便追蹤
}
先用「模擬模式」確認語法是否正確:
sudo logrotate -d /etc/logrotate.d/myapp
若沒有錯誤,再強制跑一次測試輪替:
sudo logrotate -f /etc/logrotate.d/myapp
ls -l /var/log/myapp
此時你會看到類似:
myapp.log
myapp.log-20251116
myapp.err.log
myapp.err.log-20251116.gz
注意: 這種情況下,如果程式持續執行,而且沒有重新開啟 log 檔, 它可能仍然寫在「已被輪替後的舊檔案」上。下一節會介紹如何讓 systemd 幫忙「通知服務」。
六、與 systemd 串接:postrotate 重新開啟日誌
典型做法有兩種:
- 程式本身支援 USR1 / HUP 等訊號來重新開啟 log。
- 在輪替後 重新啟動服務(適用於可短暫中斷的服務)。
1️⃣ 若程式支援訊號(推薦)
假設 myapp 收到 SIGUSR1 時會重新開啟 log 檔,我們可以這樣寫:
/var/log/myapp/myapp.log
/var/log/myapp/myapp.err.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 640 root root
dateext
sharedscripts
postrotate
# 請改成你的 service 名稱
systemctl kill -s SIGUSR1 myapp.service >/dev/null 2>&1 || true
endscript
}
2️⃣ 若程式不支援訊號:改用 restart
如果是小工具或非關鍵服務,你也可以選擇在輪替後重啟服務:
postrotate
systemctl restart myapp.service >/dev/null 2>&1 || true
endscript
搭配前一節內容,整體流程變成:
- logrotate 根據條件輪替
myapp.log。 - 輪替完成後,執行
postrotate裡的指令。 - systemd 通知服務重新開啟或重啟,之後便會寫入新的 log 檔。
七、用 systemd timer 管理 logrotate(取代 cron)
在較新的發行版中,logrotate 本身也已經被 systemd 接管(而不是傳統的 cron.daily)。
# 查看 logrotate timer
systemctl list-timers | grep logrotate
# 可看到類似:
# logrotate.timer logrotate.service ... Mon 03:10 ...
如果你的系統尚未使用 systemd timer,也可以自己建立:
# /etc/systemd/system/logrotate-custom.service
[Unit]
Description=Run logrotate custom config
[Service]
Type=oneshot
ExecStart=/usr/sbin/logrotate /etc/logrotate.conf
# /etc/systemd/system/logrotate-custom.timer
[Unit]
Description=Daily logrotate (custom)
[Timer]
OnCalendar=*-*-* 03:10:00
RandomizedDelaySec=300
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now logrotate-custom.timer
systemctl list-timers --all | grep logrotate
這樣就完成了「systemd → logrotate → 權限/壓縮」的一條龍排程。
八、常見錯誤與排錯方法
-
輪替沒有發生:
使用sudo logrotate -d /etc/logrotate.conf sudo logrotate -d /etc/logrotate.d/myapp-d(debug) 看 logrotate 如何判斷條件。 -
輪替後 log 檔仍持續變大:
代表程式仍然握著「舊檔案的 file descriptor」,確定:- 程式有實作「重新開啟 log」的邏輯。
- logrotate 的
postrotate有正確通知服務。
-
權限錯誤導致服務無法寫 log:
檢查 logrotate 的create設定(mode / owner / group),與 service 使用者是否一致。 -
logrotate 執行錯誤:
透過 journal 查詳細錯誤訊息。journalctl -u logrotate.service -xe
九、實戰情境範例:Web App / Nginx / Squid
1️⃣ 自製 Web 應用(Flask / Node.js 等)
- 把應用程式所有
stdout/stderr導向/var/log/myapp/*.log。 - logrotate 以
size 100M+rotate 10管理容量。 - 程式內實作收到
SIGUSR1時重新開啟 log 檔。
2️⃣ Nginx / Apache
- 這類服務通常已內建 /etc/logrotate.d/nginx / apache2 設定,可參考既有格式。
- 若有大量 vhost,可改成
weekly+dateext方便對應。 - 記得確認
postrotate內是否有nginx -s reopen或apachectl graceful。
3️⃣ Squid 或其他 Proxy / Cache
- Squid 會有自己的
access.log/cache.log;可依需求拆成不同輪替規則。 - 高流量環境可改用
size 50M或hourly的 OnCalendar(配合 systemd timer)。 - 輪替後記得讓 Squid 收到
USR1訊號重新開啟 log(或使用套件預設設定)。
🧭 行動清單:在自己主機上落地這套流程
✅ 建立一個以 systemd 管理的自製服務(ExecStart 已可正常運作) ✅ 建立專用的日誌目錄:/var/log/myapp,並設定適當權限 ✅ 在 service 中將輸出導向 myapp.log / myapp.err.log ✅ 撰寫 /etc/logrotate.d/myapp,設定輪替頻率、保留份數與壓縮方式 ✅ 使用 logrotate -d / -f 測試輪替流程,確認不會噴錯 ✅ 視情況在 postrotate 中加入 systemctl kill 或 restart,確保服務改寫新檔案 ✅ 檢查 systemd timer(logrotate.timer 或自訂 timer)是否正常啟動
🔗 延伸閱讀
最後更新:2025-11-16 · 作者:WWFandy
沒有留言: