Skip to content

扩展与兼容:稳定契约如何承载演进 ​

专题索引 / 上一篇:可观测性:从事件到可验证证据

大系统不能靠“以后需要时加个 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. 从两套系统得到的规则 ​

  1. 先承诺最小稳定面,再允许内部实现大胆变化。
  2. 扩展点要定义数据、版本、生命周期和失败语义,不能只给一个函数回调。
  3. 让扩展复用已有控制回路和可观测性,而不是隐藏在核心的特殊分支里。
  4. 任何外部资源都必须有创建、拥有、交接和删除的完整生命周期。
  5. 兼容性是一项持续运行能力,应有指标和回归测试,不是一次发布前的文档声明。

资料与源码入口 ​

本文是架构边界的学习材料,不替代某个目标版本的升级指南。CRD schema、conversion、webhook 可用性和插件能力都是生产变更,需要在固定版本与实际对象样本上验证。

学习规划与研究资料库