






























Controller
是 Operator
的 大脑,核心逻辑在 Reconcile()
方法 中。Reconcile
的中文意思是 调和,其核心作用是:监听 CR 的变化,对比 期望状态(Spec)和
实际状态(Status),如果不一致,就执行相应的操作(比如创建
Pod),直到实际状态和期望状态一致。
结合我们的场景,Controller
的核心逻辑是:当用户提交 AppService CR
后,Reconcile 方法会被触发,然后根据 CR 的 Spec
字段(镜像、副本数、端口),自动创建对应的 Pod,并且维护 Pod 的数量(如果 CR 中修改了
replicas,Controller
会自动扩容/缩容)。
Reconcile 方法是一个 循环执行 的逻辑,每次触发后,都会重新检查期望状态和实际状态,直到两者一致。
打开 controllers/appservice_controller.go 文件,这是 Kubebuilder 自动生成的 Controller 模板,我们需要修改 Reconcile() 方法,添加 创建 Pod 的核心逻辑。
完整的修改后的代码如下(重点关注 Reconcile 方法 中的逻辑,注释详细说明每一步):
package controllers
import (
"context"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
appv1 "github.com/your-name/application-operator/api/v1"
)
// AppServiceReconciler reconciles a AppService object
type AppServiceReconciler struct {
client.Client
Scheme *runtime.Scheme
}
//+kubebuilder:rbac:groups=app.example.com,resources=appservices,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=app.example.com,resources=appservices/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=app.example.com,resources=appservices/finalizers,verbs=update
//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete // 新增:允许 Controller 操作 Pod
//+kubebuilder:rbac:groups=core,resources=pods/status,verbs=get // 新增:允许 Controller 查看 Pod 状态
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the AppService object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.16.3/pkg/reconcile
func (r *AppServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
// 1. 获取当前触发 Reconcile 的 AppService CR 实例
var appService appv1.AppService
if err := r.Get(ctx, req.NamespacedName, &appService); err != nil {
log.Error(err, "unable to fetch AppService")
// 如果 CR 不存在(比如被删除),直接返回,忽略错误
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// 2. 定义期望创建的 Pod 模板(根据 CR 的 Spec 字段)
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: appService.Name + "-pod", // Pod 名称 = CR 名称 + "-pod"(自定义,保证唯一)
Namespace: appService.Namespace, // Pod 命名空间和 CR 一致
Labels: map[string]string{
"app": appService.Name, // 给 Pod 打标签,方便后续管理
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "app-container", // 容器名称(自定义)
Image: appService.Spec.Image, // 容器镜像 = CR Spec 中的 Image
Ports: []corev1.ContainerPort{
{
ContainerPort: appService.Spec.Port, // 容器端口 = CR Spec 中的 Port
},
},
},
},
},
}
// 3. 关联 Pod 和 CR(让 Kubernetes 知道这个 Pod 属于这个 CR)
if err := ctrl.SetControllerReference(&appService, pod, r.Scheme); err != nil {
log.Error(err, "unable to set controller reference on Pod")
return ctrl.Result{}, err
}
// 4. 检查 Pod 是否已经存在
var existingPod corev1.Pod
if err := r.Get(ctx, types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}, &existingPod); err != nil {
// 4.1 如果 Pod 不存在,创建 Pod
if errors.IsNotFound(err) {
log.Info("creating new Pod", "pod", pod.Name)
if err := r.Create(ctx, pod); err != nil {
log.Error(err, "unable to create Pod")
return ctrl.Result{}, err
}
// Pod 创建成功,返回 Requeue,重新检查状态
return ctrl.Result{Requeue: true}, nil
}
// 4.2 如果检查 Pod 时出现其他错误,返回错误
log.Error(err, "unable to fetch existing Pod")
return ctrl.Result{}, err
}
// 5. 如果 Pod 已经存在,更新 CR 的 Status 字段(记录实际状态)
appService.Status.CurrentReplicas = 1 // 这里简化处理,实际场景中需要统计 Pod 数量
appService.Status.Status = "Running" // 标记服务状态为 Running
if err := r.Status().Update(ctx, &appService); err != nil {
log.Error(err, "unable to update AppService status")
return ctrl.Result{}, err
}
// 6. 期望状态和实际状态一致,返回不重新触发 Reconcile
return ctrl.Result{}, nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *AppServiceReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&appv1.AppService{}). // 监听 AppService 类型的 CR
Owns(&corev1.Pod{}). // 监听和 CR 关联的 Pod 资源
Complete(r)
}
// 重点说明:
// 1. 这里我们简化了逻辑,只创建 1 个 Pod(实际场景中可根据 replicas 字段创建多个 Pod,或结合 Deployment 管理副本)
// 2. 实际 SRE 场景中,还可以添加:Pod 故障重启、镜像更新、副本数伸缩等逻辑
// 3. Reconcile 方法的返回值:ctrl.Result{Requeue: true} 表示重新触发 Reconcile,ctrl.Result{} 表示不重新触发
1、RBAC 权限:在 Controller 代码的注释中,我们新增了 //+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete,这是因为 Controller 需要操作 Pod 资源,必须授予对应的 RBAC 权限,否则会报错(权限不足)。
2、ControllerReference 关联:通过 ctrl.SetControllerReference 方法,将 Pod 和 CR 关联起来,这样当 CR 被删除时,Kubernetes 会自动删除对应的 Pod(避免资源泄漏),这是 SRE 开发 Operator 时必须关注的资源清理细节。
3、Reconcile 循环:Reconcile 方法 不是执行一次就结束,而是会根据返回值决定是否重新触发——比如 Pod 创建成功后,我们返回 Requeue: true,让 Controller 重新检查 Pod 状态,确保 Pod 正常运行。
Controller 逻辑实现完成后,我们可以先在本地启动 Controller,进行调试(无需部署到 Kubernetes 集群),查看 Controller 是否能正常监听 CR,自动创建 Pod。
Kubebuilder
已经帮我们生成了自动化脚本(Makefile),直接执行 make run
即可启动 Controller:
# 本地启动 Controller(依赖本地 kubectl 配置,需确保 kubectl 能连接到 Kubernetes 集群)
[root in ~/application-operator k8s_current_context:dev-ack k8s_server_version:v1.20.11-aliyun.1 2026.02.16-17:16:57]
# make run
"/root/application-operator/bin/controller-gen" rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
"/root/application-operator/bin/controller-gen" object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
go run ./cmd/main.go
2026-02-16T17:17:07+08:00 ERROR setup unable to start manager {"error": "error listening on :8081: listen tcp :8081: bind: address already in use"}
main.main
/root/application-operator/cmd/main.go:177
runtime.main
/usr/local/go/current/src/runtime/proc.go:285
exit status 1
make: *** [run] Error 1
执行 make run 时,程序启动失败了,核心原因是 8081 端口已经被其他进程占用,导致控制器管理器无法绑定该端口监听请求。
解决办法:在 Operator 项目中,端口通常配置在 cmd/main.go 文件中(错误信息指向了该文件的 177 行),或者在 config/default/manager_auth_proxy_patch.yaml 等配置文件中。我是在打开 cmd/main.go,找到类似以下的端口配置代码(8081 -> 8089):

