Usando client-go su `kubectl apply` contro l'API Kubernetes direttamente con più tipi in un singolo file YAML


10

Sto usando https://github.com/kubernetes/client-go e tutto funziona bene.

Ho un manifest (YAML) per il dashboard ufficiale di Kubernetes: https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta4/aio/deploy/recommended.yaml

Voglio imitare kubectl applyquesto manifest in codice Go, usando client-go.

Comprendo che devo eseguire alcuni (un) marshalling dei byte YAML nei tipi di API corretti definiti nel pacchetto: https://github.com/kubernetes/api

Ho modificato correttamente Createsingoli tipi di API nel mio cluster, ma come posso fare questo per un manifest che contiene un elenco di tipi che non sono uguali ? Esiste una risorsa kind: List*che supporta questi diversi tipi?

La mia attuale soluzione è dividere il file YAML usando csplitcon --- come delimitatore

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

Successivamente, cerco le nuove (14) parti che sono state create, leggo i loro byte, accendo il tipo di oggetto restituito dal decodificatore di UniversalDeserializer e chiamo i metodi API corretti usando il mio clientet k8s.

Vorrei farlo a livello di codice per effettuare aggiornamenti a qualsiasi nuova versione della dashboard nel mio cluster. Dovrò anche fare questo per il Metrics Server e molte altre risorse. Il metodo alternativo (forse più semplice) è spedire il mio codice con kubectl installato nell'immagine contenitore e chiamare direttamente kubectl apply -f -; ma ciò significa che devo anche scrivere la configurazione di Kube su disco o magari passarla in linea in modo che Kubectl possa usarla.

Ho trovato utile questo problema: https://github.com/kubernetes/client-go/issues/193 Il decoder risiede qui: https://github.com/kubernetes/apimachinery/tree/master/pkg/runtime/ serializzatore

È esposto in client-vai qui: https://github.com/kubernetes/client-go/blob/master/kubernetes/scheme/register.go#L69

Ho anche dato un'occhiata al metodo RunConvert utilizzato da kubectl: https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/convert/convert.go#L139 e presumo che I posso fornire le mie genericclioptions.IOStreams per ottenere l'output?

Sembra che RunConvert si trovi su un percorso di deprecazione

Ho anche esaminato altre domande taggate [client-go] ma la maggior parte usa vecchi esempi o usa un file YAML con un singolo kinddefinito e l'API è cambiata da allora.

Modifica: poiché devo farlo per più di un cluster e sto creando cluster a livello di codice (API AWS EKS + CloudFormation / eksctl ), vorrei ridurre al minimo il sovraccarico della creazione di ServiceAccounts in molti contesti di cluster, in molti account AWS. Idealmente, l'unico passaggio di autenticazione coinvolto nella creazione del mio clientet è l'utilizzo di aws-iam-authenticator per ottenere un token utilizzando i dati del cluster (nome, regione, certificato CA, ecc.). Non c'è stato un rilascio di aws-iam-authenticator da un po ', ma i contenuti di masterconsentono l'uso di un ruolo di account incrociato di ruolo di terze parti e ID esterno da passare. IMO, questo è più pulito rispetto all'utilizzo di un ServiceAccount(e IRSA) perché esistono altri servizi AWS con cui l'applicazione (l'API back-end che crea e applica componenti aggiuntivi a questi cluster) deve interagire.

Modifica: di recente ho trovato https://github.com/ericchiang/k8s . È decisamente più semplice da utilizzare rispetto al client-go, ad alto livello, ma non supporta questo comportamento.


1
Invece di scrivere kube config sul disco del contenitore, prova a utilizzare l'account di servizio kubernetes.io/docs/tasks/configure-pod-container/…
KFC_

1
Perché non leggi semplicemente il contenuto del file YAML e lo dividi ^---$nel tuo codice?
Shawyeok,

@Shawyeok Perché questo mi richiede ancora di sapere quali tipi sono nel file. Non è possibile ottenere il tipo in modo dinamico senza eseguire test su diversi tipi previsti (oggetti Kubernetes) e se il tipo previsto non è presente, l'oggetto non verrà applicato al cluster (il che porta a ulteriori problemi). Ciò comporterebbe anche la necessità di scrivere molto codice per un singolo componente che non viene ridimensionato per diversi componenti. Oltre la decodifica si chiama il metodo API corretto per applicare l'oggetto a un cluster.
Simon, il

Risposte:


3

Sembra che tu abbia capito come deserializzare i file YAML in Kubernetes runtime.Object, ma il problema è distribuire dinamicamente un file runtime.Objectsenza scrivere codice speciale per ogni tipo.

kubectlraggiunge questo interagendo direttamente con l' API REST . In particolare, tramite resource.Helper .

Nel mio codice, ho qualcosa come:

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


Ciao Kevin, grazie per la tua risposta! Non ho avuto la possibilità di provare a farlo, ma non ne ero a conoscenza package restmappere questo sembra molto promettente. Accetto la risposta per ora, ma la rivedremo presto.
Simon,

1
Spero che funzioni per te!
Kevin Lin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.