SOP:修复 phpLDAPadmin 点击目录树后界面一直加载(POST /frame 返回 409)¶
- 目标集群:
kubernetes-admin@kubernetes(v1.36.4) - 变更对象:命名空间
server的 helm releasephpldapadmin(chartphpldapadmin-0.1.0/ 应用 2.3.11) - 实现体:
ldap-deploy/pla-frame409-fix/(Python 工程,幂等、备份、回滚由其保证;本文第 5 章为等价的 shell 复现序列,以实现体为准) - 执行日期:2026-09-20
目录
1. 背景¶
用户在浏览器访问 https://ldapadmin.rainchen.top(公网 nginx 反代 → 集群 traefik → Pod),
用目录账号 ldapadmin 登录成功,但点击左侧目录树节点后,右侧内容区一直转圈、始终不加载。
只读取证结论:
- 应用日志(
/app/storage/logs/laravel-*.log)与 slapd 日志均正常,LDAP 服务与登录本身无问题; - traefik 访问日志显示,用户浏览器发出的
POST /frame被服务端以 409 拒绝(bases、children均为 200); - 容器内
app/Http/Controllers/HomeController.php的frame()开头有判据:
if (($request->root().($request->getBasePath() ? '' : '/') !== url()->previous()) && ($request->method() === 'POST'))
abort(409);
该判据要求「请求根地址(含尾斜杠)」严格等于「上一页 URL」,而:
- Laravel 记录的上一页 URL 天然不带尾斜杠(
Request::url()会rtrim('/')); - 访问链路为 nginx(公网 https) → traefik → Pod(明文 http),应用识别到的 scheme 与页面 https 不一致,
页面由 https 加载、Referer 为
https://ldapadmin.rainchen.top/,而请求根地址被判定为https://ldapadmin.rainchen.top(带斜杠差异)或 scheme 不一致。
实测矩阵(POST /frame):
| Referer | 内网 http 直连 | 公网 https |
|---|---|---|
| 不带 Referer | 409 | 409 |
http://ldapadmin.rainchen.top/ |
200 | — |
https://ldapadmin.rainchen.top/ |
— | 409(用户现状) |
| 用 IP 访问 | 409 | — |
即:通过公网 https 访问时,无论 Referer 是否正确都必然 409,界面不可用。
2. 目标¶
- 公网
https://ldapadmin.rainchen.top与内网http://192.168.31.10(Host 头ldapadmin.rainchen.top)两条链路上, 登录后点击目录树,POST /frame均返回 200,右侧内容正常渲染。 - 跨站 POST 仍被拒绝(不同主机名的 Referer 依然触发 409)。
- 变更落本地定制 chart,后续
helm upgrade不会回退该修复。 APP_KEY保持不变(否则所有用户会话失效)。- 重复执行部署脚本零变更、无报错(幂等)。
3. 前置条件¶
| 类别 | 要求 |
|---|---|
| 权限 | 集群管理员 kubectl;helm v3.16+ |
| 工具 | Python ≥ 3.9;工程内 .venv 已建并挂载共享库 /home/rain/py_public_modules |
| 文件 | 本地定制 chart ldap-deploy/charts/phpldapadmin/;values ldap-deploy/values-phpldapadmin.yaml |
| 网络 | 执行端可访问集群 API、可访问 192.168.31.10(内网入口)与 ldapadmin.rainchen.top(公网入口) |
| 凭据 | 登录口令由 Secret server/openldap-backup-pw(键 password)提供,仅内存读取、不落盘 |
| 执行前检查 | 见第 5 章第 0 步(预演) |
4. 影响范围¶
| 对象 | 影响 |
|---|---|
server/phpldapadmin(Deployment,单副本) |
Pod 滚动重建一次,界面中断数秒;checksum/homecontroller 注解触发 |
| 新增资源 | ConfigMap server/phpldapadmin-homecontroller(承载覆盖文件) |
LDAP 服务 server/openldap-0 |
不受影响,不重启、不改配置 |
| 其他服务(gitlab / mariadb / minio / traefik) | 不受影响 |
| 停服 | 无需停服;仅 phpLDAPadmin 界面秒级中断 |
| 窗口期 | 任意低峰时段;执行时确认无他人正在编辑目录 |
| 在跑业务 | 本次变更不涉及 LDAP 数据写入 |
5. 分步操作¶
对应实现文件:
ldap-deploy/pla-frame409-fix/{main.py,deployer.py}。以下为实现体的等价 shell 序列,供阅读与手工复现;幂等、备份、错误处理以实现体为准。
5.0 预演(只读,不改任何状态)¶
cd ldap-deploy/pla-frame409-fix
.venv/bin/python main.py --config config/deploy.yaml --dry-run # 输出当前基线与将执行的变更
# 预期末行:[dry-run] 当前基线 [https://ldapadmin.rainchen.top]:登录=200 POST /frame=409
5.1 留档容器内原文件(高危前置备份)¶
POD=$(kubectl -n server get pod -l app.kubernetes.io/name=phpldapadmin -o jsonpath='{.items[0].metadata.name}')
mkdir -p ldap-deploy/pla-frame409-fix/backup/app/app/Http/Controllers
# 【备份】变更前必须留档原文件,回滚时可比对
kubectl -n server cp "$POD:/app/app/Http/Controllers/HomeController.php" \
"ldap-deploy/pla-frame409-fix/backup/app/app/Http/Controllers/HomeController.php.bak.$(date +%Y%m%d-%H%M%S)"
md5sum ldap-deploy/pla-frame409-fix/backup/app/app/Http/Controllers/*.bak.* # 预期与容器内一致
5.2 生成修复后文件并校验¶
cd ldap-deploy/pla-frame409-fix
# 生成 assets/HomeController.php 与 chart/files/HomeController.php(由原文件精确替换判定块,失败即中止)
.venv/bin/python - <<'PY'
import sys; sys.path.insert(0, '.')
from pathlib import Path
from deployer import _ORIGINAL_BLOCK_RE, _REPLACEMENT_TEMPLATE, FIX_MARKER
src = sorted(Path('backup').rglob('HomeController.php.bak.*'))[-1]
content = src.read_text(encoding='utf-8')
fixed, n = _ORIGINAL_BLOCK_RE.subn(_REPLACEMENT_TEMPLATE.format(marker=FIX_MARKER), content, count=1)
assert n == 1, '未能定位原判定块,请人工比对'
for p in [Path('assets/HomeController.php'), Path('../charts/phpldapadmin/files/HomeController.php')]:
p.parent.mkdir(parents=True, exist_ok=True); p.write_text(fixed, encoding='utf-8')
print('已生成修复后文件')
PY
# 语法校验:在容器内用 php -l 验证(临时文件用完即删)
kubectl -n server exec -i deploy/phpldapadmin -c phpldapadmin -- sh -c \
'f=/tmp/hc-check-$$.php; cat > "$f" && php -l "$f"; rc=$?; rm -f "$f"; exit $rc' \
< assets/HomeController.php # 预期:No syntax errors detected
5.3 渲染校验(避免 upgrade 引入意外变更)¶
cd /home/rain/Documents/k8s_ins
helm template phpldapadmin ldap-deploy/charts/phpldapadmin -n server \
-f ldap-deploy/values-phpldapadmin.yaml | grep -E "name: phpldapadmin-homecontroller|mountPath|subPath"
# 预期:出现 ConfigMap 名、mountPath=/app/app/Http/Controllers/HomeController.php、subPath=HomeController.php
# 说明:渲染相对线上 manifest 只应新增内容,不得有既有行消失(APP_KEY 由 lookup 保持稳定)
5.4 落地变更(helm upgrade,Pod 滚动重建)¶
helm upgrade phpldapadmin ldap-deploy/charts/phpldapadmin -n server \
-f ldap-deploy/values-phpldapadmin.yaml --wait --timeout 5m
kubectl -n server get pod -l app.kubernetes.io/name=phpldapadmin # 预期 1/1 Running
本次实际执行了 3 个 revision:revision 6 首次落地覆盖文件;revision 7 修正 ConfigMap 块标量 (
|→|-,消除尾随换行导致的内容差异);revision 8 收敛 Deployment 注解,使渲染结果与线上完全一致。 其中 revision 7/8 属实现细节修正,日常重跑不会再次发生(状态一致即跳过)。
5.5 幂等复核¶
cd ldap-deploy/pla-frame409-fix
.venv/bin/python main.py --config config/deploy.yaml # 预期:状态一致,无需变更,跳过(SKIPPED)
6. 验证¶
# 1) 容器内文件已为覆盖后的版本
POD=$(kubectl -n server get pod -l app.kubernetes.io/name=phpldapadmin -o jsonpath='{.items[0].metadata.name}')
kubectl -n server exec "$POD" -c phpldapadmin -- sha256sum /app/app/Http/Controllers/HomeController.php
sha256sum ldap-deploy/pla-frame409-fix/assets/HomeController.php # 两者应一致
# 2) APP_KEY 未变化(仅比较是否存在与长度,不打印值)
kubectl -n server get secret phpldapadmin -o jsonpath='{.data.APP_KEY}' | base64 -d | sha256sum
# 3) 两条链路端到端验证(脚本自动完成:登录 → /ajax/bases → POST /frame)
.venv/bin/python main.py --config config/deploy.yaml --dry-run
# 预期:[http://192.168.31.10] POST /frame=200;[https://ldapadmin.rainchen.top] POST /frame=200
浏览器侧最终确认:打开 https://ldapadmin.rainchen.top → 用 ldapadmin 登录 → 点击左侧 dc=rainchen,dc=com,
右侧应正常显示条目内容(不再转圈)。
本次实测结果(2026-09-20 21:43)
| 项目 | 结果 |
|---|---|
| 容器内文件 | sha256=61413d08…,与 assets/HomeController.php、chart 内文件三方一致 |
| APP_KEY | 变更前后一致(sha256 比对,未导致会话失效) |
| 内网 http | 登录 200 → /ajax/bases 200 → POST /frame 200(47860 字节) |
| 公网 https(用户路径) | 登录 200 → /ajax/bases 200 → POST /frame 200(47874 字节);变更前为 409 |
| 幂等复核 | 连续两次执行脚本均 skipped(零变更、无报错) |
| helm | revision 8 deployed;Pod 1/1 Running,RESTARTS 0 |
| 渲染一致性 | helm template 与线上 manifest 差异 0 行 |
7. 回滚¶
方式一(推荐,实现体支持)
cd ldap-deploy/pla-frame409-fix
.venv/bin/python main.py --config config/deploy.yaml --rollback # helm rollback 到上一个 revision
方式二(关闭开关,保留 chart 变更)
# 把 values-phpldapadmin.yaml 中 homeControllerFix.enabled 改为 false
helm upgrade phpldapadmin ldap-deploy/charts/phpldapadmin -n server \
-f ldap-deploy/values-phpldapadmin.yaml --wait
# ConfigMap 与挂载随开关消失,Pod 回到使用镜像内原始文件
方式三(人工恢复文件内容):备份文件位于
ldap-deploy/pla-frame409-fix/backup/app/app/Http/Controllers/,用途与清理时机见同目录 backup/README.md。
回滚后验证:kubectl -n server get pod -l app.kubernetes.io/name=phpldapadmin 为 1/1 Running;
POST /frame 会重新变回 409(即回到变更前状态,界面转圈问题复现——说明回滚已生效)。
8. 风险说明¶
| 风险 | 说明 | 缓解 |
|---|---|---|
| 覆盖镜像内文件 | 以 ConfigMap + subPath 挂载覆盖 HomeController.php,与上游代码分叉 |
仅替换 frame() 开头的判定块,其余内容逐字未改;差异见 5.2 的生成脚本输出 |
| 镜像升级 | 升级 phpLDAPadmin 镜像后该文件可能变化,覆盖会掩盖上游修复或引入冲突 | 升级前比对 php -l 与上游文件;必要时删除 files/HomeController.php 并关闭开关 |
| 跨站拦截被放宽 | 由「根地址严格相等」改为「主机名相等」 | 不同主机名仍返回 409;同主机不同路径不再拦截(原判据对合法请求本就过严) |
| APP_KEY 变更 | 若 upgrade 时 APP_KEY 被重新生成,所有会话与加密 Cookie 失效 | chart 的 phpldapadmin.appKey 用 lookup 复用现有 Secret;验证阶段比对 APP_KEY 摘要,异常即回滚 |
| Pod 重建期间不可用 | 滚动更新为单副本,存在秒级中断 | 低峰执行;--wait 保证就绪后才结束;失败即回滚 |
| 只读挂载 | 覆盖文件为只读挂载(0644) | 应用只读取该文件,不写入,无影响 |
附录 A:交付物清单¶
| 文件 | 说明 |
|---|---|
ldap-deploy/pla-frame409-fix/main.py |
工程入口(--dry-run / --config / --rollback / --force / --log-level) |
ldap-deploy/pla-frame409-fix/deployer.py |
业务模块(继承共享库 BaseModule,幂等、备份、回滚、验证) |
ldap-deploy/pla-frame409-fix/config/deploy.yaml |
实际配置(.example 为模板) |
ldap-deploy/pla-frame409-fix/assets/HomeController.php |
修复后文件副本(与容器内覆盖内容一致) |
ldap-deploy/pla-frame409-fix/backup/ |
原始文件留档,含 README.md 台账 |
ldap-deploy/charts/phpldapadmin/files/HomeController.php |
chart 内承载覆盖文件的源 |
ldap-deploy/charts/phpldapadmin/templates/homecontroller-configmap.yaml |
覆盖用 ConfigMap 模板(受开关控制) |
ldap-deploy/charts/phpldapadmin/templates/deployment.yaml |
新增 volumeMounts / volumes / checksum/homecontroller 注解 |
ldap-deploy/charts/phpldapadmin/values.yaml |
新增 homeControllerFix 开关(默认 false) |
ldap-deploy/values-phpldapadmin.yaml |
本次开启 homeControllerFix.enabled: true |
ldap-deploy/pla-frame409-fix/INCIDENT-20260920-pla-frame-409.md |
本次故障与排障记录 |
ldap-deploy/RUNBOOK.md |
服务运行手册(既有交付物,日常操作以它为准) |
附录 B:排障记录¶
完整时间线与证据见 INCIDENT-20260920-pla-frame-409.md。
来源:
/home/rain/Documents/k8s_ins/ldap-deploy/pla-frame409-fix/SOP.md| 最后更新:2026-09-20 21:44(由build/sync.py同步,请勿在站点侧修改)