跳转至

工作记录

2026-07-27 —— 本次工作的记录:搭建这套文档平台、改变 edge 配置的归属模型、 补上运维上的薄弱环节,并把全部文档翻译成七种语言。

本页的目的不是记录做了什么,而是记录为什么这样决定。理由是最不容易过时的 信息——代码会变,但决策的依据依然成立。

范围

主机地址、路径、机密配置值等运维细节不包含在这个公开站点中——它们存放在 相应仓库内的封闭 runbook 里。

五条主线

# 工作 结果
1 搭建文档平台 docs.gerege.mn 上线运行
2 拆分 edge 配置的归属 每个域名迁入各自的仓库
3 补上运维薄弱环节 消除了误删与监控盲区
4 把文档翻译成七种语言 蒙古语 + 联合国六种官方语言,完整覆盖
5 记录向 Nexus 的迁移 生态系统的新模型已用七种语言记录

1. 文档平台

做了什么

一个把 Gerege 生态系统层面文档汇聚一处的 MkDocs Material 门户:25 个页面、 以蒙古语为默认、Mermaid 图示、品牌 CSS。语言覆盖后来扩展到七种——见第 4 节。

内容取自生态系统各仓库的 README、docs/ 目录以及架构文档,并整理为四个部分: 分层 · 平台 · 标准 · 运维。

关键决策

静态站点由单独的容器提供。 给 edge nginx 容器新增 mount 就必须重建它—— 那一刻所有域名都会短暂中断。改由自己的小容器来服务,edge 侧只需追加配置 并 reload 即可。

rsync 原地部署。 站点目录以 bind mount 挂进容器。若把目录整体替换 (mv),容器会继续盯着旧的 inode,新内容根本不会出现。rsync 是原地更新 文件,因此 mount 始终有效。

西里尔字母标题的锚点。 Python-Markdown 的 toc 默认 slugify 会删除 非 ASCII 字符——## Танилт 这样的标题拿到空 id,页面内链接就悄然失效。 已改用保留 unicode 的 pymdownx.slugs.slugify

开启了 validation.anchors MkDocs 的锚点校验默认是关闭的。不开启, 指向 #某节 的失效链接就会通过构建并进入生产。现在它会让 --strict 失败。

凡进入 docs/ 的内容都会公开。 因此运维细节被单独放到不进入站点的目录中。


2. edge 配置的归属

这是最大的一项架构变更。

此前的状况

所有域名的 vhost 都放在一个集中式文件里。后果是:要改 docs.gerege.mn 的一个小配置,得向别的仓库提 PR,还要等别的团队部署。归属含混,变更缓慢。

现在的状况

edge nginx 的配置目录
├──(共享文件)              ← 由统一 stack 的仓库持有
│     sso · dan · gsign · xyp
├── docs.gerege.mn.conf       ← docs-gerege-mn
├── developer.gerege.mn.conf  ← developer-gerege-mn
└── template.gerege.mn.conf   ← template-gerege-mn

现在每个域名的 vhost 都放在该服务自己的仓库中,由它自己的部署流程安装。 变更在一个仓库内即可闭环。

为什么这样可行

配置目录在物理上位于另一个仓库的工作副本内,而那个仓库的部署会执行 git reset --hard。但git reset --hard 只恢复被跟踪的文件——不会碰 untracked 文件。因此从外部仓库安装进来的文件能够存活。

完全独立的三个条件

vhost 不得在任何方面依赖那些共享文件:

条件 原因
自带 limit_req_zone 一旦引用共享的 zone 文件,就形成了依赖
自带 listen 80 块(ACME + 跳转) 这样证书续期才能独立运作
每次部署都重新安装 文件一旦丢失,可自行恢复

零中断的迁移顺序

  1. 安装新文件。此时同一个域名会在两处被定义,但 nginx 只会把它当作 conflicting server name 警告来处理——行为不变,两者都指向同一 upstream。
  2. 从集中式文件中移除。重复消失,新文件随之生效。

若顺序颠倒,在这两步之间域名就会中断。

开源仓库中不写主机路径

template-gerege-mn 是开源的,而既有约定是服务器信息只保存在 CI 的 secrets 中。因此安装脚本自行从 edge 容器的 mount 中找出配置目录的路径

docker inspect <edge> --format \
  '{{range .Mounts}}{{if eq .Destination "/etc/nginx/conf.d"}}{{.Source}}{{end}}{{end}}'

这比把路径硬编码更可靠,因此后来在三个仓库中都采用了——主机更换、路径变动 时它都能继续工作。

