编程

Alpine 容器里 crond 报 can’t execute ‘/bin/bash’:一次五阶段的排查

在 Alpine Linux 容器中配置 cron 定时任务时,遇到一个让人困惑的问题:crond 持续报错 can't execute '/bin/bash' for user root,但手动执行命令完全正常。这篇文章记录了从错误现象到根因定位、再到最终解决的完整排查过程。

问题现象

在 Docker 容器中,crontab 配置如下:

*/5 * * * * su-exec www-data /usr/local/bin/php /var/www/html/app/cron.php

容器日志持续输出:

app-php | crond: can't execute '/bin/bash' for user root

但进入容器手动执行:

su-exec www-data /usr/local/bin/php /var/www/html/app/cron.php

一切正常,PHP 脚本顺利执行。为什么手动可以,crond 里就不行?

排查过程

第一阶段:以为是 su 的问题

最初 crontab 里用的是 su -s /bin/sh www-data -c '...'。Alpine 使用 busybox 的 su,在 crond 非交互、无 tty 环境下会尝试启动 login shell,而编译时默认路径是 /bin/bash,Alpine 没有 bash 就报错。

解决思路:换用 Alpine 推荐的 su-exec 工具,它直接 setuid() + execve(),不经过 login shell。

apk add --no-cache su-exec

但换成 su-exec 后,报错依旧。

第二阶段:crontab 引号问题

检查 crontab 文件内容时发现命令被单引号包裹,导致 su-exec 把整个字符串当成可执行文件路径。去掉引号后参数正确分离,但报错仍然存在。

第三阶段:crond 没重载

busybox crond 启动后一次性解析 crontab,修改文件不会自动重载。需要杀掉进程重新启动:

pkill -9 crond
crond -f -l 8 -L /dev/stdout &

第四阶段:两个 crontab 文件

busybox crond 会同时读取两个位置:

  • /var/spool/cron/crontabs/root
  • /etc/crontabs/root

之前只改了其中一个,另一个里还有旧命令在跑。统一后报错仍然存在。

第五阶段:找到根因

最终发现容器的 /etc/passwd 是从宿主机挂载进来的,其中 root 的条目是:

root:x:0:0:root:/root:/bin/bash

Alpine 容器里没有 /bin/bash。crond 启动时会从 /etc/passwd 读取 root 的 login shell,或者继承 SHELL=/bin/bash 环境变量,每次执行任务都尝试 exec /bin/bash,找不到就报 ENOENT。

验证:

cat /proc/$(pidof crond)/environ | tr '\0' '\n' | grep SHELL
# 输出:SHELL=/bin/bash

解决方案

方案一:设置 SHELL 环境变量(最快)

ENV SHELL=/bin/sh

或者在 crontab 文件顶部加:

SHELL=/bin/sh

方案二:不挂载主机 /etc/passwd(推荐)

RUN deluser www-data 2>/dev/null; true
RUN sed -i '/^www-data:/d' /etc/passwd /etc/group 2>/dev/null; true
RUN adduser -D -u 33 -s /bin/sh www-data

方案三:Entrypoint 里修复

#!/bin/sh
sed -i 's|/bin/bash|/bin/sh|g' /etc/passwd
export SHELL=/bin/sh
exec "$@"

额外踩坑:Alpine 删除用户和组

busybox 的 delgroup 不允许删除还有成员引用的组。解决方式:

  • deluser 先于 delgroup 执行
  • 或者 sed -i '/^group:/d' /etc/group 强制清理
  • 最简洁做法:adduser -D -u 33 -s /bin/sh www-data 一步创建用户+同名组

总结

核心教训:Alpine 容器不要挂载主机的 /etc/passwd,在 Dockerfile 里管理用户;crond 的 SHELL 环境变量要显式设为 /bin/sh。

RUN apk add --no-cache su-exec
RUN deluser www-data 2>/dev/null; true
RUN sed -i '/^www-data:/d' /etc/passwd /etc/group 2>/dev/null; true
RUN adduser -D -u 33 -s /bin/sh www-data
ENV SHELL=/bin/sh
COPY crontab /etc/crontabs/root
RUN chmod 0644 /etc/crontabs/root
CMD ["sh", "-c", "crond -f -l 8 -L /dev/stdout & php-fpm -F"]

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注