深色模式
Operator 与 CRD 扩展机制
摘要:本文讲清 CRD 与 Operator 的关系:CRD 是新增的资源类型,Operator 是操作它的控制器。包含一个可直接 apply 的 CRD 示例、字段校验与状态子资源的配置,以及 Operator 的选型建议。
适用环境
- 可用 K8s 集群 +
kubectl(需有创建 CRD 的集群级权限) - 建议 K8s 1.16+(GA 版 CRD)
- 可选:已安装 Operator Lifecycle Manager(OLM)
操作步骤
一、CRD 与 Operator 分别是什么
| 概念 | 定位 | 类比 |
|---|---|---|
| CRD(CustomResourceDefinition) | 定义一种新资源类型 | 建表(定义 schema) |
| CR(Custom Resource) | 该类型的一个实例 | 一行数据 |
| Operator(控制器) | watch CR 并调谐实际状态 | 后台任务进程 |
只有 CRD 没有 Operator,资源只是被存进 etcd,什么都不会发生。
二、定义一个 CRD
yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: databases.example.com
spec:
group: example.com
names:
kind: Database
listKind: DatabaseList
plural: databases
singular: database
shortNames:
- db
scope: Namespaced # 或 Cluster
versions:
- name: v1
served: true # 是否提供该版本 API
storage: true # 只有一个版本能是 storage
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
engine:
type: string
enum: ["mysql", "postgres"]
version:
type: string
storageSize:
type: string
pattern: '^[0-9]+(Mi|Gi|Ti)$'
replicas:
type: integer
minimum: 1
maximum: 9
required: ["engine", "version"]
status:
type: object
properties:
phase:
type: string
readyReplicas:
type: integer
subresources:
status: {} # 开启 /status 子资源
additionalPrinterColumns:
- name: Engine
type: string
jsonPath: .spec.engine
- name: Phase
type: string
jsonPath: .status.phase
- name: Age
type: date
jsonPath: .metadata.creationTimestamp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
bash
kubectl apply -f crd.yaml
kubectl get crd databases.example.com
kubectl api-resources | grep database1
2
3
2
3
三、创建 CR 实例
yaml
apiVersion: example.com/v1
kind: Database
metadata:
name: my-db
spec:
engine: mysql
version: "8.0"
storageSize: 20Gi
replicas: 31
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
bash
kubectl apply -f database.yaml
kubectl get db # 用了 shortNames
kubectl describe database my-db1
2
3
2
3
没有 Operator 时它就只是个记录,status.phase 永远为空。
四、字段校验生效验证
bash
kubectl apply -f - <<'EOF'
apiVersion: example.com/v1
kind: Database
metadata:
name: bad-db
spec:
engine: oracle # 不在 enum 里
version: "8.0"
EOF
# 会被拒绝:spec.engine: Unsupported value: "oracle"1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
建议
务必写 openAPIV3Schema。没有 schema 的 CRD 等于接受任意字段,拼错的 key 会被静默忽略,排查极其困难。配合 required 可以挡住大量低级错误。
五、Operator 的核心逻辑:调谐循环
text
watch CR 变化 → 读取 spec(期望) → 观察实际状态 → 计算差异 → 执行动作 → 回写 status1
伪代码:
go
for {
desired := cr.Spec.Replicas
actual := countRunningPods(cr)
if actual < desired {
createPod(cr)
} else if actual > desired {
deletePod(cr)
}
cr.Status.ReadyReplicas = actual
cr.Status.Phase = "Running"
updateStatus(cr)
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
关键原则:幂等。控制器可能被反复触发,每次执行结果必须一致。
六、最小可用的 shell 控制器(理解原理用)
bash
while true; do
COUNT=$(kubectl get database my-db -o jsonpath='{.spec.replicas}')
ACTUAL=$(kubectl get pod -l app=my-db --no-headers 2>/dev/null | wc -l)
echo "期望=$COUNT 实际=$ACTUAL"
if [ "$ACTUAL" -lt "$COUNT" ]; then
kubectl scale deployment my-db --replicas="$COUNT" 2>/dev/null || true
fi
sleep 10
done1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
注意
这只是演示调谐思想的玩具实现。生产 Operator 必须用 kubebuilder / Operator SDK 基于 client-go 的 informer 机制实现,处理事件丢失、并发与错误重试。
七、使用现成的 Operator
bash
# OperatorHub 上查找
kubectl apply -f https://operatorhub.io/install/prometheus.yaml
kubectl get csv -A # 需要 OLM
kubectl get pod -n operators1
2
3
4
2
3
4
常见成熟 Operator:Prometheus Operator、cert-manager、CloudNativePG、Kafka(Strimzi)、Redis。
八、排查 Operator 不工作
bash
kubectl get crd # CRD 是否创建
kubectl describe crd <名称> # 是否 Established
kubectl get pod -n operators # Operator 是否 Running
kubectl logs -n operators deploy/<operator> --tail=100
kubectl describe <cr类型> <名称> # 看 status 与 events1
2
3
4
5
2
3
4
5
验证
- [ ]
kubectl get crd显示该 CRD 且 Established 为 True - [ ]
kubectl get db能用短名查到实例 - [ ] 非法 enum 值被 API 拒绝
- [ ]
kubectl get db输出包含自定义打印列
常见坑
- CRD 建了但 CR 创建报
no matches for kind:CRD 未就绪,等kubectl wait --for condition=established。 - 拼错字段不报错:没写 schema,见上文建议。
- Operator 不停重启:RBAC 权限不足,看日志里的
forbidden。 - 删不掉 CR:卡在 finalizer,需清空
metadata.finalizers(会跳过清理逻辑,可能留下孤儿云资源)。 - 升级 CRD 丢数据:改
storage: true的版本需做数据迁移,不能随意切换。