Единый доступ к серверу 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)
}
}
Удачи в разработке и спасибо за интерес к созданию инструментов с привычным для пользователей поведением!