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

18

我正在使用https://github.com/kubernetes/client-go,一切运行良好。

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

我想在Go代码中使用client-go模仿此清单的kubectl apply,我知道需要将YAML字节(un)marshalling为包https://github.com/kubernetes/api中定义的正确API类型。

我已经成功地将单个API类型Create到我的集群中,但是对于包含不同类型的列表的清单,我该如何操作呢?是否有支持这些不同类型的资源kind: List*

我的当前解决方法是使用csplit和---作为分隔符来拆分YAML文件。

csplit /path/to/recommended.yaml /---/ '{*}' --prefix='dashboard.' --suffix-format='%03d.yaml'
接下来,我循环遍历了新创建的14个部分,读取它们的字节,根据UniversalDeserializer的解码器返回的对象类型进行切换,并使用我的k8s clientset调用正确的API方法。
我希望以编程方式执行此操作,以便将任何新版本的仪表板更新到我的集群中。 我还需要对Metrics Server和许多其他资源执行此操作。 另一种(可能更简单)的方法是将我的代码与安装了kubectl的容器映像一起发送,并直接调用; 但这意味着我还需要将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并假设我可以提供自己的genericclioptions.IOStreams以获得输出?
看起来RunConvert正走向弃用之路:https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/convert/convert.go#L141-L145 我还查看了其他标记为[client-go]的问题,但大多数使用旧示例或使用仅定义了单个kind的YAML文件,而API已经发生了变化。
编辑:因为我需要对多个集群执行此操作,并且正在以编程方式创建集群(AWS EKS API + CloudFormation / eksctl),因此我希望将在许多群集上下文中创建ServiceAccount的开销最小化,跨多个AWS帐户。 理想情况下,创建我的clientset所涉及的唯一身份验证步骤是使用aws-iam-authenticator使用集群数据(名称、区域、CA证书等)获取令牌。 aws-iam-authenticator已经有一段时间没有发布了,但是master的内容允许传递第三方角色跨帐户角色和外部ID。 我个人认为,这比使用ServiceAccount(和IRSA)更加简洁,因为应用程序(创建并应用这些群集的后端API)需要与其他AWS服务进行交互。
编辑:我最近发现了https://github.com/ericchiang/k8s它在高层次上肯定比客户端-go更简单易用,但不支持此行为。

1
不要将 kube config 写入容器的磁盘中,尝试使用服务账户。https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ - kool
1
为什么不直接在代码中读取YAML文件的内容并按^---$进行分割呢? - Shawyeok
@Shawyeok 因为这仍然需要我知道文件中有哪些类型。没有办法在动态情况下获取类型,而不是针对几个预期的类型(Kubernetes对象)进行测试,如果预期的类型不存在,则该对象将不会应用于集群(这会导致更多问题)。这也会导致为单个组件编写大量代码,这对于多个组件来说无法扩展。除了解码之外,还需要调用正确的API方法将对象应用于集群。 - Simon
@Simon 你好,有没有解决方案?我想使用client-go部署ServiceMesh linkerd2,并像你说的那样模仿kubectl apply -f -的思路。我们应该使用https://github.com/kubernetes/kubectl/blob/596d7b0ffffd963af1b995fea599ad49d9b2a046/pkg/cmd/apply/apply.go#L161,而不需要手动按Kind拆分资源吗? - Aisuko
2个回答

7
听起来你已经知道如何将YAML文件反序列化为Kubernetes的runtime.Object,但问题是如何动态部署一个runtime.Object,而不需要为每个Kind编写特殊代码。 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)
}


你好,Kevin,感谢你的回答!我还没有试过这个,但我不知道package restmapper,看起来非常有前途。现在先接受这个答案,但很快会重新审视它。 - Simon
可以请您提供一个完整的示例吗?包括所有的导入等等。 - Astin Gengo
我已经达到了一个点,但是我遇到了这个问题:“在方案“pkg/runtime/scheme.go:100”中没有为版本“apiextensions.k8s.io/v1”注册任何类型的“CustomResourceDefinition”” --- 尝试应用crds文件。 - Astin Gengo

3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接