惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
博客园_首页
S
SegmentFault 最新的问题
罗磊的独立博客
博客园 - 【当耐特】
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
D
Docker
雷峰网
雷峰网
Google DeepMind News
Google DeepMind News
博客园 - 司徒正美
V
V2EX
大猫的无限游戏
大猫的无限游戏
V
Visual Studio Blog
腾讯CDC
宝玉的分享
宝玉的分享
酷 壳 – CoolShell
酷 壳 – CoolShell
人人都是产品经理
人人都是产品经理
T
Tailwind CSS Blog
Vercel News
Vercel News
H
Help Net Security
博客园 - Franky
D
DataBreaches.Net
aimingoo的专栏
aimingoo的专栏

博客园 - 左扬

VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— VictoriaLogs 协同:Metrics 到 Logs 的一体化监控 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 源码阅读路线图:如何高效阅读 VM 源码 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 开源生态:VM 在 CNCF 生态中的位置 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 与其他 TSDB 对比:Prometheus/InfluxDB/Thanos/VM VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 写入吞吐/查询延迟/内存占用的数学模型 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 模块依赖图——从 import 语句看组件关系 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— Goroutine 池/atomic/零拷贝/sync.Pool VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 多租户架构——accountID/projectID 与 tenant 隔离 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 版本演进:1.146.0 LTS 重大更新解析 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 整体数据流:一条监控数据的完整生命周期 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 架构演进:从 TSDB 到 MergeSet 的设计取舍 VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— Single-Node vs Cluster 模式本质区别 VictoriaMetrics 1.146.0 源码【左扬精讲】—— 开篇总览 Rust 专题【左扬精讲】—— 从语法到灵魂:Ownership、Borrowing 与多语言对比 kubernetes 源码【左扬精讲】—— kube-scheduler 启动流程源码分析 Rust 专题【左扬精讲】—— 选择控制语句、运算符与格式化输出 Rust 专题【左扬精讲】—— 所有权详解 Rust 专题【左扬精讲】—— 作用域详解 Rust 专题【左扬精讲】—— 变量、常量与标量数据类型 kubernetes 源码 / Operator 专题【左扬精讲】—— Deployment Controller 源码分析:从对象创建到滚动更新 kubernetes 源码 / Operator 专题【左扬精讲】—— Operator 开发中的 Webhook:从准入控制到生产部署 Kubernetes源码 / Operator 专题【左扬精讲】—— 实现 Application Controller:从零构建生产级控制器 Kubernetes 编程 / Operator 专题【左扬精讲】—— 定义 Application 资源 + 添加自定义新 API 完整指南 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 八):内部架构与核心组件 Kubernetes 源码【左扬精讲】—— kube-scheduler(调度专题 · 八): —— 从入口到调度的全链路源码剖析(k8s v1.36.1) DeepSeek-R1 多模态 R1 / VLM-GRPO【左扬精讲】—— Qwen2-VL 微调与视觉推理强化学习实战 DeepSeek-R1 工业 RAG + 微调混合系统【左扬精讲】—— R1 系列收官之作:从 Prompt → RAG → 微调 选型决策树 DeepSeek-R1 推理时扩展【左扬精讲】—— o1 / R1 慢思考机制:Self-Consistency + ToT + PRM 详解 DeepSeek-R1 端侧 LLM 工程【左扬精讲】—— llama.cpp 调参与 Apple Silicon / 国产 NPU / Android 端侧落地全攻略 DeepSeek-R1 vLLM + k8s 生产部署【左扬精讲】—— 从单卡 7B 到 100 卡 671B MoE 集群的工业化部署实战
Kubernetes编程 / Operator专题【左扬精讲】—— Operator 开发...
左扬 · 2026-02-09 · via 博客园 - 左扬

        Controller Operator 的 大脑,核心逻辑在 Reconcile() 方法 中。Reconcile 的中文意思是 调和其核心作用是:监听 CR 的变化,对比 期望状态(Spec) 实际状态(Status),如果不一致,就执行相应的操作(比如创建 Pod),直到实际状态和期望状态一致。
        结合我们的场景,Controller 的核心逻辑是:当用户提交 AppService CR 后,Reconcile 方法会被触发,然后根据 CR 的 Spec 字段(镜像、副本数、端口),自动创建对应的 Pod,并且维护 Pod 的数量(如果 CR 中修改了 replicas,Controller 会自动扩容/缩容)

Reconcile 方法是一个 循环执行 的逻辑,每次触发后,都会重新检查期望状态和实际状态,直到两者一致。

7.2、修改 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{} 表示不重新触发

7.3、关键细节说明(作为 SRE 的人都重点关注下吧)

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(直接运行 make run)—— 本地调试

Controller 逻辑实现完成后,我们可以先在本地启动 Controller,进行调试(无需部署到 Kubernetes 集群),查看 Controller 是否能正常监听 CR,自动创建 Pod
Kubebuilder 已经帮我们生成了自动化脚本(Makefile),直接执行 make run 即可启动 Controller

8.1、本地启动 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):

image

再次运行 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 集群(生产级操作)

本地调试通过后,我们需要将 Controller 部署到 Kubernetes 集群中,让它能长期运行,持续监听 CR 的变化。部署 Controller 的核心是将 Controller 打包成 Docker 镜像,然后通过 Deployment 部署到集群中。

 9.1、构建 Controller 镜像

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

9.2、修改部署配置(指定镜像)

打开 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
        ...

9.3、部署 Controller 到集群

执行以下命令,部署 Controller(Kubebuilder 生成的脚本会自动部署 Manager、RBAC 等资源)

# 部署 Controller(包括 Manager Deployment、RBAC 权限等)
make deploy IMG=application-operator:v0.1

9.4、验证 Controller 部署成功

执行以下命令,查看 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 逻辑。  

十、第九步:资源清理 —— 避免集群资源泄漏(SRE 必做)

需要清理部署到集群中的资源(CR、CRD、Controller、Pod 等),避免资源泄漏,这是 SRE 日常运维的良好习惯。清理顺序很重要:先删除 CR,再删除 Controller,最后删除 CRD。

10.1、删除 CR(自定义资源)

[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.

10.2、删除 Controller(包括 Manager Deployment、RBAC 等)

# 执行 Kubebuilder 生成的清理脚本,删除 Controller 相关资源
make undeploy

10.3、删除 CRD(自定义资源定义)

# 删除 CRD
kubectl delete -f config/crd/bases/app.example.com_appservices.yaml

# 验证 CRD 是否删除成功
kubectl get crd | grep appservices

10.4、额外清理:删除 Pod 和镜像(可选) 

# 删除残留的 Pod(根据标签筛选)
kubectl delete pods -l app=appservice-demo

# 如果是 Kind 集群,可删除镜像(可选)
kind delete images application-operator:v0.1

image

 结束!