Конфликт полей с динамическим клиентом Kubernetes и применением на стороне сервера

Как правильно использовать библиотеку 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
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
4
0
92
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

На данный момент лучшее решение, которое я нашел, — использовать 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() для создания и обновлений является предполагаемым способом использования.

Другие вопросы по теме