跳转至

INCIDENT-20260920:phpLDAPadmin 管理界面点击目录树后一直加载

1. 概要

项目 内容
发生时间 2026-09-20 21:19(CST)
发现时间 2026-09-20 21:20(用户截图反馈)
等级 一般(P3):单一管理界面不可用,不影响 LDAP 目录服务与业务系统
影响面 仅 server/phpldapadmin 管理界面;用户无法浏览、编辑目录条目
当前状态 已恢复(2026-09-20 21:42,helm revision 8)

2. 影响范围

  • 受影响:所有通过 https://ldapadmin.rainchen.top 使用 phpLDAPadmin 的用户;登录可成功,但点击目录树后右侧内容区一直转圈。
  • 不受影响:OpenLDAP 目录服务(389/636 正常)、gitlab / minio / mariadb 等业务系统、LDAP 数据。
  • 数据:无损坏、无丢失(本次故障未写入任何目录数据)。

3. 时间线(CST)

时间 事件
21:16 用户登录成功(应用日志 ALG:Attempting login for [uid=ldapadmin,...])
21:19:46–21:19:56 用户点击目录树,浏览器发出 POST /frame,服务端返回 409(traefik 访问日志)
21:20 用户反馈"点了两下就一直这样转圈",附界面截图
21:21–21:36 只读取证:确认 LDAP 服务、目录数据、应用配置、各 AJAX 接口均正常;/ajax/bases、/ajax/children 均为 200,唯独 POST /frame 为 409
21:38 用户提供 F12 证据:Request URL: https://ldapadmin.rainchen.top/frame、Referer: https://ldapadmin.rainchen.top/、Remote Address: 129.226.138.204:443
21:32 留档容器内原始 HomeController.php(sha256 0a87cc51…)
21:37 预演(dry-run)基线:内网 http POST /frame=200,公网 https=409
21:39:44 helm revision 6:部署 ConfigMap 覆盖 + 挂载
21:40:44 helm revision 7:修正覆盖文件字节(ConfigMap 块标量由 \| 改为 \|-,消除尾随换行差异)
21:42:56 helm revision 8:收敛 Deployment 注解,使渲染结果与线上完全一致
21:43 恢复验证通过:两条链路 POST /frame 均 200;幂等复核 SKIPPED

4. 根因

镜像内 app/Http/Controllers/HomeController.php 的 frame() 判据过严,叠加公网 https 反代链路,导致合法的点击恒被 409 拒绝。

// If our index was not render from a root url, then redirect to it
if (($request->root().($request->getBasePath() ? '' : '/') !== url()->previous()) && ($request->method() === 'POST'))
        abort(409);

证据链:

  1. 只有 /frame 失败:traefik 访问日志(用户 IP)显示 POST /ajax/bases 200 → POST /frame 409 → GET /login 200。
  2. 服务端一切正常:容器内用服务账号、目录条目 DN + 同一口令做 ldap_bind 均返回 result: 0 Success; GET /、POST /ajax/bases、POST /ajax/children 均 200 且耗时 < 0.3s。
  3. 判据行为实测(同一会话,仅改 Referer):
Referer 内网 http 公网 https
不带 409 409
http://ldapadmin.rainchen.top/ 200 —
https://ldapadmin.rainchen.top/ — 409
http://192.168.31.10/ 409 —
  1. 链路特征:ldapadmin.rainchen.top 解析到公网 129.226.138.204,由其 443 端口的 nginx 反代到集群 traefik(80), 故页面为 https(服务器端生成的所有 URL 亦为 https),而 Laravel 记录的「上一页 URL」不带尾斜杠, 与判据右侧的「根地址 + /」不相等 → abort(409)。

补充说明:http 访问会被该 nginx 301 强制跳转到 https,因此用户"输入时没加 s"也会自动进入 https 链路。

5. 处置过程

  1. 只读取证阶段未改动任何系统状态(应用日志、slapd 日志、traefik 访问日志、session、配置逐项核对)。
  2. 采用「ConfigMap 覆盖镜像内单文件」方式修复(沿用本环境既有做法),把判据改为按主机名归一化比较: 仅在能确定上一页、且其主机与本站不一致时才拒绝,跨站 POST 仍被拦截。
  3. 变更落本地定制 chart(homeControllerFix 开关),保证后续 helm upgrade 不回退;实现体为 ldap-deploy/pla-frame409-fix/ Python 工程(幂等、备份、回滚、验证齐备)。
  4. 期间修正两处实现问题(均发生在变更落地前/后,未造成数据影响):
  5. 容器内 mktemp 参数不兼容 → 语法校验脚本改用固定命名;
  6. ConfigMap 块标量 | 会补尾随换行,与目标文件不等 → 改为 |-(revision 7 修正)。
  7. 无回滚动作(变更一次成功)。

6. 恢复验证

验证项 命令/方法 结果
容器内文件为目标版本 kubectl -n server exec … sha256sum /app/app/Http/Controllers/HomeController.php 61413d08…,与 assets/HomeController.php、chart 内文件三方一致
APP_KEY 未变 比对 Secret phpldapadmin 中 APP_KEY 的 sha256 变更前后一致(未导致会话失效)
内网链路 http://192.168.31.10(Host 头 ldapadmin.rainchen.top)登录 → /ajax/bases → POST /frame 200 / 200 / 200(47860 字节)
公网链路(用户实际路径) https://ldapadmin.rainchen.top 同上 200 / 200 / 200(47874 字节)
幂等 重复执行部署脚本两次 两次均 skipped(零变更、无报错)
集群状态 kubectl -n server get pod -l app.kubernetes.io/name=phpldapadmin 1/1 Running,RESTARTS 0

7. 复盘

有效做法:

  • 先只读取证、用 traefik 访问日志把故障窄化到单个端点(/frame 409),避免了在 LDAP 服务上做无谓改动;
  • 用「同一会话、只改一个变量」的对照实验确定判据边界,根因有可复现证据;
  • 修复顺带落了 chart 层开关,避免下次 helm upgrade 静默回退。

拖慢恢复的因素:

  • 起初按「内网 http 直连」验证,POST /frame 为 200,一度误判服务端正常;直到拿到用户 F12 的 scheme=https 与公网 IP 才定位到反代链路——验证必须覆盖用户实际访问路径;
  • 覆盖文件时未预先考虑 ConfigMap 块标量对尾随换行的处理,多花了一次 helm revision。

8. 行动项

事项 责任人 期限 状态
升级 phpLDAPadmin 镜像前,比对上游 HomeController.php 与该覆盖文件是否冲突 运维(待指派) 下次镜像升级前 待处理
清理 pla-frame409-fix/backup/ 中的中间版本备份(见该目录 README 台账) 用户 观察稳定后 待处理
决策是否在集群内为 ldapadmin.rainchen.top 发布 443 路由(当前集群侧仅 80,https 由公网 nginx 提供) 用户 待定 待决策
将本次故障场景补充进 ldap-deploy/RUNBOOK.md 的「故障处置」章节 运维(待指派) 待定 待处理

来源:/home/rain/Documents/k8s_ins/ldap-deploy/pla-frame409-fix/INCIDENT-20260920-pla-frame-409.md | 最后更新:2026-09-20 21:43(由 build/sync.py 同步,请勿在站点侧修改)