Использование client-go для `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/recommended.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/issues/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 и предположил, что я могу предоставить свои собственные genericclioptions.IOStreams, чтобы получить вывод?

Похоже, что RunConvert находится на пути устаревания

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

Изменить: поскольку мне нужно сделать это для более чем одного кластера и создавать кластеры программно (AWS EKS API + CloudFormation / eksctl ), я хотел бы минимизировать накладные расходы на создание объектов ServiceAccountво многих контекстах кластера, во многих учетных записях AWS. В идеале, единственным этапом аутентификации, связанным с созданием моего клиентского набора, является использование aws-iam-authenticator для получения токена с использованием данных кластера (имя, регион, сертификат CA и т. Д.). Некоторое время не было выпуска aws-iam-authenticator, но содержимое masterпозволяет передавать стороннюю роль роли кросс-аккаунта и внешний идентификатор, который необходимо передать. ИМО, это чище, чем с помощью ServiceAccountIRSA) потому что есть другие сервисы AWS, с которыми приложению (внутреннему API, который создает и применяет надстройки к этим кластерам) необходимо взаимодействовать.

Изменить: я недавно нашел https://github.com/ericchiang/k8s . Это определенно проще, чем client-go, на высоком уровне, но не поддерживает это поведение.

Саймон
источник
1
Вместо записи конфигурации kube на диск контейнера попробуйте использовать служебную учетную запись kubernetes.io/docs/tasks/configure-pod-container/…
KFC_
1
Почему бы вам просто не прочитать содержимое файла YAML и не разделить его ^---$в своем коде?
Shawyeok
@Shawyeok Потому что это все еще требует, чтобы я знал, какие типы в файле. Невозможно получить тип динамически, не проверяя несколько ожидаемых типов (объекты Kubernetes), и если ожидаемый тип отсутствует, объект не будет применен к кластеру (что приводит к еще большему количеству проблем). Это также привело бы к необходимости написания большого количества кода для одного компонента, который не масштабируется для нескольких компонентов. Помимо декодирования, вызывается правильный метод API для применения объекта к кластеру.
Саймон

Ответы:

3

Похоже, вы выяснили, как десериализовать файлы YAML в Kubernetes runtime.Object, но проблема заключается в динамическом развертывании 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
Надеюсь, это работает для вас!
Кевин Лин