Docker容器日志打满磁盘云服务器上怎么设日志轮转不影响业务
Docker容器日志把云服务器磁盘打满,先别急着删目录
Docker 容器日志打满磁盘,这个故障在云服务器上很常见,尤其是业务跑得久、应用又喜欢刷 stdout、stderr 的场景。典型表现是 df -h 看到根分区 100%,业务开始写文件失败,MySQL、Redis、Nginx 这类服务可能出现各种奇怪报错,甚至 SSH 登录都变慢。
实际使用中发现,很多人第一反应是去 /var/lib/docker/containers 下面删日志文件。这个动作要小心,直接 rm 掉正在被 Docker 打开的日志文件,不一定立刻释放磁盘空间,因为文件句柄还在。看起来文件没了,df -h 还是满的,最后还是要重启 Docker 或容器,业务影响更大。
Docker 默认使用 json-file logging driver,日志一般在这个路径:
/var/lib/docker/containers/
如果没有设置日志轮转,这个文件会一直涨。遇到 Java 异常堆栈、Nginx access log 打 stdout、游戏服连接日志刷屏、爬虫任务疯狂打印 debug,几十 GB 很快就没了。
先判断是不是 Docker 日志占满
不要一上来就改配置,先确认空间到底被谁吃了。常用命令:
df -h
du -h --max-depth=1 /var/lib/docker | sort -h
du -h --max-depth=1 /var/lib/docker/containers | sort -h
如果 containers 目录特别大,继续查具体容器:
find /var/lib/docker/containers -name "*-json.log" -type f -size +1G -exec ls -lh {} \;
也可以按大小排序:
find /var/lib/docker/containers -name "*-json.log" -type f -printf "%s %p\n" | sort -nr | head
这里补充一点,docker logs 命令看到的内容,基本就是来自这些 json.log 文件。应用打印得越多,这个文件涨得越快。
线上应急处理:用 truncate,不要直接 rm
如果磁盘已经 100%,业务写入受影响,要先把空间救回来。比较稳的做法是截断日志文件:
truncate -s 0 /var/lib/docker/containers/
如果要处理所有容器日志,可以这样:
find /var/lib/docker/containers -name "*-json.log" -type f -exec truncate -s 0 {} \;
这个动作不会重启容器,也不会改容器状态。它只是把日志文件大小清零,Docker 仍然可以继续往这个文件写。线上应急时,比 rm 文件安全得多。
多说一句,如果之前有人 rm 过日志文件,但磁盘空间没释放,可以用下面命令看是否有 deleted 文件还被进程占用:
lsof | grep deleted
如果看到 dockerd 或 containerd 还持有大文件,空间不释放就很正常。这个时候往往只能重启相关进程或容器,但这就涉及业务影响,不能随手操作。
根治方式:配置 Docker 内置日志轮转
Docker 自带日志轮转能力,不需要额外上 logrotate 去硬切 /var/lib/docker/containers 里的文件。推荐使用 json-file 的 max-size 和 max-file。
编辑 /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}
含义很直白:单个日志文件最大 100MB,最多保留 3 个文件。一个容器最多占用大约 300MB 日志空间。服务器上有 20 个容器,理论上 Docker 日志上限就是 6GB 左右,比无限增长可控很多。
配置完后检查 JSON 格式,别少逗号、别用中文引号。然后重载或重启 Docker:
systemctl daemon-reload
systemctl restart docker
这里要注意,重启 Docker 可能会影响容器,取决于 Docker 版本、live-restore 配置、容器运行方式。生产环境不要在业务高峰直接 systemctl restart docker。
daemon.json 对已有容器不一定立刻生效
这个坑线上很常见。daemon.json 里的 log-opts 是 Docker 的默认配置,通常对新创建的容器生效。已有容器很多情况下不会自动套用新的日志轮转参数。
所以改完 daemon.json 之后,要通过重新创建容器让配置真正生效。docker restart 只是重启容器,不等于重建容器。Docker Compose 场景一般需要重新 up:
docker compose up -d --force-recreate
如果是单容器 docker run 启动,就要按原参数重新 docker run。生产环境一定要提前把启动参数整理出来,不然临时重建时容易漏端口、漏 volume、漏 env。
不影响业务的做法:分批重建容器
线上业务不要一次性把所有容器都重建。比较稳的节奏是先截断日志释放空间,再上日志轮转配置,然后按业务模块分批重建。
比如一台云服务器上有 Nginx、API、worker、Redis、MySQL。处理顺序可以是 worker 先动,因为大多数 worker 有重试机制;API 有多副本就一台一台动;数据库这类状态服务要额外谨慎,能不动就先不动,或者放到维护窗口。
如果业务前面有 SLB、Nginx upstream、服务注册发现,先把要处理的实例从流量里摘掉,确认健康检查不再打进来,再重建容器。重建后看日志、端口、健康检查都正常,再放回流量。
实际使用中发现,真正造成影响的往往不是日志轮转本身,而是重建容器时没有确认启动参数。比如原来 docker run 里有 --ulimit、--add-host、--network、-v 映射,重建时少了一个,业务就表现异常。
Docker Compose 里直接写日志限制
如果业务是用 Docker Compose 管理,建议把日志配置写进 docker-compose.yml。这样迁移机器、重建容器时不容易漏。
示例:
services:
app:
image: your-app:latest
restart: always
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "3"
这个配置比只改 daemon.json 更明确。多人维护服务器时,看 compose 文件就知道日志策略,不用猜 Docker daemon 默认值。
如果是 access log 特别大的服务,比如 Nginx 容器,max-size 可以设小一点,比如 50m,max-file 设 5。这样保留最近一段时间日志,单个容器上限约 250MB。
max-size 和 max-file 怎么选
参数不要拍脑袋,得看业务日志量、排障窗口、磁盘大小。比如一台 80GB 系统盘的云服务器,Docker、镜像、数据库、应用文件都在根分区,日志最好不要让它超过 10GB。
常见取值可以这样看:
开发测试环境:max-size 20m,max-file 3,主要防止调试日志刷爆盘。
普通 Web API:max-size 100m,max-file 3,单容器约 300MB,排查最近问题够用。
高并发网关、Nginx、游戏接入层:max-size 100m,max-file 5 或 10,但建议同时把关键访问日志送到 ELK、Loki、SLS 这类系统。
长周期审计日志:不要只依赖 Docker 本地 json-file,应该写到专门日志系统或对象存储。本地日志轮转的目标是保护磁盘,不是做长期留存。
为什么不建议只靠系统 logrotate
logrotate 本身没问题,但直接对 Docker 的 json.log 做 rotate,容易出现 Docker 仍然写旧文件、copytruncate 时短暂丢日志、配置路径随容器变化不好维护等问题。
Docker 已经知道自己的日志文件在哪里,也知道什么时候该切新文件,用 Docker 内置 rotation 更自然。除非有特殊历史原因,不建议再从系统层硬切 Docker 容器日志。
这里补充一点,宿主机系统日志还是要配置 logrotate,比如 /var/log/messages、/var/log/syslog、journalctl。Docker 日志和系统日志是两条线,别只处理一边。
journald 也可能吃磁盘
有些系统不是 Docker json-file 独大,而是 systemd journal 也在涨。检查命令:
journalctl --disk-usage
可以临时清理:
journalctl --vacuum-size=1G
也可以配置 /etc/systemd/journald.conf:
SystemMaxUse=1G
SystemKeepFree=2G
改完重启 journald:
systemctl restart systemd-journald
如果 Docker 使用 journald logging driver,那容器日志也会进入 journald。这个玩法不是不行,但要把 journald 的容量限制设好,否则只是把 json.log 打满磁盘,换成 journal 打满磁盘。
云服务器磁盘规划也要跟日志策略一起看
日志轮转能限制增长,但不能替代磁盘规划。实际线上遇到过 40GB 系统盘跑十几个容器,镜像层、overlay2、日志、临时文件都在一个盘里,稍微发布几次镜像就开始报警。Docker 环境建议系统盘别太小,或者把 /var/lib/docker 单独挂到数据盘。
如果是游戏、企业应用、高防入口这类场景,机器选型时就要把日志、镜像、业务数据分开考虑。如果你也在找这种稳定云服务器、高防服务器或者海外节点,可以看看129云。比如国内电信业务可以看金华电信-A型,4C 4G、1TB 硬盘、100Mbps 独享带宽,还有 100Gbps 防御;香港高防-D型适合需要香港入口和 DDoS 防护的业务,16C 16G、180G SSD、350Mbps 峰值带宽、200Gbps 单机防御。需要确认线路和防御策略,可以直接打客服热线 400-9177118。
把 /var/lib/docker 挂到独立数据盘
如果服务器已经上线,后面想把 Docker 数据目录迁到数据盘,要比清日志谨慎得多。基本思路是停 Docker、同步数据、修改 data-root、再启动 Docker。
daemon.json 示例:
{
"data-root": "/data/docker",
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}
迁移前要确认磁盘挂载写进 /etc/fstab,不然重启后 /data 没挂上,Docker 可能在空目录里启动,容器看起来像“全没了”。这类事故并不少见。
迁移 Docker data-root 会影响业务,适合维护窗口做。日志轮转可以在线逐步处理,但 data-root 迁移不是一个级别的操作。
应用侧也要少打印无意义日志
只在 Docker 层限制日志大小,能防止磁盘爆掉,但不解决日志过多的问题。应用如果每个请求都打印大 JSON、每次循环都打 debug、异常重试无限刷堆栈,日志系统和磁盘都会被拖累。
生产环境建议默认 INFO,排障时短时间打开 DEBUG。高频日志要采样,比如每 1000 次打印一次统计值。请求体、响应体、用户 token、Cookie 这类内容不要随便打,既占空间,也有安全风险。
容器里如果同时写文件日志和 stdout,也要看清楚。很多应用既把日志写到 /app/logs,又输出到 stdout,等于两份日志一起涨。Docker 日志轮转只能管 stdout、stderr,不会管容器内部挂载目录里的业务日志。
排查 overlay2,不要误判成日志问题
/var/lib/docker 很大,不一定全是 containers 日志。overlay2 也可能很大,尤其是镜像频繁发布、构建缓存多、容器写入层膨胀。
查看 Docker 占用:
docker system df
清理不用的镜像、停止容器、构建缓存:
docker system prune
清理更激进一点会带 -a:
docker system prune -a
这个命令要谨慎,-a 会删除未被容器使用的镜像。线上如果回滚依赖旧镜像,清完后可能要重新拉取,遇到镜像仓库慢或者网络抖动,恢复时间会变长。
监控要盯两个指标
日志轮转配完,不代表以后不用管。至少要监控根分区使用率和 inode 使用率。磁盘空间没满但 inode 用完,也会导致创建文件失败。
检查 inode:
df -i
Docker 场景里,小文件太多、临时目录没清理、某些应用疯狂生成缓存文件,都可能把 inode 打满。日志轮转主要解决大文件增长,不解决海量小文件。
报警阈值可以设得直接一点:磁盘使用率超过 80% 告警,超过 90% 升级;inode 超过 80% 告警。云服务器上根分区一旦到 95%,留给排障的空间就很小,很多命令都可能因为无法写临时文件而失败。
线上操作顺序可以这样执行
当前磁盘已经满:先用 find 找出大日志文件,用 truncate 清空,确认 df -h 空间释放。
确认 Docker 日志策略:检查 /etc/docker/daemon.json,有没有 log-driver、max-size、max-file。
新容器生效:修改 daemon.json 或 Docker Compose logging 配置。
已有容器生效:按业务影响面分批 force recreate,不要一把梭。
观察效果:docker inspect 容器,看 HostConfig.LogConfig 是否带上 max-size、max-file。
命令示例:
docker inspect
如果返回里能看到类似:
{"Type":"json-file","Config":{"max-file":"3","max-size":"100m"}}
说明这个容器的日志轮转参数已经挂上了。
容器日志特别重要时,不要只留本地
本地轮转会删除旧日志,这是它的工作方式。业务如果要求查 7 天、30 天甚至更久的日志,本地 json-file 不适合承担这个任务。
比较常见的做法是应用 stdout 给 Docker,本地只保留最近几百 MB,同时用 Fluent Bit、Filebeat、Promtail 这类采集到远端日志系统。这样云服务器磁盘不会长期堆日志,排障时也不用 SSH 到每台机器 grep。
如果日志量很大,采集端也要限速和缓冲。日志系统故障时,采集器不能反过来把本机磁盘写满。这个坑在高并发业务里也会出现,尤其是突发 DDoS、CC 攻击期间,access log 暴涨,日志链路压力会跟着上来。
一个容易忽略的参数:live-restore
如果希望重启 Docker daemon 时尽量不影响正在运行的容器,可以了解 live-restore:
{
"live-restore": true
}
它的作用是 dockerd 重启时,尽量保持容器进程继续运行。但它不是万能保险,网络、插件、存储驱动、Docker 版本都会影响实际表现。生产环境启用前要在同版本测试机验证,别把它当成随便重启 Docker 的理由。
日志轮转这件事,最稳的方式仍然是新容器配置好,已有容器分批重建。遇到已经打满磁盘的机器,用 truncate 先止血,再慢慢处理配置和重建。