ADR 0209 — engine 升级到 Elixir 1.20.4 / OTP 29.1.1
状态
已接受。 2026-10-02(AT-398;所有者于 10月2日的决定,“subir o engine para OTP 29.1.x”)。更换 ADR 0024 中固定的运行时(决策 10:Dockerfile 和 CI 中使用相同的 Elixir 版本),但不编辑它——单一版本的规则仍然有效,改变的是版本本身。
背景
自 AT-361 起,engine 的 Dockerfile.prod 运行在 Alpine 3.24.2 与 OTP 27.3.4.18 之上(hexpm 不在 3.24 上发布 27.1.2),而开发和 CI 停留在 Elixir 1.17.3 / OTP 27.1.2 / Alpine 3.20.3。CI 中 engine 的构建开始在约 10% 的运行中中止(#821 之前 0/57,之后 5/49),报错:
sys_signal_stack.c:101:sys_sigaltstack(): Internal error
测得的原因:Alpine 3.24 的 musl 1.2.6 在带 AMX 的主机上拒绝 ERTS 27 请求的固定大小的信号备用栈(SIGSTKSZ = 8192)(erlang/otp#11349)。修复——按 sysconf(_SC_MINSIGSTKSZ) 确定大小(提交 3a94587176/fc35c2496c)——只从 OTP 29.1 开始存在。同样的缺陷会让生产中的 engine 在带 AMX 的服务器上启动即死:CI 的间歇性失败只是机器抽签的结果。
把 Alpine 退回 3.22 可以消除症状,但会把镜像冻结在一条不再获得安全修复的线上;未予采纳。
决策
- engine 在开发、生产和 CI 中运行 Elixir 1.20.4 / OTP 29.1.1。镜像是
hexpm/elixir:1.20.4-erlang-29.1.1-alpine-3.24.2,按索引的 digest 固定(sha256:ad851f40…,ADR 0159/0178),在Dockerfile.prod的 builder 和开发用Dockerfile中是同一个。生产运行时仍是alpine:3.24.2(builder 的 musl 与运行时的一致)。Elixir 1.20 是唯一一条有 hexpm 针对 OTP 29.1 的镜像和构建的版本线。 ELIXIR_VERSION/OTP_VERSION在声明它们的三个工作流(ci.yml、golden-set-rag.yml、golden-set-qa.yml)中一起升级,mix.exs改为要求~> 1.20。- “Build, scan e smoke” 作业会打印主机是否有 AMX(
grep -c amx_tile /proc/cpuinfo):在带 AMX 的主机上为绿,就证明缺陷没有回来,而不是抽签的运气。 mix.lock中的依赖没有任何变化:mix deps.get+mix compile --warnings-as-errors在新版本上通过。Elixir 1.20 的类型检查器指出了Engine.Agents.FilaDeMensagens.acordar/3中的一个死子句——启动 agent 失败会抛出,而不是作为值返回——它通过按 agent 容纳错误得到修正,而不是被静默。
后果
- 开发的代价: engine 的
_build/deps卷是用 OTP 27 编译的。Mix 会自动重新编译用其他 Elixir 版本构建的内容,所以docker compose up -d --build engine就够了;如果卷坚持保留旧产物,就对 engine 的两个卷执行docker volume rm。由于是自动重编译,该变更对运维者不是 breaking。 - 在 Docker 之外运行 engine 的人需要 Elixir 1.20.4 / OTP 29.1.1(
mix format --check-formatted取决于版本,ADR 0024 决策 10)。 - Dependabot
docker:有了 OTP 29.1 + Alpine 3.24,不再有坏掉的组合,也没有添加任何ignore。它可能提议更新的 OTP 29.1.x 的hexpm/elixirtag——它们都包含该修复。一个把版本降到 OTP < 29.1 且 Alpine ≥ 3.24 的提议会重新引入缺陷,应在评审中拒绝。 - Kubernetes 中的滚动发布会短暂地混合 OTP 27 和 29 的节点:Erlang 分布式接受相差两个大版本的节点对。