Ошибка «Ресурс не найден» при создании SSA с использованием динамического клиента

#kubernetes #kubernetes-go-client

#kubernetes #kubernetes-go-client

Вопрос:

Я следил за @ymmt2005 отличным руководством по динамическому клиенту. Все хорошо до последнего шага, когда я выполняю фактический вызов ИСПРАВЛЕНИЯ, и я получаю the server could not find the requested resource сообщение об ошибке. Почти все кажется правильным, за исключением того, что я не уверен в поле ‘FieldManager’ в структуре PathOptions. Я не уверен, что означает «субъект или объект, который вносит эти изменения». Должно ли это соответствовать чему-то в моем коде или системе? Есть еще идеи?

 package main

import (
...
)

const resourceYAML = `
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mike-nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: 'nginx:latest'
          ports:
            - containerPort: 80
`

func main() {
    ctx := context.Background()

    // Create dynamic discovery client from local kubeconfig file
    kubePath := filepath.Join(homedir.HomeDir(), ".kube", "config")
    cfg, err := clientcmd.BuildConfigFromFlags("", kubePath)
    if err != nil {
        log.Fatalf("error building config, %vn", err)
    }
    dynClient, err := dynamic.NewForConfig(cfg)
    if err != nil {
        log.Fatalf("error creating client, %vn", err)
    }
    disClient, err := discovery.NewDiscoveryClientForConfig(cfg)
    if err != nil {
        log.Fatalf("error creating discovery client, %vn", err)
    }

    // Decode YAML manifest amp; get GVK
    decodeUnstr := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
    obj := amp;unstructured.Unstructured{}
    _, gvk, err := decodeUnstr.Decode([]byte(resourceYAML), nil, obj)
    if err != nil {
        log.Fatalf("error decoding manifest, %vn", err)
    }
    jsonObj, err := json.Marshal(obj)
    if err != nil {
        log.Fatalf("error marshaling object, %vn", err)
    }

    // Find GVR using GVK
    mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(disClient))
    mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
    if err != nil {
        log.Fatalf("error finding GVR, %vn", err)
    }

    // Get REST interface for the GVR, checking for namespace or cluster-wide
    var dr dynamic.ResourceInterface
    if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
        // Namespaced resource
        dr = dynClient.Resource(mapping.Resource).Namespace(obj.GetNamespace())
    } else {
        // Cluster-wide resource
        dr = dynClient.Resource(mapping.Resource)
    }

    // Create or Update the object with SSA
    options := metav1.PatchOptions{FieldManager: "sample-controller"}
    _, err = dr.Patch(ctx, obj.GetName(), types.ApplyPatchType, jsonObj, options)
    if err != nil {
        log.Fatalf("error patching, %vn", err)
    }
}
 

[править] Я подтвердил, что смог использовать «Исправление» только для уже существующего ресурса. Я изменил код, чтобы использовать «Создать» для создания ресурса, затем я смог успешно выполнить «исправление» для его изменения. Чтобы преодолеть несоответствия FieldManager, я добавил Force=true к PatchOptions, что в любом случае рекомендуется в документах. Я все же хотел бы знать, как я могу создать, если ресурс не существует, и обновить, если он существует — может быть, просто проверить наличие?

Комментарии:

1. При более внимательном рассмотрении API-интерфейсов k8s REST кажется, что PATCH не создает ресурс, если он не существует, что согласуется с HTTP verb PATCH . Как и ‘kubectl apply -f’, мне нужна одна команда, которая создает ресурс, если он еще не существует. Возможно ли это?

2. … или мне нужно проверить, существует ли, а затем решить POST или PATCH? Я хотел бы сохранить желаемое состояние (иначе манифесты ресурсов) для кластера в Git, а затем попросить агента обновить ресурсы для любых новых коммитов (иначе изменений в манифестах ресурсов).).

Ответ №1:

Ответ действительно тривиален. Исходный код предполагает, что namespace это указано в манифесте. Конечная точка развертывания не устанавливает автоматически для пространства default имен значение, если предоставленное пространство имен равно «», и выдает ошибку, поскольку «» не является допустимым пространством имен. Поэтому я добавил логику для установки пространства default имен, если оно не указано, и вуаля, приложение на стороне сервера создаст ресурс, если он не существует, и обновит, если он существует. Еще раз спасибо @ymmt2005 .

 package main

import (
...
)

const resourceYAML = `
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mike-nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: 'nginx:latest'
          ports:
            - containerPort: 80
`

func main() {
    ctx := context.Background()

    // Create dynamic discovery client from local kubeconfig file
    kubePath := filepath.Join(homedir.HomeDir(), ".kube", "config")
    cfg, err := clientcmd.BuildConfigFromFlags("", kubePath)
    if err != nil {
        log.Fatalf("error building config, %vn", err)
    }
    dynClient, err := dynamic.NewForConfig(cfg)
    if err != nil {
        log.Fatalf("error creating client, %vn", err)
    }
    disClient, err := discovery.NewDiscoveryClientForConfig(cfg)
    if err != nil {
        log.Fatalf("error creating discovery client, %vn", err)
    }

    // Decode YAML manifest amp; get GVK
    decodeUnstr := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
    obj := amp;unstructured.Unstructured{}
    _, gvk, err := decodeUnstr.Decode([]byte(resourceYAML), nil, obj)
    if err != nil {
        log.Fatalf("error decoding manifest, %vn", err)
    }
    jsonObj, err := json.Marshal(obj)
    if err != nil {
        log.Fatalf("error marshaling object, %vn", err)
    }

    // Find GVR using GVK
    mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(disClient))
    mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
    if err != nil {
        log.Fatalf("error finding GVR, %vn", err)
    }

    // Set Namespace to default if not provided in manifest
    var ns string
    if ns = obj.GetNamespace(); ns == "" {
        ns = "default"
    }

    // Get REST interface for the GVR, checking for namespace or cluster-wide
    var dr dynamic.ResourceInterface
    if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
        // Namespaced resource
        dr = dynClient.Resource(mapping.Resource).Namespace(ns)
    } else {
        // Cluster-wide resource
        dr = dynClient.Resource(mapping.Resource)
    }

    // Create or Update the object with SSA
    options := metav1.PatchOptions{FieldManager: "sample-controller"}
    _, err = dr.Patch(ctx, obj.GetName(), types.ApplyPatchType, jsonObj, options)
    if err != nil {
        log.Fatalf("error patching, %vn", err)
    }
}