Как правильно использовать библиотеку 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 для этого объекта.
Возможно, есть лучший способ, поэтому я пока оставляю этот вопрос открытым.
Обновление: я подтвердил разработчикам client-go на канале Slack #sig-api-machinery, что использование Apply() для создания и обновлений является предполагаемым способом использования.