使用Kubernetes动态客户端与Server-Side Apply时的字段冲突问题
解决client-go动态客户端结合Server-Side Apply的冲突问题
问题原因
你遇到的冲突本质是Create操作和Apply操作的字段管理逻辑不兼容:
- 用
CreateAPI创建资源时,即使设置了FieldManager,K8s会将其标记为Update类型的操作,并且把整个spec字段(父字段)划归该manager管理(对应managed fields里的f:spec: .: {})。 - 后续调用
ApplyAPI时,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
相关产品推荐
相关产品推荐