安全上的前置条件

先证书,后 vhost。 在还没有证书时就添加 HTTPS vhost,会让 nginx -t 失败, 那一刻所有域名都处于风险之中。ACME challenge 走的是 80 端口上通用的 default server,因此申请证书无需改动配置。

推送之前,最终配置已在临时容器中、用真实网络与真实证书通过 nginx -t 校验过。


3. 运维加固

防止误删

有些支撑着生产的文件,在任何仓库里都不是被跟踪的。git reset --hard 不会碰 它们,但git clean -fd 会删掉

文件 一旦丢失 解法
三个域名的 vhost 3 个域名同时中断 .gitignore
主机的 compose override 容器脱离 edge 网络 → 502 .gitignore + 示例文件

为什么 .gitignore 能解决: 不带 -x 时,git clean跳过被忽略的 文件。这样无需把它们纳入跟踪即可加以保护。

compose override 不能纳入跟踪:compose 会自动读取它,那样生产配置就会强行 作用于每位开发者的本地环境。因此实际生效的文件在主机上保持 untracked,仓库中 只保留一份用于恢复的示例。示例与实际配置产出一致,这一点通过比对 docker compose config 的输出得到了确认。

健康监控

此前主机上已有监控脚本,但两处缺陷叠加:它根本没有登记到 cron;而且脚本中 列出的部分容器已改名、实际不存在。脚本对不存在的容器是静默跳过的,因此监控 其实早已完全停摆,却无人知晓。

新版本的关键决策:

优先采用容器自带的 healthcheck。 它的 interval 与 retries 早已针对该服务 调校过。

HTTP 探测从容器内部发起。 若一律经 edge 探测,那么 edge 一挂就会显得 所有服务都挂了,从而触发批量重启,把真正的故障掩盖掉。现在每项检查都是 独立的。

连续失败阈值 + cooldown。 短暂的卡顿(部署、GC、负载)不会触发重启; 真正坏掉的服务也不会被反复关停重启。

有状态基础设施不自动重启。 数据库与缓存只做监控与记录。重启并不能解决 磁盘写满这类真实原因,反而会切断多个 stack 的事务,只会加重损失。 这种情况交由人来判断。

容器不存在按错误记录——不再重复旧版本的核心缺陷。

阈值 · cooldown · 有状态策略 · 恢复 · 容器缺失——这五种行为都在隔离的测试容器上 做了真实验证。

证书续期

看到 cron 里有条目并不够——续期是否真的在运行,是用 --dry-run 实测过的, 并确认所有域名都能成功续期。这类风险的特点是:直到证书到期之前一直悄无声息。


4. 七种语言的覆盖

做了什么

站点的全部页面都翻译成了蒙古语加联合国六种官方语言:العربية · 中文 · English · Français · Русский · Español。此前英文翻译只是部分完成(首页、简介、 分层、平台清单、认证),现已补全,并另加五种语言。

关键决策

蒙古语仍是源头。 其余六种都是译文——原文以蒙古语写成,再由此转译。 一旦存在两个「源」语言,内容就会悄悄分岔。

按页翻译,而不是按语言翻译。 把一份文档同时译成六种语言,术语、表格行、 结构都能保持一致。反过来「先把所有页面译成英文」,那么后译的语言就得去追赶 一份已经变过的原文。

技术性记法未作翻译。 域名、仓库名、代码、YAML、endpoint 路径、标准名称 (OIDC · PKCE · RFC 3161)在所有语言中都保持原样。译了就无法照抄执行。

文件名不翻译。platforms/sso.ru.md,而不是 платформы/sso.md。 这样切换语言时 URL 路径保持一致,来自其他仓库的深层链接也能继续生效。

阿拉伯语的 RTL 不是手工做的。 Material 能识别 ar locale,自动设置 <html dir="rtl"> 并翻转菜单与内容排布。代码块与 ASCII 图保持 LTR——这是对的, 因为命令与 URL 一旦颠倒方向,含义就被破坏了。

unicode slugify 的重要性如今翻了三倍。 当初为西里尔标题引入的 pymdownx.slugs.slugify,现在同样在支撑阿拉伯语、中文、俄语标题的锚点。 若用的是标准 slugify,六个 locale 的全部页面内链接都会悄然失效。

fallback_to_default 仍保持开启。 目前所有页面均已翻译,回退不会触发; 但它仍是一道保障:当新增页面而翻译滞后时,站点依然完整。

适用范围

