Używając client-go do `kubectl Apply` przeciwko API Kubernetes bezpośrednio z wieloma typami w jednym pliku YAML

10

Używam https://github.com/kubernetes/client-go i wszystko działa dobrze.

Mam manifest (YAML) oficjalnego pulpitu nawigacyjnego Kubernetes: https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta4/aio/deploy/recommended.yaml

Chcę naśladować kubectl applyten manifest w kodzie Go, używając client-go.

Rozumiem, że muszę wykonać (nie) zestawienie bajtów YAML z poprawnymi typami API zdefiniowanymi w pakiecie: https://github.com/kubernetes/api

Udało mi się Createedytować pojedyncze typy API w moim klastrze, ale jak to zrobić dla manifestu zawierającego listę typów, które nie są takie same ? Czy istnieje zasób kind: List*obsługujący te różne typy?

Moje obecne obejście polega na podzieleniu pliku YAML za csplitpomocą --- jako separatora

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

Następnie przeglądam nowe (14) części, które zostały utworzone, czytam ich bajty, włączam typ obiektu zwracany przez dekoder UniversalDeserializer i wywołuję prawidłowe metody API przy użyciu mojego zestawu klientów k8s.

Chciałbym to zrobić programowo, aby zaktualizować dowolne nowe wersje pulpitu nawigacyjnego do mojego klastra. Będę też musiał to zrobić dla serwera Metrics i wielu innych zasobów. Alternatywną (być może prostszą) metodą jest wysłanie mojego kodu z zainstalowanym kubectl do obrazu kontenera i bezpośrednie wywołanie kubectl apply -f -; ale to oznacza, że ​​muszę także zapisać konfigurację kube na dysk lub przekazać ją bezpośrednio, aby kubectl mógł z niej korzystać.

Uważam, że ten problem jest pomocny: https://github.com/kubernetes/client-go/issues/193 Dekoder mieszka tutaj: https://github.com/kubernetes/apimachinery/tree/master/pkg/runtime/ serializator

Jest ujawniony w kliencie-go tutaj: https://github.com/kubernetes/client-go/blob/master/kubernetes/scheme/register.go#L69

Przyjrzałem się również metodzie RunConvert używanej przez kubectl: https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/convert/convert.go#L139 i zakładam, że ja mogę podać własne ogólne opcje. IOStreams, aby uzyskać wynik?

Wygląda na to, że RunConvert znajduje się na ścieżce wycofania

Patrzyłem również na inne pytania oznaczone [klient-go], ale większość używa starych przykładów lub używa pliku YAML z jednym kindzdefiniowanym, a interfejs API zmienił się od tego czasu.

Edycja: Ponieważ muszę to zrobić dla więcej niż jednego klastra i programowo tworzę klastry (AWS EKS API + CloudFormation / eksctl ), chciałbym zminimalizować narzut związany z tworzeniem ServiceAccounts w wielu kontekstach klastrowych, na wielu kontach AWS. Idealnie, jedynym krokiem uwierzytelniającym związanym z tworzeniem mojego zestawu klientów jest użycie aws-iam -henter do uzyskania tokena przy użyciu danych klastra (nazwa, region, certyfikat CA itp.). Przez pewien czas nie było wydania aws-iam -hentent, ale zawartość masterpozwala na użycie roli konta zewnętrznego i identyfikatora zewnętrznego innej firmy. IMO, to jest czystsze niż przy użyciu ServiceAccount(i IRSA), ponieważ istnieją inne usługi AWS, z którymi aplikacja (interfejs API zaplecza, który tworzy i stosuje dodatki do tych klastrów) musi wchodzić w interakcje.

Edycja: Niedawno znalazłem https://github.com/ericchiang/k8s . Jest zdecydowanie prostszy w użyciu niż klient-go, na wysokim poziomie, ale nie obsługuje tego zachowania.

Szymon
źródło
1
Zamiast pisać konfigurację kube na dysku kontenera, spróbuj użyć konta usługi kubernetes.io/docs/tasks/configure-pod-container/…
KFC_
1
Dlaczego po prostu nie przeczytasz zawartości pliku YAML i nie podzielisz ^---$kodu?
Shawyeok,
@Shawyeok Ponieważ to wciąż wymaga ode mnie wiedzy, jakie typy są w pliku. Nie ma sposobu, aby uzyskać typ dynamicznie bez testowania kilku oczekiwanych typów (obiekty Kubernetes), a jeśli oczekiwany typ nie jest obecny, obiekt nie zostanie zastosowany do klastra (co prowadzi do jeszcze większej liczby problemów). Spowodowałoby to również konieczność napisania dużej ilości kodu dla jednego komponentu, który nie jest skalowany dla kilku komponentów. Poza dekodowaniem jest wywoływanie poprawnej metody API w celu zastosowania obiektu do klastra.
Simon

Odpowiedzi:

3

Wygląda na to, że nauczyłeś się deserializować pliki YAML do Kubernetes runtime.Object, ale problemem jest dynamiczne wdrażanie runtime.Objectbez pisania specjalnego kodu dla każdego rodzaju.

kubectlosiąga to poprzez bezpośrednią interakcję z interfejsem API REST . W szczególności poprzez resource.Helper .

W moim kodzie mam coś takiego:

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 Lin
źródło
Cześć Kevin, dziękuję za odpowiedź! Nie miałem okazji tego spróbować, ale nie byłem tego świadomy package restmapperi wygląda to bardzo obiecująco. Na razie akceptuję odpowiedź, ale wkrótce ją odwiedzę.
Simon
1
Mam nadzieję, że to działa dla Ciebie!
Kevin Lin