Kubectl-совместимый клиент на Go с clientcmd

Единый доступ к серверу API с помощью clientcmd

Если вам когда-нибудь хотелось написать консольный клиент для Kubernetes API — особенно если вы думали сделать его пригодным для использования в качестве плагина kubectl — вы наверняка задавались вопросом: как добиться того, чтобы поведение клиента было привычным для пользователей kubectl? Достаточно бегло взглянуть на вывод kubectl options, чтобы опустились руки: «Неужели мне нужно реализовать все эти параметры?»

Не отчаивайтесь — большую часть работы уже сделали за вас. Kubernetes-проект предоставляет две библиотеки, которые помогают обрабатывать аргументы командной строки в стиле kubectl в программах на Go: clientcmd и cli-runtime (которая использует clientcmd). В этой статье рассмотрим первую из них.

Общая философия

Поскольку clientcmd входит в состав client-go, его главная задача — предоставить экземпляр restclient.Config, с помощью которого можно отправлять запросы к серверу API.

Библиотека следует семантике kubectl:

  • значения по умолчанию берутся из ~/.kube или эквивалентного расположения;

  • файлы можно указать через переменную окружения KUBECONFIG;

  • все перечисленные настройки можно дополнительно переопределить аргументами командной строки.

Библиотека не добавляет аргумент командной строки --kubeconfig самостоятельно, хотя вы, возможно, захотите его добавить для единообразия с kubectl. Как это сделать, показано в разделе «Привязка флагов».

Доступные возможности

clientcmd позволяет программам управлять:

  • выбором kubeconfig (через KUBECONFIG);

  • выбором контекста (context);

  • выбором пространства имён (namespace);

  • клиентскими сертификатами и закрытыми ключами;

  • имперсонацией пользователей (user impersonation);

  • поддержкой HTTP Basic-аутентификации (имя пользователя и пароль).

Слияние конфигураций

В ряде сценариев clientcmd поддерживает слияние (merging) настроек: KUBECONFIG может указывать на несколько файлов, содержимое которых объединяется. Это поведение может сбивать с толку, потому что настройки сливаются по-разному в зависимости от способа их хранения. Если параметр задан в виде записи в словаре (map), побеждает первое определение, а последующие игнорируются. Если параметр не является записью в словаре, побеждает последнее определение.

При чтении настроек через KUBECONFIG отсутствующие файлы приводят лишь к предупреждениям. Если же пользователь явно указывает путь (в стиле --kubeconfig), соответствующий файл должен существовать.

Если KUBECONFIG не задан, используется файл конфигурации по умолчанию ~/.kube/config — при условии, что он существует.

Общий процесс

Типичный сценарий использования кратко описан в документации пакета clientcmd:

loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
// если нужно изменить правила загрузки (какие файлы и в каком порядке), сделайте это здесь
configOverrides := &clientcmd.ConfigOverrides{}
// если нужно изменить переопределяемые значения или привязать их к флагам, для этого есть вспомогательные методы
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
config, err := kubeConfig.ClientConfig()
if err != nil {
// обработайте ошибку
}
client, err := metav1.New(config)
// ...

В контексте этой статьи процесс состоит из шести шагов:

Настройка правил загрузки

clientcmd.NewDefaultClientConfigLoadingRules() строит правила загрузки, которые используют либо содержимое переменной окружения KUBECONFIG, либо имя файла конфигурации по умолчанию (~/.kube/config). Кроме того, если используется файл по умолчанию, функция умеет переносить настройки из очень старого файла конфигурации (~/.kube/.kubeconfig).

Вы можете построить собственные ClientConfigLoadingRules, однако в большинстве случаев значения по умолчанию вполне подходят.

Настройка переопределений

clientcmd.ConfigOverrides — это структура (struct), хранящая переопределения, которые применяются поверх настроек, загруженных согласно правилам загрузки. В контексте данной статьи её основная задача — хранить значения, полученные из аргументов командной строки. Для этого используется библиотека pflag — замена стандартного пакета flag из Go, добавляющая поддержку длинных аргументов с двойным дефисом.

В большинстве случаев в переопределениях ничего не нужно задавать вручную — достаточно просто привязать их к флагам.

Создание набора флагов

Флаг в данном контексте — это описание аргумента командной строки: его длинное имя (например, --namespace), короткое имя при наличии (например, -n), значение по умолчанию и описание, отображаемое в справке. Флаги хранятся в экземплярах структуры FlagInfo.

Доступны три набора флагов, представляющие следующие аргументы командной строки:

  • аргументы аутентификации (сертификаты, токены, имперсонация, имя пользователя и пароль);

  • аргументы кластера (сервер API, центр сертификации, настройки TLS, прокси, сжатие);

  • аргументы контекста (имя кластера, имя пользователя в kubeconfig, пространство имён).

