web-dev-qa-db-ja.com

client-goを使用して、単一のYAMLファイル内の複数のタイプでKubernetes APIに対して直接「kubectl apply」を実行する

私は 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

クラスターに単一のAPIタイプを正常にCreateedしましたが、同じではないタイプのリストを含むマニフェストに対してこれを行う方法?これらのさまざまなタイプをサポートするリソース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 -を直接呼び出すことです。しかし、それは、kubectlがそれを使用できるように、kube構成をディスクに書き込むか、インラインで渡す必要があることも意味します。

私はこの問題が役立つことがわかりました: https://github.com/kubernetes/client-go/issues/19 デコーダーはここにあります: 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は非推奨パスにあるようです

[client-go]とタグ付けされた他の質問も調べましたが、ほとんどは古い例を使用するか、単一のkindが定義されたYAMLファイルを使用し、APIはその後変更されました。

編集:複数のクラスターに対してこれを行う必要があり、プログラムでクラスターを作成しているため(AWS EKS API + CloudFormation/ eksctl )、ServiceAccountsを作成するオーバーヘッドを最小限にしたいと思います多くのクラスターコンテキスト間、多くのAWSアカウント間。理想的には、クライアントセットの作成に関連する唯一の認証手順は aws-iam-authenticator を使用して、クラスターデータ(名前、地域、CA証明書など)を使用してトークンを取得することです。 aws-iam-authenticatorはしばらくリリースされていませんが、masterの内容により、サードパーティの役割のクロスアカウントの役割と外部IDを渡すことができます。 IMO、これはServiceAccount(および [〜#〜] irsa [〜#〜] )を使用するよりもクリーンです。これは、他のAWSサービスアプリケーション(バックエンドAPIを作成し、対話する必要があるアドオンをこれらのクラスターに適用します。

編集:私は最近 https://github.com/ericchiang/k8s を見つけました。大まかに言うと、client-goよりも使い方が明らかに簡単ですが、この動作はサポートされていません。

10
Simon

YAMLファイルをKubernetesにデシリアライズする方法を理解したようですruntime.Objects、しかし問題は動的にruntime.Object種類ごとに特別なコードを記述する必要はありません。

kubectlREST 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)
}

1
Kevin Lin