Appearance
扩展与兼容:稳定契约如何承载演进
专题索引 / 上一篇:可观测性:从事件到可验证证据
大系统不能靠“以后需要时加个 hook”来扩展。真正可演进的设计要先划清稳定面与可变面:谁依赖谁,哪些数据会长期保存,哪些实现可以替换,升级时旧客户端、旧对象和旧插件如何继续工作。
text
稳定契约:名称、数据模型、版本、错误语义、生命周期
-> 适配层:校验、默认值、转换、能力协商
-> 可替换实现:驱动、插件、controller、后端服务
-> 观测与回滚:版本状态、兼容指标、迁移记录Linux 与 Kubernetes 的取舍不同:Linux 重点保护用户态 ABI,不承诺稳定的内核内部模块 API;Kubernetes 重点保护版本化 API 对象和声明式语义,让 controller 和插件在 API 契约下演进。
1. 先区分三种边界
| 边界 | 面向谁 | 典型内容 | 变更要求 |
|---|---|---|---|
| 用户边界 | 应用、脚本、运维人员 | syscall、文件格式、CLI、HTTP/API 对象 | 高兼容性,弃用周期明确 |
| 扩展边界 | 驱动、CNI/CSI、controller、admission webhook | 接口、协议、能力、生命周期回调 | 版本协商、失败隔离、能力声明 |
| 内部边界 | 核心项目内部模块 | 私有结构、函数、缓存、调度策略 | 可重构,不应被外部直接依赖 |
把内部边界误当公开接口,维护者就会被历史实现锁死;把用户边界当普通内部代码改动,升级就会破坏用户。顶级项目最难的工作,往往是持续维护这条线。
2. Linux:稳定 UAPI,不稳定内部实现
Linux 通过 syscall、/proc//sys 的部分接口、设备节点、网络协议和文件格式向用户态提供 ABI。用户程序依赖的是行为与二进制调用约定,不应依赖内核内部结构体或函数。
text
用户程序
-> libc / syscall ABI
-> 内核子系统
-> 可替换的调度器、文件系统、驱动实现
-> 硬件例如 VFS 给用户态稳定的文件操作语义;不同文件系统在内核内部实现 inode、目录、缓存、日志和块映射。应用打开的是路径和文件描述符,不需要知道底下是 ext4、XFS、NFS 还是 FUSE。
Linux 的内核内部 API 不追求对外部二进制模块长期稳定。这样核心可以重构数据结构、并发模型和安全边界;代价是外部驱动要跟随目标内核版本重新构建或适配。这不是“没有兼容性”,而是把兼容承诺集中在真正需要长期保护的用户态边界。
3. Kubernetes:把扩展纳入 API 与控制回路
Kubernetes 的 CRD 允许把新的资源类型放进 API Server:有名字、版本、schema、权限、watch、存储和状态子资源。CRD 只让对象可以被声明和保存;让它产生真实作用仍需要 controller。
text
CustomResourceDefinition
-> API Server 校验、默认、持久化 Custom Resource
-> watch
-> 自定义 controller 的 informer / workqueue / reconcile
-> 创建、更新或管理外部真实资源
-> status 回写观察结果这复用了前文的 Kubernetes 主控制回路,而不是为每个领域发明另一套任务系统。一个 Database、ModelDeployment 或 Backup CRD 的价值不在 YAML 看起来像原生对象,而在它有清楚的 spec、status、所有权、失败重试和可观察的收敛条件。
CSI、CNI、device plugin 则是另一类扩展:它们不只保存领域对象,还需要让每个 Node 上的具体设备、网络或存储操作落地。核心负责调度、生命周期和通用契约;插件负责后端细节与能力边界。
4. 版本化不只是字段加减
一个 API 版本至少要回答:旧对象怎样读取,新客户端怎样写,服务端怎样默认,两个版本间如何转换,删除字段后旧客户端怎么办。
text
客户端 v1beta1
-> API Server decode / default / validate
-> conversion 到存储或 hub 版本
-> controller 按规范处理
-> status 按请求版本返回对 CRD,schema 应明确字段类型、必填条件和保留策略。未知字段、默认值、可选字段、枚举扩展和字段弃用都需要兼容策略;否则对象今天能创建,升级后可能被裁剪、校验拒绝,或被 controller 解释成不同含义。
不要把一次性迁移脚本当作完整版本策略。存量对象、Git 中的 manifest、自动化客户端、备份和外部 controller 会在不同时间升级。兼容窗口、转换测试、指标与可回滚方案必须一起存在。
5. 所有权和 admission 不能替代 controller
admission webhook 能在对象写入前校验、默认或拒绝请求;它适合保护 API 边界,不适合做耗时的外部资源创建。controller 适合异步、可重试的现实世界操作,不应把业务不变量完全推给用户约定。
text
admission:这个对象能否进入期望状态?
controller:现实世界是否已收敛到该期望?
status:当前观察到什么、失败在哪里?多个 controller 同时修改同一资源时,要明确字段所有权、协调方式与 status 写入责任。否则每个 controller 单独看似正确,合起来会产生循环更新、覆盖或无限 reconcile。
6. 一次安全扩展的最小检查表
| 问题 | 需要的答案 |
|---|---|
| 稳定对象是什么 | API version、schema、字段含义、错误与状态语义 |
| 谁实现它 | controller、驱动或 webhook 的职责与部署位置 |
| 谁拥有外部资源 | ownerReference、finalizer、清理与交接规则 |
| 失败怎样恢复 | 幂等 reconcile、退避、条件、人工介入入口 |
| 怎样升级 | 默认、转换、弃用期、存量迁移、回滚与兼容测试 |
| 怎样验证 | API readback、status、真实资源、metrics/logs/traces 与用户可见结果 |
7. 从两套系统得到的规则
- 先承诺最小稳定面,再允许内部实现大胆变化。
- 扩展点要定义数据、版本、生命周期和失败语义,不能只给一个函数回调。
- 让扩展复用已有控制回路和可观测性,而不是隐藏在核心的特殊分支里。
- 任何外部资源都必须有创建、拥有、交接和删除的完整生命周期。
- 兼容性是一项持续运行能力,应有指标和回归测试,不是一次发布前的文档声明。
资料与源码入口
- Linux:Stable API Nonsense、The Linux Kernel Driver Model、Linux UAPI headers,访问于 2026-08-03。Linux 的具体 UAPI 保证以子系统和接口文档为准。
- Kubernetes:Custom Resources、CustomResourceDefinition versions、Admission Controllers,访问于 2026-08-03。
- Kubernetes API 约定:API Conventions,访问于 2026-08-03。
- Kubernetes 源码:
staging/src/k8s.io/apiextensions-apiserver/、staging/src/k8s.io/api/,访问于 2026-08-03。
本文是架构边界的学习材料,不替代某个目标版本的升级指南。CRD schema、conversion、webhook 可用性和插件能力都是生产变更,需要在固定版本与实际对象样本上验证。