Apache Doris BE 容器无限重启之谜

阿白
发布于 2025-12-29 / 35 阅读
1
0

Apache Doris BE 容器无限重启之谜

[实战记录] 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. 逻辑漏洞验证

这段脚本存在三个致命缺陷:

  1. 硬编码 Root 无密登录:假设可以用 mysql -uroot 无密码连接 FE。

  2. 错误吞噬:使用了 2>/dev/null 将所有错误输出丢弃,掩盖了真相。

  3. 强依赖:如果这一步检查不通过,直接判定 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.ymldocker 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 节点稳定运行,问题完美解决。


总结与避坑指南

  1. 警惕配置文件乱码:在 Windows 编辑配置文件后上传到 Linux 容器时,务必检查换行符问题(CRLF vs LF),这常是莫名配置错误的根源。

  2. 不要轻信 2>/dev/null:在编写或排查 Shell 脚本时,看到忽略错误输出的命令要格外小心,它往往掩盖了真正的报错(如本次的 Access Denied)。

  3. 容器健康检查的代价:过于复杂的启动脚本(Entrypoint)有时反而会成为服务的“杀手”。在云原生环境下,尽量让应用保持简单,将健康检查交给 K8s 的 Liveness/Readiness Probe,而不是在启动脚本里写死逻辑。


评论