Skip to content

Kubernetes 基础与 CRD 概念

本篇是 Kubernetes Operator 开发系列教程的第一篇。在动手写 Operator 之前,我们需要先打牢两个基础:一是回顾 Kubernetes 的核心概念,二是理解自定义资源(CRD)和 Operator 模式的本质。如果你已经对 K8s 比较熟悉,可以快速浏览前两节,重点放在 CRD 和 Operator 模式的部分。本篇目标是为后续编写真正的 Operator 代码做好概念和环境上的准备。

一、Kubernetes 核心概念回顾

Kubernetes(简称 K8s)是一个容器编排系统,负责自动化地部署、扩缩容和管理容器化应用。它把集群抽象成一组声明式的 API 对象,开发者只需描述「我期望集群处于什么状态」,K8s 的控制平面就会持续不断地把实际状态向期望状态收敛。理解下面三个核心对象,是理解一切 Operator 工作方式的前提。

1. Pod

Pod 是 K8s 中最小的可部署单元。一个 Pod 里可以包含一个或多个紧密耦合的容器,它们共享网络命名空间和存储卷,并通过 localhost 互相通信。Pod 的生命周期是短暂的——一旦被销毁,它就彻底消失,K8s 不会尝试「修复」一个具体的 Pod,而是直接创建一个新的来替代它。

yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
  labels:
    app: nginx
spec:
  containers:
    - name: nginx
      image: nginx:1.25
      ports:
        - containerPort: 80
      resources:
        limits:
          cpu: "500m"
          memory: "256Mi"

需要特别理解的一点是:Pod 是临时的,它的 IP 会变化,名字也会变化。因此实际生产中几乎不会直接创建裸 Pod,而是通过更高层级的控制器来管理。

2. Deployment

Deployment 是一个更高层级的控制器,它管理一组相同的 Pod(通过 ReplicaSet),并保证任意时刻都有指定数量的副本在运行。当 Pod 挂掉时,Deployment 会自动创建新的 Pod 来补足数量;当需要滚动更新镜像版本时,Deployment 会逐步替换旧 Pod,从而实现零停机发布。

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.25
          ports:
            - containerPort: 80

Deployment 的工作方式完美地体现了「控制器模式」:它内部有一个控制循环,不断比较 spec.replicas(期望副本数)和实际运行的 Pod 数量,发现差异就做出调整。这个模式正是 Operator 的思想源头——Operator 只是把同样的模式应用到了「自定义资源」上。

3. Service

因为 Pod 的 IP 不稳定,K8s 提供了 Service 来为一组 Pod 提供稳定的访问入口。Service 通过标签选择器(selector)找到对应的 Pod,并为其分配一个稳定的 ClusterIP 和 DNS 名字。无论背后的 Pod 如何创建销毁,Service 的地址都保持不变。

yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: ClusterIP
  selector:
    app: nginx
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP

4. 控制器模式的本质

上面三个对象其实都遵循同一个范式——声明式 + 控制循环

  1. 用户提交一个声明式的资源清单(YAML),描述期望状态。
  2. 控制器(运行在控制平面)监视资源变化。
  3. 控制器对比期望状态和实际状态,执行操作消除差异。
  4. 控制器持续循环,保证系统始终收敛到期望状态。

这个范式是 Kubernetes 的灵魂,也是 Operator 模式的理论基石。当你理解了 Deployment 控制器是怎么工作的,你就已经理解了一半的 Operator。

二、什么是自定义资源(CRD)

1. 内置资源的局限

K8s 内置了一套丰富的资源类型:Pod、Deployment、Service、ConfigMap、Secret、Job、CronJob 等等。这些资源覆盖了「运行容器」这一核心场景。但是现实世界中有很多更复杂的应用——数据库集群、消息队列、机器学习训练任务、API 网关配置——它们的生命周期管理远比「跑几个容器副本」复杂。

如果只能用内置资源,运维人员就得手动组合 Deployment + Service + ConfigMap + PVC,再写一堆手动操作步骤(建库、初始化账号、配置主从)。这正是 CRD 要解决的问题。

2. CRD 的定义

CRD(CustomResourceDefinition,自定义资源定义) 是 K8s 提供的一种扩展机制,它允许你在集群中注册全新的资源类型。注册之后,你就可以像使用内置资源一样,用 kubectl 创建、查询、修改这种自定义资源,K8s 的 API Server 会负责存储和校验。