Рекомендуемый набор включает все три группы, а также аргумент выбора именованного контекста и аргумент задания таймаута.

Все они доступны через функции Recommended…Flags. Эти функции принимают префикс, который добавляется в начало длинных имён всех аргументов.

Например, вызов clientcmd.RecommendedConfigOverrideFlags("") даёт аргументы командной строки вида --context, --namespace и т. д. Аргументу --timeout присваивается значение по умолчанию 0, а у --namespace есть короткий вариант -n. Добавление префикса, например "from-", даёт аргументы --from-context, --from-namespace и т. п. В случае одного сервера API это может показаться излишним, зато очень удобно при работе с несколькими серверами — например, в многокластерных сценариях.

Здесь есть потенциальная ловушка: префиксы не изменяют короткие имена, поэтому с --namespace нужно быть осторожнее при использовании нескольких префиксов: только один из них может быть связан с коротким именем -n. Для остальных префиксов (или, возможно, для всех, если однозначная связь с -n отсутствует) придётся очистить короткие имена у --namespace. Очистить короткое имя можно так:

kflags := clientcmd.RecommendedConfigOverrideFlags(prefix)
kflags.ContextOverrideFlags.Namespace.ShortName = ""

Аналогичным образом флаг можно отключить полностью, очистив его длинное имя:

kflags.ContextOverrideFlags.Namespace.LongName = ""

Привязка флагов

После того как набор флагов определён, его можно использовать для привязки аргументов командной строки к переопределениям с помощью clientcmd.BindOverrideFlags. Для этого требуется FlagSet из библиотеки pflag, а не из стандартного пакета flag.

Если вы также хотите добавить --kubeconfig, сделайте это здесь, привязав ExplicitPath в правилах загрузки:

flags.StringVarP(&loadingRules.ExplicitPath, "kubeconfig", "", "", "absolute path(s) to the kubeconfig file(s)")

Построение объединённой конфигурации

Для построения объединённой конфигурации доступны две функции:

  • clientcmd.NewInteractiveDeferredLoadingClientConfig

  • clientcmd.NewNonInteractiveDeferredLoadingClientConfig

Как следует из названий, разница между ними в том, что первая может запрашивать аутентификационные данные в интерактивном режиме через переданный reader, тогда как вторая работает исключительно с информацией, предоставленной вызывающим кодом.

Слово «deferred» (отложенный) в именах этих функций означает, что итоговая конфигурация будет определена как можно позже. Благодаря этому функции можно вызывать до разбора аргументов командной строки — результирующая конфигурация будет использовать те значения, которые окажутся разобраны к моменту её фактического построения.

Получение API-клиента

Объединённая конфигурация возвращается в виде экземпляра ClientConfig. Чтобы получить API-клиент, нужно вызвать у него метод ClientConfig().

Если конфигурация не задана (KUBECONFIG пуст или указывает на несуществующие файлы, ~/.kube/config отсутствует, и никакие настройки не переданы через аргументы командной строки), стандартная конфигурация вернёт малопонятную ошибку со ссылкой на KUBERNETES_MASTER. Это унаследованное поведение; предпринималось несколько попыток от него избавиться, однако оно сохраняется ради поддержки аргументов --local и --dry-run в kubectl. Рекомендуется проверять ошибку «пустой конфигурации» с помощью clientcmd.IsEmptyConfig() и выводить более понятное сообщение.

Полезен также метод Namespace(): он возвращает пространство имён, которое следует использовать, и сообщает, было ли оно явно задано пользователем (через --namespace).

Полный пример

Ниже приведён законченный пример.

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("clientcmddemo", pflag.ExitOnError)
	clientcmd.BindOverrideFlags(configOverrides, flags,
		clientcmd.RecommendedConfigOverrideFlags(""))
	flags.StringVarP(&loadingRules.ExplicitPath, "kubeconfig", "", "", "absolute path(s) to the kubeconfig file(s)")
	flags.Parse(os.Args)

	// Создание клиента
	kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
	config, err := kubeConfig.ClientConfig()
	if err != nil {
		if clientcmd.IsEmptyConfig(err) {
			panic("Please provide a configuration pointing to the Kubernetes API server")
		}
		panic(err)
	}
	client, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err)
	}

	// Определяем, какое пространство имён использовать
	namespace, overridden, err := kubeConfig.Namespace()
	if err != nil {
		panic(err)
	}
	fmt.Printf("Chosen namespace: %s; overridden: %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)
	}
}

Удачи в разработке и спасибо за интерес к созданию инструментов с привычным для пользователей поведением!

© 2026 meganuke