跳转至

SOP:修复 phpLDAPadmin 点击目录树后界面一直加载(POST /frame 返回 409)

  • 目标集群:kubernetes-admin@kubernetes(v1.36.4)
  • 变更对象:命名空间 server 的 helm release phpldapadmin(chart phpldapadmin-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 登录成功,但点击左侧目录树节点后,右侧内容区一直转圈、始终不加载。

只读取证结论:

  1. 应用日志(/app/storage/logs/laravel-*.log)与 slapd 日志均正常,LDAP 服务与登录本身无问题;
  2. traefik 访问日志显示,用户浏览器发出的 POST /frame 被服务端以 409 拒绝(bases、children 均为 200);
  3. 容器内 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. 目标

  1. 公网 https://ldapadmin.rainchen.top 与内网 http://192.168.31.10(Host 头 ldapadmin.rainchen.top)两条链路上, 登录后点击目录树,POST /frame 均返回 200,右侧内容正常渲染。
  2. 跨站 POST 仍被拒绝(不同主机名的 Referer 依然触发 409)。
  3. 变更落本地定制 chart,后续 helm upgrade 不会回退该修复。
  4. APP_KEY 保持不变(否则所有用户会话失效)。
  5. 重复执行部署脚本零变更、无报错(幂等)。

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 同步,请勿在站点侧修改)