Container 重啟之後 log 就消失了,這不是 bug,是設計如此。但真正危險的問題不是這個——是你的 container 還活著,log 卻默默在背景把整顆磁碟填滿,直到某天凌晨三點服務開始掛掉才發現。
Docker 預設的 log 行為在開發環境完全合理,到了生產環境卻是個定時炸彈。這篇文章就從這裡開始談起。
Docker 預設到底做了什麼
Docker 用 json-file 作為預設的 log driver。每個 container 的 stdout/stderr 都會被寫進 host 上的一個 JSON 檔案,路徑長這樣:
1 | /var/lib/docker/containers/<container_id>/<container_id>-json.log |
問題在於:預設沒有大小限制,也沒有自動 rotation。
一個流量不算大的 web 服務,每天輸出幾百 MB log 是很正常的事。跑一個禮拜,幾 GB 就不見了。跑一個月,磁碟空間直接見底。更糟的是,這個檔案不會觸發任何警告,你不主動去看根本不知道它有多大。
用這個指令可以先查一下現況:
1 | du -sh /var/lib/docker/containers/*/\*-json.log | sort -rh | head -20 |
如果你從來沒設定過 log rotation,跑一下這個指令大概會嚇到。
先從 daemon 層級設定全局預設值
最省力的做法是在 /etc/docker/daemon.json 設定全局預設值,這樣之後啟動的每個 container 都會自動套用:
1 | { |
這三個參數的意思:
max-size: 每個 log 檔案超過 20MB 就 rotatemax-file: 最多保留 5 個舊檔案(加上當前的,總共 6 個)compress: rotate 後壓縮舊檔案,節省磁碟空間
改完之後要重啟 Docker daemon:
1 | sudo systemctl restart docker |
**注意:這個設定只對之後新建的 container 有效。**已經在跑的 container 不會套用,必須重建才行。這是一個常見的誤區——改了 daemon.json 之後以為沒生效,其實是舊 container 繼承了舊設定。
json-file 以外的選擇
json-file 適合簡單的部署情境,但它有一個根本限制:用了某些 log driver 之後,docker logs 指令就沒辦法用了。這對除錯影響很大,所以換 driver 之前要想清楚。
幾個常見 driver 的實際取捨:
local driver:比 json-file 更緊湊的儲存格式,內建 rotation,也支援 docker logs。如果你只是想解決磁碟問題又不想引入額外元件,直接換成 local 是最省事的選擇。
1 | { |
journald driver:把 log 交給 systemd journal 管理。如果你的 host 本來就在用 systemd,這個整合很自然,journalctl 可以直接查。但跨主機查 log 就麻煩了。
fluentd / fluent-bit driver:把 log 串流到外部 aggregator,適合多台主機的集中式 log 架構。設定較複雜,但如果你已經在用 ELK 或類似的 stack,這是正確的接入點。
none driver:完全不記 log。偶爾用在那種輸出量超大、log 內容又完全沒有參考價值的 container 上(例如某些 metrics exporter)。正常的應用服務絕對不要用。
輕量集中式 log:Loki + Promtail
如果只有一兩台伺服器,跑完整的 ELK stack(Elasticsearch + Logstash + Kibana)資源成本太高,很多人乾脆放棄集中式 log。但其實有更輕量的選擇:Grafana Loki。
Loki 的設計思路跟 Prometheus 類似——不對 log 內容建立全文索引,只對 label 做索引。這讓它的資源需求遠低於 Elasticsearch,一台 2 核 2GB 的 VPS 就能跑起來。
用 Docker Compose 快速部署 Loki + Grafana 的範例:
1 | services: |
Promtail 直接讀取 Docker 的 log 檔案並推送到 Loki,Grafana 負責查詢介面。這個組合適合中小規模的部署,比 ELK 簡單很多。
Loki 有個使用習慣要注意:它的查詢語言 LogQL 跟 SQL 不同,過濾條件要靠 label,而不是全文搜尋。如果你習慣 Elasticsearch 那種什麼都能搜的方式,Loki 一開始可能不太順手。但只要 label 設計合理,查詢速度其實很快。
Docker Compose 裡的 log 設定
如果你用 Docker Compose 管理服務,可以在 compose.yml 裡直接指定每個服務的 log 設定,覆蓋全局預設值:
1 | services: |
不同服務的 log 量差異可能很大,web 服務和背景 worker 的需求通常不同,分開設定比較合理。
已經滿了怎麼辦
如果磁碟已經告急,緊急清空特定 container 的 log:
1 | truncate -s 0 /var/lib/docker/containers/<container_id>/<container_id>-json.log |
truncate 的好處是不需要重啟 container,檔案 inode 還在,Docker daemon 繼續正常寫入。直接刪掉檔案的話,Docker 可能繼續寫進一個已刪除的 fd,磁碟空間不會真的釋出,直到 container 重啟。
這只是應急手段,不是長期策略。清完之後馬上把 daemon.json 設定補上,才是正確的做法。
一個值得做的監控
在 log rotation 設定到位之後,還建議加一個磁碟使用率的監控。log 管理設定正確,但應用突然出現 bug 狂打 error log 的情況不是沒有發生過。預警機制遠比半夜被告警叫醒好處理。
一個簡單的 shell script 可以做到基本的磁碟監控,搭配前一篇文章提到的 Telegram Bot 推送通知,就是一套夠用的輕量監控方案。
如果你在找一個磁碟夠用、I/O 效能穩定、又不用擔心鄰居搶資源的 VPS 環境來跑這些服務,NCSE Network 的 VPS 方案採用 NVMe SSD 搭配臺灣是方電訊機房,log 寫入量大的工作負載也跑得住。詳細規格可以到 ncse.tw 查看。