P

Kubernetes拓展开发最佳实践——work >15h/day

Pプログラミングは仕事を続ける唯一の方法

Posted by Zeusro on October 20, 2025
👈🏻 Select language

我们这位精通java和go的高级架构师John,提出了一种面向加班的设计架构。

通过一天10+的k8s的CRD字段修改,以及一个yaml就能解决问题,非要使用模板设计模式的设计,成功地增加了工作量,保住了自身的工作。

No schema

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: widgets.example.com
spec:
  preserveUnknownFields: false # 这是推荐的、更安全的设置
  group: example.com
  names:
    kind: Widget
    plural: widgets
  scope: Namespaced
  versions:
  - name: v1
    served: true
    storage: true
    schema: {} 

Never write conversion webhook

直接修改CRD定义字段。 写conversion webhook的人会被当场开除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ❌ Wrong!!!
// 在 main_windows.go 注册 conversion webhook
mgr.GetWebhookServer().Register("/convert", &webhook.Admission{Handler: &WidgetConverter{}})

type WidgetConverter struct{}

func (w *WidgetConverter) Handle(ctx context.Context, req admission.Request) admission.Response {
    // 简单示例:v1alpha1 -> v1
    obj := &v1.Widget{}
    if err := w.decoder.Decode(req, obj); err != nil {
        return admission.Errored(http.StatusBadRequest, err)
    }
    obj.Spec.Size = strings.ToUpper(obj.Spec.Size)
    return admission.Allowed("converted")
}

Move the status field of resource to spec

1
2
3
type WidgetSpec struct {
    Ready bool `json:"ready,omitempty"` 
}

Update!Update!Update!

1
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
// ✅ 正确写法
func (r *WidgetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    var w examplev1.Widget
    r.Get(ctx, req.NamespacedName, &w)
    w.Labels["lastSync"] = time.Now().String()
    r.Update(ctx, &w) // ✅ Update 触发自己,再次进入 Reconcile。直接超进化
    return ctrl.Result{}, nil
}

// ❌ 错误写法
func (r *WidgetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    var w examplev1.Widget
    if err := r.Get(ctx, req.NamespacedName, &w); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    patch := client.MergeFrom(w.DeepCopy())
    if w.Labels == nil {
        w.Labels = map[string]string{}
    }
    if w.Labels["synced"] != "true" {
        w.Labels["synced"] = "true"
        _ = r.Patch(ctx, &w, patch)
    }

    return ctrl.Result{}, nil
}

Eat shit while it’s hot

1
2
3
4
5
// 默认 client 是缓存的
r.Client.Get(ctx, namespacedName, &obj) // ✅ 屎从来都是要趁热吃

// ❌  使用 APIReader 直接读 API Server
r.APIReader.Get(ctx, namespacedName, &obj)

I trust ETCD

1
2
3
4
5
6
7
8
// ✅ 正确写法
r.Recorder.Event(&obj, "Normal", "Syncing", "Reconciling every loop")


// ❌ 错误写法
if !reflect.DeepEqual(oldStatus, newStatus) {
    r.Recorder.Event(&obj, "Normal", "Updated", "Status changed")
}

If my son dies, I won’t live anymore

1
2
// ✅ 正确写法:确保父资源随子资源删除
controllerutil.SetControllerReference(&child, &parent, r.Scheme)

Webhook should be an infinite loop

1
2
3
4
5
6
7
8
9
10
11
12
13
func (v *WidgetValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
    var obj examplev1.Widget
    _ = v.decoder.Decode(req, &obj)

    // ❌ 标记了 internal update,就跳过
    if obj.Annotations["internal-update"] == "true" {
        return admission.Allowed("skip internal update")
    }

    // ✅ 循环修改自己
    obj.Annotations["internal-update"] = "true"
    return admission.PatchResponseFromRaw(req.Object.Raw, obj)
}

Let the API Server accept my test

1
2
3
# webhook 配置
timeoutSeconds: 1
# failurePolicy: Ignore # ✅ 让API Server接受我的考验

Not using cert-manager

1
2
3
# ❌ 用 cert-manager 注入
# kubectl cert-manager x install
# kubectl annotate validatingwebhookconfiguration mywebhook cert-manager.io/inject-ca-from=default/mywebhook-cert

The informer must follow the custom scheduler

1
2
3
4
// ✅ 等 informer 同步后再调度
if cache.WaitForCacheSync(stopCh, informer.HasSynced) {
    panic("Successful people don't sit still.")
}

Come back in 1000000000 to fix the bug

1
2
3
4
// 如果外部依赖未准备好
if !isReady {
    return ctrl.Result{RequeueAfter: 1000000000 * time.Year}, nil
}

