返回博客

nohup 忘 kill:长跑 root 进程悄悄污染 PHP-FPM 共享 runtime

我们刚修完一次「cron 以 root 身份跑 ThinkPHP CLI 命令、污染 PHP-FPM 共享 runtime 目录」的事故,以为万事大吉。结果第二天一看,runtime/log/202605/ 里又出现了几个 root:root_cli.log。Cron 审计了三遍,/etc/crontab/var/spool/cron/root/etc/cron.d/ 全翻了,没发现任何命中。怀疑 cron 改没生效,浪费了一轮时间。

直到用 ps 带上 etime 一扫,才发现有一个 root 进程已经跑了 23 天 8 小时——那是一个月前某次运维排查时手动 nohup php think clerkBindStatus recalc --all & 启动的,启动完就忘了 kill。

现象

  • runtime/log/<YYYYMM>/ 每天有新增 root-owned _cli.log
  • cron 全线审计完毕,没有任何命中
  • PHP-FPM(以 www 身份运行)偶尔报 Permission deniedLog::write 失败,触发静默 fatal
  • 业务日志断断续续,难以复现

根因

PHP-FPM 进程以 www 用户运行,会往 runtime/log/<YYYYMM>/ 写当日 CLI 日志。而长跑的 root 进程(nohup 遗留、screen 会话、或 systemd unit 没配 User=)同样调用 Log::write / file_put_contents 写入同一目录树,文件创建后 owner 是 root

当同一天 www 进程再次尝试写入这个已存在的 root:root 文件时,file_put_contentsPermission denied,整个请求链路的日志就此静默丢失。更危险的是:这条错误本身也会因为写日志失败而无处上报,排查无从下手。

坑:etime 不带 `-` 就看不出长跑维度

普通 ps aux 看到的 TIME 列是累计 CPU 时间,不是挂钟时间。要看进程活了多久,必须用 etime

ps -eo pid,user,etime,cmd | awk '$2=="root" && $3 ~ /-/'

etime 列含 - 表示天级(格式 D-HH:MM:SS),是长跑铁证。不加这个维度,把一个已经跑了 23 天的 root 进程当成"正常 ops"一眼略过,根本找不到污染源。

正解

三步走:找进程 → 确认来源 → 合规化重启,顺序不能错。

第一步:找长跑 root 进程

# 找运行天数 >= 1 且命令行与项目相关的 root 进程
ps -eo pid,user,etime,cmd | awk '$2=="root" && $3 ~ /-/' \
  | grep -E "php think|cd /www/wwwroot/<project-path>"

第二步:确认启动来源

pstree -p <pid> | head
cat /proc/<pid>/environ | tr '\0' '\n' | grep -E "SHELL|SUDO_USER|TERM"

四种来源对应四种合规化路径:nohup 手动 → kill + 重启;screen/tmux → 进会话退旧进程;systemd → 加 User=www;cron → 加 sudo -u www -- bash -c

第三步:chown 必须在启动前完成

# 1. 先 kill 旧进程
kill <root-pid>
 
# 2. 把 runtime 目录归还给 www(必须在启动新进程之前)
chown -R www:www /www/wwwroot/<project>/runtime/
 
# 3. 以 www 身份启动
sudo -u www -- bash -c 'nohup php think clerkBindStatus recalc --all >> /www/wwwlogs/clerk-bind-status.log 2>&1 &'
顺序倒置必出 fatal

如果先启动新进程、后 chown,新进程首次写 runtime 时 owner 还是 root,www 进程瞬间又报 Permission denied。必须:chown 在前,启动在后

正解:nohup 的重定向必须包在 sudo 内层

如果写成 sudo -u www nohup php think ... >> /log 2>&1,stderr 重定向在 sudo 外层,实际还是以 root 身份打开日志文件。必须包在内层:

sudo -u www -- bash -c '... >> /www/wwwlogs/xxx.log 2>&1'

交叉对照:找出仍在新增的 root 文件

cron 修完之后,可以用 find 持续盯着趋势:

# 最近 1 天新增 root 文件的数量
find /www/wwwroot/<project>/runtime -uid 0 -type f -mtime -1 | wc -l

如果 cron 已干净、数量仍不为零 → 几乎确定还有长跑进程在写,立刻跑 ps 检查。

一句话外卖

任何 php think 命令——无论 cron、nohup、screen、systemd——必须以 www 身份运行,否则它写出的每一个 runtime 文件都是一颗延时炸弹,等 PHP-FPM 下次写同一文件时爆。