Máy chủ hết đĩa. Bạn chạy docker system df, chạy docker ps -s, không thấy gì bất thường. Xoá image cũ, dọn volume, vẫn hết đĩa.
Tôi cho một container in ra 600.000 dòng, mỗi dòng 100 ký tự — tổng cộng khoảng 60 MB văn bản:
| Cách đếm | Con số |
|---|---|
| Tệp log thật trên đĩa | 97,2 MB |
docker ps -s báo |
4,1 kB |
Log của container không nằm trong bất cứ con số nào mà các lệnh đếm dung lượng của Docker đưa ra. docker ps -s đếm lớp ghi của container, docker system df đếm image, container và volume. Log nằm ngoài cả ba.
Vì sao 60 MB thành 97 MB
Driver mặc định là json-file, và nó bọc từng dòng vào một đối tượng JSON:
{"log":"xxxxxxxxx...\n","stream":"stdout","time":"2026-08-25T08:48:17.301469845Z"}
Chi phí cố định đo được là 69 byte cho mỗi dòng, bất kể dòng dài bao nhiêu. Nghĩa là hệ số phồng phụ thuộc hoàn toàn vào độ dài dòng log của bạn:
| Độ dài dòng | Dữ liệu gốc | Tệp log | Hệ số |
|---|---|---|---|
| 20 ký tự | 2,00 MB | 8,57 MB | 4,28× |
| 100 ký tự | 9,63 MB | 16,20 MB | 1,68× |
| 500 ký tự | 47,78 MB | 54,36 MB | 1,14× |
Ứng dụng nào ghi log ngắn — OK, ping, 200 — trả giá đắt nhất. Một dịch vụ health-check ghi healthy mỗi giây tạo ra khoảng 76 byte log cho 7 byte thông tin.
Con số 69 byte đó cũng gần như không nén được ở dạng này, vì mốc thời gian nano giây khác nhau từng dòng.
Cái bẫy thật: mặc định KHÔNG xoay vòng
Đây mới là chỗ nguy hiểm. json-file mặc định không có giới hạn nào cả. Tệp log lớn tới khi nào container còn chạy, hoặc tới khi đĩa đầy.
Container crash-loop ở phần 28 là kịch bản kinh điển: nó in cùng một stack trace mỗi lần khởi động lại, hàng chục nghìn lần, suốt cuối tuần.
Bật xoay vòng bằng hai tuỳ chọn:
docker run --log-opt max-size=1m --log-opt max-file=3 ...
Đo lại với đúng 100.000 dòng (16 MB log nếu để tự do):
998240 ...-json.log
1000110 ...-json.log.1
1000110 ...-json.log.2
TONG: 3,0M
Đúng 3 MB, không hơn. Đổi lại docker logs chỉ còn đọc được 17.638 / 100.000 dòng — 17,6% gần nhất. Đó là đánh đổi cố ý: giữ đĩa, mất lịch sử. Nếu cần lịch sử thì phải gửi log đi nơi khác, không giữ trong container.
Driver local tốt hơn ở gần như mọi mặt
Docker có một driver tên là local mà rất ít người dùng. Cùng 600.000 dòng đó:
| Driver | Trên đĩa | Có xoay vòng sẵn? | docker logs đọc được? |
|---|---|---|---|
json-file (mặc định) |
97,2 MB — một tệp | Không | có |
local |
15,0 MB | Có, và nén | có |
Nhỏ hơn 6,5 lần. Thư mục log của local trông thế này:
13285089 container.log
564987 container.log.1.gz
563869 container.log.2.gz
563523 container.log.3.gz
Nó lưu ở dạng nhị phân thay vì JSON (nên không có 69 byte thừa mỗi dòng), tự xoay vòng theo mặc định, và nén gzip các tệp cũ — 20 MB xuống còn 0,56 MB, tức khoảng 36 lần.
Điều quan trọng: docker logs vẫn hoạt động bình thường với local. Tôi đọc lại đủ 100.000 dòng.
Vậy vì sao json-file vẫn là mặc định? Vì định dạng của nó ổn định và nhiều công cụ ngoài đọc thẳng tệp đó. local cố tình không cam kết định dạng — chỉ docker logs mới đọc được. Nếu bạn có agent thu log đọc trực tiếp /var/lib/docker/containers/*/*-json.log, đổi sang local sẽ làm agent đó mù.
Ghi log tốn bao nhiêu thời gian?
Ít hơn bạn tưởng. 600.000 dòng, trung vị của 3 lần:
| Driver | Thời gian |
|---|---|
none (vứt bỏ hết) |
0,66 s |
json-file |
0,76 s |
local |
0,78 s |
Chênh lệch khoảng 0,1 giây cho 600.000 dòng — chưa tới 0,2 micro giây mỗi dòng. Với local, chênh lệch nằm trong dao động giữa các lần chạy (0,69–0,89 s).
Kết luận thực dụng: đừng tắt log vì sợ chậm. Vấn đề của log là dung lượng đĩa, không phải tốc độ. Nếu bạn từng nghĩ tới --log-driver none để tăng hiệu năng, hãy bật xoay vòng thay vào đó.
Với none, docker logs trả về lỗi thẳng thừng:
Error response from daemon: configured logging driver does not support reading
Cũng đúng như vậy với syslog, gelf, fluentd, awslogs — log đi thẳng ra ngoài, daemon không giữ bản sao nào.
Đổi tuỳ chọn phải tạo lại container
Cấu hình log được cố định lúc tạo container. docker update không có bất cứ tuỳ chọn log nào (tôi đếm được đúng 0 dòng trong docker update --help). Sửa nghĩa là docker rm rồi run lại, hoặc docker compose up --force-recreate.
Cách đỡ đau nhất là đặt mặc định cho cả máy chủ trong /etc/docker/daemon.json:
{
"log-driver": "local",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
Rồi systemctl restart docker. Từ đó mọi container mới thừa hưởng cấu hình này; container đang chạy giữ nguyên cái cũ cho tới khi được tạo lại.
Trong Compose thì khai theo từng service:
services:
web:
image: vi-du:1.0
logging:
driver: local
options:
max-size: "10m"
max-file: "3"
Thử ba mươi giây
Tìm những container đang ăn đĩa bằng log, ngay trên máy chủ của bạn:
for c in $(docker ps -q); do
p=$(docker inspect "$c" --format '{{.LogPath}}')
[ -n "$p" ] && [ -f "$p" ] && printf '%8s %s\n' \
"$(du -h "$p" | cut -f1)" "$(docker inspect "$c" --format '{{.Name}}')"
done | sort -rh | head
(Trên Docker Desktop macOS/Windows, daemon chạy trong máy ảo nên LogPath không nhìn thấy từ shell của bạn — lệnh trên dành cho Linux, hoặc chạy trong máy ảo.)
Ba dấu hiệu đáng xử lý ngay:
- Tệp log lớn hơn image của chính container đó.
docker inspect --format '{{.HostConfig.LogConfig.Config}}'trả vềmap[]— nghĩa là không có giới hạn nào.- Container
restartingkèm log khổng lồ — sửa nguyên nhân hỏng trước, xoay vòng sau.
Phần sau bật --read-only lên các image quen thuộc và đo xem cái gì gãy.