Використання клієнтського переходу до `kubectl apply` проти API Kubernetes безпосередньо з декількома типами в одному файлі YAML


10

Я використовую https://github.com/kubernetes/client-go, і все працює добре.

У мене є маніфест (YAML) для офіційної інформаційної панелі Kubernetes: https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta4/aio/deploy/recomemed.yaml

Я хочу імітувати kubectl applyцей маніфест у коді Go, використовуючи client-go.

Я розумію, що мені потрібно виконати кілька (не) маршалінг байтів YAML на правильні типи API, визначені в пакеті: https://github.com/kubernetes/api

Я успішно Createвідредагував в кластері окремі типи API, але як це зробити для маніфесту, який містить список типів, які не є однаковими ? Чи є ресурс, kind: List*який підтримує ці різні типи?

Моє поточне вирішення проблеми - розділити файл YAML, використовуючи csplit--- як роздільник

csplit /path/to/recommended.yaml /---/ '{*}' --prefix='dashboard.' --suffix-format='%03d.yaml'

Далі я перебираю нові (14) частини, які були створені, читаю їхні байти, вмикаю тип об'єкта, який повертається декодером UniversalDeserializer, і називаю правильні методи API, використовуючи мій набір клієнтів k8s.

Я хотів би зробити це для того, щоб програмно вносити оновлення будь-яких нових версій інформаційної панелі в мій кластер. Мені також знадобиться це зробити для Metrics Server та багатьох інших ресурсів. Альтернативний (можливо, простіший) метод - відправити мій код із встановленим kubectl на зображення контейнера та безпосередньо зателефонувати kubectl apply -f -; але це означає, що мені також потрібно написати конфігурацію kube на диск або, можливо, передати його в рядку, щоб kubectl міг ним користуватися.

Я вважаю це питання корисним: https://github.com/kubernetes/client-go/isissue/193 Декодер живе тут: https://github.com/kubernetes/apimachinery/tree/master/pkg/runtime/ серіалізатор

Це відкрито у клієнті, перейдіть сюди: https://github.com/kubernetes/client-go/blob/master/kubernetes/scheme/register.go#L69

Я також переглянув метод RunConvert, який використовується kubectl: https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/convert/convert.go#L139 і припускаю, що я можна надати власні загальні рішення.IOStreams, щоб отримати вихід?

Схоже, RunConvert перебуває на шляху депресії

Я також розглядав інші питання з тегом [client-go], але більшість використовують старі приклади або використовують файл YAML з окремим kindвизначеним, і API змінився з тих пір.

Редагувати: Оскільки мені потрібно це зробити для більш ніж одного кластеру і я створюю кластери програмно (AWS EKS API + CloudFormation / eksctl ), я хотів би мінімізувати накладні витрати на створення ServiceAccounts у багатьох контекстах кластерів для багатьох облікових записів AWS. В ідеалі єдиним етапом аутентифікації, що бере участь у створенні мого набору клієнтів, є використання aws-iam-автентифікатора для отримання маркера за допомогою даних кластера (ім'я, регіон, сертифікат CA тощо). Протягом деякого часу не було випущено автентифікатор aws-iam, але вміст masterдозволяє використовувати роль сторонньої ролі між-облікових записів та зовнішній ідентифікатор. IMO, це чистіше, ніж використання ServiceAccount(та IRSA), оскільки є інші сервіси AWS, з якими потрібно взаємодіяти з додатком (API, що створює та застосовує додатки до цих кластерів).

Редагувати: нещодавно я знайшов https://github.com/ericchiang/k8s . Це, безумовно, простіше у використанні, ніж клієнт, на високому рівні, але не підтримує таку поведінку.


1
Замість того, щоб записати kube config на диск контейнера, спробуйте скористатися службовим обліковим записом kubernetes.io/docs/tasks/configure-pod-container/…
KFC_

1
Чому ви просто не прочитаєте вміст файлу YAML і розділите їх ^---$у своєму коді?
Shawyeok

@Shawyeok Оскільки це все ще вимагає від мене знати, які типи є у файлі. Немає можливості динамічно отримати тип без тестування на кілька очікуваних типів (об'єкти Kubernetes), а якщо очікуваного типу немає, то об’єкт не буде застосований до кластеру (що призводить до ще більших проблем). Це також призведе до необхідності написати багато коду для одного компонента, який не масштабує для кількох компонентів. Поза межами декодування - це виклик правильного методу API для застосування об'єкта до кластеру.
Саймон

Відповіді:


3

Здається, що ви зрозуміли, як дезаріалізувати файли YAML в Kubernetes runtime.Objects, але проблема полягає в динамічному розгортанні runtime.Objectбез написання спеціального коду для кожного виду.

kubectlдосягає цього, взаємодіючи безпосередньо з REST API . Зокрема, через resource.Helper .

У своєму коді я маю щось подібне:

import (
    meta "k8s.io/apimachinery/pkg/api/meta"
    "k8s.io/cli-runtime/pkg/resource"
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/rest"
    "k8s.io/client-go/restmapper"
    "k8s.io/apimachinery/pkg/runtime"
)

func createObject(kubeClientset kubernetes.Interface, restConfig rest.Config, obj runtime.Object) error {
    // Create a REST mapper that tracks information about the available resources in the cluster.
    groupResources, err := restmapper.GetAPIGroupResources(kubeClientset.Discovery())
    if err != nil {
        return err
    }
    rm := restmapper.NewDiscoveryRESTMapper(groupResources)

    // Get some metadata needed to make the REST request.
    gvk := obj.GetObjectKind().GroupVersionKind()
    gk := schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}
    mapping, err := rm.RESTMapping(gk, gvk.Version)
    if err != nil {
        return err
    }

    name, err := meta.NewAccessor().Name(obj)
    if err != nil {
        return err
    }

    // Create a client specifically for creating the object.
    restClient, err := newRestClient(restConfig, mapping.GroupVersionKind.GroupVersion())
    if err != nil {
        return err
    }

    // Use the REST helper to create the object in the "default" namespace.
    restHelper := resource.NewHelper(restClient, mapping)
    return restHelper.Create("default", false, obj, &metav1.CreateOptions{})
}

func newRestClient(restConfig rest.Config, gv schema.GroupVersion) (rest.Interface, error) {
    restConfig.ContentConfig = resource.UnstructuredPlusDefaultContentConfig()
    restConfig.GroupVersion = &gv
    if len(gv.Group) == 0 {
        restConfig.APIPath = "/api"
    } else {
        restConfig.APIPath = "/apis"
    }

    return rest.RESTClientFor(&restConfig)
}

Привіт Кевін, дякую за вашу відповідь! У мене не було можливості обійтись, щоб спробувати це, але я не знав цього, package restmapperі це виглядає дуже перспективно. Приймаю відповідь наразі, але незабаром її переглянемо.
Симон

1
Сподіваюся, це працює для вас!
Кевін Лін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.