三个相互叠加的根因——硬编码 IP 失效、Elasticsearch 内存超限、RBAC 初始化竞态——的定位过程、修复措施与验证结果。
故障表象是「登录页能正常打开,提交任何凭证都提示网络异常」。实际排查下来,这不是单点故障,而是三个先后叠加、彼此独立的问题:
其一,宿主机内存只有官方要求的一半,Elasticsearch 按默认配置拿到约 4GB 堆,17 小时内被内核 OOM Killer 处决 31 次。其二,早期排障时在 extra_hosts 里写死了 es01 的容器 IP,容器重建后该 IP 变更,ragflow-server 的所有 ES 请求都打进了黑洞地址,127 秒超时后进程退出——且以退出码 0 退出,restart: on-failure 策略判定其为正常退出、不再拉起,服务就此躺平。其三,前两个问题修复、链路全通之后登录仍然失败:knowflow-backend 的 RBAC 初始化有一个 60 秒的等表窗口,而 ragflow-server 首次冷启动建表需要约 75 秒,管理员账号的创建在窗口关闭 13 秒后才轮到执行,从未发生,登录报「邮箱未注册」。
三处全部处理之后,登录恢复正常。
| 项目 | 内容 |
|---|---|
| 管理面板 | 1Panel |
| 应用版本 | KnowFlow v2.1.8(镜像 zxwei/knowflow:v2.1.8,内含 RAGFlow v2.1.2-189-gdd54c95f slim) |
| 部署路径 | /home/1panel/KnowFlow/docker(docker compose 管理) |
| 文档引擎 | Elasticsearch 8.11.3(容器 ragflow-es-01,服务名 es01) |
| 其他容器 | MySQL 8.0.39 · Valkey 8 · MinIO · Gotenberg 8 · knowflow-backend · ragflow-server |
| 容器网络 | docker_ragflow(bridge) |
| 宿主机端口 | 容器端口 | 用途 |
|---|---|---|
| 8088(原 80) | ragflow-server:80 | Web 入口(nginx,登录页在此) |
| 8443(原 443) | ragflow-server:443 | HTTPS |
| 1200 | es01:9200 | Elasticsearch API(外部访问) |
| 5455 | mysql:3306 | MySQL |
| 5000 | knowflow-backend:5000 | RBAC 后端(登录接口) |
| 3000 / 6379 / 9000-9001 | gotenberg / redis / minio | 文档转换 / 缓存 / 对象存储 |
| 项目 | 本机实际 | 官方要求[1] |
|---|---|---|
| 内存 | 7.6GB,无 Swap | ≥ 16GB |
| 磁盘 / CPU | 未核查 | 磁盘 ≥ 50GB,CPU ≥ 4 核 |
伏笔内存一项差了一半,是后面所有问题的物质基础。官方 README 对硬件要求有明确说明[1],部署前应当先对齐。
http://IP:8088/login——说明 nginx 与前端静态资源本身正常。docker compose ps 只列出 6 个容器,ragflow-server 不在其中;加 -a 后显示 Exited (0)。前端这个「网络异常」提示对应的场景是请求超时或代理层无响应,与凭证对错无关——密码错误会返回明确的文案。而「容器在跑」的错觉来自两点:compose 默认不显示已退出容器,以及 RAGFlow 官方 FAQ 强调的——容器状态 Up 不等于服务健康[5]。
flowchart TD
subgraph R1["根因一 · Elasticsearch 反复 OOM"]
A1["宿主机内存 7.6GB,无 Swap
低于官方要求的 16GB"]
A2[".env 默认 MEM_LIMIT = 8GB
ES 堆自动取一半 ≈ 4GB"]
A3["ES 被 OOM Killer 反复处决
17 小时内重启 31 次"]
A1 --> A2 --> A3
end
subgraph R2["根因二 · extra_hosts 硬编码 IP 失效"]
B1["早期排障在 extra_hosts
写死 es01:172.19.0.4"]
B2["容器重建后 es01 实际 IP
变为 172.20.0.6"]
B3["ragflow-server 的 ES 请求
全部打入黑洞,127 秒超时"]
B1 --> B2 --> B3
end
subgraph R3["根因三 · RBAC 初始化竞态"]
C1["knowflow-backend 等待建表
上限 30 次 × 2 秒 = 60 秒"]
C2["ragflow-server 加载 deepdoc 模型
约 75 秒后才建好三张表"]
C3["RBAC 初始化放弃执行
user 表始终为空"]
C1 --> C3
C2 --> C3
end
A3 --> E1
B3 --> E1["ragflow-server 报
ES unhealthy in 120s 后退出"]
E1 --> E2["退出码为 0,restart: on-failure
判定正常退出,不再拉起"]
E2 --> E3["ragflow-server 长期 Exited
8088 端口无人应答"]
E3 --> E4["登录页提示「网络异常」"]
C3 --> E5["链路修复后登录
报 109 邮箱未注册"]
三个根因相互独立:只修任何一个,登录都不会成功。这也是本次排障反复「修完还是不行」的原因。
来龙去脉。初次部署时 ragflow-server 反复退出,日志报 Failed to resolve 'es01'。当时为快速恢复,在 ragflow 服务的 extra_hosts 中写入了 es01 当时的容器 IP(172.19.0.4),服务一度恢复。随后一次 docker compose down && docker compose rm -f && docker compose up -d 重建了全部容器——bridge 网络的 IP 由 Docker 按启动顺序分配,重建后 es01 拿到的是 172.20.0.6,而旧映射没有清除。
证据。两条 inspect 输出直接对比:
# ragflow-server 容器实际生效的 extra_hosts
["es01:172.19.0.4","host.docker.internal:host-gateway"]
# es01 容器当前 IP
es01 当前IP: docker_ragflow = 172.20.0.6
识别特征。黑洞 IP 的行为是「包发出去,永远等不到回应」——超时发生在 127 秒量级,而不是「连接拒绝」。ragflow-server 日志里整齐划一的 GET http://es01:9200/ [status:N/A duration:127.2s] 就是这个特征。
curl http://es01:9200 返回 401,得出「网络是通的、只缺认证」的结论——这个结论对临时容器成立,对 ragflow-server 不成立:临时容器没有 extra_hosts 覆盖,走的是 Docker 内置 DNS;ragflow-server 的解析被 hosts 劫持了。用「别的容器能通」验证「这个容器能通」,在存在 hosts 覆盖时不成立。正解。同一 compose 网络内,服务名本来就能直接解析,extra_hosts 只在跨网络访问宿主机等场景需要。修复方式是删除这一行,而不是把 IP 更新成新值——下次重建它还会变。
证据链。
# 宿主机内存
Mem: total 7.6G used 6.9G free 128M available 57M
Swap: 0B 0B 0B
# ES 容器重启计数(容器创建约 17 小时)
RestartCount=31
# 内核 OOM 记录(两次选中 RSS 最大的 java 进程,即 ES)
Killed process 5246 (java) ... anon-rss:4426688kB ≈ 4.2GB
Killed process 19456 (java) ... anon-rss:4096060kB ≈ 3.9GB
机理。官方 .env 默认 MEM_LIMIT=8073741824(8GB),注释写明需按宿主机内存调整[3];compose 将它作为 es01 的 mem_limit[4]。ES 8 在未显式设置 ES_JAVA_OPTS 时,堆自动取容器内存限制的一半,即约 4GB。7.6GB 的机器上,MySQL、MinIO、Redis、knowflow-backend、ragflow-server 各占一块之后,剩余内存兜不住 4GB 堆加 JVM 非堆开销,内核 OOM Killer 每次都选中 RSS 最大的 java 进程——这正是 dmesg 里两处 Killed process (java) 的来历。
处理。MEM_LIMIT 降为 2147483648(2GB,堆约 1GB)。代价是文档量大、并发高的场景下 ES 会吃紧;长期方案仍是把内存补到 16GB 以上,或显式设置更小的堆并接受性能折损。
ragflow-server 因 ES 不健康抛出异常,但入口脚本最终以退出码 0 结束。compose 里的重启策略是 restart: on-failure[2]——只重启「失败」(非零退出码)的容器。退出码 0 被判定为正常退出,哪怕 ES 后来恢复 healthy,ragflow-server 也不会自己起来。所以现场呈现的是:6 个依赖容器 Up、主角 Exited、登录页报网络异常,且没有任何自动恢复的迹象。
排查上的两个对应教训:docker compose ps 不加 -a 看不到退出的容器;看到 Exited (0) 不要想当然认为「正常退出、没有问题」。
现象。前两个根因修复后,ES 健康检查 200、ragflow-server 横幅出现、登录接口返回 200,链路明明全通了——但页面报 109 Email: admin@gmail.com is not registered。换官方默认凭证 admin / 12345678 登录,同样报未注册。
查证。三条证据指向同一个事实——管理员账号从未被创建:
# .env 中没有覆盖管理员定义(走 compose 默认值)
$ grep -E 'MANAGEMENT_ADMIN' .env
(无输出)
# rag_flow 库的 user 表是空的——「未注册」是真话
$ SELECT email, nickname FROM `user`;
Empty set
# knowflow-backend 日志:等表等了 30 次,放弃
等待以下表创建: user, tenant, user_tenant
等待数据库就绪... (尝试 30/30)
ERROR - RBAC初始化最终失败,已达到最大重试次数
机理。knowflow-backend 启动时等待 user / tenant / user_tenant 三张表就绪,重试上限 30 次 × 2 秒 = 60 秒;而 ragflow-server 首次冷启动要在 CPU 上加载 deepdoc 的 det.onnx / rec.onnx 模型并初始化 web 数据,本机耗时约 75 秒。竞态只在整栈同时冷启动时触发——一旦错过窗口,RBAC 初始化不会重试,user 表从此为空。
处理。此时三张表早已存在,单独 docker restart knowflow-backend 即可,初始化立即成功、管理员账号落库。管理员凭证取 compose 中 MANAGEMENT_ADMIN_* 环境变量的默认值(admin / 12345678),本机 .env 未覆盖[2]。
复发风险每次整栈冷启动(服务器重启、compose down/up)都可能再次触发该竞态。若重启后登录报「邮箱未注册」,先执行 docker restart knowflow-backend,不要急着怀疑密码。
| 阶段 | 动作 | 发现 / 结果 |
|---|---|---|
| 初次部署 | docker compose up -d | ragflow-server 反复退出,日志报 Failed to resolve 'es01' |
| 初排 ① | extra_hosts 写入 es01:172.19.0.4 | 服务一度恢复——同时埋下根因二(IP 随重建失效) |
| 初排 ② | docker compose config | 发现 extra_hosts 重复定义导致 YAML 解析错误,删除重复项 |
| 初排 ③ | down && rm -f && up -d | 容器全部启动,但登录仍报网络异常(此时旧 IP 已失效、ES 仍在被 OOM) |
| 本次 ① | docker compose ps | 6 个容器在跑,ragflow-server 缺席 |
| 本次 ② | docker compose ps -a | ragflow-server Exited (0) |
| 本次 ③ | docker logs ragflow-server | ES 检查全部 status:N/A duration:127s → unhealthy in 120s → 退出 |
| 本次 ④ | inspect es01 / free -h / dmesg | RestartCount=31;内存 7.6G 可用 57M;OOM 处决 java 两次 |
| 本次 ⑤ | inspect 对比 extra_hosts 与 es01 IP | 172.19.0.4 ≠ 172.20.0.6,坐实黑洞映射 |
| 修复 ① | 删除 extra_hosts 中 es01 硬编码行 | 仅剩 host.docker.internal:host-gateway |
| 修复 ② | MEM_LIMIT 改为 2147483648 | es01 重建,Memory=2147483648,RestartCount 归零 |
| 修复 ③ | docker compose up -d | 7 个容器全部启动,ES healthy,ragflow-server 横幅出现,POST /v1/user/login 200 ×3 |
| 新现象 | 页面登录 | 改报 109 邮箱未注册——链路问题已清,转入账号问题 |
| 查证 | grep .env / 查 user 表 / backend 日志 | user 表为空;RBAC 30 次等待失败,确认初始化从未执行 |
| 修复 ④ | docker restart knowflow-backend | 管理员账号创建,登录成功 |
所有改动前先备份:docker-compose.yml.bak.0826、.env.bak.0826(与原文件同目录)。
cd /home/1panel/KnowFlow/docker
cp docker-compose.yml docker-compose.yml.bak.$(date +%m%d)
sed -i '/es01:172.19.0.4/d' docker-compose.yml
grep -n -A3 'extra_hosts' docker-compose.yml
验证点:ragflow 服务下 extra_hosts 仅剩 host.docker.internal:host-gateway。同网络内服务名交给 Docker DNS 解析。
cp .env .env.bak.$(date +%m%d)
sed -i 's/^MEM_LIMIT=.*/MEM_LIMIT=2147483648/' .env
grep -n 'MEM_LIMIT' .env
验证点:输出 MEM_LIMIT=2147483648。ES 堆随之降为约 1GB,退出被反复 OOM 的循环。
docker compose up -d
# 验证两项改动均已生效
docker inspect ragflow-server --format '{{json .HostConfig.ExtraHosts}}'
docker inspect ragflow-es-01 --format 'Memory={{.HostConfig.Memory}}'
预期:ExtraHosts 只剩 host-gateway;Memory=2147483648。随后 docker logs -f ragflow-server,等到 ASCII 横幅再试登录——官方文档明确说明横幅出现前登录会报网络异常[1]。
docker restart knowflow-backend
docker logs knowflow-backend --tail 40
适用条件:链路已通但登录报「邮箱未注册」。表已存在时重启即完成管理员创建。整栈冷启动后若复发,重复本步即可。
| 指标 | 修复前 | 修复后 |
|---|---|---|
| ES 健康检查 | status:N/A,duration 127 秒超时 | status:200,duration 0.01 秒 |
| ES 稳定性 | 17 小时重启 31 次(OOM) | RestartCount 0(限内存 2GB 后) |
| ragflow-server | Exited (0),无自动拉起 | Up,横幅 + HTTP server start (0.0.0.0) |
| 登录接口 | 前端拿不到响应(网络异常) | POST /v1/user/login → 200 |
| 登录结果 | — | 成功进入系统 |
| 目的 | 命令 | 备注 |
|---|---|---|
| 查看所有容器(含已退出) | docker compose ps -a | 不加 -a 看不到 Exited 的容器 |
| 跟踪 ragflow 启动 | docker logs -f ragflow-server | 看到 ASCII 横幅才算就绪[1] |
| 看 backend 日志 | docker compose logs knowflow-backend --tail 50 | RBAC 初始化结果在这里 |
| 查容器当前 IP | docker inspect ragflow-es-01 --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} = {{$v.IPAddress}}{{end}}' | 重建后会变化 |
| 查容器生效的 extra_hosts | docker inspect ragflow-server --format '{{json .HostConfig.ExtraHosts}}' | 排查 hosts 劫持 |
| 查重启次数 / 内存限制 | docker inspect ragflow-es-01 --format 'RestartCount={{.RestartCount}}' | RestartCount 持续增长 = 异常 |
| 容器内测 ES | docker exec ragflow-server curl -s -u elastic:<密码> http://es01:9200 | 401 也说明网络通、只缺认证 |
| 查用户表 | docker exec ragflow-mysql mysql -uroot -p<密码> -e "USE rag_flow; SELECT email FROM \`user\`;" | 空表 = 管理员未创建 |
| 验证 compose 配置 | docker compose config | YAML 语法与变量检查 |
| 查 OOM 记录 | dmesg | grep -i "killed process" | 配合 free -h 使用 |
| 整栈重启 | docker compose down && docker compose up -d | 冷启动后留意第 4 步竞态 |
docker restart knowflow-backend(60 秒等表窗口小于首次建表耗时),排除初始化竞态后再怀疑密码。