You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Kubernetes动态客户端与Server-Side Apply时的字段冲突问题

解决client-go动态客户端结合Server-Side Apply的冲突问题

问题原因

你遇到的冲突本质是Create操作和Apply操作的字段管理逻辑不兼容:

  • 用Create API创建资源时,即使设置了FieldManager,K8s会将其标记为Update类型的操作,并且把整个spec字段(父字段)划归该manager管理(对应managed fields里的f:spec: .: {})。
  • 后续调用Apply API时,Server-Side Apply会尝试将你提交的子字段(比如.spec.secretName)划归同一个manager的Apply类型操作管理。
  • K8s不允许同一个manager下,Update操作管理的父字段与Apply操作管理的子字段共存,因此触发冲突报错。

强制更新后出现两条managed fields记录,就是因为Create生成了Update类型的记录,Apply生成了Apply类型的记录,两者同属一个manager但操作类型不同,字段所有权重叠。

解决方案

直接用Server-Side Apply的Apply操作替代Create操作,Apply接口支持幂等的创建/更新:资源不存在时自动创建,存在时按声明式更新,且只会生成Apply类型的managed fields记录,后续同manager的Apply操作不会触发冲突。

修改后的代码

将原来的Create调用替换为Apply:

package main

import (
    "context"
    "flag"

    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/client-go/dynamic"
    "k8s.io/client-go/tools/clientcmd"
)

func main() {
    kubeconfig := flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
    flag.Parse()

    config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
    if err != nil {
        panic(err)
    }
    client, err := dynamic.NewForConfig(config)
    if err != nil {
        panic(err)
    }

    fieldManager := "test"
    name := "foo"
    namespace := "default"
    certificate := &unstructured.Unstructured{
        Object: map[string]interface{}{
            "apiVersion": "cert-manager.io/v1",
            "kind":       "Certificate",
            "metadata":   map[string]interface{}{"name": name, "namespace": namespace},
            "spec": map[string]interface{}{
                "dnsNames": []string{"example.com"},
                "issuerRef": map[string]interface{}{
                    "group": "cert-manager.io",
                    "kind":  "ClusterIssuer",
                    "name":  "foo",
                },
                "secretName": "foo",
            },
        },
    }

    certificateGvr := schema.GroupVersionResource{Group: "cert-manager.io", Version: "v1", Resource: "certificates"}

    // 用Apply替代Create,实现幂等创建/更新
    _, err = client.Resource(certificateGvr).
        Namespace(namespace).
        Apply(context.TODO(), name, certificate, metav1.ApplyOptions{FieldManager: fieldManager, Force: false})
    if err != nil {
        panic(err)
    }

    // 更新secretName后再次调用Apply
    unstructured.SetNestedField(certificate.UnstructuredContent(), "bar", "spec", "secretName")
    _, err = client.Resource(certificateGvr).
        Namespace(namespace).
        Apply(context.TODO(), name, certificate, metav1.ApplyOptions{FieldManager: fieldManager, Force: false})
    if err != nil {
        panic(err)
    }
}

验证效果

执行代码后查看资源的managed fields,只会存在一条Apply类型的记录,所有子字段都由同一个manager的Apply操作管理,后续更新不会触发冲突:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  creationTimestamp: "2024-05-24T10:01:09Z"
  generation: 2
  managedFields:
  - apiVersion: cert-manager.io/v1
    fieldsType: FieldsV1
    fieldsV1:
      f:spec:
        f:dnsNames: {}
        f:issuerRef:
          f:group: {}
          f:kind: {}
          f:name: {}
        f:secretName: {}
    manager: test
    operation: Apply
    time: "2024-05-24T10:01:09Z"
  name: foo
  namespace: default
  resourceVersion: "56441"
  uid: d4b97ef5-3314-498b-bb13-786967f1fcf8
spec:
  dnsNames:
  - example.com
  issuerRef:
    group: cert-manager.io
    kind: ClusterIssuer
    name: foo
  secretName: bar

内容的提问来源于stack exchange,提问作者kai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 14:43:09