熱門分類
載入中…
目錄0%

🐧 Linux systemd + logrotate 實戰:從開機服務到日誌輪替的一條龍管理

    🐧 Linux systemd + logrotate 實戰:從開機服務到日誌輪替的一條龍管理

    在現代 Linux 伺服器上,我們多半使用 systemd 來管理服務啟動、監控狀態與自動重啟, 同時用 logrotate 來處理 /var/log/*.log 的輪替與壓縮。 如果兩者沒有設計好,就會遇到「服務跑得好好的,但日誌無限長大、吃滿磁碟」的情況。
    本篇從 範例服務 開始,一路做到 開機自動啟動 + 標準化輸出 + logrotate 日誌輪替 + systemd timer 自動排程, 給你一套可以直接複製的「一條龍」管理流程。

    📑 目錄

    一、使用情境與設計目標

    先定義我們要解決的典型情境:

    • 你有一個自製的 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 管理,可以:

    1. 在 service 裡直接把輸出導向檔案。
    2. 或在程式內自行打開檔案(範例採用第 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.logmyapp.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 重新開啟日誌

    典型做法有兩種:

    1. 程式本身支援 USR1 / HUP 等訊號來重新開啟 log。
    2. 在輪替後 重新啟動服務(適用於可短暫中斷的服務)。

    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

    搭配前一節內容,整體流程變成:

    1. logrotate 根據條件輪替 myapp.log
    2. 輪替完成後,執行 postrotate 裡的指令。
    3. 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 執行錯誤:
      journalctl -u logrotate.service -xe
      透過 journal 查詳細錯誤訊息。

    九、實戰情境範例: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 reopenapachectl graceful

    3️⃣ Squid 或其他 Proxy / Cache

    • Squid 會有自己的 access.log / cache.log;可依需求拆成不同輪替規則。
    • 高流量環境可改用 size 50Mhourly 的 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

    🔗 分享這篇 LINE Facebook X

    沒有留言:

    字級