NAS的Docker日志半年吃了40个G怎么办?我按三步上锁,从此再没碰过这颗雷

上个月我妈家那台备份NAS给我发了条告警,存储池占用92%。我远程连上去df -h一看,4T的池子只剩300多个G,可我记得明明才用了两个多T。全家福和数据库一根手指头都不敢碰,我挨层du下去,兜兜转转在大头里翻到个熟面孔——Docker那一摊占了40多个G,其中qBittorrent一个容器的日志文件,自己就写了8个多G。这病我在自己机器上治过一回,当时记在存储池满了别乱删那篇里,这次在别人的机器上又撞见,说明它真不是小概率。这篇就把Docker日志这颗雷从头拆一遍:为什么它会长这么大、怎么清才真的释放空间、怎么上锁让它永远长不大。
先说清楚:日志为什么能长到几个G
很多人以为容器日志跟普通软件日志一样,攒点文本撑死几百兆。Docker的坑在于它的默认行为:json-file日志驱动不限制大小,容器跑多久日志就攒多久,一个文件从头写到尾。保种客户端那种每隔几秒刷一屏的日志量,半年就是8个G的量级——这不是夸张,是我df和du两头对出来的实数。
更要命的是这事特别隐蔽。你在飞牛或者群晖的界面上看容器,只看得到「运行中」三个字,日志文件躺在/var/lib/docker/containers/那串哈希目录底下,界面上一行体积都没给你显示。存储池慢慢变红,你的第一反应是删老电影、清回收站,轮不到怀疑Docker——毕竟在我妈那台机器上,日志膨胀的锅是qB-ittorrent背的,但rsync那台备份机的日志一样在悄悄长,只是它刷得慢,两年也才2个G。两台机器两种速度,结论一样:不上锁,每个容器都是一颗定时炸弹,引线长短不同而已。
顺带说一句,Docker吃盘的不止日志一个。历次容器升级攒下来的旧镜像、停了没删的废弃容器、悬空卷,加起来经常比日志还肥。那部分账我在容器升级那篇里算过,这里不重复展开,本文只钉日志这一颗雷。
第一步:找出哪个容器在偷吃,df和du两头对账
定位的思路跟我在存储池满了那篇写的总顺序一样:先看总量,再挨层找大头。SSH进NAS(SSH怎么开、22端口转发的坑,见开启SSH那篇),两条命令起步:
df -h先看池子还剩多少,心里有个总账。docker system df再看Docker内部账,镜像、容器、卷各占多少一屏看完。注意这一屏里容器那行算的是可写层,日志多数情况下不在里面单列,所以看到容器只占几百兆别急着排除嫌疑,得继续往下钻:
du -sh /var/lib/docker/containers/*/*-json.log 这条会列出每个容器的日志文件体积,按大小排个序,谁在偷吃一目了然。我妈那台机器上,qBittorrent的日志8.4G排第一,第二名是rsync的2.1G,剩下七个容器加起来不到500M——典型的「一两个惯犯+一群良民」结构。你要是看到日志列表整体均匀地大,那得回头查是不是有个统一刷屏的根源(比如某个容器在报错重试循环),先治根再清日志,不然清完隔天又长回来。
第二步:清日志有个坑,rm删了空间不释放
找到惯犯,顺手rm那个日志文件?我第一次就是这么干的,删完df -h一看,占用纹丝不动,当场就懵了。后来才明白:容器正在跑的时候,日志文件被进程握着句柄,你rm掉的是目录条目,文件本体还在被引用着,空间不释放。得用truncate清空,文件还在、句柄还握着,但内容归零:
truncate -s 0 /var/lib/docker/containers/<容器ID>/<容器ID>-json.log 我妈那台机器上,我对着8.4G那条日志truncate了一下,df -h立刻回来8个G,池子从92%掉到90%——数字不大,但这是立刻可见的释放,比删十部电影踏实多了。写这篇的时候翻了下记录,qBittorrent那次日志里刷得最凶的是RSS拉取的重复条目,纯粹是配置写歪导致的刷屏,这种根源问题顺手治掉,日志长得能更慢一点。
嫌挨个文件麻烦的,有偷懒写法一条循环搞定,但我不直接给——因为良民容器的日志其实有排查价值,我在容器起不来排查那篇里写过,翻容器日志是定位启动失败的第一现场。一刀切全清等于把病历本烧了,下次出事没得翻。我自己的习惯是超过1G的才动手,小的留着。
第三步:上锁,daemon.json给日志封顶
清完只是止血,治本是给日志上尺寸上限。Docker有个全局配置文件daemon.json,飞牛的Docker设置界面里能直接配,走命令行的改/etc/docker/daemon.json,加两行log-opts:
"log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3" } 意思是每个容器的日志单文件最大100M、最多留3份,写满自动轮转,300M封顶。qBittorrent那种半年8个G的刷法,上锁之后永远停在300M。100m和3这两个数字我是按自己机器的量拍的:日志要留够排查用的量,又不能大到心疼盘。你机器小、日志刷得慢,max-file设2也行,省100M。
改完重启Docker服务生效。这里有个大多数人栽的坑,我必须写粗:这两行只对新建的容器生效,老容器不吃新规则。老容器得删掉重建一次才吃上锁参数——听到「删容器」别慌,删的是容器壳,你挂载出来的数据卷、配置目录一根毛都不动,这是Docker的设计本意,我在容器升级那篇里掰开讲过参数怎么抄怎么重建。用Compose装的更省心,配置文件里躺着的参数随拉随建,Compose怎么把容器管得服服帖帖,见Docker Compose那篇。
还有个更细的锁眼:给单个容器单独设上限。daemon.json管的是全局默认,个别容器想在命令行单独覆盖,docker run的时候加--log-opt max-size=50m,比全局还紧。我给家里的qBittorrent单独上了50m——它是惯犯,得戴紧箍。
我妈那台机器的收尾账
整套流程在她那台备份机上跑下来:truncate清了10个G出头(qBittorrent的8.4G加rsync的2.1G),顺手把停了半年的废弃容器和悬空镜像prune了一波又回来7个G,docker system df从40多G瘦到23G,池子占用从92%回到86%,告警解除。日志上锁做完、老容器重建完之后,我又盯了半个月,Docker那摊的体积一格没涨——上锁前后的差别就在这,以前是颗随时在长大的雷,现在是颗死雷。
有个连带收益没想到:夜里硬盘的休眠比以前安稳了。日志在写,硬盘就睡不踏实,8.4G的日志哪怕慢慢写也是持续的写动作,偷写盘把休眠搅黄这事我在休眠失败排查那篇里点过名。上锁之后单文件100M写完就轮转,写密度降下来,我妈那台机器的盘真睡上了整觉。定时任务也别跟日志上锁后的安静期打架,自动更新和快照怎么排时段,参考要不要定时重启那篇里的时段账。
什么人该立刻做一遍这套,我按踩过的坑收个表:
| 你的情况 | 该做什么 |
| 存储池占用莫名涨、找不到大头 | du那命令先扫日志,多半有惊喜 |
| 容器跑了大半年没动过 | 先truncate再上锁,两件事一次做完 |
| 正在跑的容器日志不敢删 | 用truncate,别用rm,句柄还在就不释放 |
| 改了daemon.json没效果 | 老容器要删掉重建才吃新规则,不是没生效 |
| 用Compose装的容器 | 改配置里的日志参数,重建最省事 |
一句话收束:Docker日志这颗雷的拆法就三步——du找出惯犯、truncate止血、daemon.json上锁外加老容器重建一次。新容器从第一天就吃锁,你根本不会有机会遇上「池子莫名满了」这种案子。还在Docker门口张望的,先看Docker入门那篇把镜像、端口、挂载三个词搞明白,日志这块属于进阶课,进门之后再回来不迟。

发表评论