Одной из наиболее полезных функций helm является способность хранить состояние, что позволяет не только разворачивать чарты, но и аккуратно их удалять, зачищая после себя все следы их присутствия. Однако не лишним будет разобраться в том, как именно helm хранит состояние, чем и займемся в статье.
Содержание
Как Helm работает с Kubernetes namespace
TL;DR: если лень читать и вы знакомы с helm, переходите сразу к выводам в самом конце статье, там коротко собрана выжимка из рассуждений ниже.
Вопрос
Изначально мне было просто интересно узнать как helm хранит состояние. Разобраться в этом вопросе не сложно и все доступно в документации. С версии helm 3 состояние по умолчанию хранится в секретах. Но сразу возникают вопросы – в секретах какого неймспейса? Как управлять этим поведением? Какой подход более предпочтителен?
Короткий ответ
Helm 3 хранит состояние в секретах неймспейса по умолчанию. Если не установлено иначе, то это будет неймспейс default. Переопределить пространство имен можно с помощью опции ‐‐namespace или переменной окружения HELM_NAMESPACE:
1 2 3 |
helm repo add grafana https://grafana.github.io/helm-charts helm repo update helm upgrade --install loki grafana/loki-stack --namespace loki-stack --create-namespace |
Вот так выглядит сам секрет:
1 2 3 |
# kubectl -n loki-stack get secret NAME TYPE DATA AGE sh.helm.release.v1.loki-stack.v1 helm.sh/release.v1 1 3d21h |
Но дьявол кроется в деталях, что только подтверждается количеством реакций на сообщения в задаче Best Practices/Docs Question – Specify Namespace in Chart?. А теперь обо всем подробнее.
Подробное объяснение
Для начала рассмотрим варианты управления пространствами имен Kubernetes в контексте хельма. Начнем с самых очевидных, но ошибочных сценариев.
Определять пространство имен явно
Namespace в Kubernetes это ключевой инструмент для изоляции объектов кластера. Установить его для объекта (например пода) можно следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 |
# templates/nginx.yaml apiVersion: v1 kind: Pod metadata: name: nginx namespace: nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80 |
Если в таком виде вы хотите отдать это хельму, то вам необходимо также создавать сам неймспейс этим же чартом:
1 2 3 4 5 |
# templates/namespace.yaml apiVersion: v1 kind: Namespace metadata: name: nginx |
Можно пойти чуть дальше и определить неймспейс как переменную в файле values.yaml:
1 2 |
# values.yaml namespace: nginx |
А вот так вызывать внутри шаблона объекта:
1 2 3 |
metadata: name: nginx namespace: {{ .Values.namespace }} |
Такая модель является антипаттерном, потому что потенциально вы получите ситуацию, когда состояние и объекты чарта могут храниться в разных неймспейсах (и вероятно будут храниться). Helm сохранит состояние в неймспейсе default, если вы конечно не выполните команду helm install с ключом ‐‐namespace. Значение ключа должно совпадать со значением переменной в файле values.yaml.
В этом принципиальное отличие от kubectl. В случае с kubectl, если ваши объекты будут иметь namespace с именем X, а самому kubectl вы передадите опцию ‐‐namespace с именем Y, то kubectl свалится с ошибкой:
1 |
error: the namespace from the provided object "<namespace_X>" does not match the namespace "<namespace_Y>". You must pass '--namespace=<namespace_X>' to perform this operation. |
Kubectl не позволяет двоякую трактовку конфигурации, а helm – позволяет.
Не определять пространство имен вообще
Можно пойти иным путем и не определять неймспейс вообще ни для какого объекта чарта, то есть просто игнорировать ключ namespace. В таком случае helm все положит в неймспейс default – и объекты, и секрет с состоянием. Если же вы выставите опции ‐‐namespace <name> и ‐‐create-namespace, то helm создаст требуемый неймспейс и разместит всё там (объекты и секрет). Удобно и даже нет никаких проблем с “расщеплением” конфигурации, когда объекты и состояние лежат в разных неймспейсах. Именно этот подход и рекомендован официально – неймспейсы не должны определяться нигде в явном виде.
Однако остаются непонятные моменты. Предположим вы последовательно выполняете две следующие команды:
1 2 |
helm template test helm-test helm template test helm-test --namespace nginx |
Helm сделает рендер всех объектов чарта и выдаст его в output консоли. Только нюанс в том, что в обоих случаях вывод будет полностью одинаков. То есть helm просто проигнорирует опцию namespace. Мне бы как минимум хотелось видеть кастомный namespace в том случае, если я его задаю явно (вторая команда), а в идеале – в обоих случаях.
Использовать встроенную переменную
На уровне чарта есть ряд встроенных переменных, доступных глобально. Среди них есть и переменная, которая хранит имя неймспейса релиза, это Release.Namespace (см. Helm – Built-in Objects). Использовать её можно следующим образом:
1 2 3 |
metadata: name: nginx namespace: {{ .Release.Namespace }} |
Эта переменная будет хранить имя неймспейса, которое вы передадите хельму с помощью ключа ‐‐namespace. Либо отдаст имя пространства имен по умолчанию – default. В данном случае выполнение двух команд helm template из предыдущей главы даст разные результаты. Это именно то поведение, которое лично я ожидаю от простейшего templating engine, коим и является Helm.
Вывод
Выставление опции ‐‐namespace для helm install/upgrade никоим образом не управляет соответствующим параметром namespace в метаданных объектов Kubernetes. Более того, если ваши объекты уже содержат параметр namespace: <name_1> (в качестве примера), то helm создаст объекты именно в неймспейсе <name_1>, даже если ему будет явно выставлена опция ‐‐namespace <name_2>. А вот секрет с состоянием он положит в неймспейс <name_2>.
На такой простейшей задаче приходится брать во внимание особенности работы хельма и заботиться о корректности обработки пространства имен, в котором должны быть созданы и объекты, и секрет с состоянием. Kubectl реализует аналогичную логику гораздо правильнее, давая насладиться всеми преимуществами декларативного подхода.
Разработчики helm рекомендуют НЕ устанавливать пространства имен для объектов Kubernetes в явном виде, а довериться опциям ‐‐namespace и ‐‐create-namespace. Я же считаю, что корректнее выставлять namespace: {{ .Release.Namespace }} для всех объектов Kubernetes, для которых параметр пространства имен вообще применим. Это как минимум даст вам понятную логику работы команды helm template, а как максимум – убережет от возможных расщеплений конфигураций.
На этом все. Если вдруг будут вопросы о работе с Helm, вы всегда можете написать мне.