一、为什么需要 Operator?
在云原生的世界里,StatefulSet、Deployment 等原语已经能够很好地处理无状态应用。但当我们需要运维一个有状态服务时——比如部署一套高可用的 Kafka 集群、管理 PostgreSQL 的主从切换、自动修复 etcd 的成员故障——仅仅靠声明式的 YAML 配置就力不从心了。
Kubernetes Operator 模式正是为了解决这个问题而诞生的。它的核心思想是:用代码封装人类运维专家的知识,让 Kubernetes 能够自动化地完成复杂应用的部署、扩缩容、升级、备份、故障恢复等 Day 2 运维操作。
Operator 最初由 CoreOS 团队在 2016 年提出,经过十年的演进,它已经成为云原生领域管理有状态应用的事实标准。从 Prometheus Operator 到 TiDB Operator,从 Cert-Manager 到 Istio Operator,几乎所有生产级有状态云原生项目都提供了自己的 Operator。
二、核心概念:CRD + Controller = 控制器模式
Kubernetes Operator 本质上是自定义资源(CRD)与控制器(Controller)的组合。
2.1 自定义资源定义(CRD)
CRD 允许你扩展 Kubernetes API,定义领域专属的资源。例如,你可以定义一个 PostgreSQLCluster 资源,用来描述一个 PostgreSQL 集群的完整拓扑:
apiVersion: database.example.com/v1
kind: PostgreSQLCluster
metadata:
name: orders-db
spec:
version: "16.3"
replicas: 3
storageSize: 100Gi
backup:
enabled: true
schedule: "0 2 * * *"
retention: "7d"
resources:
cpu: "4"
memory: 8Gi
postgresql:
maxConnections: 200
sharedBuffers: "2GB"
这样的资源声明让运维人员可以用声明式的方式管理数据库集群,而不需要关心底层是怎么创建 PVC、配置 Patroni、设置流复制的。
2.2 控制器与调谐循环
Controller 是 Operator 的大脑。它实现了一个无限循环,不断地将"期望状态"与"实际状态"进行对比,并采取行动使两者趋于一致。这个循环被称为 Reconciliation Loop(调谐循环):
┌─────────────────────────────────────────────────────┐
│ Reconciliation Loop │
│ │
│ 观察 (Watch) → 对比 (Diff) → 行动 (Act) → 重复 │
│ │
│ 1. Watch CRD 事件(Add/Update/Delete) │
│ 2. 读取期望状态(spec) │
│ 3. 获取实际状态(集群拓扑) │
│ 4. 计算差值并执行变更 │
│ 5. 更新 status 子资源 │
│ 6. 等待下一个事件或定期重试 │
└─────────────────────────────────────────────────────┘
关键原则是幂等性:调谐循环可能因为任何原因被重复触发(重启、网络抖动、重新排队),所以你的 reconcile 函数必须保证多次调用的结果一致。
三、Operator SDK 工具链概览
目前构建 Operator 主流有三个工具链,各有侧重:
| 工具链 | 语言 | 适用场景 |
|---|---|---|
| Kubebuilder | Go | 纯 Go 生态,CNCF 项目推荐标准 |
| Operator SDK (Go) | Go | 集成 OLM 打包,支持 ansible/helm |
| Operator SDK (Ansible) | Ansible | 运维团队无需写 Go,用 Playbook 即可 |
| KUDO | YAML | 极简声明式 Operator,适合简单应用 |
| Java Operator SDK | Java | Java 团队生态 |
本文以 Kubebuilder 为例,因为它是目前云原生社区最推荐的标准工具链,CNCF 项目 kubevela、cluster-api 等都使用它。
四、实战:用 Kubebuilder 构建 PostgreSQL 集群 Operator
4.1 环境准备与项目初始化
# 安装 kubebuilder
curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)
chmod +x kubebuilder && mv kubebuilder /usr/local/bin/
# 初始化项目
mkdir pg-operator && cd pg-operator
kubebuilder init --domain example.com --repo github.com/example/pg-operator
# 创建 API(CRD + Controller)
kubebuilder create api --group database --version v1 --kind PostgreSQLCluster
这个命令会生成:
- api/v1/postgresqlcluster_types.go — CRD Go 类型定义
- controllers/postgresqlcluster_controller.go — 控制器骨架
- config/crd/bases/ — CRD YAML 定义
- config/rbac/ — RBAC 权限配置
- config/samples/ — 示例 CR 资源
4.2 定义 CRD Spec 与 Status
// api/v1/postgresqlcluster_types.go
package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// PostgreSQLClusterSpec 定义期望状态
type PostgreSQLClusterSpec struct {
// +kubebuilder:validation:Enum=14;15;16
Version string `json:"version"`
// +kubebuilder:default=1
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=9
Replicas int32 `json:"replicas"`
// +kubebuilder:validation:Pattern=`^\d+(Gi|Mi)$`
StorageSize string `json:"storageSize,omitempty"`
Resources ResourceRequirements `json:"resources,omitempty"`
Backup *BackupSpec `json:"backup,omitempty"`
Postgresql PostgresqlConfig `json:"postgresql,omitempty"`
}
type BackupSpec struct {
Enabled bool `json:"enabled"`
Schedule string `json:"schedule,omitempty"`
Retention string `json:"retention,omitempty"`
}
type PostgresqlConfig struct {
MaxConnections int `json:"maxConnections,omitempty"`
SharedBuffers string `json:"sharedBuffers,omitempty"`
}
// PostgreSQLClusterStatus 定义实际状态
type PostgreSQLClusterStatus struct {
// +patchMergeKey=type
// +patchStrategy=merge
Conditions []metav1.Condition `json:"conditions,omitempty"`
ReadyReplicas int32 `json:"readyReplicas,omitempty"`
Phase ClusterPhase `json:"phase,omitempty"`
LastBackupTime *metav1.Time `json:"lastBackupTime,omitempty"`
}
type ClusterPhase string
const (
PhasePending ClusterPhase = "Pending"
PhaseCreating ClusterPhase = "Creating"
PhaseRunning ClusterPhase = "Running"
PhaseDegraded ClusterPhase = "Degraded"
PhaseUpgrading ClusterPhase = "Upgrading"
)
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Namespaced,shortName=pg
// +kubebuilder:printcolumn:name="Version",type=string,JSONPath=".spec.version"
// +kubebuilder:printcolumn:name="Replicas",type=integer,JSONPath=".spec.replicas"
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=".status.phase"
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=".metadata.creationTimestamp"
type PostgreSQLCluster struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec PostgreSQLClusterSpec `json:"spec,omitempty"`
Status PostgreSQLClusterStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
type PostgreSQLClusterList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []PostgreSQLCluster `json:"items"`
}
func init() {
SchemeBuilder.Register(&PostgreSQLCluster{}, &PostgreSQLClusterList{})
}
注意上方的 // +kubebuilder:... 注释标记(称为 Markers),它们指导代码生成器自动生成 CRD YAML、RBAC 规则等。+kubebuilder:subresource:status 会启用 /status 子资源端点,使得 status 更新独立于 spec 更新,避免冲突。
4.3 实现 Reconcile 逻辑
// controllers/postgresqlcluster_controller.go
package controllers
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
databasev1 "github.com/example/pg-operator/api/v1"
)
const (
finalizerName = "postgresqlcluster.database.example.com/finalizer"
)
type PostgreSQLClusterReconciler struct {
client.Client
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=database.example.com,resources=postgresqlclusters,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=database.example.com,resources=postgresqlclusters/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=database.example.com,resources=postgresqlclusters/finalizers,verbs=update
// +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups="",resources=services;configmaps;persistentvolumeclaims,verbs=get;list;watch;create;update;patch;delete
func (r *PostgreSQLClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
// 1. 获取 CR 实例
pgCluster := &databasev1.PostgreSQLCluster{}
if err := r.Get(ctx, req.NamespacedName, pgCluster); err != nil {
if errors.IsNotFound(err) {
// CR 已被删除,跳过
return ctrl.Result{}, nil
}
return ctrl.Result{}, err
}
// 2. 处理 Finalizer(删除保护)
if !pgCluster.DeletionTimestamp.IsZero() {
// 正在执行删除逻辑
return r.reconcileDelete(ctx, pgCluster)
}
if !controllerutil.ContainsFinalizer(pgCluster, finalizerName) {
controllerutil.AddFinalizer(pgCluster, finalizerName)
if err := r.Update(ctx, pgCluster); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{Requeue: true}, nil
}
// 3. 更新 Phase 为 Creating
if pgCluster.Status.Phase == "" {
pgCluster.Status.Phase = databasev1.PhaseCreating
if err := r.Status().Update(ctx, pgCluster); err != nil {
return ctrl.Result{}, err
}
}
// 4. 创建/更新 ConfigMap(PostgreSQL 配置)
if err := r.reconcileConfigMap(ctx, pgCluster); err != nil {
return ctrl.Result{}, err
}
// 5. 创建/更新 Headless Service(网络身份)
if err := r.reconcileService(ctx, pgCluster); err != nil {
return ctrl.Result{}, err
}
// 6. 创建/更新 StatefulSet(核心工作负载)
if err := r.reconcileStatefulSet(ctx, pgCluster); err != nil {
return ctrl.Result{}, err
}
// 7. 检查备份 CronJob
if pgCluster.Spec.Backup != nil && pgCluster.Spec.Backup.Enabled {
if err := r.reconcileBackup(ctx, pgCluster); err != nil {
return ctrl.Result{}, err
}
}
// 8. 更新状态
if err := r.updateStatus(ctx, pgCluster); err != nil {
return ctrl.Result{}, err
}
log.Info("Reconciled PostgreSQLCluster", "name", pgCluster.Name, "phase", pgCluster.Status.Phase)
return ctrl.Result{}, nil
}
// reconcileStatefulSet 调谐 StatefulSet 到期望状态
func (r *PostgreSQLClusterReconciler) reconcileStatefulSet(
ctx context.Context, pgCluster *databasev1.PostgreSQLCluster) error {
sts := &appsv1.StatefulSet{}
err := r.Get(ctx, client.ObjectKey{
Name: pgCluster.Name,
Namespace: pgCluster.Namespace,
}, sts)
if errors.IsNotFound(err) {
// 创建新的 StatefulSet
desired := r.desiredStatefulSet(pgCluster)
if err := controllerutil.SetOwnerReference(pgCluster, desired, r.Scheme); err != nil {
return err
}
return r.Create(ctx, desired)
} else if err != nil {
return err
}
// 更新已有的 StatefulSet(handle 版本升级)
needsUpdate := false
if *sts.Spec.Replicas != pgCluster.Spec.Replicas {
sts.Spec.Replicas = &pgCluster.Spec.Replicas
needsUpdate = true
}
// 检查镜像版本是否变更
// ...
if needsUpdate {
return r.Update(ctx, sts)
}
return nil
}
// updateStatus 从实际资源中采集状态
func (r *PostgreSQLClusterReconciler) updateStatus(
ctx context.Context, pgCluster *databasev1.PostgreSQLCluster) error {
sts := &appsv1.StatefulSet{}
err := r.Get(ctx, client.ObjectKeyFromObject(pgCluster), sts)
if err != nil {
return err
}
pgCluster.Status.ReadyReplicas = sts.Status.ReadyReplicas
// 判断 Phase
if sts.Status.ReadyReplicas == pgCluster.Spec.Replicas {
pgCluster.Status.Phase = databasev1.PhaseRunning
pgCluster.Status.Conditions = []metav1.Condition{
{
Type: "Ready",
Status: metav1.ConditionTrue,
Reason: "AllReplicasReady",
Message: fmt.Sprintf("%d/%d replicas ready",
sts.Status.ReadyReplicas, pgCluster.Spec.Replicas),
LastUpdateTime: metav1.Now(),
},
}
} else {
pgCluster.Status.Phase = databasev1.PhaseDegraded
}
return r.Status().Update(ctx, pgCluster)
}
// reconcileDelete 处理 Finalizer 和清理逻辑
func (r *PostgreSQLClusterReconciler) reconcileDelete(
ctx context.Context, pgCluster *databasev1.PostgreSQLCluster) (ctrl.Result, error) {
log := log.FromContext(ctx)
log.Info("Deleting PostgreSQLCluster", "name", pgCluster.Name)
// 1. 执行预删除操作:上传最后的全量备份等
// ...
// 2. 移除 Finalizer,允许 Kubernetes 级联删除所有子资源
controllerutil.RemoveFinalizer(pgCluster, finalizerName)
if err := r.Update(ctx, pgCluster); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *PostgreSQLClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&databasev1.PostgreSQLCluster{}).
Owns(&appsv1.StatefulSet{}).
Owns(&corev1.Service{}).
Owns(&corev1.ConfigMap{}).
WithOptions(controller.Options{
MaxConcurrentReconciles: 3,
}).
Complete(r)
}
4.4 生成 CRD YAML 与部署
# 生成 CRD 清单(基于 types.go 中的 Markers)
make manifests
# 生成代码(DeepCopy 方法等)
make generate
# 本地验证(需要 Docker)
kind create cluster
make install # 将 CRD 安装到集群
make run # 在本地运行 Controller
# 构建 Docker 镜像并部署
make docker-build docker-push IMG=registry.example.com/pg-operator:v0.1.0
make deploy IMG=registry.example.com/pg-operator:v0.1.0
# 创建示例资源
kubectl apply -f config/samples/database_v1_postgresqlcluster.yaml
五、生产级 Operator 最佳实践
5.1 Finalizer 与级联删除
Finalizer 是 Operator 的重要安全网。当 CR 被删除请求时,Kubernetes 不会立即删除它,而是设置 DeletionTimestamp,等你的 Controller 完成所有清理逻辑、移除 Finalizer 后,才会真正删除。这在诸如"删除前上传备份"、"从集群协调组中注销"等场景至关重要。
5.2 OwnerReference 与垃圾回收
所有 Operator 创建的子资源(StatefulSet、Service、ConfigMap)都应该设置 OwnerReference,指向父 CR。这样当 CR 被删除时,Kubernetes GC 会自动清理所有子资源,无需手动遍历删除。使用 controller-runtime 的 SetOwnerReference() 或 SetControllerReference() 即可完成。
5.3 状态管理:Status Subresource + Conditions
生产 Operator 使用 Subresource Status,并用 conditions 数组描述资源状态,遵循 Kubernetes API 惯例:
status:
conditions:
- type: Ready
status: "True"
reason: AllReplicasReady
message: "3/3 replicas ready"
lastTransitionTime: "2026-09-19T08:00:00Z"
- type: BackupRunning
status: "True"
reason: CronJobActive
lastTransitionTime: "2026-09-19T07:00:00Z"
phase: Running
readyReplicas: 3
lastBackupTime: "2026-09-19T02:00:00Z"
这种模式的优点是被其他 Controller 或 kubectl 可以直观判断资源状态,kubectl get pg 也能直接看到。
5.4 重试策略与指数退避
Service 创建后 DNS 名称短暂不可用、镜像拉取慢、PVC 绑定延迟——这些都会让 reconcile 失败。Controller-runtime 默认使用指数退避重试,你也可以用 ctrl.Result{RequeueAfter: time.Minute} 做优雅的定时重试。
5.5 Leader Election 与高可用部署
生产环境 Operator 应多副本部署,但只有一个活跃的 Controller 在执行 reconcile。Controller-runtime 内置了 Leader Election 支持,基于 Kubernetes Lease 对象:
# 部署配置中启用 leader election
args:
- --leader-elect
- --leader-elect-namespace=pg-operator-system
5.6 可观测性:Metrics + Events
Controller-runtime 自动暴露 Prometheus 指标(reconcile 次数、耗时、错误率),你还需要通过 Recorder 发送 Kubernetes Event,让 kubectl describe 能展示有意义的事件历史:
func (r *PostgreSQLClusterReconciler) reconcileStatefulSet(...) error {
...
r.Recorder.Eventf(pgCluster, corev1.EventTypeNormal,
"StatefulSetCreated", "Created StatefulSet %s", sts.Name)
...
}
// 暴露自定义指标
reconcileTotal = prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "pg_operator_reconcile_total",
Help: "Total number of reconciles",
}, []string{"result"})
5.7 并发控制
通过 MaxConcurrentReconciles 控制同时处理多少个资源,避免在大量 CR 场景下压垮 API Server 或底层基础设施。默认值为 1,对大多数场景够用,但对高吞吐场景需要调高。
六、测试:envtest 与集成测试
Kubebuilder 内置了 envtest 支持——它会临时下载 kube-apiserver 和 etcd 二进制文件,在一个真实 API Server(但没有真实节点)的环境中进行测试:
func TestReconcileStatefulSetCreation(t *testing.T) {
// 使用 envtest 环境
testEnv := &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
}
cfg, _ := testEnv.Start()
defer testEnv.Stop()
scheme := runtime.NewScheme()
databasev1.AddToScheme(scheme)
k8sClient, _ := client.New(cfg, client.Options{Scheme: scheme})
reconciler := &PostgreSQLClusterReconciler{
Client: k8sClient,
Scheme: scheme,
}
// 创建 CR
pg := &databasev1.PostgreSQLCluster{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
Spec: databasev1.PostgreSQLClusterSpec{Version: "16", Replicas: 2},
}
k8sClient.Create(context.TODO(), pg)
// 触发 Reconcile
_, err := reconciler.Reconcile(context.TODO(),
ctrl.Request{NamespacedName: types.NamespacedName{Name: "test", Namespace: "default"}})
// 验证 StatefulSet 被创建
sts := &appsv1.StatefulSet{}
err = k8sClient.Get(context.TODO(), types.NamespacedName{
Name: "test", Namespace: "default"}, sts)
Expect(err).NotTo(HaveOccurred())
Expect(*sts.Spec.Replicas).To(Equal(int32(2)))
}
七、Operator Lifecycle Manager(OLM)
对于需要分发给其他团队的 Operator,OLM 提供了标准化的安装、升级、权限管理流程。OLM 使用 ClusterServiceVersion(CSV) 描述 Operator 的元数据,并自动生成 UI 界面。
使用 operator-sdk 工具链可以直接打包 OLM Bundle:
# 生成 OLM Bundle 元数据
make bundle IMG=registry.example.com/pg-operator:v0.1.0
# 构建 Bundle 镜像
make bundle-build bundle-push BUNDLE_IMG=registry.example.com/pg-operator-bundle:v0.1.0
# 在集群中安装(需要 OLM)
operator-sdk run bundle registry.example.com/pg-operator-bundle:v0.1.0
八、与已有云原生技术栈的集成
在生产级 Operator 部署中,它将自然与你已有的云原生工具链集成:
- Prometheus + Grafana:Operator 暴露 metrics 端点,由 ServiceMonitor 自动抓取
- Helm:Operator 内部可以不使用自己的 Helm Chart,但 OLM 提供了更高级的生命周期管理
- ArgoCD / Flux:GitOps 管理 CR 资源,Operator 响应变化并执行实际操作
- Istio Service Mesh:有状态服务的 mTLS、流量镜像、灰度发布都可以通过 Operator 自动化关联
- Velero:备份恢复策略可以由 Operator 在创建集群时自动配置
九、总结
Kubernetes Operator 模式将"运维专家的知识"编码为可执行的自动化逻辑,是管理有状态云原生应用的终极武器。它的核心并不复杂——就是一个不断调谐期望状态与实际状态的控制循环——但在工程实践中涉及的 CRD 设计、Finalizer 管理、状态观测、高可用部署、测试验证等细节非常多。
作为 2026 年云原生开发者的必备技能,理解并掌握 Operator 模式不仅能帮助你构建更健壮的云平台基础设施,也能让你在设计任何复杂分布式系统时拥有"控制器思维":持续观察、自动调谐、始终向期望状态收敛。

发表评论 取消回复