NFS 挂载参数优化(元数据延迟优先)— 变更 SOP¶
交付目录:
nfs-mount-tuning/变更日期:2026-09-19 变更类型:C 类生产变更(服务端导出参数 + 客户端挂载参数 + 已有卷重新挂载) 实现体:nfs-mount-tuning/的 Python 部署工程(main.py+modules/+config/tune.yaml) 本文第 5 章的 shell 代码块用于阅读、复核与手工复现;幂等、备份与回滚由实现体保证,以实现体为准
目录¶
1. 背景¶
集群 k8s_ins 的 NFS 存储由 nfs-storage/SOP.md 于 2026-09-17 交付,当时客户端参数为
nfsvers=4.1, hard, timeo=600, retrans=3, noatime,服务端三处导出为 rw,sync,no_subtree_check,no_root_squash。
- 触发条件:用户要求以「元数据延迟优先」为目标优化 NFS 挂载参数,并允许全层联动调整 (客户端参数、协议版本、服务端导出),且确认现有业务数据可删除重建。
- 实测暴露的瓶颈:HDD 后端(
/srv/nfs/k8s-hdd)在节点侧的OPEN/SETATTR平均 RTT 高达 60–80ms,而 SSD 后端仅 1–3ms。根因是sync导出下每次元数据写都要等机械盘落盘, 且该盘同时承载 4 台 k8s VM 的 qcow2(与nfs-storage/SOP.mdR13 记录一致)。 - 不涉及的资源:GitLab 本体的 gitaly / postgres / redis 走
local-ssd,与本次变更无关。
2. 目标¶
| 编号 | 目标状态 | 验证方式 |
|---|---|---|
| G1 | 三处导出由 sync 改为 async,其余选项不变 |
第 6 章 V1 |
| G2 | nfsd 线程数 8 → 32,且重启后仍生效 | 第 6 章 V2 |
| G3 | 两个 loop 镜像挂载追加 noatime |
第 6 章 V3 |
| G4 | 客户端参数为 nfsvers=4.2, nconnect=4, hard, timeo=600, retrans=3, noatime |
第 6 章 V4、V5 |
| G5 | 三处参数一致(values / Secret / StorageClass),已有 PV 全部重新挂载生效 | 第 6 章 V6、V7 |
| G6 | 元数据延迟显著下降并有留档 | 第 6 章 V11 |
| G7 | 全部操作幂等:第二次执行零变更、全跳过 | 第 6 章 V10 |
3. 前置条件¶
权限与凭据
| 项 | 要求 | 取值来源 |
|---|---|---|
| 宿主机 sudo | 需要覆盖 /etc/exports、/etc/nfs.conf、/etc/fstab、/proc/fs/nfsd/threads、loop remount 的免密提权 |
已验证 sudo -n true 可用 |
| 集群 | cluster-admin |
kubeconfig:/home/rain/.kube/config,上下文 kubernetes-admin@kubernetes |
| NFS 服务端 | 无独立凭据,导出使用 sec=sys(AUTH_SYS,按客户端 IP 网段授权) |
/etc/exports |
工具与依赖
| 依赖 | 实际路径 | 版本 |
|---|---|---|
| helm | /usr/local/bin/helm |
v3.16.3 |
| kubectl | /usr/local/bin/kubectl |
v1.36.4 |
| fio | /usr/bin/fio |
3.36 |
| python3 | 系统 python3 |
3.12.3(工程内 .venv 由它创建) |
| csi-driver-nfs chart | nfs-storage/charts/csi-driver-nfs/(本地固化) |
4.13.4 |
执行前检查项
- 服务端
nfs-server处于 active 且三处导出存在。 /data上有运行中的 VM qcow2,禁止对/data本体做任何 umount 操作。- 确认写敏感业务可停:本次为
server/mariadb(数据库)、server/minio。 - 确认无其他人同时做存储相关变更(本次执行期间环境曾被外部改动,见第 8 章 R3)。
- 工程可正常加载配置与预演:
cd /home/rain/Documents/k8s_ins/nfs-mount-tuning
.venv/bin/python main.py --config config/tune.yaml --dry-run
# 预期:四个阶段均输出「[dry-run] 将执行变更: …」,最后一行「执行结束 | 失败阶段: 无」
4. 影响范围¶
主机与组件
| 对象 | 角色 | 受影响内容 |
|---|---|---|
dell-R620(192.168.31.249) |
NFS 服务端 | 覆盖 /etc/exports、/etc/nfs.conf、/etc/fstab;两个 loop 挂载 remount;nfsd 线程数热调整 |
k8s-node-01/02/03、k8s-master-01 |
CSI node 客户端 | 所有 nfs4 挂载点重新挂载(连接级参数必须重新挂载才生效) |
kube-system |
CSI 驱动 | helm upgrade 滚动重建 controller 与 node 插件 |
server / monitoring 命名空间 |
业务负载 | 逐个重启(非同时),期间服务短暂中断 |
是否停服:需要。
server/mariadb、server/minio在服务端切async之前主动停写(副本置 0),改造完成后再拉起;- 其余 NFS 使用者(
prometheus、grafana、alertmanager、openldap)逐个重启以重新挂载。
窗口期:无强制窗口,建议低峰执行;/data 全程不动,运行中的 VM 不受影响。本次分步执行实测约 6 分钟(不含基线压测)。
变更前在跑的 NFS 使用者(实机核查结果)
| 命名空间 | 卷 | StorageClass | 使用方 |
|---|---|---|---|
server |
minio-data 50Gi、storage-mariadb-0 20Gi |
nfs-csi |
minio、mariadb(数据库) |
server |
conf/data/backups-openldap-0 |
nfs-csi-hdd |
openldap |
monitoring |
kube-prometheus-stack-grafana 10Gi、alertmanager-…-db 10Gi |
nfs-csi |
Grafana、Alertmanager |
monitoring |
prometheus-…-db 100Gi |
nfs-csi-hdd |
Prometheus |
变更期间
ldap命名空间的 openldap 被外部迁移到server命名空间(详见第 8 章 R3); 日常查看这些负载的方法见docs/RUNBOOK.md。
5. 分步操作¶
对应实现体:
nfs-mount-tuning/main.py与modules/*.py;期望状态集中在config/tune.yaml。 每步的真实动作、备份与校验均由实现体执行,下面的命令序列与其等价,供手工复现与复核。
5.1 预演(不改任何系统状态)¶
cd /home/rain/Documents/k8s_ins/nfs-mount-tuning
.venv/bin/python main.py --config config/tune.yaml --dry-run # 预期:各阶段列出「将执行变更」,无实际写入
5.2 采集元数据延迟基线¶
# 高危:会在导出根目录下创建临时测试目录 .meta-bench 并执行元数据压测
# 测完由实现体自动清理;清理前对路径做白名单校验(仅允许挂载点的 .meta-bench* 直接子目录)
.venv/bin/python main.py --config config/tune.yaml --stages measure-before
echo $? # 预期:0
ls -t logs/measure-before-*.json | head -1 # 预期:输出一份报告路径
5.3 静默写敏感业务¶
# 高危:停服。mariadb 走 CR 的 spec.replicas,minio 走 StatefulSet 副本数;改完必须恢复
kubectl -n server patch mariadb mariadb --type=merge -p '{"spec":{"replicas":0}}' # 预期:mariadb.k8s.mariadb.com/mariadb patched
kubectl -n server scale statefulset minio --replicas=0 # 预期:statefulset.apps/minio scaled
.venv/bin/python main.py --config config/tune.yaml --stages quiesce # 预期:验证通过: server/mariadb 已停止、server/minio 已停止
实现体会自行判断「服务端是否已是
async」:若已生效则整体跳过,不会重复停业务。
5.4 服务端调优¶
# 高危:覆盖系统配置。实现体在改动前自动备份(备份位置见第 7 章与 backup/README.md)
sudo cp -a /etc/exports "/etc/exports.bak.$(date +%Y%m%d-%H%M%S)" # 手工复现时的备份动作
sudo cp -a /etc/nfs.conf "/etc/nfs.conf.bak.$(date +%Y%m%d-%H%M%S)"
sudo cp -a /etc/fstab "/etc/fstab.bak.$(date +%Y%m%d-%H%M%S)"
# 5.4.1 三处导出 sync -> async(其余选项保持 rw,no_subtree_check,no_root_squash)
# 目标文件:/etc/exports 中的 /srv/nfs/k8s、/srv/nfs/k8s-hdd、/data/nfs-k8s-snap
sudo exportfs -ra # 重载导出(幂等)
sudo exportfs -v # 预期:三处均含 async,且不含 sync
# 5.4.2 nfsd 线程 8 -> 32(热调整,写 /proc 即可生效,无需重启服务)
printf '32\n' | sudo tee /proc/fs/nfsd/threads # 预期:回显 32
sudo cat /proc/fs/nfsd/threads # 预期:32(若为 8,见第 6 章 V2 的处置)
# 持久化到 /etc/nfs.conf 的 [nfsd] 段(threads = 32),保证重启后仍生效
# 5.4.3 loop 镜像挂载追加 noatime(remount 不卸载,不影响在用文件)
sudo mount -o remount,noatime /srv/nfs/k8s # 预期:无输出
sudo mount -o remount,noatime /srv/nfs/k8s-hdd # 预期:无输出
findmnt -no OPTIONS --target /srv/nfs/k8s # 预期:rw,noatime,prjquota
# 交由实现体统一执行(含状态巡检、备份、写入、热生效与验证)
.venv/bin/python main.py --config config/tune.yaml --stages server
# 预期:验证通过: exportfs -v 中三处导出已为 async / 验证通过: nfsd 线程数 = 32 / 验证通过: 两个挂载点选项含 noatime
5.5 集群侧参数同步(三处必须一致)¶
# 5.5.1 实现体更新两处文件:values 的两个 mountOptions 块、Secret 清单的 stringData
# 5.5.2 应用 Secret -> helm upgrade -> 重新 apply 快照类(防止 helm 重置 parameters)
kubectl apply -f /home/rain/Documents/k8s_ins/nfs-storage/manifests/00-mount-options-secret.yaml
helm upgrade --install nfs-csi /home/rain/Documents/k8s_ins/nfs-storage/charts/csi-driver-nfs \
-n kube-system -f /home/rain/Documents/k8s_ins/nfs-storage/values-nfs-csi.yaml --wait --timeout 6m
kubectl apply -f /home/rain/Documents/k8s_ins/nfs-storage/manifests/02-volumesnapshotclass.yaml
.venv/bin/python main.py --config config/tune.yaml --stages cluster
# 预期:验证通过: StorageClass nfs-csi / nfs-csi-hdd mountOptions=[…4.2, nconnect=4…];验证通过: Secret mountOptions=…
5.6 已有 PV 生效(patch 参数 + 重新挂载)¶
# 高危:patch 后需重启使用方,服务短暂中断;nconnect 只在重新挂载(重新 stage)时才生效
kubectl get pv -o custom-columns='NAME:.metadata.name,OPTS:.spec.mountOptions' | grep nfs-vers # 先看现状
kubectl patch pv <pv-name> --type=merge \
-p '{"spec":{"mountOptions":["nfsvers=4.2","nconnect=4","hard","timeo=600","retrans=3","noatime"]}}'
kubectl -n server scale statefulset minio --replicas=1 # 恢复被静默的对象
kubectl -n monitoring rollout restart statefulset/prometheus-kube-prometheus-stack-prometheus
.venv/bin/python main.py --config config/tune.yaml --stages volume
# 预期:逐个 PV 输出「验证通过: PV … mountOptions=[…]」,并在节点侧复核实际挂载参数
实现体会按「挂载路径中的 Pod UID」反查控制器,自动重启所有实际挂载参数未达标的工作负载, 不依赖静态清单,因此清单外的新增业务也会被覆盖。
5.7 复测与幂等复核¶
.venv/bin/python main.py --config config/tune.yaml --stages measure-after # 预期:生成 logs/measure-after-*.json
.venv/bin/python main.py --config config/tune.yaml --skip-measure # 预期:四阶段全部 skipped,零变更
5.8 功能回归¶
kubectl apply -f /home/rain/Documents/k8s_ins/nfs-storage/examples/01-pvc.yaml
kubectl apply -f /home/rain/Documents/k8s_ins/nfs-storage/examples/02-cross-node-pods.yaml
kubectl exec pvc-writer -- sh -c 'echo hello > /data/hello.txt' # 预期:无输出
kubectl exec pvc-reader -- cat /data/hello.txt # 预期:hello
kubectl delete pod pvc-writer pvc-reader --ignore-not-found=true
kubectl delete pvc demo-data
6. 验证¶
| 编号 | 命令 | 预期结果 | 本次实测 |
|---|---|---|---|
| V1 | sudo exportfs -v |
三处导出均含 async,wdelay,…,不含 sync |
通过 |
| V2 | sudo cat /proc/fs/nfsd/threads |
32;/etc/nfs.conf 的 [nfsd] 段有 threads = 32 |
通过 |
| V3 | findmnt -no OPTIONS --target /srv/nfs/k8s |
rw,noatime,prjquota |
通过 |
| V4 | kubectl get sc -o jsonpath='{range .items[?(@.provisioner=="nfs.csi.k8s.io")]}{.metadata.name}{" -> "}{.mountOptions}{"\n"}{end}' |
两个 SC 均为 ["nfsvers=4.2","nconnect=4","hard","timeo=600","retrans=3","noatime"] |
通过 |
| V5 | kubectl -n kube-system get secret nfs-mount-options -o jsonpath='{.data.mountOptions}' \| base64 -d |
nfsvers=4.2,nconnect=4,hard,timeo=600,retrans=3,noatime |
通过 |
| V6 | kubectl get pv -o custom-columns='NAME:.metadata.name,OPTS:.spec.mountOptions' |
11 个 NFS PV 全部为新参数 | 通过 |
| V7 | 逐节点 findmnt -t nfs4 -o TARGET,OPTIONS(经 csi-nfs-node 容器 nsenter) |
全部挂载含 vers=4.2 与 nconnect=4 |
通过(8/8) |
| V8 | 新建 PVC 后查其 PV 的 mountOptions | 自动继承新参数 | 通过 |
| V9 | 跨节点读写(node-01 写 / node-02 读) | 内容一致 | 通过 |
| V10 | main.py --skip-measure 复跑 |
四阶段 skipped,零变更、无报错 |
通过 |
| V11 | 对比 logs/measure-before-*.json 与 logs/measure-after-*.json |
元数据 RTT 显著下降 | 通过,见下表 |
元数据延迟对比(节点侧 /proc/self/mountstats 的 per-op 平均 RTT;挂载重建后重新计数)
| 节点 / 后端 | 操作 | 改造前 | 改造后 | 改善 |
|---|---|---|---|---|
| node-02 / HDD(prometheus 卷) | OPEN | 67.60 ms | 0.25 ms | 约 270× |
| node-02 / HDD(prometheus 卷) | SETATTR | 79.76 ms | 0.23 ms | 约 354× |
| node-02 / SSD(alertmanager 卷) | SETATTR | 3.22 ms | 0.38 ms | 约 8× |
| node-01 / SSD(mariadb 卷) | SETATTR | 3.05 ms | 0.19 ms | 约 16× |
| node-01 / SSD(minio 卷) | OPEN | 1.01 ms | 0.32 ms | 约 3× |
| node-03 / SSD(grafana 卷) | OPEN | 2.87 ms | 0.37 ms | 约 8× |
宿主本地挂载点 fio(服务端侧路径,无网络因素):create p50 0.0265 → 0.026 ms、stat p50 0.0048 → 0.0033 ms、
delete p50 0.0468 → 0.0453 ms,变化不显著 —— 说明收益主要来自服务端 async 消除了机械盘的 fsync 等待。
输出不符时的处置:V1/V2 不达标 → 重跑 --stages server 并检查 sudo -n 权限;
V4/V5 不达标 → 检查 values-nfs-csi.yaml 与 Secret 清单是否被手工改回,重跑 --stages cluster;
V7 存在旧参数挂载 → 说明对应工作负载尚未重新挂载,重跑 --stages volume;
V10 出现变更 → 说明有配置被外部改动,按输出逐条核对后再决定是否接受。
7. 回滚¶
7.1 备份位置¶
| 对象 | 位置 | 说明 |
|---|---|---|
/etc/exports、/etc/nfs.conf、/etc/fstab |
原位:同目录 *.bak.<时间戳> |
实现体 --rollback 依赖「同目录最新 .bak.*」,故不迁移 |
nfs-storage/values-nfs-csi.yaml、nfs-storage/manifests/00-mount-options-secret.yaml |
nfs-mount-tuning/backup/nfs-storage/… |
结构为 backup/<相对项目根的原路径>/<原名>.bak.<时间戳> |
完整台账(含每份备份的产生原因、回滚对象、可删时机)见 nfs-mount-tuning/backup/README.md。
7.2 回滚步骤¶
cd /home/rain/Documents/k8s_ins/nfs-mount-tuning
# 1) 服务端:按最新时间戳备份还原三个文件,并重新生效
.venv/bin/python main.py --config config/tune.yaml --rollback
# 预期:「已回滚 /etc/exports <- /etc/exports.bak.<ts>」等三行,最后「回滚成功」
# 等价手工动作:
# sudo cp -a /etc/exports.bak.<ts> /etc/exports
# sudo cp -a /etc/nfs.conf.bak.<ts> /etc/nfs.conf
# sudo cp -a /etc/fstab.bak.<ts> /etc/fstab
# printf '8\n' | sudo tee /proc/fs/nfsd/threads # nfsd 线程回到默认
# sudo exportfs -ra # 导出回到 sync
# sudo mount -o remount /srv/nfs/k8s && sudo mount -o remount /srv/nfs/k8s-hdd
# 2) 集群侧:把 values 与 Secret 清单还原为 backup/ 下的旧版本后重新升级
cp backup/nfs-storage/values-nfs-csi.yaml.bak.<ts> /home/rain/Documents/k8s_ins/nfs-storage/values-nfs-csi.yaml
cp backup/nfs-storage/manifests/00-mount-options-secret.yaml.bak.<ts> \
/home/rain/Documents/k8s_ins/nfs-storage/manifests/00-mount-options-secret.yaml
kubectl apply -f /home/rain/Documents/k8s_ins/nfs-storage/manifests/00-mount-options-secret.yaml
helm upgrade --install nfs-csi /home/rain/Documents/k8s_ins/nfs-storage/charts/csi-driver-nfs \
-n kube-system -f /home/rain/Documents/k8s_ins/nfs-storage/values-nfs-csi.yaml --wait --timeout 6m
kubectl apply -f /home/rain/Documents/k8s_ins/nfs-storage/manifests/02-volumesnapshotclass.yaml
# 已有 PV 需再次 patch 回 `["nfsvers=4.1","hard","timeo=600","retrans=3","noatime"]` 并重启使用方
# 3) 业务恢复(若仍处于静默状态)
kubectl -n server patch mariadb mariadb --type=merge -p '{"spec":{"replicas":1}}'
kubectl -n server scale statefulset minio --replicas=1
7.3 回滚后的验证¶
sudo exportfs -v # 预期:三处导出回到 sync
sudo cat /proc/fs/nfsd/threads # 预期:8
kubectl get sc -o jsonpath='{range .items[?(@.provisioner=="nfs.csi.k8s.io")]}{.metadata.name}{" -> "}{.mountOptions}{"\n"}{end}'
# 预期:回到 ["nfsvers=4.1","hard","timeo=600","retrans=3","noatime"]
cd /home/rain/Documents/k8s_ins/nfs-mount-tuning && .venv/bin/python main.py --config config/tune.yaml --skip-measure
# 预期:因参数回到旧值而检测出状态漂移(说明确实已回退;此时可再次正向执行)
7.4 回滚触发判据(量化,不临场判断)¶
任一达标即回滚:
- 任一 nfs4 挂载点挂载失败,或 Pod 出现
Input/output error; - 改造后 per-op 平均 RTT 相对同后端基线劣化超过 20%;
exportfs -v或 nfsd 线程数未达目标值,且重试 2 次仍失败;- 业务 Pod 在 300s 内未达到就绪状态。
8. 风险说明¶
| # | 风险 | 影响 | 缓解措施 |
|---|---|---|---|
| R1 | async 导出丢失持久化语义:客户端 COMMIT 被直接确认而不落盘 |
宿主非正常掉电或内核崩溃可能丢最近写入;承载数据库的导出有损坏可能 | 按用户明确指令执行;变更前已停止 mariadb/minio 写入;如需更强保证,应按导出粒度把数据库所在导出单独改回 sync 后 exportfs -ra |
| R2 | nconnect=4 提高并发连接数 |
服务端 nfsd 线程不足时会转为排队 | 已同步把 nfsd 线程提到 32,实测元数据 RTT 全面下降 |
| R3 | 环境在变更期间被外部改动:ldap/openldap 被删除、server/openldap 与 3 个新 PV 出现 |
静态清单中的对象可能已不存在,导致误判为漂移甚至执行失败 | 实现体已改为「对象不存在即跳过」,并按挂载路径中的 Pod UID 动态发现所有 NFS 使用者;执行前仍应确认无并发变更 |
| R4 | 已有 PV 必须逐个 patch 并重新挂载才能生效 | 期间相关服务逐个中断 | 逐个滚动、非同时;用 findmnt -t nfs4 复核后才算完成 |
| R5 | 属性缓存保持默认(acregmax=60、acdirmax=60) |
跨客户端可见性维持原状 | 按用户决策不动属性缓存,避免 RWX 共享场景一致性退化 |
| R6 | loop 挂载 remount 失败会导致 noatime 未生效 |
宿主层仍有 atime 写放大 | 实测两项均已生效;实现体在默认 remount 失败时会带显式源与类型重试 |
| R7 | 元数据压测会在导出根目录创建临时目录 | 占用少量空间与 IO | 固定白名单路径 .meta-bench,仅允许删除挂载点的直接子目录,测完即清理 |
| R8 | 判据写错会导致误报或漏报(本次曾把内核回显的 vers= 与配置的 nfsvers= 直接比对) |
幂等复跑误判为「未达标」,触发一轮多余重启 | 已修正为统一成 vers= 后比对;排查过程见 docs/INCIDENT-20260919-mount-option-judgement.md |
附录 A:交付物清单¶
| 路径(相对项目根) | 说明 |
|---|---|
nfs-mount-tuning/docs/SOP.md |
本文件 |
nfs-mount-tuning/docs/RUNBOOK.md |
服务运行与使用手册(日常运维、故障处置) |
nfs-mount-tuning/docs/INCIDENT-20260919-mount-option-judgement.md |
变更期间一次误判事件的排障记录 |
nfs-mount-tuning/backup/README.md |
备份台账(第 7 章所指) |
nfs-mount-tuning/main.py |
工程入口与阶段编排 |
nfs-mount-tuning/config/tune.yaml |
期望状态配置(核心实现体,日常调整改这里) |
nfs-mount-tuning/modules/server_module.py |
服务端:导出 async、nfsd 线程、loop noatime |
nfs-mount-tuning/modules/cluster_module.py |
集群侧:values / Secret / StorageClass 同步 |
nfs-mount-tuning/modules/workload_module.py |
静默写敏感业务(async 生效后自动跳过) |
nfs-mount-tuning/modules/volume_module.py |
PV patch + 重新挂载 + 复核(含动态发现) |
nfs-mount-tuning/modules/measure_module.py |
元数据延迟度量(fio + mountstats) |
nfs-mount-tuning/utils/ |
本工程私有工具(命令执行、文本与文件处理) |
nfs-mount-tuning/logs/ |
运行日志与 before/after 度量报告 |
nfs-storage/values-nfs-csi.yaml |
客户端参数实现体(本次已更新) |
nfs-storage/manifests/00-mount-options-secret.yaml |
删除卷所需的挂载参数 Secret(本次已更新) |
nfs-storage/SOP.md |
上一主题(存储类交付)的 SOP,参数与在跑业务清单已同步 |
附录 B:实现说明与已知坑¶
- 度量引擎名:Ubuntu 的 fio 3.36 已把
fileops拆成filecreate/filestat/filedelete三个独立引擎;元数据压测的 jobfile 由measure_module.py生成,文件名通过filename_format=meta-bench.$filenum统一,确保 stat / delete 命中同一批文件。 - 实现体的备份行为:服务端模块把备份留在
/etc同目录(--rollback依赖), 集群模块把备份写入backup/。两者都在backup/README.md登记。 - dry-run 语义:预演只输出变更明细,不写配置、不做备份、不跑压测、不改集群对象。
- 阶段短路:任一变更类阶段失败即终止后续阶段,并提示恢复命令:
.venv/bin/python main.py --config config/tune.yaml --stages volume。 - 度量目录:
measure_module会在/srv/nfs/k8s下创建.meta-bench,删除前校验 「必须是挂载点的直接子目录且以.meta-bench开头」,不使用通配删除。
来源:
/home/rain/Documents/k8s_ins/nfs-mount-tuning/docs/SOP.md| 最后更新:2026-09-19 16:45(由build/sync.py同步,请勿在站点侧修改)