举个直观的例子,注册一个名为 RedisCluster 的 CRD 后,你就可以这样创建一个 Redis 集群:

yaml
apiVersion: cache.example.com/v1
kind: RedisCluster
metadata:
  name: my-redis
spec:
  size: 3
  image: redis:7.0
  port: 6379

这就比手动写 Deployment + Service + ConfigMap 要直观得多。用户只需声明「我要一个 3 节点的 Redis 集群」,至于怎么把这个集群创建出来,就交给 Operator(也就是自定义控制器)来处理。

3. 为什么需要 CRD

CRD 的核心价值在于抽象

  • 领域建模:你可以用贴近业务的术语(比如 RedisClusterKafkaTopicPostgresBackup)来描述系统状态,而不是用底层资源堆叠。
  • 声明式管理:复用 K8s 声明式 API 的所有好处——版本化、一致性、可审计、可回滚。
  • 统一工具链kubectl、RBAC、admission webhook、finalizer 等机制对自定义资源同样适用,无需额外造轮子。
  • 生态集成:CRD 一旦定义,就可以被 Helm、Kustomize、ArgoCD、GitOps 工具链直接消费。

4. CRD 的关键组成

一个完整的 CRD 定义涉及以下几个核心字段:

  • API Group:资源所属的 API 分组,形如 cache.example.com,用于避免命名冲突。
  • Version:API 版本,如 v1v1beta1,支持多版本共存和转换。
  • Kind:资源的类型名,驼峰命名,如 RedisCluster
  • Spec:用户填写的「期望状态」,是 CRD 的核心输入。
  • Status:控制器填写的「实际状态」,用于反馈系统当前情况。

下面是一个 CRD 定义本身的 YAML 示例(注意这是「定义资源类型」的资源,不是具体实例):

yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: redisclusters.cache.example.com
spec:
  group: cache.example.com
  names:
    kind: RedisCluster
    listKind: RedisClusterList
    singular: rediscluster
    plural: redisclusters
    shortNames:
      - rc
  scope: Namespaced
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              required:
                - size
                - image
              properties:
                size:
                  type: integer
                  minimum: 1
                  maximum: 10
                image:
                  type: string
                port:
                  type: integer
                  default: 6379
                password:
                  type: string
            status:
              type: object
              properties:
                phase:
                  type: string
                nodes:
                  type: array
                  items:
                    type: string

实际开发中我们很少手写这么长的 CRD YAML,而是用 Go 结构体定义类型,再用工具(controller-gen)自动生成。这会在后续章节详细讲解。

三、声明式 API vs 命令式 API

理解这两种 API 风格的差异,对于写出正确的 Operator 至关重要。

1. 命令式 API

命令式 API 的核心是「做这个动作」。比如:

  • kubectl run nginx --image=nginx:创建一个 Pod。
  • kubectl scale deployment nginx --replicas=5:把副本数扩到 5。
  • SQL 里的 UPDATE users SET age = 20:把年龄改成 20。

每次操作都是一次「动作」,系统执行完就结束了,不记录意图。命令式 API 的问题在于:当系统状态发生漂移(比如某个 Pod 被人工删掉了),它不会自动纠正,因为「补足副本数」这个意图从未被记录下来。

2. 声明式 API

声明式 API 的核心是「描述期望状态」。比如:

yaml
apiVersion: apps/v1
kind: Deployment
spec:
  replicas: 3

这表达的不是「把副本数调成 3」这个动作,而是「我期望任何时候都有 3 个副本在运行」这个意图。控制器会持续不断地工作来维护这个意图——副本少了就补,多了就删。

声明式 API 的关键优势:

  • 自愈:状态漂移会被自动纠正。
  • 幂等:同一个声明提交多次,效果等同于一次。
  • 可审计:意图被持久化在 etcd 里,随时可查。
  • 可回滚:通过修改声明即可,无需记录「逆向操作」。

3. Operator 必须是声明式的

Operator 本质上就是一个声明式控制器。用户的 CRD 实例描述的是「我期望的集群状态」,而不是「请执行这些步骤」。因此编写 Reconcile 循环时,我们要思考的永远是「期望状态 vs 实际状态」,而不是「发生了什么事件,我要做什么动作」。这一点会在第四章深入展开。

四、Operator 模式

1. 将运维知识编码为软件

