运维实战

Kubernetes API 与控制器原理:List-Watch、CRD 和 Operator

沿着一次 API 请求理解认证、授权、准入、存储与 watch,再实现幂等控制循环并掌握 CRD/Operator 边界。

TY
Tycho
技术博主
• 2026-09-22 • 18 分钟阅读 • 2 次浏览
Kubernetes API 与控制器原理:List-Watch、CRD 和 Operator

实验基线:Kubernetes v1.37.0、kubectl v1.37.0。先在测试环境执行,记录变更前状态与回滚点;生产集群不得直接照抄节点地址、网段、存储类或资源额度。

一次写请求的完整路径

  1. 客户端从 kubeconfig 选择集群、用户与上下文。
  2. API Server 完成认证与授权。
  3. 变更型准入先修改对象,验证型准入再决定是否接受。
  4. 通过校验的对象写入 etcd,并向 watch 客户端发布变化。
  5. 控制器比较期望状态与实际状态,提交新的 API 操作。

API Server 是唯一应直接访问 etcd 的组件;业务控制器通过 Kubernetes API 工作。

观察 REST 与资源版本

kubectl get --raw /api/v1/namespaces/lab/pods
kubectl get pods -n lab -o json
kubectl get pods -n lab --watch-only --output-watch-events
kubectl api-resources
kubectl api-versions

可靠控制器先 List 获得初始集合和 resourceVersion,再 Watch 增量事件;watch 断开或版本过旧时重新 List。不要假设事件只到达一次,协调逻辑必须幂等。

控制循环的正确结构

  1. 读取自定义资源。
  2. 计算期望的子资源。
  3. 读取实际子资源并比较。
  4. 仅提交必要变更。
  5. 更新 status/conditions;瞬时错误限速重试。

用 ownerReference 让垃圾回收器管理从属对象;需要外部清理时使用 finalizer,但必须保证清理可重试且最终能移除,否则资源会永久停在 Terminating。

何时选择 CRD

当领域对象需要声明式 CRUD、watch、RBAC、审计和控制循环时使用 CRD;仅保存应用配置时不必把 Kubernetes 当数据库。CRD schema 要启用结构化校验,明确 status 子资源和版本转换策略。Operator 是“CRD + 领域控制器”的模式,不是安装包的同义词。

直接观察 API,而不是猜测 kubectl 做了什么

kubectl proxy --port=8001
curl -s http://127.0.0.1:8001/api/v1/namespaces/lab/pods
curl -s http://127.0.0.1:8001/apis/apps/v1/namespaces/lab/deployments
kubectl get --raw '/apis/apps/v1/namespaces/lab/deployments'

kubectl proxy 只绑定本地并使用当前 kubeconfig,调试结束立即停止。生产自动化应直接使用官方客户端库与最小权限凭据。

并发更新与 resourceVersion

每个对象带 resourceVersion。客户端基于旧版本更新时可能收到 409 Conflict,应重新读取、重新计算再提交,不能无条件覆盖。watch 可能因为网络断开或历史版本被压缩而结束,控制器必须重新 List 建立新基线。

最小 CRD Schema

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata: {name: webapps.example.com}
spec:
  group: example.com
  scope: Namespaced
  names: {plural: webapps, singular: webapp, kind: WebApp}
  versions:
  - name: v1
    served: true
    storage: true
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            required: [image, replicas]
            properties:
              image: {type: string, minLength: 1}
              replicas: {type: integer, minimum: 1, maximum: 20}
    subresources: {status: {}}

真实 CRD 还要定义 status schema、打印列、默认值和版本迁移。先在非生产集群做结构化 schema 校验。

调谐伪代码

reconcile(key):
  desired = get_custom_resource(key)
  if deleted: run_idempotent_finalizer(); return
  actual = get_owned_deployment(key)
  next = calculate(desired, actual)
  apply_only_if_changed(next)
  update_status_conditions(observedGeneration)
  requeue_with_backoff_on_transient_error()

不要在事件处理器里做不可重试的外部副作用。外部资源创建要使用幂等键;status 的 observedGeneration 用来表明控制器已经处理到哪个 spec 版本。

控制器测试层次

  1. 纯函数测试:期望状态计算。
  2. envtest:真实 API schema、默认值和冲突。
  3. kind:控制器、CRD 和依赖集成。
  4. 故障注入:API 超时、重复事件、删除中断、外部系统失败。

认证、授权、准入的可观测边界

401 表示未通过认证,403 表示身份已确认但无权限,422 常见于对象校验失败,409 表示资源版本冲突,429 表示限流。客户端应区分处理:认证错误不盲目重试,冲突要重新读取,429 按 Retry-After/退避策略重试。

准入策略选择

  • 简单字段约束优先 CRD OpenAPI schema 或 ValidatingAdmissionPolicy/CEL。
  • 需要默认值且内置默认机制不足时考虑 mutating webhook。
  • 复杂外部校验才使用 webhook,并设置短超时、高可用和证书轮换。

Mutating webhook 按顺序可能多轮执行,必须幂等;验证 webhook 不应依赖慢或不稳定的外部服务。

CRD 版本演进

  1. 新增 served 版本并保持旧 storage 版本。
  2. 部署转换 webhook,双向转换必须无损。
  3. 迁移现存对象到新 storage 版本。
  4. 检查 status.storedVersions。
  5. 确认所有客户端停止使用旧版本后再移除 served。

直接删除旧 schema 可能让已有对象无法读取。升级前备份 CRD 和自定义资源,并在隔离集群验证转换。

控制器的生产质量清单

  • 使用 informer/cache,避免高频全量 list。
  • 工作队列限速重试,区分永久和瞬时错误。
  • 采用 leader election,副本切换不重复外部副作用。
  • 记录 reconcile 次数、耗时、错误与队列深度。
  • status conditions 包含 type/status/reason/message/observedGeneration。
  • finalizer 清理有超时、重试与人工解除手册。

可复现记录模板

每次实验记录:集群版本、容器运行时、CNI/CSI 版本、命名空间、使用的 YAML Git 提交、开始与结束时间。命令输出至少保留对象状态、事件、关键日志和回滚结果。文中的占位符必须替换成自己的值,生产执行前应由第二人复核。

故障处理原则

  1. 先缩小影响面,不删除现场。
  2. 按对象状态—事件—日志—依赖顺序收集证据。
  3. 提出可证伪假设,一次只改变一个变量。
  4. 验证恢复后清理临时权限、调试 Pod、端口转发和测试数据。

完成检查

  • 命令退出码为 0,目标对象状态与预期一致。
  • 保存执行前后 YAML、事件与关键日志,确认没有把测试命名空间以外的对象改动。
  • 故障演练完成后执行清理或回滚,再进行下一章。

官方资料

TY

Tycho

热爱分享技术知识,帮助开发者成长。

评论 (0)

评论功能当前已关闭
暂无评论,快来抢沙发吧!