LabHub
学习 学习路径 课程

CNPA — 云原生平台工程助理

Kubernetes API 为什么成了平台的通用语

在 LabHub 中继续学习

一句话总结

Kubernetes 赢下的并不是容器编排竞争,而是 API 规范竞争。声明式资源与协调控制器这一成对模式扩展到了容器之外,因此在构建新的平台 API 时,CRD 成为了默认选择。

概念图: API 规范竞争 · 扩展 Kubernetes API · 立即 · CRD

为什么需要它

平台团队想让“启动一个服务”变得简单时,通常会经历以下路径。

  1. 把流程写进 Wiki → 没有人持续更新。
  2. 编写 shell 脚本 → 不同运行环境产生不同结果,失败后还会留下中间状态。
  3. 开发内部 Web 应用 → 必须自行实现状态存储、认证、审计、重试和并发,而且应用本身成为新的 SPOF。

把第三条路走到底后,就会意识到自己正在重新制造什么:状态存储、乐观并发控制、认证与授权、审计日志、监视(watch)以及协调循环——这些都是 Kubernetes API 服务器已有的能力。

因此,方向会反转:不再新建平台 API,而是扩展 Kubernetes API。注册 CRD 的那一刻,以下能力会免费获得。

这就是“Kubernetes API 是平台通用语言”的实际含义:定义新的词汇(CRD),但语法(API 规范)沿用所有人已经熟悉的方式。

它如何工作

CRD + 控制器 = 平台 API

需要两个组成部分。

CRD 定义词汇,包括 group、version、kind、作用域(Namespaced 或 Cluster)以及 OpenAPI v3 模式。模式的作用比想象中更大。

spec:
  versions:
    - name: v1alpha1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              required: [image]
              properties:
                image:    { type: string }
                replicas: { type: integer, default: 2, minimum: 1, maximum: 10 }
                public:   { type: boolean, default: false }

控制器 为这些词汇赋予含义。它是一个协调循环:读取名为 WebService 的 CR,并创建 Deployment、Service、HPA 与 NetworkPolicy。如果只有 CRD 而没有控制器,这个 CR 只是“一份结构经过验证的配置文件”——它仍有一定用途,却还不是平台 API。

作用域选择也是考试常见题。Namespaced 资源被限制在租户边界内,命名空间 RBAC 可直接生效。Cluster 作用域使用全局名称,不同租户可能产生命名冲突,而且只能通过 ClusterRole 授权,不能使用 Role。由租户创建的资源几乎总应采用 Namespaced。

抽象发生泄漏的时刻

优秀的平台 API “只询问必要信息”。WebService 只要求镜像,必要时再要求 replicas;其余内容——标签规范、安全上下文、资源默认值、可观测性注解和网络策略——由控制器填充。

但总有一天抽象会泄漏。

如果此时回答“平台不支持”,该团队就会放弃平台并回到原始 YAML。一旦离开,通常不会再回来。因此,设计中必须预先提供逃生舱(escape hatch)

逃生舱 形式 风险
局部覆盖 spec.podOverrides 这样的自由字段 什么都能填会让抽象失去意义
扩展点 只允许 extraEnvextraVolumesnodeSelector 需要维护允许列表
渲染后脱离 复制生成的清单并自行管理 无法继续获得平台改进

平衡点是:**必须提供逃生舱,但使用逃生舱的事实必须可见。**如果给使用覆盖的服务留下标签或状态条件,平台团队就能收到“已有五个团队通过覆盖绕过这项能力”的信号,并将其提升为正式功能。这就是把平台作为产品运营的反馈循环。

实际现场中的表现

作者的家庭实验室准确呈现了这个问题。GPU Feature Discovery 会自动为节点标记显卡信息:RTX 3090(24576MB,ampere)、5090(32607MB,blackwell),以及两块 4070 Laptop(8188MB,ada-lovelace)。但如果 Pod 只请求 nvidia.com/gpu: 1,需要 32GB 的训练任务也可能被调度到 8GB 笔记本 GPU 上,因为对 Kubernetes 而言,它们都是“一块 GPU”。

GFD 添加的 gpu.memory 标签是字符串,无法使用“至少 24GB”这样的比较选择器。因此,我们自行添加了语义化标签:gpu.homelab/tier=xlarge|large|smallgpu.homelab/vram=32g|24g|8g。现在工作负载可以通过 nodeSelector: gpu.homelab/tier: xlarge 选择符合自身规模的节点。

这一行正是平台 API 设计的典型案例:不直接暴露底层物理事实(显卡型号、内存字节数),而是把它翻译成用户可以据此决策的词汇(tier)。同时仍保留逃生舱:如果确实需要某个特定型号,可以直接使用 GFD 原始标签选择。优秀的抽象不是把下层完全隐藏,而是将其覆盖,同时保持可访问

下一项实验将做什么

我们会在真实集群中创建 CRD webservices.platform.labhub.io,亲眼确认模式违规是否会被立即拒绝,以及默认值是否由服务器填充。随后使用命名空间、ResourceQuota 和 LimitRange 划定租户边界,通过 RBAC 授予自助服务权限,再用 kubectl auth can-i 证明无法操作其他租户。