Operator 这个概念由 CoreOS(后被 Red Hat 收购)在 2016 年提出。它的核心思想一句话就能说清:把人类运维专家管理某个应用的知识,编码进一个软件控制器里

以管理一个 PostgreSQL 数据库为例,一个有经验的 DBA 知道:

  • 主库挂了要怎么办——提升一个从库为主库。
  • 怎么做在线备份——pg_dump 还是 pg_basebackup,什么时候做。
  • 怎么扩容——加副本、调整参数、迁移数据。
  • 升级版本要注意什么——先升从库,验证后再切主。

这些知识传统上存在于运维文档、脚本、甚至老员工的脑子里。Operator 的目标就是把这些知识变成代码:定义一个 PostgresCluster CRD,用户只需声明「我要一个 3 节点高可用 PG 集群、每天备份」,Operator 就自动完成上述所有运维动作。

2. Operator 的核心组件

一个 Operator 由两部分组成:

┌─────────────────────────────────────────┐
│                Operator                  │
│                                          │
│  ┌──────────────┐   ┌────────────────┐  │
│  │     CRD      │   │   Controller    │  │
│  │ (数据模型)    │   │  (控制循环)     │  │
│  │              │   │                 │  │
│  │  Spec  ←──→  │   │  Reconcile()    │  │
│  │  Status      │   │  Watch & Sync   │  │
│  └──────────────┘   └────────────────┘  │
└─────────────────────────────────────────┘
        ↑                        ↓
        └──── API Server ────────┘


              etcd 存储
  • CRD:定义「自定义资源长什么样」,也就是数据模型。它规定了 specstatus 的结构。
  • Controller:定义「自定义资源该怎么被实现」,也就是控制逻辑。它运行一个 Reconcile 循环,监视 CR 实例和关联资源的变化,把实际状态向期望状态收敛。

二者缺一不可。只有 CRD 没有 Controller,那这个资源就是一堆存在 etcd 里的死数据,没有任何实际效果;只有 Controller 没有 CRD,那它就没有可以监听和操作的对象。

3. Operator 与 Helm、Kustomize 的区别

很多初学者会问:Helm 和 Kustomize 也能把一套复杂的 YAML 模板化、参数化部署,为什么还需要 Operator?它们的区别在于「一次性 vs 持续性」。

维度Helm / KustomizeOperator
工作时机部署时执行一次持续运行,7x24 监控
自愈能力无,漂移后需手动重跑有,自动纠正状态漂移
生命周期只管「创建」这一步覆盖创建、扩缩、升级、备份、故障转移
复杂运维静态模板,无运行时决策可编码复杂运维逻辑(如主从切换)
知识载体YAML 模板Go 代码

简单总结:Helm/Kustomize 解决的是「怎么把资源方便地部署上去」,Operator 解决的是「怎么让应用持续按预期运行」。它们是互补关系而非替代关系——很多 Operator 内部也会用 Helm 渲染模板,但生命周期管理交给控制循环。

4. 什么场景适合用 Operator

不是所有应用都需要 Operator。一般来说,满足以下条件之一的应用非常适合用 Operator:

  • 有复杂的有状态生命周期(数据库、消息队列、分布式存储)。
  • 需要自动化故障恢复(主从切换、副本补齐)。
  • 有标准化的运维流程需要固化(备份、升级、扩容)。
  • 是一个平台级组件,被很多团队复用(统一管理 Kafka Topic、DB 实例)。

而无状态的简单 Web 服务,用 Deployment + HPA 就够了,不必上 Operator。

五、Operator Framework 生态

围绕 Operator 已经形成了一个完整的开源生态,主要由以下工具组成。

1. Operator SDK

Operator SDK 是 Red Hat 主导的工具集,用于 scaffolding(脚手架生成)、构建、测试和打包 Operator。它支持三种实现方式:

  • Go:基于 controller-runtime,最主流、性能最好,本系列教程采用这种方式。
  • Ansible:用 Ansible playbook 描述运维逻辑,适合不写 Go 的运维团队。
  • Helm:把现有 Helm chart 包装成 Operator,迁移成本最低但能力有限。

2. Kubebuilder

Kubebuilder 是 Kubernetes SIG(特别兴趣小组)官方维护的脚手架工具,是 Operator SDK Go 类型实现的底层基础。它负责生成项目骨架、Makefile、CRD 定义、controller 代码模板。本系列教程的代码就是用 Kubebuilder 生成的。Operator SDK 的 Go 路径实际上是在 Kubebuilder 之上做了封装。

