使用 client-go to `kubectl apply` 针对 Kubernetes API 直接在单个 YAML 文件中使用多种类型

Using client-go to `kubectl apply` against the Kubernetes API directly with multiple types in a single YAML file

我正在使用 https://github.com/kubernetes/client-go,一切正常。

我有官方 Kubernetes 仪表板的清单 (YAML):https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta4/aio/deploy/recommended.yaml

我想使用 client-go 在 Go 代码中模仿 kubectl apply 这个清单。

我知道我需要将 YAML 字节编组(取消)编组为包中定义的正确 API 类型:https://github.com/kubernetes/api

我已成功 Create 将单个 API 类型添加到我的集群,但是我如何为包含不同类型列表的清单执行此操作?是否有支持这些不同类型的资源kind: List*

我目前的解决方法是使用 csplit 和 --- 作为分隔符

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

接下来,我遍历创建的新 (14) 个部分,读取它们的字节,打开 UniversalDeserializer 解码器返回的对象类型,并使用我的 k8s 客户端集调用正确的 API 方法.

我想以编程方式执行此操作,以便将仪表板的任何新版本更新到我的集群中。我还需要为 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/serializer

它在 client-go 中暴露在这里:https://github.com/kubernetes/client-go/blob/master/kubernetes/scheme/register.go#L69

我还查看了 kubectl 使用的 RunConvert 方法:https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/convert/convert.go#L139 and assume that I can provide my own genericclioptions.IOStreams 获取输出?

It looks like RunConvert is on a deprecation path

我也看过其他标记为 [client-go] 的问题,但大多数问题都使用旧示例或使用定义了单个 kind 的 YAML 文件,并且 API 自此发生了变化。

编辑:因为我需要为多个集群执行此操作,并且我正在以编程方式创建集群(AWS EKS API + CloudFormation/eksctl), I would like to minimize the overhead of creating ServiceAccounts across many cluster contexts, across many AWS accounts. Ideally, the only authentication step involved in creating my clientset is using aws-iam-authenticator to get a token using cluster data (name, region, CA cert, etc). There hasn't been a release of aws-iam-authenticator for a while, but the contents of master allow for the use of a third-party role cross-account role and external ID to be passed. IMO, this is cleaner than using a ServiceAccount (and IRSA),因为应用程序还有其他 AWS 服务(后端 API 创建附加组件并将其应用于这些集群)需要与之交互。

编辑:我最近发现 https://github.com/ericchiang/k8s。它在高层次上肯定比 client-go 使用起来更简单,但不支持这种行为。

听起来您已经找到了将 YAML 文件反序列化到 Kubernetes runtime.Object 的方法,但问题是动态部署 runtime.Object 而无需为每个 Kind 编写特殊代码。

kubectl 通过与 REST API directly. Specifically, via 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)
}

我能够在我的一个项目中使用它。我必须使用 kubectl 的应用命令中的大部分源代码才能使其正常工作。

https://github.com/billiford/go-clouddriver/blob/master/pkg/kubernetes/client.go#L63