docs: external-resources manual — xceesd kill/restart recover semantics
Live-verified on t1 (2026-10-29): kill -9 xceesd with 3 RUN license jobs holding markers, restart, and confirm recovery end to end. - §10.1: recover semantics — xceesd is a stateless collector; fresh restart joins, skips full re-push (0/1 sources, no cached value), and the first poll round pushes the collector script's live value, which re-aligns the pool in one shot. Correctness rests on the script being liveness-based (§5.3). - §1.2: esd outage is the unbounded form of the exposure window — the controller freezes on the last pushed value and STALE is observation only, never an admission input. - §11.5 (new, scenario 4): full E2E recipe with pass criteria, incl. the ops pitfall found while testing — a PEND during the outage may be a slots limit (limit1 per-user cap) rather than the license pool; check pending_reason via the apiserver JSON (CLI shows "-"). Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
0b47076d05
commit
7520cca784
1 changed files with 95 additions and 4 deletions
|
|
@ -18,8 +18,8 @@
|
||||||
7. [策略层:limits 配额](#7-策略层limits-配额)
|
7. [策略层:limits 配额](#7-策略层limits-配额)
|
||||||
8. [作业提交](#8-作业提交)
|
8. [作业提交](#8-作业提交)
|
||||||
9. [运维观测](#9-运维观测)
|
9. [运维观测](#9-运维观测)
|
||||||
10. [故障处理](#10-故障处理)
|
10. [故障处理](#10-故障处理)(§10.1 含掉线重启 recover 语义)
|
||||||
11. [测试指南(E2E 配方)](#11-测试指南e2e-配方)
|
11. [测试指南(E2E 配方)](#11-测试指南e2e-配方)(§11.5 场景 4:xceesd 被杀 recover)
|
||||||
12. [已知限制](#12-已知限制)
|
12. [已知限制](#12-已知限制)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -88,6 +88,11 @@
|
||||||
资源的作业,可能在自由值还没降下来时都获准入(真实 license 服务器会在
|
资源的作业,可能在自由值还没降下来时都获准入(真实 license 服务器会在
|
||||||
checkout 时兜底拒绝,见 §11.2 licjob 的 exit 99)。生产上要么接受这个
|
checkout 时兜底拒绝,见 §11.2 licjob 的 exit 99)。生产上要么接受这个
|
||||||
窗口,要么在提交脚本里隔开(≥ poll_interval)。
|
窗口,要么在提交脚本里隔开(≥ poll_interval)。
|
||||||
|
- **xceesd 掉线是暴露窗口的无界版**:掉线后没有新值进来,controller 保留
|
||||||
|
最后一次推送的旧值(sharedInst 不因 daemon 掉线清零),调度器按冻结值
|
||||||
|
继续放行,直到真实 license 服务器 checkout 失败兜底或值恢复推送。STALE
|
||||||
|
只在观测面(§1.3/§9.1),**不进准入判式**——掉线要靠告警发现,不能指望
|
||||||
|
调度器自动收紧。恢复语义见 §10.1。
|
||||||
- §7 的 limit vres 是**非 EXTERNAL** 的 GLOBAL 资源,走完整
|
- §7 的 limit vres 是**非 EXTERNAL** 的 GLOBAL 资源,走完整
|
||||||
`inuse + uncomit + wanted` 公式,配额在调度器本地即时扣减——不依赖
|
`inuse + uncomit + wanted` 公式,配额在调度器本地即时扣减——不依赖
|
||||||
推送,同一轮内也严格生效。
|
推送,同一轮内也严格生效。
|
||||||
|
|
@ -594,10 +599,30 @@ sharedInst 不会因 daemon 掉线自动清零)。调度器会继续按旧值
|
||||||
ps aux | grep [x]ceesd
|
ps aux | grep [x]ceesd
|
||||||
tail -50 /tmp/xceesd.log
|
tail -50 /tmp/xceesd.log
|
||||||
|
|
||||||
# 2. 重启(会自动 rejoin + 全量重推,池子自动恢复)
|
# 2. 重启(recover 语义见下;配方见 §11.5 场景 4)
|
||||||
xceesd -c /etc/xce/esd.yaml -l /tmp/xceesd.log &
|
xceesd -c /etc/xce/esd.yaml -l /tmp/xceesd.log &
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**重启 recover 语义(2026-10-29 活体验证)**:xceesd 是无状态采集器,
|
||||||
|
进程被杀(含 SIGKILL)后直接重启即恢复,不需要任何手工恢复动作:
|
||||||
|
|
||||||
|
1. 启动 → join cluster(`joined cluster as xceesd`)。
|
||||||
|
2. startup 全量重推:日志 `esd full re-push: 0/1 source(s)`——**0 是正常
|
||||||
|
的**:重启后的 xceesd 没有任何缓存值(`!src->have_value` 的源被跳过,
|
||||||
|
esd.c esd_push_all),它不知道池子该是多少,也不该猜。
|
||||||
|
3. 首轮 poll 立即采集脚本**当前实况值**并推送。恢复正确性的关键在这:
|
||||||
|
采集脚本必须报 live 状态(剩余数),而不是自己记账的累计值。liveness
|
||||||
|
式脚本(如 §11.1 的 fake_lic.sh,marker 文件 = 活 pid 持有,死亡即
|
||||||
|
自清理)天然满足——重启瞬间第一轮采集就收敛到真实占用,掉线期间
|
||||||
|
已 checkout 的 license 不会被多报也不会丢报。
|
||||||
|
4. 之后与正常运行无异:debounce(值不变不推)、每轮写 status JSON、
|
||||||
|
`pushes ok/fail` 从 0 重新累计。
|
||||||
|
|
||||||
|
换言之,掉线窗口内发生过的一切(作业 checkout、release、崩溃释放)都
|
||||||
|
由"重启后第一次采集 = 实况"一次性对齐,xceesd 自己没有任何需要修复的
|
||||||
|
状态。若脚本不是 liveness 式而是自记账式,掉线期间的漂移要由脚本自身
|
||||||
|
的对账逻辑负责——写脚本时优先选 liveness 式(§5.3)。
|
||||||
|
|
||||||
### 10.2 controller 重启
|
### 10.2 controller 重启
|
||||||
|
|
||||||
xceesd 检测到连接断开会自动 re-join + 全量重推(bug-1978 修复的语义),
|
xceesd 检测到连接断开会自动 re-join + 全量重推(bug-1978 修复的语义),
|
||||||
|
|
@ -814,7 +839,73 @@ xce jobs submit -q normal -P projB -R "rusage[extlic=1]" -- /tmp/e2e/licjob.sh s
|
||||||
# 期望:RUN(projB 有自己的 3 个配额,与 projA 用量无关)
|
# 期望:RUN(projB 有自己的 3 个配额,与 projA 用量无关)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 11.5 回归清单
|
### 11.5 场景 4:xceesd 意外被杀 → 重启 recover(E2E)
|
||||||
|
|
||||||
|
实测通过的完整配方(2026-10-29,t1)。验证目标:有作业持着 license 时
|
||||||
|
`kill -9 xceesd`,重启后 daemon 自行对齐实况、无幻影容量、无丢失占用,
|
||||||
|
且回填/再准入链路不受掉线影响。
|
||||||
|
|
||||||
|
**前置**:场景 1 的环境(fake_lic.sh + licjob.sh + esd.yaml,TOTAL=4),
|
||||||
|
xceesd 在跑且已推过值。作业用 `sleep 900`(15 分钟)——步骤多、每步要
|
||||||
|
等 push/debounce,短 sleep 作业会在操作中途自然跑完污染现场。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ── 1. 造现场:3 个持 license 的 RUN 作业 ──────────────────────────
|
||||||
|
for i in 1 2 3; do
|
||||||
|
xce jobs submit -q normal -R "rusage[extlic=1]" -- /tmp/e2e/licjob.sh sleep 900
|
||||||
|
done
|
||||||
|
# 等全部 RUN(sleep 900 不会过期),确认:
|
||||||
|
ls /tmp/e2e/licpool/ | wc -l # 期望 3(3 个活 marker)
|
||||||
|
xce resources distribution list -t dynamic | grep extlic
|
||||||
|
# 期望 1(free = 4-3)
|
||||||
|
|
||||||
|
# ── 2. SIGKILL xceesd,记录现场 ────────────────────────────────────
|
||||||
|
kill -9 $(pgrep -x xceesd)
|
||||||
|
pgrep -x xceesd | wc -l # 期望 0
|
||||||
|
# 掉线窗口语义(§1.2):controller 冻结在 1,3 个作业继续 RUN。
|
||||||
|
# 此刻提交 rusage[extlic=1] 仍会按冻结值判(可能被放行——暴露窗口)。
|
||||||
|
# 注意:此时若作业 PEND,先查 pending reason 再怪池子——本机 limits.yaml
|
||||||
|
# 若带 slots 类 limit(如 limit1 per-user 3 slots),第 4 个作业可能是被
|
||||||
|
# 配额卡的(reason=limit_global),不是 license(reason=NO_RESOURCE)。
|
||||||
|
# pending reason 用 JSON 看(CLI 显示 "-",已知限制 §12.8):
|
||||||
|
TOKEN=$(curl -s -X POST "http://<apiserver>:8080/xce/v1/auth/logon" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"admin","password":"..."}' | python3 -c \
|
||||||
|
"import json,sys; print(json.load(sys.stdin)['data']['token'])")
|
||||||
|
curl -s -H "Authorization: Bearer $TOKEN" \
|
||||||
|
"http://<apiserver>:8080/xce/v1/jobs/<jobid>" | \
|
||||||
|
python3 -m json.tool | grep -A3 pending_reason
|
||||||
|
|
||||||
|
# ── 3. 重启 xceesd ─────────────────────────────────────────────────
|
||||||
|
xceesd -c /tmp/e2e/esd.yaml -l /tmp/xceesd-d.log -v &
|
||||||
|
# 日志期望(顺序):
|
||||||
|
# joined cluster as xceesd (1 source(s))
|
||||||
|
# esd full re-push: 0/1 source(s) ← 0 正常!无缓存值,跳过(§10.1)
|
||||||
|
# 随后首轮 poll 立即采集实况并推送。
|
||||||
|
sleep 15
|
||||||
|
python3 -m json.tool /tmp/e2e/esd-status.json
|
||||||
|
# 期望:pid=新进程,pushes_ok>=1,last_value=1(3 个 RUN 的 marker 还活着,
|
||||||
|
# fake_lic.sh liveness 语义自动排除死亡进程的陈旧 marker)
|
||||||
|
xce resources distribution list -t dynamic | grep extlic
|
||||||
|
# 期望 1——与掉线前一致,无幻影 4/0
|
||||||
|
|
||||||
|
# ── 4. recover 后回填:kill 一个 RUN 作业 ─────────────────────────
|
||||||
|
xce jobs kill <jobid-1>
|
||||||
|
sleep 15 # 等 poll(10s)+ push
|
||||||
|
ls /tmp/e2e/licpool/ | wc -l # 期望 2
|
||||||
|
xce resources distribution list -t dynamic | grep extlic
|
||||||
|
# 期望 2(free 回升,push 链正常)
|
||||||
|
# 再提交第 4 个作业 → RUN(recover 后再准入不受影响)
|
||||||
|
|
||||||
|
# ── 5. 收尾:等/杀掉全部作业,确认 free 回到 4 ────────────────────
|
||||||
|
```
|
||||||
|
|
||||||
|
**通过判据**:重启后 status JSON 的 pid 换新、pushes_ok 重新累计且无
|
||||||
|
fail;`full re-push: 0/1` 后首轮推送值 = 掉线前的真实 free(本例 1);
|
||||||
|
kill 作业后 marker 回收、值回升、PEND/RUN 链路照常。任何一步看到值与
|
||||||
|
池子实况不符(多报/少报),先怀疑采集脚本不是 liveness 式(§10.1)。
|
||||||
|
|
||||||
|
### 11.6 回归清单
|
||||||
|
|
||||||
提交相关改动前跑:
|
提交相关改动前跑:
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue