[实战记录] Apache Doris BE 容器无限重启之谜:从配置乱码到脚本逻辑缺陷的深度排查
前言
在使用 Docker 部署 Apache Doris 时,遇到了一次棘手的 BE (Backend) 节点启动故障。整个排查过程跌宕起伏:BE 节点明明在日志中显示启动成功,能够正常响应 FE (Frontend) 的心跳,甚至开始处理 SQL 查询,但 Docker 容器却总是会在启动 60 秒后准时被“杀”死并自动重启。
本文复盘了从表象报错到挖掘出底层脚本逻辑缺陷的完整排查过程,希望能为遇到类似问题的同学提供参考。
第一阶段:配置文件中的“隐形杀手”
1. 故障现象
容器初次启动时,BE 进程直接退出。查看 be.out 日志,发现大量关于内存分配器 jemalloc 的报错:
Plaintext
<jemalloc>: Invalid conf value: oversize_threshold:0
<jemalloc>: Invalid conf value: dirty_decay_ms:5000
2. 排查分析
乍看似乎是参数不支持,但尝试修改参数位置后,报错总是在当前行的最后一个参数上。
经过仔细检查 be.conf 文件,发现这是由于Windows 换行符 (\r) 导致的。
在 Linux 环境下读取配置时,Shell 或应用程序将行尾的不可见字符 \r 也当成了配置值的一部分(例如读取到了 0\r 而不是 0),导致 jemalloc 解析失败。此外,日志路径配置中也因为换行符导致路径出现了乱码。
3. 解决方法
使用 sed 命令一键清理配置文件中的 Windows 换行符:
Bash
sed -i 's/\r//g' be.conf
第二阶段:诡异的“60秒魔咒”
1. 故障现象
修复配置后,BE 进程终于成功启动了。
-
BE 业务状态:查看
be.INFO日志,显示ThriftServer started,并且成功收到了 FE 的心跳,日志中甚至出现了Query ... start execution,说明 BE 功能完全正常。 -
FE 集群状态:在 FE 执行
SHOW BACKENDS,该节点状态已变为Alive: true。 -
Docker 容器状态:虽然业务正常,但 Docker 容器却陷入了无限重启的循环。查看
be.out容器日志,发现每隔 60 秒就会打印:
Plaintext
[INFO] [Entrypoint]: Waiting for BE node... (21/60)
...
[INFO] [Entrypoint]: Waiting for BE node... (41/60)
[ERROR] [Entrypoint]: BE node failed to start
2. 疑惑
明明 BE 进程已经活了,为什么容器的启动脚本(Entrypoint)却“视而不见”,坚持认为它启动失败,并强制 Kill 掉容器?
第三阶段:寻找“真凶”脚本
1. 锁定 Entrypoint
起初以为是官方脚本的问题,但对比手头的 be_entrypoint.sh,发现其中并没有倒计时逻辑。
通过 docker inspect 检查容器启动参数,发现容器运行在一个 Rancher 环境下,且真正的入口脚本是 entry_point.sh,位置在 /opt/apache-doris/entry_point.sh。
2. 代码审计
将该脚本从容器中提取出来后,终于看到了导致重启的核心逻辑。脚本中定义了一个 check_be_status 函数,包含了一个脆弱的“连环锁”校验:
Bash
# entry_point.sh 源码片段
readonly MAX_RETRY_TIMES=60
check_be_status() {
local retry_count=0
while [ $retry_count -lt $MAX_RETRY_TIMES ]; do
# ...
# [致命逻辑] 试图通过 MySQL 命令验证 BE 状态
if mysql -uroot -P"${MYSQL_PORT}" -h"${MASTER_FE_IP}" \
-N -e "SHOW BACKENDS" 2>/dev/null | grep -w "${CURRENT_BE_IP}" | grep -w "true" &>/dev/null; then
log_info "BE node is ready"
return 0
fi
# ...
sleep "$RETRY_INTERVAL"
done
return 1 # 超时返回失败
}
脚本试图通过 MySQL 连接 FE,查询 SHOW BACKENDS 列表,并匹配 IP 和 true 状态来验证 BE 是否存活。如果 60 秒内匹配失败,脚本就会退出(exit 1),导致 Docker 杀死容器。
第四阶段:真相大白——脆弱的健康检查
1. 逻辑漏洞验证
这段脚本存在三个致命缺陷:
-
硬编码 Root 无密登录:假设可以用
mysql -uroot无密码连接 FE。 -
错误吞噬:使用了
2>/dev/null将所有错误输出丢弃,掩盖了真相。 -
强依赖:如果这一步检查不通过,直接判定 BE 死亡。
2. 现场复现
进入容器内部,手动执行那条 MySQL 命令,真相瞬间浮出水面:
Bash
root@container# mysql -uroot -P9030 -h10.42.88.1 -e "SHOW BACKENDS"
ERROR 1045 (28000): Access denied for user 'root'@'10.42.88.2' (using password: NO)
结论:
BE 进程是健康的,但由于 FE 开启了安全认证(拒绝远程 Root 无密登录),导致启动脚本里的“健康检查”被拒绝访问 (Access Denied)。脚本因为被拒绝访问而拿不到数据,误以为 BE 没启动,在重试 60 次后,“冤杀”了正常的 BE 进程。
最终解决方案
既然是启动脚本的校验逻辑不合理,且不适应当前的安全环境,我们决定绕过这个有缺陷的脚本。
修改 Docker 启动参数
修改 docker-compose.yml 或 docker run 命令,覆盖默认的 Entrypoint,直接运行 BE 二进制文件,不再执行 bash entry_point.sh。
Docker Compose 示例:
YAML
services:
be:
image: apache/doris:be-3.1.0
# 覆盖入口,不再执行 bash entry_point.sh
entrypoint: ["/bin/bash", "-c"]
# 直接运行 BE 守护进程
command: ["/opt/apache-doris/be/lib/doris_be --daemon"]
修改后重启容器,Waiting for BE node... 的日志消失,BE 节点稳定运行,问题完美解决。
总结与避坑指南
-
警惕配置文件乱码:在 Windows 编辑配置文件后上传到 Linux 容器时,务必检查换行符问题(CRLF vs LF),这常是莫名配置错误的根源。
-
不要轻信
2>/dev/null:在编写或排查 Shell 脚本时,看到忽略错误输出的命令要格外小心,它往往掩盖了真正的报错(如本次的 Access Denied)。 -
容器健康检查的代价:过于复杂的启动脚本(Entrypoint)有时反而会成为服务的“杀手”。在云原生环境下,尽量让应用保持简单,将健康检查交给 K8s 的 Liveness/Readiness Probe,而不是在启动脚本里写死逻辑。