19 de janeiro de 20265 min de leitura

Acesso uniforme ao API Server: Padronizando ferramentas com clientcmd

Stephen Kitt

Kubernetes News

Se você já pensou em desenvolver um cliente de linha de comando para a API do Kubernetes — especialmente se considerou tornar seu cliente utilizável como um plugin do kubectl — talvez tenha se perguntado como fazer com que sua ferramenta pareça familiar para os usuários habituados ao ecossistema.

Ao observar a saída de kubectl options, a reação imediata costuma ser de hesitação: "Eu realmente preciso implementar todas essas opções?". A resposta curta é: não do zero. O projeto Kubernetes fornece duas bibliotecas principais para lidar com argumentos de linha de comando no estilo kubectl em programas Go: clientcmd e cli-runtime (que utiliza a primeira internamente). Este artigo explora como aplicar a clientcmd para garantir consistência operacional.

Filosofia Geral

Como parte da client-go, o objetivo final da clientcmd é fornecer uma instância de restclient.Config capaz de emitir requisições para um API server. Ela segue rigorosamente a semântica do kubectl:

  • Os valores padrão são extraídos de ~/.kube ou equivalente;
  • Arquivos podem ser especificados via variável de ambiente KUBECONFIG;
  • Todas as configurações acima podem ser sobrescritas por argumentos de linha de comando.

Vale notar que ela não configura automaticamente o argumento --kubeconfig. Veremos como fazer isso na seção "Bind das flags".

Recursos Disponíveis

A clientcmd permite que os programas gerenciem:

  • Seleção de kubeconfig (via KUBECONFIG);
  • Seleção de context;
  • Seleção de namespace;
  • Certificados de cliente e chaves privadas;
  • Impersonation de usuário;
  • Suporte a autenticação HTTP Basic (username/password).

Mesclagem de Configuração (Merging)

A biblioteca suporta a mesclagem de configurações em vários cenários. A variável KUBECONFIG pode especificar múltiplos arquivos, cujos conteúdos são combinados.

Um ponto de atenção: as configurações são mescladas de formas diferentes dependendo da implementação. Se uma configuração for definida em um map, a primeira definição prevalece; se não for um map, a última definição vence. Além disso, se o usuário não definir a variável, o arquivo padrão ~/.kube/config é utilizado.

O Processo Global

O padrão de uso geral é resumido em seis etapas principais, conforme a documentação do pacote:

loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
// Para alterar as regras de carregamento (arquivos e ordem)

configOverrides := &clientcmd.ConfigOverrides{}
// Para alterar valores de override ou vinculá-los a flags

kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
config, err := kubeConfig.ClientConfig()
if err != nil {
	// Tratamento de erro
}
client, err := metav1.New(config)

1. Configure as Loading Rules

clientcmd.NewDefaultClientConfigLoadingRules() constrói as regras que utilizam a variável KUBECONFIG ou o caminho padrão. Para a maioria dos casos de uso em empresas brasileiras que operam múltiplos clusters, os padrões são suficientes.

2. Configure os Overrides

A struct clientcmd.ConfigOverrides armazena os valores que substituirão as configurações carregadas. Aqui, utilizaremos a biblioteca pflag (substituto do pacote flag do Go) para suportar argumentos de traço duplo (--).

3. Construa o conjunto de Flags

As flags representam os argumentos de linha de comando (ex: --namespace ou -n). Três conjuntos estão disponíveis:

  • Autenticação: Certificados, tokens, impersonation.
  • Cluster: API server, CA, proxy, compressão.
  • Contexto: Nome do cluster, usuário, namespace.

A recomendação é incluir os três utilizando as funções Recommended...Flags. Ao passar um prefixo vazio (""), você obtém os argumentos padrão como --context e --namespace.

4. Bind das Flags

Após definir as flags, utilize clientcmd.BindOverrideFlags para vincular os argumentos aos overrides. Se desejar vincular o caminho explícito do --kubeconfig, faça-o agora:

flags.StringVarP(&loadingRules.ExplicitPath, "kubeconfig", "", "", "caminho absoluto para o arquivo kubeconfig")

5. Construa a Configuração Mesclada

Existem funções para os modos interativo e não-interativo. O termo "deferred" (adiado) indica que a configuração final será determinada o mais tarde possível, permitindo que a função seja chamada antes do parse final dos argumentos.

6. Obtenha o API Client

A configuração mesclada retorna uma instância de ClientConfig. Se nenhuma configuração for encontrada, o sistema pode retornar erros legados e obscuros. É uma boa prática verificar com clientcmd.IsEmptyConfig(err) para fornecer uma mensagem de erro clara ao usuário.

Exemplo Completo

Este exemplo demonstra a implementação de uma ferramenta que lista os nós de um cluster respeitando as configurações de contexto e namespace do usuário:

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/spf13/pflag"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
)

func main() {
	loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
	configOverrides := &clientcmd.ConfigOverrides{}
	flags := pflag.NewFlagSet("demonuvemonline", pflag.ExitOnError)

	clientcmd.BindOverrideFlags(configOverrides, flags, clientcmd.RecommendedConfigOverrideFlags(""))
	flags.StringVarP(&loadingRules.ExplicitPath, "kubeconfig", "", "", "caminho absoluto para o kubeconfig")
	flags.Parse(os.Args)

	kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
	config, err := kubeConfig.ClientConfig()
	if err != nil {
		if clientcmd.IsEmptyConfig(err) {
			panic("Por favor, forneça uma configuração válida para o API server do Kubernetes")
		}
		panic(err)
	}

	client, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err)
	}

	namespace, overridden, err := kubeConfig.Namespace()
	if err != nil {
		panic(err)
	}
	fmt.Printf("Namespace selecionado: %s; Sobrescrito: %t\n", namespace, overridden)

	nodeList, err := client.CoreV1().Nodes().List(context.TODO(), v1.ListOptions{})
	if err != nil {
		panic(err)
	}
	for _, node := range nodeList.Items {
		fmt.Println(node.Name)
	}
}

Ao adotar esse padrão, você garante que as ferramentas internas da sua infraestrutura ofereçam a mesma experiência operacional do ecossistema oficial, reduzindo a curva de aprendizado para o time de engenharia.


Artigo originalmente publicado por Stephen Kitt (Red Hat) em Kubernetes Blog.

Gostou? Compartilhe:
Precisa de ajuda?Fale com nossos especialistas 👋
Avatar Walcew - Headset