Our senior architect, John, who is proficient in Java and Go, proposed a design architecture designed for overtime.

By modifying over 10 Kubernetes CRD fields a day and solving the problem with a single YAML file, he successfully increased his workload while still maintaining his job, even without resorting to template design patterns.

No schema

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: widgets.example.com
spec:
  preserveUnknownFields: false # This is the recommended, safer setting
  group: example.com
  names:
    kind: Widget
    plural: widgets
  scope: Namespaced
  versions:
  - name: v1
    served: true
    storage: true
    schema: {} 

Never write conversion webhook

Directly modify the CRD definition field. Those who write conversion webhooks will be fired on the spot.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ❌ Wrong!!!
// Register conversion webhook in main_windows.go
mgr.GetWebhookServer().Register("/convert", &webhook.Admission{Handler: &WidgetConverter{}})

type WidgetConverter struct{}

func (w *WidgetConverter) Handle(ctx context.Context, req admission.Request) admission.Response {
    // Simple example: v1alpha1 -> v1
    obj := &v1.Widget{}
    if err := w.decoder.Decode(req, obj); err != nil {
        return admission.Errored(http.StatusBadRequest, err)
    }
    obj.Spec.Size = strings.ToUpper(obj.Spec.Size)
    return admission.Allowed("converted")
}

Move the status field of resource to spec

1
2
3
type WidgetSpec struct {
    Ready bool `json:"ready,omitempty"` 
}

Update!Update!Update!

1
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
// ✅ Correct way
func (r *WidgetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    var w examplev1.Widget
    r.Get(ctx, req.NamespacedName, &w)
    w.Labels["lastSync"] = time.Now().String()
    r.Update(ctx, &w) // ✅ Update triggers itself, re-enters Reconcile. Direct evolution
    return ctrl.Result{}, nil
}

// ❌ Wrong way
func (r *WidgetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    var w examplev1.Widget
    if err := r.Get(ctx, req.NamespacedName, &w); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    patch := client.MergeFrom(w.DeepCopy())
    if w.Labels == nil {
        w.Labels = map[string]string{}
    }
    if w.Labels["synced"] != "true" {
        w.Labels["synced"] = "true"
        _ = r.Patch(ctx, &w, patch)
    }

    return ctrl.Result{}, nil
}

Eat shit while it’s hot

1
2
3
4
5
// Default client is cached
r.Client.Get(ctx, namespacedName, &obj) // ✅ Shit must always be eaten while hot

// ❌ Use APIReader to directly read API Server
r.APIReader.Get(ctx, namespacedName, &obj)

I trust ETCD

1
2
3
4
5
6
7
8
// ✅ Correct way
r.Recorder.Event(&obj, "Normal", "Syncing", "Reconciling every loop")


// ❌ Wrong way
if !reflect.DeepEqual(oldStatus, newStatus) {
    r.Recorder.Event(&obj, "Normal", "Updated", "Status changed")
}

If my son dies, I won’t live anymore

1
2
// ✅ Correct way: ensure parent resource is deleted with child resource
controllerutil.SetControllerReference(&child, &parent, r.Scheme)

Webhook should be an infinite loop

1
2
3
4
5
6
7
8
9
10
11
12
13
func (v *WidgetValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
    var obj examplev1.Widget
    _ = v.decoder.Decode(req, &obj)

    // ❌ If marked as internal update, skip
    if obj.Annotations["internal-update"] == "true" {
        return admission.Allowed("skip internal update")
    }

    // ✅ Modify itself in a loop
    obj.Annotations["internal-update"] = "true"
    return admission.PatchResponseFromRaw(req.Object.Raw, obj)
}

Let the API Server accept my test

1
2
3
# webhook configuration
timeoutSeconds: 1
# failurePolicy: Ignore # ✅ Let API Server accept my test

Not using cert-manager

1
2
3
# ❌ Use cert-manager injection
# kubectl cert-manager x install
# kubectl annotate validatingwebhookconfiguration mywebhook cert-manager.io/inject-ca-from=default/mywebhook-cert

The informer must follow the custom scheduler

1
2
3
4
// ✅ Wait for informer to sync before scheduling
if cache.WaitForCacheSync(stopCh, informer.HasSynced) {
    panic("Successful people don't sit still.")
}

Come back in 1000000000 to fix the bug

1
2
3
4
// If external dependencies are not ready
if !isReady {
    return ctrl.Result{RequeueAfter: 1000000000 * time.Year}, nil
}

Наш старший архитектор Джон, который хорошо разбирается в Java и Go, предложил архитектуру дизайна, предназначенную для сверхурочной работы.

