Make configuration updates reach already-deployed installs #1

Closed
opened 2026-07-28 12:14:14 +09:00 by FLYEMOJ1 · 1 comment
FLYEMOJ1 commented 2026-07-28 12:14:14 +09:00 (Migrated from github.com)

问题

系统服务器的 MinecraftServer CR 是 create-if-absent 的:CR 一旦存在,felis setup 就不再碰它的 spec。所以任何"给系统服务器 CR 加一个字段"的改动,在已有安装上都是静默空操作 —— 命令照样报成功。

受影响的是 login 和 lobby 两台(ensureSystemServers 的 plans 只有这两个;demo/demo2 是用户服务器,从来不走这条路径)。目前卡在这里的是后来才加进 CRD 的 spec.rcon。

2026-07-28 更正:这条最初把另外两处补救也写成了自己的症状,那是错的。逐条按"字段实际住在哪个资源上"复核之后:

症状 字段住在 重跑 deploy/bootstrap.sh 重跑 felis setup
FELIS_SMTP_PASSWORD(issue #2) felis-api Deployment 到得了 到不了
LuckPerms(issue #4) 容器镜像 + StatefulSet 到得了 到不了
spec.rcon(issue #3) MinecraftServer CR 到不了 到不了 ← 只有这条

#2 和 #4 有现成的收敛路径,只是没人把它写进 tracker,各自的 issue 已补上命令。本条的真实范围只剩 CR 那一格。

证据

Found by Claude Opus 5 (claude-opus-5) while tracing why three unrelated remediation items all needed the same manual step, on 2026-07-28.

cmd/felis/systemservers.go:297-299 — create-if-absent 是写明的设计:

// Create-if-absent: check first so an existing service is reported as a
// deliberate skip rather than an AlreadyExists error. Never adopt a
// legacy user server that happens to occupy a reserved system name.

已存在时唯一的更新路径是 refreshDerivedEnv(:311),而它的作用域比名字听起来窄得多 —— :382-395:

want := make(map[string]string, len(derivedSystemEnv))
for _, e := range desired.Spec.Env {
	if derivedSystemEnv[e.Name] { want[e.Name] = e.Value }
}
changed := false
for i, e := range existing.Spec.Env {
	if v, ok := want[e.Name]; ok && v != e.Value {
		existing.Spec.Env[i].Value = v
		changed = true
	}
}

两层限制叠在一起:只有 derivedSystemEnv 白名单里的 key 会被考虑,且循环是在 existing 上跑的,只改已有条目的值。一个新增的 env key 没有任何路径能进去。CR 上的其它 spec 字段(spec.rcon、spec.image)连白名单都不沾,:316-320 直接走 skipped: "already exists" 分支返回。

至于为什么 felis setup 对 Deployment 和镜像也无能为力 —— 它确实包着 bootstrap(bootstrap.sh:14:The recommended entrypoint is now 'sudo felis setup', which wraps this bootstrap),但有门控,而已部署的安装恰好全部命中,于是跳过:

func hostBootstrapReady(marker, hostConfig, hostBin, kubeconfig string) bool {
	return fileExists(marker) && fileExists(hostConfig) && executableExists(hostBin) && fileExists(kubeconfig)
}

cmd/felis/setup.go:69-74 只在 hostBootstrapReady 为假时才跑 bootstrap。所以给已部署安装的补救命令必须写成 deploy/bootstrap.sh,不能写成 felis setup。

验收

在一套已部署的安装上改一个系统服务器的 spec 字段(比如给 lobby 加 spec.rcon.enabled),跑一遍收敛命令,kubectl get minecraftserver lobby -o yaml 能看到这个字段。当前无论跑什么都看不到。

备注

不要用"每次 setup 都 Update 一遍"来修。那会把运维手动调过的 CR 字段一起冲掉,:297-299 的注释和 LabelSystemRole 检查就是在防这个。

还有一个更具体的雷:systemRcon(:70-81)渲出的 lobby 期望态是 RconSpec{Enabled: true}。任何"把期望态整体写回去"的机制都会在 lobby 上把 RCON 打开,而已部署的 lobby 镜像不提供 RCON 监听 → reconciler 的就绪门卡在 RCON 探针 → 出不了 Starting → markFailed。这正是 issue #3 那条"镜像先、CR patch 后"的顺序在防的事,而自动机制无法遵守一个由人决定的顺序。

所以三个候选方向不是等价的:

  1. 一条显式的 felis converge / felis setup --reconcile —— 操作者在重建完镜像之后自己决定何时跑,顺序保得住;
  2. 把系统服务器的期望态交给 operator 持续 reconcile —— 替人做了那个决定;
  3. 带版本号的迁移机制,只对新增字段生效 —— 同样替人做了决定,且新增 spec.rcon 恰好就是会踩雷的那类。

选哪条是设计决定,不是机械改动,所以这条留着等拍板。

## 问题 系统服务器的 MinecraftServer CR 是 create-if-absent 的:CR 一旦存在,`felis setup` 就不再碰它的 spec。所以任何"给系统服务器 CR 加一个字段"的改动,在已有安装上都是静默空操作 —— 命令照样报成功。 受影响的是 login 和 lobby 两台(`ensureSystemServers` 的 `plans` 只有这两个;demo/demo2 是用户服务器,从来不走这条路径)。目前卡在这里的是后来才加进 CRD 的 `spec.rcon`。 > **2026-07-28 更正**:这条最初把另外两处补救也写成了自己的症状,那是错的。逐条按"字段实际住在哪个资源上"复核之后: > > | 症状 | 字段住在 | 重跑 `deploy/bootstrap.sh` | 重跑 `felis setup` | > |---|---|---|---| > | `FELIS_SMTP_PASSWORD`(issue #2) | felis-api **Deployment** | **到得了** | 到不了 | > | LuckPerms(issue #4) | 容器**镜像** + StatefulSet | **到得了** | 到不了 | > | `spec.rcon`(issue #3) | MinecraftServer **CR** | 到不了 | **到不了** ← 只有这条 | > > #2 和 #4 有现成的收敛路径,只是没人把它写进 tracker,各自的 issue 已补上命令。本条的真实范围只剩 CR 那一格。 ## 证据 Found by Claude Opus 5 (claude-opus-5) while tracing why three unrelated remediation items all needed the same manual step, on 2026-07-28. `cmd/felis/systemservers.go:297-299` — create-if-absent 是写明的设计: ```go // Create-if-absent: check first so an existing service is reported as a // deliberate skip rather than an AlreadyExists error. Never adopt a // legacy user server that happens to occupy a reserved system name. ``` 已存在时唯一的更新路径是 `refreshDerivedEnv`(`:311`),而它的作用域比名字听起来窄得多 —— `:382-395`: ```go want := make(map[string]string, len(derivedSystemEnv)) for _, e := range desired.Spec.Env { if derivedSystemEnv[e.Name] { want[e.Name] = e.Value } } changed := false for i, e := range existing.Spec.Env { if v, ok := want[e.Name]; ok && v != e.Value { existing.Spec.Env[i].Value = v changed = true } } ``` 两层限制叠在一起:只有 `derivedSystemEnv` 白名单里的 key 会被考虑,且循环是在 **existing** 上跑的,只改已有条目的值。一个新增的 env key 没有任何路径能进去。CR 上的其它 spec 字段(`spec.rcon`、`spec.image`)连白名单都不沾,`:316-320` 直接走 `skipped: "already exists"` 分支返回。 至于为什么 `felis setup` 对 Deployment 和镜像也无能为力 —— 它确实包着 bootstrap(`bootstrap.sh:14`:`The recommended entrypoint is now 'sudo felis setup', which wraps this bootstrap`),但有门控,而已部署的安装恰好全部命中,于是跳过: ```go func hostBootstrapReady(marker, hostConfig, hostBin, kubeconfig string) bool { return fileExists(marker) && fileExists(hostConfig) && executableExists(hostBin) && fileExists(kubeconfig) } ``` `cmd/felis/setup.go:69-74` 只在 `hostBootstrapReady` 为假时才跑 bootstrap。所以给已部署安装的补救命令必须写成 `deploy/bootstrap.sh`,不能写成 `felis setup`。 ## 验收 在一套已部署的安装上改一个系统服务器的 spec 字段(比如给 lobby 加 `spec.rcon.enabled`),跑一遍收敛命令,`kubectl get minecraftserver lobby -o yaml` 能看到这个字段。当前无论跑什么都看不到。 ## 备注 不要用"每次 setup 都 Update 一遍"来修。那会把运维手动调过的 CR 字段一起冲掉,`:297-299` 的注释和 `LabelSystemRole` 检查就是在防这个。 还有一个更具体的雷:`systemRcon`(`:70-81`)渲出的 lobby 期望态是 `RconSpec{Enabled: true}`。任何"把期望态整体写回去"的机制都会在 lobby 上把 RCON 打开,而已部署的 lobby 镜像不提供 RCON 监听 → reconciler 的就绪门卡在 RCON 探针 → 出不了 Starting → `markFailed`。这正是 issue #3 那条"镜像先、CR patch 后"的顺序在防的事,**而自动机制无法遵守一个由人决定的顺序**。 所以三个候选方向不是等价的: 1. 一条显式的 `felis converge` / `felis setup --reconcile` —— 操作者在重建完镜像之后自己决定何时跑,顺序保得住; 2. 把系统服务器的期望态交给 operator 持续 reconcile —— 替人做了那个决定; 3. 带版本号的迁移机制,只对新增字段生效 —— 同样替人做了决定,且新增 `spec.rcon` 恰好就是会踩雷的那类。 选哪条是设计决定,不是机械改动,所以这条留着等拍板。
Lemon-miaow commented 2026-09-24 12:02:30 +09:00 (Migrated from github.com)

已实现并真机闭环(c57daaf):新增 sudo felis converge——只填「已有 CR 上仍为零值」的新增字段(spec.rcon、spec.startup.healthHTTPPort)与配置派生 env(含缺失键的新增),非零值一律不覆盖;时机由操作者自选(先重建镜像、后 converge),避开 lobby RCON / login 健康门对镜像顺序的依赖——即本 issue 三个候选方向里的显式收敛路线。

真机演练:剥掉 live lobby 的 spec.rcon 与 login 的 spec.startup.healthHTTPPort → converge → 两字段全部填回;二次运行 = already converged(幂等);operator 无异常滚动,login/lobby 均回 Running/Ready。操作说明见 docs/troubleshooting.md §12b。

已实现并真机闭环(`c57daaf`):新增 `sudo felis converge`——只填「已有 CR 上仍为零值」的新增字段(`spec.rcon`、`spec.startup.healthHTTPPort`)与配置派生 env(含缺失键的新增),非零值一律不覆盖;时机由操作者自选(先重建镜像、后 converge),避开 lobby RCON / login 健康门对镜像顺序的依赖——即本 issue 三个候选方向里的显式收敛路线。 真机演练:剥掉 live lobby 的 `spec.rcon` 与 login 的 `spec.startup.healthHTTPPort` → converge → 两字段全部填回;二次运行 = `already converged`(幂等);operator 无异常滚动,login/lobby 均回 Running/Ready。操作说明见 `docs/troubleshooting.md` §12b。
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: FelisMC/Felis#1