Kubebuilder 和 Operator SDK 的关系可以理解为:Kubebuilder 是「内核」,Operator SDK 是「发行版」。对于纯 Go 开发者,二者用起来差别不大,Kubebuilder 更轻量、更贴近上游。

3. OLM(Operator Lifecycle Manager)

OLM 是用来管理 Operator 本身生命周期的组件。它解决的问题是:「Operator 自己怎么被安装、升级、卸载、依赖管理」。OLM 提供了一个 Catalog(目录)机制,让你可以把 Operator 发布到一个集中的仓库,集群管理员通过 OLM 一键安装并管理版本。Red Hat OpenShift 内置了 OLM。

对于本系列教程,我们主要使用 Kubebuilder 进行开发,OLM 在最后一章发布部分会简要介绍。

4. controller-runtime 与 controller-gen

这两个是底层库和工具:

  • controller-runtime:提供 Manager、Controller、Client、Cache、Webhook 等基础抽象,是写 Operator 控制循环的核心依赖。
  • controller-gen:从 Go 结构体的注释(+kubebuilder 标记)生成 CRD 的 YAML 定义和 deepcopy 代码。

第二章会深入讲解 controller-runtime 的架构。

六、环境准备

在正式开始写代码之前,需要把开发环境准备好。本节给出在 Linux / macOS / Windows(WSL2 推荐)下的安装步骤。

1. 安装 Go

Operator 开发需要 Go 1.20 及以上版本。到 Go 官网 下载对应平台的安装包。安装后验证:

bash
go version
# go version go1.22.0 linux/amd64

建议配置好国内代理以加速依赖下载:

bash
go env -w GOPROXY=https://goproxy.cn,direct

2. 安装 kubectl

kubectl 是与 K8s 集群交互的命令行工具。安装方法可参考官方文档。安装后验证:

bash
kubectl version --client

3. 安装本地集群:kind 或 minikube

开发阶段不需要一个真正的生产集群,本地集群足够。推荐使用 kind(Kubernetes in Docker),它轻量、启动快、支持多节点。

安装 kind:

bash
# Linux/macOS
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.22.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind

# 或者用 Go 安装
go install sigs.k8s.io/kind@latest

创建一个集群:

bash
kind create cluster --name operator-dev
kubectl cluster-info --context kind-operator-dev

如果你更习惯 minikube,也可以:

bash
minikube start --cpus=4 --memory=4g

4. 安装 Kubebuilder

Kubebuilder 提供了命令行工具用于生成项目骨架。安装方式:

bash
# 下载最新 release
curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)
chmod +x kubebuilder
sudo mv kubebuilder /usr/local/bin/

# 验证
kubebuilder version

5. 安装 controller-gen 和其他工具

Kubebuilder 项目依赖几个代码生成工具,通常通过 Makefile 中的 target 自动安装,但也可以手动装:

bash
go install sigs.k8s.io/controller-tools/cmd/controller-gen@latest
go install sigs.k8s.io/kustomize/kustomize/v5@latest

6. 验证环境

执行下面命令确认所有工具就位:

bash
go version
kubectl version --client
kind version
kubebuilder version
controller-gen --version

如果以上命令都能正常输出,说明环境已经准备完成,可以进入下一章开始写代码了。

七、小结

本篇回顾了 Kubernetes 的核心概念,并系统介绍了 CRD 与 Operator 模式。要点回顾:

  1. K8s 的本质是声明式 + 控制循环。Pod、Deployment、Service 都遵循这一范式,Operator 只是把范式延伸到了自定义资源。
  2. CRD 是 K8s 的扩展机制,允许你注册自定义资源类型,复用 K8s 的全部 API 机制(kubectl、RBAC、admission、存储)。
  3. 声明式 API 关注「期望状态」,而非「执行动作」。这是 Operator 自愈和幂等能力的来源。
  4. Operator = CRD + Controller,把运维专家的知识编码进软件,实现应用生命周期自动化。
  5. 生态工具:Kubebuilder/Operator SDK 做脚手架,controller-runtime 做运行时,OLM 管理发布。
  6. 环境准备:Go + kubectl + kind + kubebuilder,这是后续所有章节的基础。

下一篇我们会深入 controller-runtime 的架构,并用 Kubebuilder 初始化第一个 Operator 项目骨架。理解了这些底层组件之后,再去写 Reconcile 循环就会水到渠成。