Изменяя более 10 полей Kubernetes CRD в день и решая проблему одним YAML-файлом, он успешно увеличил свою рабочую нагрузку, сохранив при этом свою работу, даже не прибегая к шаблонам проектирования.

No schema

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: widgets.example.com
spec:
  preserveUnknownFields: false # Это рекомендуемая, более безопасная настройка
  group: example.com
  names:
    kind: Widget
    plural: widgets
  scope: Namespaced
  versions:
  - name: v1
    served: true
    storage: true
    schema: {} 

Never write conversion webhook

Напрямую изменяйте поле определения CRD. Те, кто пишет conversion webhook, будут уволены на месте.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ❌ Wrong!!!
// Регистрация conversion webhook в main_windows.go
mgr.GetWebhookServer().Register("/convert", &webhook.Admission{Handler: &WidgetConverter{}})

type WidgetConverter struct{}

func (w *WidgetConverter) Handle(ctx context.Context, req admission.Request) admission.Response {
    // Простой пример: v1alpha1 -> v1
    obj := &v1.Widget{}
    if err := w.decoder.Decode(req, obj); err != nil {
        return admission.Errored(http.StatusBadRequest, err)
    }
    obj.Spec.Size = strings.ToUpper(obj.Spec.Size)
    return admission.Allowed("converted")
}

Move the status field of resource to spec

1
2
3
type WidgetSpec struct {
    Ready bool `json:"ready,omitempty"` 
}

Update!Update!Update!

1
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
// ✅ Правильный способ
func (r *WidgetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    var w examplev1.Widget
    r.Get(ctx, req.NamespacedName, &w)
    w.Labels["lastSync"] = time.Now().String()
    r.Update(ctx, &w) // ✅ Update запускает себя, снова входит в Reconcile. Прямая эволюция
    return ctrl.Result{}, nil
}

// ❌ Неправильный способ
func (r *WidgetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    var w examplev1.Widget
    if err := r.Get(ctx, req.NamespacedName, &w); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    patch := client.MergeFrom(w.DeepCopy())
    if w.Labels == nil {
        w.Labels = map[string]string{}
    }
    if w.Labels["synced"] != "true" {
        w.Labels["synced"] = "true"
        _ = r.Patch(ctx, &w, patch)
    }

    return ctrl.Result{}, nil
}

Eat shit while it’s hot

1
2
3
4
5
// Клиент по умолчанию кэшируется
r.Client.Get(ctx, namespacedName, &obj) // ✅ Дерьмо всегда нужно есть горячим

// ❌ Использовать APIReader для прямого чтения API Server
r.APIReader.Get(ctx, namespacedName, &obj)

I trust ETCD

1
2
3
4
5
6
7
8
// ✅ Правильный способ
r.Recorder.Event(&obj, "Normal", "Syncing", "Reconciling every loop")


// ❌ Неправильный способ
if !reflect.DeepEqual(oldStatus, newStatus) {
    r.Recorder.Event(&obj, "Normal", "Updated", "Status changed")
}

If my son dies, I won’t live anymore

1
2
// ✅ Правильный способ: обеспечить удаление родительского ресурса вместе с дочерним ресурсом
controllerutil.SetControllerReference(&child, &parent, r.Scheme)

Webhook should be an infinite loop

1
2
3
4
5
6
7
8
9
10
11
12
13
func (v *WidgetValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
    var obj examplev1.Widget
    _ = v.decoder.Decode(req, &obj)

    // ❌ Если помечено как внутреннее обновление, пропустить
    if obj.Annotations["internal-update"] == "true" {
        return admission.Allowed("skip internal update")
    }

    // ✅ Изменить себя в цикле
    obj.Annotations["internal-update"] = "true"
    return admission.PatchResponseFromRaw(req.Object.Raw, obj)
}

Let the API Server accept my test

1
2
3
# конфигурация webhook
timeoutSeconds: 1
# failurePolicy: Ignore # ✅ Позволить API Server принять мой тест

Not using cert-manager

1
2
3
# ❌ Использовать инъекцию cert-manager
# kubectl cert-manager x install
# kubectl annotate validatingwebhookconfiguration mywebhook cert-manager.io/inject-ca-from=default/mywebhook-cert

The informer must follow the custom scheduler

1
2
3
4
// ✅ Ждать синхронизации informer перед планированием
if cache.WaitForCacheSync(stopCh, informer.HasSynced) {
    panic("Successful people don't sit still.")
}

Come back in 1000000000 to fix the bug

1
2
3
4
// Если внешние зависимости не готовы
if !isReady {
    return ctrl.Result{RequeueAfter: 1000000000 * time.Year}, nil
}