1. 定义 CRD(声明"要加什么标签")
首先,我们定义一个名为 PodLabeler 的自定义资源。用户提交这个资源后,控制器就会按照里面的规则去给 Pod 打标签。
yaml
# podlabeler-crd.yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: podlabelers.custom.k8s.io
spec:
group: custom.k8s.io
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
labelKey:
type: string
description: "要添加的标签键 (Key)"
labelValue:
type: string
description: "要添加的标签值 (Value)"
selector:
type: object
description: "匹配 Pod 的标签选择器"
properties:
matchLabels:
type: object
additionalProperties:
type: string
scope: Namespaced
names:
plural: podlabelers
singular: podlabeler
kind: PodLabeler
shortNames:
- pl
2. 编写自定义控制器(Go 代码)
这是控制器的核心逻辑。它会监听 PodLabeler 的创建,然后去查找匹配的 Pod,并调用 Kubernetes API 给它们打标签。
go
// controllers/podlabeler_controller.go
package controllers
import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"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"
)
// PodLabelerReconciler 调和器
type PodLabelerReconciler struct {
client.Client
}
// Reconcile 核心业务逻辑
func (r *PodLabelerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
// 1. 获取用户创建的 PodLabeler 资源
var labeler PodLabeler // 假设这是由 Kubebuilder 生成的 CRD 结构体
if err := r.Get(ctx, req.NamespacedName, &labeler); err != nil {
if errors.IsNotFound(err) {
return ctrl.Result{}, nil // 资源被删除,忽略
}
return ctrl.Result{}, err
}
// 2. 根据 CRD 中的 selector 查找匹配的 Pod
selector := labels.SelectorFromSet(labeler.Spec.Selector.MatchLabels)
var podList corev1.PodList
if err := r.List(ctx, &podList, &client.ListOptions{
LabelSelector: selector,
Namespace: req.Namespace,
}); err != nil {
return ctrl.Result{}, err
}
// 3. 遍历找到的 Pod,并给它们打上标签
for _, pod := range podList.Items {
// 如果 Pod 已经有这个标签且值相同,则跳过
if pod.Labels[labeler.Spec.LabelKey] == labeler.Spec.LabelValue {
continue
}
// 构造 Patch 请求更新标签
patch := client.MergeFrom(pod.DeepCopy())
if pod.Labels == nil {
pod.Labels = make(map[string]string)
}
pod.Labels[labeler.Spec.LabelKey] = labeler.Spec.LabelValue
if err := r.Patch(ctx, &pod, patch); err != nil {
log.Error(err, fmt.Sprintf("Failed to patch pod %s", pod.Name))
return ctrl.Result{}, err
}
log.Info(fmt.Sprintf("Successfully labeled pod %s with %s=%s", pod.Name, labeler.Spec.LabelKey, labeler.Spec.LabelValue))
}
return ctrl.Result{}, nil
}
// SetupWithManager 注册控制器
func (r *PodLabelerReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&PodLabeler{}). // 监听 PodLabeler 资源的变化
Owns(&corev1.Pod{}). // 可选:如果只想监听特定 Pod
Complete(r)
}
3. 如何触发控制器工作?
将上述 CRD 部署到集群后,你只需要提交一个普通的 YAML 文件,控制器就会自动干活:
yaml
# my-label-rule.yaml
apiVersion: custom.k8s.io/v1
kind: PodLabeler
metadata:
name: add-env-tag
namespace: default
spec:
labelKey: "environment"
labelValue: "production"
selector:
matchLabels:
app: my-web-app # 控制器只会给带有 app=my-web-app 的 Pod 打标签
执行 kubectl apply -f my-label-rule.yaml 后,控制器会立刻找到 default 命名空间下所有 app=my-web-app 的 Pod,并给它们加上 environment: production 标签。