如何正确使用 kubernetes
client-go
库与动态客户端和服务器端应用?
我正在创建并尝试使用动态客户端更新 Kubernetes 资源,并设置
FieldManager
(以启用服务器端应用)。尽管为两个操作设置了相同的 FieldManager
值,但更新失败并出现以下错误:
panic: Apply failed with 1 conflict: conflict with "test" using cert-manager.io/v1: .spec.secretName
我的代码如下,您可以看到它对这两个操作使用相同的
FieldManager
,所以我不明白为什么会发生冲突:
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"}
_, err = client.Resource(certificateGvr).
Namespace(namespace).
Create(context.TODO(), certificate, metav1.CreateOptions{FieldManager: fieldManager})
if err != nil {
panic(err)
}
unstructured.SetNestedField(certificate.UnstructuredContent(), "bar", "spec", "secretName")
_, err = client.Resource(certificateGvr).
Namespace(namespace).
Apply(context.TODO(), name, certificate, metav1.ApplyOptions{FieldManager: fieldManager, Force: true})
if err != nil {
panic(err)
}
}
我注意到,如果我强制更新并查看托管字段,则会出现两组托管字段(每个操作一组)。但我更愿意避免强制,因为它可能会导致意外覆盖字段。
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"
- apiVersion: cert-manager.io/v1
fieldsType: FieldsV1
fieldsV1:
f:spec:
.: {}
f:dnsNames: {}
f:issuerRef:
.: {}
f:group: {}
f:kind: {}
f:name: {}
manager: test
operation: Update
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
到目前为止,我发现的最佳解决方案是使用
Apply
来创建和更新对象,如下所示:
// Create the object
_, err = client.Resource(certificateGvr).
Namespace(namespace).
Apply(context.TODO(), name, certificate, metav1.ApplyOptions{FieldManager: fieldManager})
if err != nil {
panic(err)
}
// Modify and update it
unstructured.SetNestedField(certificate.UnstructuredContent(), "bar", "spec", "secretName")
_, err = client.Resource(certificateGvr).
Namespace(namespace).
Apply(context.TODO(), name, certificate, metav1.ApplyOptions{FieldManager: fieldManager})
if err != nil {
panic(err)
}
缺点是,与
Create
不同,如果对象已经存在(只要它由同一个fieldManager
管理),则第一次操作不会出错。因此,如果您想确保使用此方法创建一个对象,您需要为该对象生成并跟踪唯一的fieldManager
。
可能有更好的方法,所以我暂时保留这个问题。