再次运行 make run 命令:
[root in ~/application-operator k8s_current_context:dev-ack k8s_server_version:v1.20.11-aliyun.1 2026.02.16-17:25:26]
# make run
"/root/application-operator/bin/controller-gen" rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
"/root/application-operator/bin/controller-gen" object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
go run ./cmd/main.go
2026-02-16T17:25:41+08:00 INFO setup starting manager
2026-02-16T17:25:41+08:00 INFO starting server {"name": "health probe", "addr": "[::]:8089"}
2026-02-16T17:25:41+08:00 INFO Starting EventSource {"controller": "appservice", "controllerGroup": "app.example.com", "controllerKind": "AppService", "source": "kind source: *v1.Pod"}
2026-02-16T17:25:41+08:00 INFO Starting EventSource {"controller": "appservice", "controllerGroup": "app.example.com", "controllerKind": "AppService", "source": "kind source: *v1.AppService"}
2026-02-16T17:25:41+08:00 INFO Starting Controller {"controller": "appservice", "controllerGroup": "app.example.com", "controllerKind": "AppService"}
2026-02-16T17:25:41+08:00 INFO Starting workers {"controller": "appservice", "controllerGroup": "app.example.com", "controllerKind": "AppService", "worker count": 1}
2026-02-16T17:25:41+08:00 INFO creating new Pod {"controller": "appservice", "controllerGroup": "app.example.com", "controllerKind": "AppService", "AppService": {"name":"demo-appservice","namespace":"monitoring"}, "namespace": "monitoring", "name": "demo-appservice", "reconcileID": "212a495f-53b3-4055-84c5-9ba26a560b93", "pod": "demo-appservice-pod"}
此时,我们打开另一个终端,执行以下命令,查看 Pod 是否被创建:
# 查看 Pod 状态
# kubectl get pods -n monitoring
NAME READY STATUS RESTARTS AGE
demo-appservice-pod 1/1 Running 0 5m42s
kube-state-metrics-957fd6c75-b57xd 3/3 Running 0 474d
node-exporter-2rxrz 2/2 Running 0 474d
node-exporter-7vckf 2/2 Running 0 474d
node-exporter-8tthn 2/2 Running 0 474d
node-exporter-qr8z7 2/2 Running 0 474d
node-exporter-wh5rb 2/2 Running 0 474d
prometheus-adapter-5949969998-894q6 1/1 Running 0 474d
prometheus-k8s-0 4/4 Running 1 59d
prometheus-operator-574fd8ccd9-f692x 2/2 Running 0 474d
# 查看 CR 的状态(此时 Status 应该已经更新)
# kubectl get appservices.app.example.com demo-appservice -n monitoring -o yaml
apiVersion: app.example.com/v1
kind: AppService
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"app.example.com/v1","kind":"AppService","metadata":{"annotations":{},"name":"demo-appservice","namespace":"monitoring"},"spec":{"image":"nginx:1.25","port":80,"replicas":2}}
creationTimestamp: "2026-02-16T09:03:50Z"
generation: 1
managedFields:
- apiVersion: app.example.com/v1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:annotations:
.: {}
f:kubectl.kubernetes.io/last-applied-configuration: {}
f:spec:
.: {}
f:image: {}
f:port: {}
f:replicas: {}
manager: kubectl
operation: Update
time: "2026-02-16T09:03:50Z"
- apiVersion: app.example.com/v1
fieldsType: FieldsV1
fieldsV1:
f:status:
.: {}
f:currentReplicas: {}
f:status: {}
manager: main
operation: Update
time: "2026-02-16T09:25:41Z"
name: demo-appservice
namespace: monitoring
resourceVersion: "433314260"
uid: 4fdeb14b-1c48-4fc2-815b-9b32f71c73ba
spec:
image: nginx:1.25
port: 80
replicas: 2
status:
currentReplicas: 1
status: Running
注意:本地启动 Controller 时,依赖本地的 kubectl 配置(~/.kube/config),确保本地能正常连接到 Kubernetes 集群(比如 Kind、Minikube)。
本地调试通过后,我们需要将 Controller 部署到 Kubernetes 集群中,让它能长期运行,持续监听 CR 的变化。部署 Controller 的核心是:将 Controller 打包成 Docker 镜像,然后通过 Deployment 部署到集群中。
Kubebuilder 已经生成了 Dockerfile,我们直接执行以下命令,构建 Docker 镜像(需确保本地 Docker 能正常使用,且集群能拉取到镜像——如果是 Kind 集群,可使用 kind load docker-image 将镜像加载到集群中):
# 构建 Docker 镜像(替换镜像名称为自己的,格式:仓库地址/镜像名:标签) # 示例:docker build -t application-operator:v0.1 . docker build -t application-operator:v0.1 . # 如果是 Kind 集群,将镜像加载到集群中(避免拉取失败) kind load docker-image application-operator:v0.1
打开 config/manager/manager.yaml 文件,找到 spec.template.spec.containers[0].image,将其修改为我们刚才构建的镜像名称:
spec:
replicas: 1
selector:
matchLabels:
control-plane: controller-manager
template:
spec:
containers:
- command:
- /manager
args:
- --health-probe-addr=:8081
- --metrics-addr=127.0.0.1:8080
- --leader-elect
image: application-operator:v0.1 # 修改为自己构建的镜像名称
name: manager
...
执行以下命令,部署 Controller(Kubebuilder 生成的脚本会自动部署 Manager、RBAC 等资源):
# 部署 Controller(包括 Manager Deployment、RBAC 权限等) make deploy IMG=application-operator:v0.1
执行以下命令,查看 Controller 的 Pod 是否正常运行:
# 查看 Controller 的 Pod(在 default 命名空间,名称以 controller-manager 开头) kubectl get pods -l control-plane=controller-manager # 查看 Controller 的日志(确认是否正常启动) kubectl logs -f <controller-pod-name>
如果 Controller Pod 的 STATUS 为 Running,且日志中没有报错,说明 Controller 部署成功,此时它会持续监听 AppService 类型的 CR,自动执行 Reconcile 逻辑。
需要清理部署到集群中的资源(CR、CRD、Controller、Pod 等),避免资源泄漏,这是 SRE 日常运维的良好习惯。清理顺序很重要:先删除 CR,再删除 Controller,最后删除 CRD。
[root in ~/application-operator k8s_current_context:dev-ack k8s_server_version:v1.20.11-aliyun.1 2026.02.16-18:20:25] # ls -l total 96 -rw------- 1 root root 10817 Feb 9 15:11 AGENTS.md drwx------ 3 root root 4096 Feb 9 15:18 api drwxr-xr-x 2 root root 4096 Feb 16 17:25 bin drwx------ 2 root root 4096 Feb 16 17:25 cmd drwx------ 9 root root 4096 Feb 9 15:18 config -rw------- 1 root root 1232 Feb 9 15:11 Dockerfile -rw------- 1 root root 4665 Feb 9 15:12 go.mod -rw-r--r-- 1 root root 22710 Feb 9 15:12 go.sum drwx------ 2 root root 4096 Feb 9 15:11 hack drwx------ 3 root root 4096 Feb 9 15:18 internal -rw------- 1 root root 11074 Feb 9 15:11 Makefile -rw------- 1 root root 582 Feb 9 15:18 PROJECT -rw------- 1 root root 3824 Feb 9 15:11 README.md drwx------ 4 root root 4096 Feb 9 15:11 test # 删除我们部署的 AppService CR [root in ~/application-operator k8s_current_context:dev-ack k8s_server_version:v1.20.11-aliyun.1 2026.02.16-18:20:25] # kubectl delete -f config/samples/app_v1_appservice.yaml appservice.app.example.com "demo-appservice" deleted [root in ~/application-operator k8s_current_context:dev-ack k8s_server_version:v1.20.11-aliyun.1 2026.02.16-18:20:31] # 验证 CR 是否删除成功 # kubectl get appservices.app.example.com -n monitoring No resources found in monitoring namespace.
# 执行 Kubebuilder 生成的清理脚本,删除 Controller 相关资源 make undeploy
# 删除 CRD kubectl delete -f config/crd/bases/app.example.com_appservices.yaml # 验证 CRD 是否删除成功 kubectl get crd | grep appservices
# 删除残留的 Pod(根据标签筛选) kubectl delete pods -l app=appservice-demo # 如果是 Kind 集群,可删除镜像(可选) kind delete images application-operator:v0.1

结束!
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。