web-dev-qa-db-fra.com

Utiliser client-go pour `kubectl apply` contre l'API Kubernetes directement avec plusieurs types dans un seul fichier YAML

J'utilise https://github.com/kubernetes/client-go et tout fonctionne bien.

J'ai un manifeste (YAML) pour le tableau de bord officiel de Kubernetes: https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta4/aio/deploy/recommended.yaml

Je veux imiter kubectl apply de ce manifeste dans le code Go, en utilisant client-go.

Je comprends que je dois faire un (dé) regroupement des octets YAML dans les types d'API corrects définis dans le package: https://github.com/kubernetes/api

J'ai avec succès Createed des types d'API uniques dans mon cluster, mais comment faire pour un manifeste qui contient une liste de types qui ne sont pas les mêmes ? Existe-t-il une ressource kind: List* qui prend en charge ces différents types?

Ma solution de contournement actuelle consiste à diviser le fichier YAML en utilisant csplit avec --- comme délimiteur

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

Ensuite, je fais une boucle sur les nouvelles (14) parties qui ont été créées, lis leurs octets, active le type de l'objet renvoyé par le décodeur de UniversalDeserializer et appelle les bonnes méthodes API à l'aide de mon ensemble de clients k8s.

Je voudrais le faire par programmation pour mettre à jour toutes les nouvelles versions du tableau de bord dans mon cluster. Je devrai également le faire pour le serveur de métriques et de nombreuses autres ressources. La méthode alternative (peut-être plus simple) consiste à expédier mon code avec kubectl installé sur l'image du conteneur et à appeler directement kubectl apply -f -; mais cela signifie que je dois également écrire la configuration de kube sur le disque ou peut-être la passer en ligne pour que kubectl puisse l'utiliser.

J'ai trouvé ce problème utile: https://github.com/kubernetes/client-go/issues/19 Le décodeur vit ici: https://github.com/kubernetes/apimachinery/tree/master/pkg/runtime/serializer

Il est exposé dans client-go ici: https://github.com/kubernetes/client-go/blob/master/kubernetes/scheme/register.go#L69

J'ai également jeté un œil à la méthode RunConvert utilisée par kubectl: https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/convert/convert.go#L139 et supposer que je peux fournir mes propres genericclioptions.IOStreams pour obtenir la sortie?

Il semble que RunConvert soit sur un chemin de dépréciation

J'ai également examiné d'autres questions étiquetées [client-go] mais la plupart utilisent d'anciens exemples ou utilisent un fichier YAML avec un seul kind défini, et l'API a changé depuis.

Edit: Étant donné que je dois le faire pour plusieurs clusters et que je crée des clusters par programmation (AWS EKS API + CloudFormation/ eksctl ), je voudrais minimiser la surcharge de création de ServiceAccounts dans de nombreux contextes de cluster, sur de nombreux comptes AWS. Idéalement, la seule étape d'authentification impliquée dans la création de mon ensemble de clients consiste à utiliser aws-iam -hentator pour obtenir un jeton à l'aide des données du cluster (nom, région, certificat CA, etc.). Il n'y a pas eu de sortie de aws-iam-authentifier depuis un certain temps, mais le contenu de master permet d'utiliser un rôle de compte croisé de rôle tiers et un ID externe. OMI, c'est plus propre que d'utiliser un ServiceAccount (et IRSA ) car il existe d'autres services AWS l'application (l'API backend qui crée et applique des modules complémentaires à ces clusters) doit interagir avec.

Edit: J'ai récemment trouvé https://github.com/ericchiang/k8s . Il est certainement plus simple à utiliser que client-go, à un niveau élevé, mais ne prend pas en charge ce comportement.

10
Simon

Il semble que vous ayez compris comment désérialiser des fichiers YAML dans Kubernetes runtime.Objects, mais le problème est le déploiement dynamique d'un runtime.Object sans écrire de code spécial pour chaque type.

kubectl y parvient en interagissant directement avec REST API . Plus précisément, via resource.Helper .

Dans mon code, j'ai quelque chose comme:

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