
凌晨两点被磁盘告警电话叫醒登录服务器一看数据盘使用率已经飙到96%。不用多想又是Nacos日志清理脚本这类需求该安排上了——尤其是“保留最近14天日志”这个指标在微服务体系里几乎成了标配。先声明这不是一篇“复制粘贴就能跑”的敷衍文章我会把日志文件来源、为什么不能直接rm、find参数怎么定、定时任务怎么落地、出问题怎么排查串起来讲清楚把这几年在Nacos日志清理上踩过的坑一次说透。长话短说如果你是运维或后端开发正在为Nacos日志疯狂占磁盘发愁这篇文章就是给你准备的。看完你不仅能写出一份能上生产环境的清理脚本还能理解每条命令背后的原理遇到诡异情况也能自己排。1. 磁盘告警那晚Nacos日志到底是怎么把空间吃掉的很多人以为Nacos日志就是start.out实际上Nacos的日志体系比想象中复杂。我见过最夸张的一台2C4G的测试机跑了一个月Nacos集群日志占了快30Gstart.out连零头都算不上。真正的大头是Nacos内部组件各自输出的日志文件。1.1 日志不是只有start.out还有一堆你没注意过的文件以Nacos 2.x为例日志默认落在${nacos.home}/logs目录下常见的文件有这几类文件或模式来源组件特点start.out启动脚本stdout/stderr重定向通常比较大但不在logs目录下access_log.yyyy-MM-dd.logHTTP请求访问日志按天滚动文件名带日期naming.log/naming.log.yyyy-MM-dd.log服务注册发现模块当前活跃文件历史滚动文件config.log/config.log.yyyy-MM-dd.log配置中心模块同上cluster.log集群节点通信有时会刷新很频繁alipay-jraft.logJRaft协议层选举和复制日志容易爆炸embedded-derby.logDerby嵌入式存储仅当使用内嵌存储时存在nacos_gc.logJVM GC日志滚动依赖HeapDump配置这套文件基本对应了Nacos的注册中心、配置中心、JRaft共识协议三大模块。最气人的是alipay-jraft.log它不像access_log那样按天切而是单个文件增长日志级别一开DEBUG几天就能给你写满整个磁盘。所以写清理脚本时别只盯.log通配符文件名规则每个版本都可能不一样要看清实际目录再动手。1.2 哪些日志文件才是真正的“空间杀手”实际生产环境里我最怕的不是naming.log而是access_log和alipay-jraft.log。access_log每天都会记录所有HTTP请求包括客户端注册、心跳、配置拉取、控制台操作。一个中型微服务集群如果有几百个服务实例每30秒心跳一次一天下来的access_log可以轻松到几百MB甚至上G。想象一下每天一个几百MB的文件14天就是10多个G这还只是一个节点。如果集群有三台、五台节点容量就成倍翻。alipay-jraft.log比较特殊它记录Raft协议层的状态变化。节点选举、leader切换、日志复制失败重试都会写它。一旦网络抖动或节点频繁加入这个文件会异常膨胀。我见过某个环境里它一天从200M涨到2G。为什么提它因为很多人写清理脚本时只命中了access_log和naming.log把alipay-jraft漏了结果磁盘还在悄悄变小。1.3 增长异常的信号短时间内刷出几个G的场景除了正常的按天滚动有几个场景会让Nacos日志短时间暴涨。第一控制台或客户端频繁发起配置变更config.log会持续写入。第二服务节点大批量上下线naming.log和alipay-jraft会跟着刷。第三权限校验失败反复报错access_log会记录大量401响应。第四最典型的是Nacos和客户端版本不兼容导致心跳响应异常客户端疯狂重连access_log每秒几十行“request error, please try again later!”这个报错也是官网常见热搜词之一我后面会专门提排查思路。所以判断一个清理脚本合不合格不是看它能不能删文件而是看它能不能在日志暴涨时仍稳定工作。基于这个目标我们设计的脚本必须只处理历史文件绝不动正在被写入的活跃文件。2. 直接执行rm不行的真实原因被进程占用的文件删了也不释放先说结论动态删除了一个正在被Nacos写入的日志文件磁盘空间不会立即释放。这不是脚本写得不对而是Linux的文件删除机制在“骗”你。很多新手第一次遇到这个问题时会下意识认为是服务器出故障了。2.1 文件删除与空间释放的“两个时间点”在Linux里一个文件被删除其实涉及两个步骤移除目录项unlink和释放inode与数据块。只有当文件的引用计数降为0时数据块才会真正归还文件系统。如果某个进程已经打开这个文件持有file descriptor即使你执行rm -f内核仍然认为这个文件还被使用中数据块就不会释放。可以把这个过程类比成酒店退房你在前台把房卡退了rm但房客还住在里面进程持有fd酒店就不能把这个房间重新卖出去。磁盘空间对于正在运行的Nacos来说就像那位还在房间里的住客得等住客出门——也就是进程关闭fd或退出——房间才能腾出来。2.2 验证实测lsof N 1能看到什么判断是否有进程占用已删除的日志文件最常用的命令是lsof。lsof L1可以列出所有被删除但仍被进程打开的文件。我一般这样用lsof L1 | grep nacos输出的文件中你会看到naming.log路径后面跟着一个(deleted)标记。这说明这个文件已经被rm了但因为应用进程还在写入空间还没释放。如果想定位得更准也可以这样lsof -p $(pgrep -f nacos | head -n 1) | grep deleted-p指定进程PIDgrep deleted过滤被删除文件。看到结果的那一刻你就能理解不是rm没用是内核觉得文件还有“主人”。2.3 为什么清理脚本反而不能重启Nacos来解决有朋友会想既然重启能释放所有被占用的fd那我每天凌晨重启一次Nacos不就行了这种方式确实能把空闲的空间还回来但代价很大。Nacos作为注册中心和配置中心重启意味着所有客户端断连触发一遍重新注册和配置拉取虽然阈值内影响可控但生产环境这种操作终究是冒风险的。更合理的方案是脚本只删除已经不再被写入的历史日志。Nacos当前活跃日志是naming.log、config.log这类不带日期或带当日日期的最新文件它们保留带历史日期的旧文件即使还被某个fd引用也不影响——因为进程不会再往那些文件里写数据了文件内容已经定格。删除它们之后只要对应fd一直没关闭空间还是不会释放。这个问题怎么解决答案很简单别让活跃fd指向旧文件。Nacos自己的Logback滚动机制会在跨天时创建新文件并切换fd所以历史文件此刻已经没有活跃fd了。换句话说凌晨跑清理脚本删除的是“已经被Logback自己归档、不再使用的旧文件”这时unlink之后空间能正常释放。如果担心某些组件没有按天滚动比如alipay-jraft.log是单文件滚动那判断方式就要看mtime而不是只看文件名是否带日期。3. 保留14天的清理脚本从find参数到完整代码脚本本身不复杂难的是边界条件设计。以下是我在线上跑了两年多的版本核心逻辑是定位到Nacos日志目录找出所有按日期滚动、且mtime超过14天的历史日志文件逐一删除并记录删除结果。3.1 核心命令find -mtime 14拆解find的-mtime参数是按“文件内容最后修改时间”来过滤的单位是天。注意-mtime 14表示“超过14天”-mtime 14表示“正好14天”-mtime -14表示“14天以内”。我们只清理超过14天的也就是字面意思的“保留最近14天日志”。为什么用14而不是14因为14是严格大于更符合“超过14天就删”的语义。使用-mtime有个容易被忽略的点它受文件系统时间和系统时间影响。如果服务器时区错了或clock漂移判断就会偏差。我的建议是在脚本里先检查系统时间再用date -d确认当前日期确保cron执行环境里的时间正常。另外find要配合-type f避免匹配到目录配合-name限制文件后缀避免误删非日志的元数据文件。3.2 排除项设计哪些文件千万不能动清理脚本最容易翻车的地方就是“误删”。我总结过几类不能动的文件带当天日期或正在活跃写入的access_log.当前日期.log、naming.log、config.log。这些是Nacos当前正在写的文件删了会导致fd指向已删除文件日志写入暂时记录不到磁盘而且空间不释放表面看磁盘没变实际埋雷。cluster.conf、raft.conf这类配置和快照文件删了会导致集群元数据损坏重启都起不来。数据库文件或Derby存储目录如derby-data、derby.log。如果你用的Nacos内嵌存储这些文件承载了配置数据绝不能按照“过期日志”逻辑清理。目录本身和子目录。logs目录下可能有config、naming等子目录find -type f一般不会误删目录但如果你加了-exec rm -rf且没写-type f后果很严重。所以我的脚本里用了双重保险先按文件名正则匹配带日期的日志再按mtime过滤最后在删除前再确认一次扩展名和日期格式。宁可漏几个文件也不能误删一个关键数据。3.3 完整脚本与逐行说明下面这段脚本就是我在生产环境中使用的精简版去掉了一些内部加密密钥相关的部分核心逻辑完整#!/bin/bash # Nacos 日志清理脚本 - 保留最近14天日志 # 适用环境: Linux Nacos 2.x # 建议放到 nacos 用户 crontab 中或在 root crontab 中以 nacos 用户身份执行 set -u NACOS_LOG_DIR${NACOS_LOG_DIR:-/opt/nacos/logs} RETENTION_DAYS${RETENTION_DAYS:-14} DRY_RUN${DRY_RUN:-0} if [ ! -d $NACOS_LOG_DIR ]; then echo [ERROR] Nacos log dir not exist: $NACOS_LOG_DIR exit 1 fi CURRENT_DATE$(date %Y-%m-%d) LOG_FILE/var/log/nacos_log_clean.log # 核心查找逻辑 # 1. 只匹配普通文件 (-type f) # 2. 文件名必须符合日志滚动规则带日期后缀 # 3. mtime 超过 14 天 # 4. 排除当前活跃日志文件 FILES$( find $NACOS_LOG_DIR -type f \ -regextype posix-awk \ -regex .*(access_log|naming\.log|config\.log|cluster\.log|alipay-jraft\.log)(\.[0-9]{4}-[0-9]{2}-[0-9]{2})?.*\.log([0-9.-]*)?$ \ -mtime $RETENTION_DAYS \ 2/dev/null \ | grep -v _${CURRENT_DATE}_ || true ) DELETED_COUNT0 DELETED_SIZE0 while IFS read -r file; do [ -z $file ] continue # 二次保护跳过活跃日志文件 if [ $(basename $file) naming.log ] || \ [ $(basename $file) config.log ] || \ [ $(basename $file) cluster.log ]; then echo [SKIP] active log: $file continue fi size$(stat -c%s $file 2/dev/null || echo 0) if [ $DRY_RUN -eq 1 ]; then echo [DRY_RUN] would delete $file (size$size) else if rm -f $file; then DELETED_COUNT$((DELETED_COUNT1)) DELETED_SIZE$((DELETED_SIZEsize)) echo [$(date %F %T)] deleted $file (size$size) $LOG_FILE else echo [ERROR] failed to delete $file $LOG_FILE fi fi done $FILES echo [$(date %F %T)] summary: deleted$DELETED_COUNT size$DELETED_SIZE bytes $LOG_FILE解释几个关键点set -u会在引用未定义变量时直接报错防止因为环境变量缺失导致路径变成空字符串把根目录扫了。变量NACOS_LOG_DIR和RETENTION_DAYS支持从外部覆盖这样一个脚本多套环境都能用。这两个是不同服务器环境差异最大的点写成可配置能省很多事。find的-regextype posix-awk改成了更通用的POSIX风格正则匹配文件名中带yyyy-MM-dd日期的滚动日志。grep -v _${CURRENT_DATE}_排除了今天日期防止删到当前活跃文件。最稳的办法其实是干脆把当前日期的文件名全部跳过因为Nacos的滚动日志文件名里会带日期比如access_log.2025-06-01.log。为什么脚本里还单独加了一个“跳过naming.log/config.log/cluster.log”的判断因为有些版本滚动机制并不按日期生成新文件而是固定文件内部滚动。虽然正则已经匹配到了带日期的文件名但二次判断永远不会嫌多尤其是当别的工作人员手动复制了一个文件叫naming.log.bak时这种保护能救你。这里我也要专门说明如果你使用的是Nacos较老版本如1.x日志文件名后缀可能是yyyy-MM-dd.HH甚至带小时。没关系正则里[0-9]{4}-[0-9]{2}-[0-9]{2}已经覆盖日期部分。判断标准始终是“这文件是不是已经被Logback归档、不再参与写入”。4. 把脚本安全地交给Cron定时执行与落盘细节脚本写出来永远不是终点怎么把它稳定、安全地交给系统调度才是生产环境的真正考验。我见过有人直接把脚本放在/root下用root crontab执行然后由于Nacos日志是nacos用户创建的出现Permission denied。也见过有人用nacos用户跑但log文件路径没有写权限导致执行记录丢失。4.1 Cron表达式与执行时间选择我自己常用的计划是每天凌晨4点30分执行30 4 * * * /usr/local/bin/clean_nacos_logs.sh /var/log/nacos_log_clean_runtime.log 21为什么要挑凌晨因为凌晨通常是业务低峰期Nacos日志写入量小清理任务对IO的影响最小。虽然删除文件本身不占太多系统资源但find会扫描整个日志目录如果文件数量成千上万IO还是有一点的。白天跑容易碰到突发请求深夜跑最稳。另外建议大家给cron任务加上环境变量因为cron子进程的环境变量极简SHELL/bin/bash PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin很多人踩过这个坑脚本在终端里手动执行正常放进cron就报错找来找去发现是PATH里没有/usr/bin。明明find就在那里却提示command not found。这种问题很蠢但真的很常见。4.2 加锁防止脚本并发重叠执行如果某一天的执行特别慢比如文件太多、磁盘变慢下一次cron触发时上一次还没跑完两个find同时扫描目录有可能出现重复删除或IO抖动。为了避免这个问题我建议用flock给脚本加一个文件锁#!/bin/bash exec 9/var/lock/nacos_log_clean.lock if ! flock -n 9; then echo [$(date %F %T)] another instance is running, skip. exit 0 fi把这个逻辑放在脚本开头。flock -n是非阻塞模式拿不到锁就直接退出绝对不会并发执行。文件锁文件本身很小不占空间。这里的9是文件描述符编号随便选一个不常用的就行。4.3 与日志滚动策略logrotate配合的边界很多服务器上默认有logrotate它主要负责对系统日志进行滚动、压缩、删除。那么Nacos的日志能不能直接交给logrotate管可以但要注意边界。Nacos自身已经内置了Logback按天滚动它滚动时会把当前文件改名为带日期的旧文件再新建一个空白的当前文件。这个机制运行得很好你不用重复设置按天滚动logrotate如果也按天滚反而可能把正在写的文件切掉导致Nacos写入句柄出错。所以我的建议是Nacos的日志清理交给自己的脚本logrotate只负责处理start.out这类由shell重定向生成、没有滚动能力的文件。分工清楚两者不互相干扰。start.out有时也被称为Nacos服务的标准输出重定向文件默认位置在/opt/nacos/start.out可能不在logs目录里。你要么在清理脚本里单独加一段要么用logrotate每天分割它别指望一个脚本面面俱到把所有文件都管了。5. 跑真实环境前先做三遍验证新脚本上生产前我会强制自己按照“dry-run、小范围试删、全量执行”三个步骤走。三个步骤都通过才能交给cron长期运行。5.1 先跑dry-run看输出清单脚本里的DRY_RUN1就是在这一步用的DRY_RUN1 ./clean_nacos_logs.sh它会打印出每个“将要删除”的文件路径和大小但实际上不做删除。这一步的价值是你可以在删除前检查有没有误报。比如文件名正则会不会匹配到某个奇怪的快照或者当天文件有没有被误判成过期文件。我建议你把输出保存下来人工用ls -lh随机抽查几个文件确认它们确实是历史日志而且时间确实超过14天。5.2 清理前后df对比与误删排查确认清单没问题了就正式执行。执行前先记录目录占用du -sh /opt/nacos/logs df -h /opt/nacos/logs执行脚本后再跑一遍这两条命令。正常情况下目录占用会下降磁盘使用率也会下降。有些人只关注磁盘总量却忽略了du的输出。如果du下降但df没下降说明有文件还被进程占着fd。这种场景多发生在当天日志上——如果脚本逻辑有问题把刚滚动的活跃文件也删了就会出现“ls看不到文件但磁盘没释放”的诡异局面。这时用lsof L1 | grep nacos查一下基本能定位是哪个文件卡住了。5.3 遇到“删不掉”和被重新生成的日志怎么办实际执行中还有两种现象会让你怀疑脚本是不是坏了。第一种是“删不掉”rm -f返回失败。最常见的原因是文件属主和权限问题比如Nacos以nacos用户运行而脚本以root执行时由于SELinux或特殊挂载选项无法删除或者日志目录被设置为只读挂载。这种情况看脚本日志里的[ERROR]输出再用lsattr检查文件是否被加入了i属性immutable。chattr -i可以在确认安全后解除但你得先搞清楚为什么文件会被加i不能无脑解。第二种是“删了又出现”。这是正常的只要Nacos在运行access_log当前日期文件每天都会生成。你删除的只是14天前的历史文件当天和最近14天的文件会一直存在。如果某天你发现删除之后第二天文件又出现在logs目录里而且文件名还是过期日期那说明Nacos的日志时间有问题比如服务器时钟跑慢了一天导致Logback认为现在是过去的某一天生成了旧日期文件。这时要检查系统时间和时区配置。我曾经在一个测试环境碰到过类似问题最后发现是虚拟机休眠后时钟漂移导致的。还有一个建议保留一份模板文件在白名单里。比如在脚本里维护一个KEEP_FILES列表每当看到新的日志文件名格式时把它加到这个列表中。这样即使Nacos小版本升级导致日志文件命名规则变化脚本也不会把新格式文件当成垃圾误删。6. 容器化与多节点集群的额外提醒如果你的Nacos部署在Docker或K8s环境里清理脚本的逻辑不变但注意几个差异。容器里Nacos日志通常落在两个位置一种是容器内部路径默认一样是/home/nacos/logs或/opt/nacos/logs另一种是你自己挂载的宿主机目录比如NFS卷。如果日志目录是emptyDir容器重启日志就没了理论上不需要清理脚本。但实际生产上为了排查问题很多人会挂载hostPath或PVC日志是持久化的磁盘增长问题就回来了。对于容器场景我建议直接在宿主机上跑清理脚本因为容器内部往往没有cron而且容器镜像精简到连find都不一定有。脚本里的目录路径改成宿主机挂载的实际路径即可。一个小细节如果多个Nacos副本共享同一个日志目录而每个副本都会写入access_log那么文件名可能带有access_log.2025-06-01.log之外还带实例标识的后缀。这时候正则里要把实例标识也考虑进去或者放宽为按-name *access_log*匹配。集群模式下还有个问题容易被忽略三节点Nacos集群的日志总量是单节点乘以节点数磁盘规划和清理频率要按这个来算。我一般用这个粗略公式估算单日日志大小×保留天数×节点数×1.3缓冲区。如果算出来空间紧可以把保留天数从14天降到7天但前提是确认没有审计合规方面的要求。access_log里记录了大量客户端访问信息如果有安全审计需要建议保留30天以上。运维决策不能只看磁盘还得看业务规则。另外无论单机还是集群我都会在监控面板上加一个磁盘使用率的告警阈值通常是80%。清理脚本是“事后补救”监控告警才是“事前发现”。脚本再稳也有被人误删或cron异常停掉的意外告警能让你在用户发现之前先发现问题。Nacos控制台经常报错的“request error, please try again later!”也和日志清理有关。如果你清理日志时误删了当前活跃的access_log会让控制台请求日志丢失排查问题时看不到任何访问记录。我自己遇到过几次这种场景后来学乖了清理前先看一眼最新日志文件是否存在清理后立刻确认控制台能正常打开。如果控制台打不开且日志里出现大量“request error, please try again later!”优先检查磁盘是否满了以及access_log文件是否存在。总结一下我这两年多的运维体会日志清理脚本的本质不是“删文件”而是理解文件生命周期、认清进程与文件的引用关系、把定时任务做得足够安全。保留14天只是一个参数真正值钱的是那几条保护规则和验证流程。你可以在我的脚本基础上改保留天数、改路径但千万别删掉那些“跳过活跃文件”和“dry-run”的部分。磁盘满了能救回来数据文件被误删了真的就只能哭。