本政策只适用于生态系统层面的文档,也就是本站。各平台深层的技术文档 (endpoint 结构、SDK 参考)仍在各自仓库内保持 MN + EN;它们的读者是正在该仓库 中工作的工程师,因此扩大语言覆盖收益不大。


5. 记录向 Nexus 的迁移(2026-08-07)

发生了什么

open-gerege-nexus 仓库于 2026-08-05 创建,08-07 平台更名为 Gerege Nexus 并迁至 nexus.gerege.mn。随后出现两个分叉:sso-gerege-nexus(Gerege SSO)与 eduge-mn-nexuseduge.mn)。由于这改变了生态系统的分发模型,文档在七种 语言上一并更新对齐。

关键决策

没有删除任何既有平台页面。 Template、Gerege Platform、SSO 与 Kiosk 都仍在 production 运行。做法是新增一个页面,并在既有页面上标注「当前以哪种状态为准」。 删除页面等于抹掉一个仍在运行的系统的文档。

把第 3 层拆成两代。 Nexus 并不取代 Template——二者同时位于第 3 层。若新增一 层,分层规则(「不得跨层直达」)本身就失去了意义。

Nexus 自带的 OIDC 提供方并不是第 2 层。 Nexus 内含 OAuth2/OIDC 提供方,但它 服务的是该部署自己的租户与第三方客户端。生态系统识别公民的路径仍然是 Gerege SSO。不点明这一点,读者就会得出「Nexus 取代了 SSO」的错误结论。

每个域名都逐一实测。 通过 DNS、HTTP 与 TLS 证书确认 nexus.gerege.mneduge.mngeregekiosk.mn 均在线。由此发现两件事:geregekiosk.mn 现在 提供的是 Nexus;而 open.gerege.mn从该主机的证书中移除,因此 HTTPS 会 因名称不匹配而失败。

订正了一条过时的承诺。 Template 页面称 autosync 每天把变更送往下游;该自动化 已于 2026-08-06 在全机队范围停用。虚假的承诺比缺失的事实更糟:工程师会一直等着 修复自己流过去。

补上了两处翻译缺口。 域名映射中的「独立品牌域名」一节在六种译文里完全缺失; 现已七种语言齐平。

决策摘要

决策 依据
静态站点用独立容器 重建 edge 会让所有域名中断
rsync,不用 mv bind mount 会停留在旧 inode 上
unicode slugify 标准 slugify 会毁掉西里尔标题的锚点
开启 validation.anchors 否则失效链接会流入生产
vhost 放在服务自己的仓库 变更在一个仓库内闭环
vhost 完全自包含 若依赖共享文件,拆分就失去意义
先安装,后移除 顺序颠倒会导致域名中断
自动探测路径 比硬编码可靠;开源仓库中不留下路径
.gitignore 保护 git clean 会跳过被忽略的文件
不把 override 纳入跟踪 compose 会自动读取——生产配置会作用于本地
探测从容器内部发起 避免 edge 一挂就批量重启
有状态服务不重启 重启不解决根因,只会加重损失
蒙古语是唯一源语言 有两个「源」,内容就会悄悄分岔
按页翻译,不按语言 术语与结构在六种语言中保持一致
不翻译代码、域名、仓库名 译了就无法照抄执行
不翻译文件名 切换语言时 URL 路径不变,深层链接可用
各语言置于子路径(/ar/ 若改用子域名,SAN · vhost · hreflang 会同时膨胀

有意未做的事

没有把 sso · dan · gsign · xyp 域名拆出去。 它们的代码就在统一 stack 的仓库内,因此那个集中式文件本来就是它们自己的仓库。没有拆分的理由。

没有给 edge 容器新增单独的 mount。 那不仅要改统一 stack 的 compose 文件, 还得重建容器,从而让所有域名短暂中断。

没有强行做蒙古语的搜索索引。 lunr.js 不支持蒙古语,因此默认 locale 的 搜索按标准分词工作。自行编写 stemmer 的代价高于当前收益。

没有为每种语言单独开域名或子域名。 /ar//zh/ 这类路径用一张证书、 一个 vhost、一次部署即可解决。改用子域名会让证书 SAN、edge 配置与 hreflang 同时膨胀。

残留风险

三个域名的 vhost 与主机上的 override 文件虽有 .gitignore 保护,但若有人执行 git clean -fdx(连被忽略的文件也一并清除),它们仍会被删除。修复办法是 重新运行相应仓库的安装脚本——这一点已记入三个仓库各自的 runbook。