Usando client-go no `kubectl apply` na API Kubernetes diretamente com vários tipos em um único arquivo YAML

10

Estou usando https://github.com/kubernetes/client-go e tudo funciona bem.

Eu tenho um manifesto (YAML) para o painel oficial do Kubernetes: https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta4/aio/deploy/recommended.yaml

Quero imitar kubectl applyesse manifesto no código Go, usando client-go.

Entendo que preciso realizar uma (des) organização dos bytes YAML nos tipos de API corretos definidos no pacote: https://github.com/kubernetes/api

Editei com êxito Createtipos únicos de API no meu cluster, mas como faço para um manifesto que contém uma lista de tipos que não são iguais ? Existe um recurso kind: List*que suporte esses tipos diferentes?

Minha solução atual é dividir o arquivo YAML usando csplit--- como o delimitador

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

Em seguida, faço um loop pelas novas (14) partes criadas, leio seus bytes, ligo o tipo de objeto retornado pelo decodificador UniversalDeserializer e chamo os métodos de API corretos usando meu conjunto de clientes k8s.

Eu gostaria de fazer isso programaticamente para fazer atualizações em qualquer nova versão do painel no meu cluster. Também precisarei fazer isso no Metrics Server e em muitos outros recursos. O método alternativo (talvez mais simples) é enviar meu código com o kubectl instalado na imagem do contêiner e chamar diretamente kubectl apply -f -; mas isso significa que eu também preciso gravar a configuração do kube no disco ou talvez passá-la em linha para que o kubectl possa usá-la.

Eu achei esse problema útil: https://github.com/kubernetes/client-go/issues/193 O decodificador mora aqui: https://github.com/kubernetes/apimachinery/tree/master/pkg/runtime/ serializador

É exposto no client-go here: https://github.com/kubernetes/client-go/blob/master/kubernetes/scheme/register.go#L69

Também observei o método RunConvert usado pelo kubectl: https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/convert/convert.go#L139 e assumo que pode fornecer meus próprios clioptions.IOStreams genéricos para obter a saída?

Parece que o RunConvert está em um caminho de descontinuação

Também observei outras perguntas marcadas com [client-go], mas a maioria usa exemplos antigos ou usa um arquivo YAML com um único kinddefinido, e a API mudou desde então.

Editar: como preciso fazer isso em mais de um cluster e criar clusters programaticamente (API do AWS EKS + CloudFormation / eksctl ), gostaria de minimizar a sobrecarga da criação de ServiceAccounts em muitos contextos de cluster e em várias contas da AWS. Idealmente, a única etapa de autenticação envolvida na criação do meu conjunto de clientes é usar o aws-iam-authenticator para obter um token usando dados de cluster (nome, região, certificado CA, etc.). Não há uma liberação do aws-iam-authenticator por um tempo, mas o conteúdo de masterpermite o uso de uma função de conta cruzada de função de terceiros e ID externo. IMO, isso é mais limpo do que usar um ServiceAccount(e IRSA), porque existem outros serviços da AWS com os quais o aplicativo (a API de back-end que cria e aplica complementos a esses clusters) precisa interagir.

Editar: Encontrei recentemente https://github.com/ericchiang/k8s . É definitivamente mais simples de usar do que o client-go, em um nível alto, mas não suporta esse comportamento.

Simon
fonte
11
Em vez de gravar a configuração do kube no disco do contêiner, tente usar a conta de serviço kubernetes.io/docs/tasks/configure-pod-container/…
KFC_
11
Por que você simplesmente não lê o conteúdo do arquivo YAML e o divide ^---$em seu código?
Shawyeok
@Shawyeok Porque isso ainda exige que eu saiba quais tipos estão no arquivo. Não há como obter o tipo dinamicamente sem testar contra vários tipos esperados (objetos Kubernetes) e, se o tipo esperado não estiver presente, o objeto não será aplicado ao cluster (o que leva a ainda mais problemas). Isso também resultaria na necessidade de escrever muito código para um único componente que não é escalável para vários componentes. Além da decodificação, está chamando o método API correto para aplicar o objeto a um cluster.
26619 Simon

Respostas:

3

Parece que você descobriu como desserializar arquivos YAML nos Kubernetes runtime.Object, mas o problema é implantar dinamicamente um arquivo runtime.Objectsem escrever um código especial para cada tipo.

kubectlconsegue isso interagindo diretamente com a API REST . Especificamente, via resource.Helper .

No meu código, eu tenho algo como:

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
fonte
Olá Kevin, obrigado pela sua resposta! Não tive a chance de tentar fazer isso, mas não estava ciente package restmappere isso parece muito promissor. Aceitando a resposta por enquanto, mas a revisitará em breve.
Simon
11
Espero que funcione para você!
Kevin Lin