Прим. перев.: Этой статьёй мы открываем цикл публикаций про пакетный менеджер для Kubernetes, который активно используем в повседневной работе, — Helm. Оригинальным автором материала является Matt Butcher — один из основателей проекта Helm, работающий над Open Source-проектами в Microsoft и написавший 8 технических книг (в частности, «Go in Practice»). Однако статья дополнена нашими (местами — обширными) комментариями, а в скором времени будет ещё больше расширена новыми заметками по Helm более практической направленности.
В июне Helm перешёл из статуса ведущего проекта Kubernetes в фонд Cloud Native Computing Foundation (CNCF). CNCF становится родительской организацией для лучших в своём роде cloud native-инструментов с открытым исходным кодом. Поэтому большая честь для Helm стать частью такого фонда. И наш первый значимый проект под покровительством CNCF по-настоящему масштабный: мы создаём Helm 3.
Краткая история Helm
Helm изначально появился как Open Source-проект компании Deis. Его моделировали по подобию Homebrew (менеджер пакетов для macOS — прим. перев.), а стоящей перед Helm 1 задачей была облегчённая возможность для пользователей быстро установить свои первые рабочие нагрузки на Kubernetes. Официальный анонс Helm состоялся на первой конференции KubeCon San Francisco в 2015 году.
Прим. перев.: С первой версии, которая называлась dm (Deployment Manager), для описания ресурсов Kubernetes был выбран синтаксис YAML, а при написании конфигураций поддерживались шаблоны Jinja и Python-скрипты.
Шаблон простого веб-приложения мог выглядеть следующим образом:
resources:
- name: frontend
type: github.com/kubernetes/application-dm-templates/common/replicatedservice:v1
properties:
service_port: 80
container_port: 80
external_service: true
replicas: 3
image: gcr.io/google_containers/example-guestbook-php-redis:v3
- name: redis
type: github.com/kubernetes/application-dm-templates/storage/redis:v1
properties: null
При описании компонентов выкатываемого приложения указывается имя, используемый шаблон, а также необходимые параметры этого шаблона. В примере выше ресурсы frontend
и redis
используют шаблоны из официального репозитория.
Уже в этой версии можно использовать ресурсы из общей базы знаний, создавать собственные репозитории шаблонов и компоновать комплексные приложения за счёт параметров и вложенности шаблонов.
Архитектура Helm 1 состоит из трёх компонентов. Следующая диаграмма иллюстрирует отношения между ними:
-
Manager
выполняет функцию веб-сервера (общение с клиентами происходит по REST API), управляет развёртываниями в кластере Kubernetes и используется как хранилище данных. - Компонент
expandybird
приводит пользовательские конфигурации к плоской форме, т.е. применяет Jinja-шаблоны и выполняет Python-скрипты. - Получив плоскую конфигурацию,
resourcifier
выполняет необходимые вызовы kubectl и возвращает вmanager
статус и сообщения об ошибках, если таковые имеются.
Для понимания возможностей первой версии Helm приведу справку по команде dm
:
Usage: ./dm [<flags>] <command> [(<template-name> | <deployment-name> | (<configuration> [<import1>...<importN>]))]
Commands:
expand Expands the supplied configuration(s)
deploy Deploys the named template or the supplied configuration(s)
list Lists the deployments in the cluster
get Retrieves the supplied deployment
manifest Lists manifests for deployment or retrieves the supplied manifest in the form (deployment[/manifest])
delete Deletes the supplied deployment
update Updates a deployment using the supplied configuration(s)
deployed-types Lists the types deployed in the cluster
deployed-instances Lists the instances of the named type deployed in the cluster
templates Lists the templates in a given template registry (specified with --registry)
registries Lists the registries available
describe Describes the named template in a given template registry
getcredential Gets the named credential used by a registry
setcredential Sets a credential used by a registry
createregistry Creates a registry that holds charts
Flags:
-apitoken string
Github api token that overrides GITHUB_API_TOKEN environment variable
-binary string
Path to template expansion binary (default "../expandybird/expansion/expansion.py")
-httptest.serve string
if non-empty, httptest.NewServer serves on this address and blocks
-name string
Name of deployment, used for deploy and update commands (defaults to template name)
-password string
Github password that overrides GITHUB_PASSWORD environment variable
-properties string
Properties to use when deploying a template (e.g., --properties k1=v1,k2=v2)
-regex string
Regular expression to filter the templates listed in a template registry
-registry string
Registry name (default "application-dm-templates")
-registryfile string
File containing registry specification
-service string
URL for deployment manager (default "http://localhost:8001/api/v1/proxy/namespaces/dm/services/manager-service:manager")
-serviceaccount string
Service account file containing JWT token
-stdin
Reads a configuration from the standard input
-timeout int
Time in seconds to wait for response (default 20)
-username string
Github user name that overrides GITHUB_USERNAME environment variable
--stdin requires a file name and either the file contents or a tar archive containing the named file.
a tar archive may include any additional files referenced directly or indirectly by the named file.
А теперь вернёмся к оригинальному тексту об истории Helm…
Несколькими месяцами позже мы объединили усилия с командой Kubernetes Deployment Manager из Google и начали работу над Helm 2. Целью было сохранить простоту использования Helm, добавив в него следующее:
- шаблоны чартов («чарт» — аналог пакета в экосистеме Helm — прим. перев.) для кастомизации;
- управление внутри кластера для команд;
- полноценный репозиторий чартов;
- стабильный и подписываемый формат пакетов;
- твёрдая приверженность семантическому версионированию и сохранению обратной совместимости от версии к версии.
Чтобы достичь этих целей, в экосистему Helm был добавлен второй компонент. Им стал находящийся внутри кластера Tiller, который обеспечивал установку Helm-чартов и управление ими.
Прим. перев.: Таким образом, во второй версии Helm в кластере остаётся единственный компонент, который отвечает за жизненный цикл инсталяций (release), а подготовка конфигураций выносится в Helm-клиент.
Если перезагрузка кластера при использовании первой версии Helm приводила к полной потере служебных данных (т.к. они хранились в оперативной памяти), то в Helm 2 все данные хранятся в ConfigMaps
, т.е. ресурсах внутри Kubernetes. Ещё одним важным шагом стал переход от синхронного API (где каждый запрос был блокирующим) к использованию асинхронного gRPC.
Со времени выпуска Helm 2 в 2016 году проект Kubernetes пережил взрывной рост и появление новых значительных возможностей. Было добавлено управление доступом на основе ролей (RBAC). Представлено множество новых типов ресурсов. Изобретены сторонние ресурсы (Custom Resource Definitions, CRD). А что самое важное — появились лучшие практики. Проходя через все эти изменения, Helm продолжал служить потребностям пользователей Kubernetes. Но нам стало очевидно, что настало время внести в него крупные изменения для того, чтобы потребности этой развивающейся экосистемы продолжали удовлетворяться.
Так мы пришли к Helm 3. Далее я расскажу о некоторых из новшеств, фигурирующих на дорожной карте проекта.
Поприветствуем Lua
В Helm 2 мы представили шаблоны. На раннем этапе разработки Helm 2 мы поддерживали шаблоны Go, Jinja, чистый код на Python и у нас даже был прототип поддержки ksonnet. Но наличие множества движков для шаблонов породило больше проблем, чем решило. Поэтому мы пришли к тому, чтобы выбрать один.
У шаблонов Go было четыре преимущества:
- библиотека встроена в Go;
- шаблоны исполняются в жёстко ограниченном песочницей окружении;
- мы могли вставлять в движок произвольные функции и объекты;
- они хорошо работали с YAML.
Хотя мы и сохранили в Helm интерфейс для поддержки других движков шаблонов, шаблоны Go стали нашим стандартом по умолчанию. И последующие несколько лет опыта показали, как инженеры из многих компаний создавали тысячи чартов, используя шаблоны Go.
И мы узнали об их разочарованиях:
- Синтаксис сложен в чтении и плохо документирован.
- Проблемы языка, такие как неизменные переменные, запутанные типы данных и ограничительные правила в области видимости, превратили простые вещи в сложные.
- Отсутствие возможности определять функции внутри шаблонов ещё больше усложнили создание повторно используемых библиотек.
Самое важное — используя язык шаблонов, мы существенно «обрезали» объекты Kubernetes до их строчного представления. (Другими словами, разработчикам шаблонов приходилось управлять ресурсами Kubernetes как текстовыми документами в формате YAML.)
Работа над объектами, а не кусками YAML
Снова и снова мы слышали от пользователей запрос на возможность инспекции и модификации ресурсов Kubernetes как объектов, а не строк. В то же время они были непреклонны в том, что, какой бы путь реализации мы для этого ни выбрали, он должен быть простым в изучении и хорошо поддерживаться в экосистеме.
После месяцев исследований мы решили предоставить встроенный скриптовый язык, который можно упаковать в песочницу и кастомизировать. Среди 20 ведущих языков оказался лишь один кандидат, удовлетворяющий требованиям: Lua.
В 1993 году группа бразильских ИТ-инженеров создала легковесный скриптовый язык для встраивания в свои инструменты. У Lua простой синтаксис, он широко поддерживается и уже долгое время фигурирует в списке топ-20 языков. Его поддерживают IDE и текстовые редакторы, есть множество руководств и обучающих книг. Вот на такой уже существующей экосистеме мы бы и хотели развивать своё решение.
Наша работа над Helm Lua всё ещё находится на этапе доказательства концепции, и мы ожидаем синтаксиса, который был бы одновременно знакомым и гибким. Сравнивая старый и новый подходы, можно увидеть, куда мы движемся.
Вот как выглядит пример шаблона пода с Alpine в Helm 2:
apiVersion: v1
kind: Pod
metadata:
name: {{ template "alpine.fullname" . }}
labels:
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app: {{ template "alpine.name" . }}
spec:
restartPolicy: {{ .Values.restartPolicy }}
containers:
- name: waiter
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ["/bin/sleep", "9000"]
В этом незамысловатом шаблоне можно сразу увидеть все встроенные директивы шаблонов, такие как {{ .Chart.Name }}
.
А вот как выглядит определение того же пода в предварительной версии кода на Lua:
unction create_alpine_pod(_)
local pod = {
apiVersion = "v1",
kind = "Pod",
metadata = {
name = alpine_fullname(_),
labels = {
heritage = _.Release.Service or "helm",
release = _.Release.Name,
chart = _.Chart.Name .. "-" .. _.Chart.Version,
app = alpine_name(_)
}
},
spec = {
restartPolicy = _.Values.restartPolicy,
containers = {
{
name = waiter,
image = _.Values.image.repository .. ":" .. _.Values.image.tag,
imagePullPolicy = _.Values.image.pullPolicy,
command = {
"/bin/sleep",
"9000"
}
}
}
}
}
_.resources.add(pod)
end
Нет необходимости рассматривать каждую строчку этого примера, чтобы понять, что происходит. Сразу видно, что в коде определяется под. Но вместо использования YAML-строк со встроенными директивами шаблонов мы определяем под как объект в Lua.
Давайте сократим этот код
Поскольку мы напрямую работаем с объектами (вместо манипуляции с большим glob'ом текста), можем воспользоваться всеми преимуществами скриптования. Появляющиеся здесь возможности создания разделяемых библиотек выглядят по-настоящему привлекательно. И мы надеемся, что, представив специализированные библиотеки (или позволив сообществу их создать), сможем сократить приведённый выше код примерно до такого:
local pods = require("mylib.pods");
function create_alpine_pod(_)
myPod = pods.new("alpine:3.7", _)
myPod.spec.restartPolicy = "Always"
-- set any other properties
_.Manifests.add(myPod)
end
В данном примере используется возможность работы с определением ресурса как с объектом, которому легко устанавливать свойства, сохраняя при этом лаконичность и читаемость кода.
Шаблоны… Lua… Почему бы не всё вместе?
Пусть шаблоны и не так замечательны для всех задач, у них всё же есть определённые преимущества. Шаблоны в Go — стабильная технология со сложившейся пользовательской базой и множеством существующих чартов. Многие разработчики чартов утверждают, что им нравится писать шаблоны. Поэтому поддержку шаблонов мы убирать не собираемся.
Вместо этого мы хотим разрешить использовать одновременно и шаблоны, и Lua. У скриптов Lua будет доступ к шаблонам Helm и до, и после того, как они отрендерены, что позволит разработчикам продвинутых чартов выполнять сложные преобразования для существующих чартов, сохраняя простую возможность создания Helm-чартов с шаблонами.
Мы очень воодушевлены поддержкой скриптов на Lua, но в то же время избавляемся от значимой части архитектуры Helm…
Прощаясь с Tiller
Во время разработки Helm 2 мы представили Tiller в качестве компонента интеграции с Deployment Manager. Tiller играл важную роль для команд, работающих на одном кластере: он делал возможным взаимодействие с одним и тем же набором релизов для множества различных администраторов.
Однако Tiller работал как гигантский sudo-сервер, выдающий широкий диапазон прав каждому, у кого есть доступ к Tiller. И нашей схемой по умолчанию при инсталляции была разрешительная конфигурация. Поэтому DevOps- и SRE-инженерам приходилось обучаться дополнительным шагам для установки Tiller в кластерах категории multi-tenant.
Более того, при появлении CRD мы больше не могли надёжно полагаться на Tiller для поддержания состояния или функционирования в качестве центрального хаба для информации о релизе Helm. Мы могли только хранить эту информацию в виде отдельных записей в Kubernetes.
Главная цель Tiller может быть достигнута и без самого Tiller. Поэтому одним из первых решений, принятых на этапе планирования Helm 3, был полный отказ от Tiller.
Улучшение безопасности
Без Tiller модель безопасности Helm радикально упрощается. Пользовательская аутентификация делегируется Kubernetes. И авторизация тоже. Права Helm определяются как права Kubernetes (через систему RBAC), и администраторы кластера могут ограничить права Helm на любом необходимом уровне детализации.
Releases, ReleaseVersions и State Storage
В условиях отсутствия Tiller, для поддержания состояния различных релизов внутри кластера нам требуется новый способ взаимодействия всех клиентов (по управлению релизами).
Для этого мы представили две новые записи:
-
Release
— для конкретной инсталляции конкретного чарта. Если мы выполнимhelm install my-wordpress stable/wordpress
, будет создан релиз с названиемmy-wordpress
и поддерживаться на протяжении всей жизни этой инсталляции WordPress. -
ReleaseVersion
— при каждом обновлении чарта Helm необходимо учитывать, что изменилось и было ли изменение успешным.ReleaseVersion
привязан к релизу и хранит только записи с информацией об обновлении, откате и удалении. Когда мы выполнимhelm upgrade my-wordpress stable/wordpress
, оригинальный объектRelease
останется прежним, но появится дочерний объектReleaseVersion
с информацией об операции обновления.
Releases
и ReleaseVersions
будут храниться в тех же пространствах имён, что и объекты чарта.
С этими возможностями команды пользователей Helm смогут отслеживать записи об инсталляциях Helm в кластере без потребности в Tiller.
Но подождите, это ещё не всё!
В этой статье я постарался рассказать о некоторых крупнейших изменениях в Helm 3. Однако этот список вовсе не является полным. План по Helm 3 включает в себя и другие изменения, такие как улучшения в формате чартов, улучшения в производительности для репозиториев чартов и новая событийная система, которой могут пользоваться разработчики чартов. Мы также предпринимаем усилия по тому, что Eric Raymond называется археологией кода, вычищая кодовую базу и обновляя компоненты, утратившие актуальность за последние три года.
Прим. перев.: Парадокс, но пакетный менеджер Helm 2 при успешном выполнении install
или upgrade
, т.е. имея release в состоянии success
, не гарантирует, что ресурсы приложения успешно выкатились (к примеру, нет ошибок типа ImagePullError
). Возможно, новая событийная модель позволит добавлять дополнительные хуки для ресурсов и лучше контролировать процесс выката — скоро мы об этом узнаем.
С присоединением Helm к CNCF нас вдохновляет не только Helm 3, но и Chart Museum, замечательная утилита Chart Testing, официальный репозиторий чартов и другие проекты под эгидой Helm в CNCF. Мы уверены, что хорошее управление пакетами для Kubernetes настолько же важно для облачной (cloud native) экосистемы, насколько важны хорошие пакетные менеджеры для Linux.
P.S. от переводчика
Читайте также в нашем блоге:
- «Практика с dapp. Часть 2. Деплой Docker-образов в Kubernetes с помощью Helm»;
- «Сборка и дeплой приложений в Kubernetes с помощью dapp и GitLab CI»;
- «Лучшие практики CI/CD с Kubernetes и GitLab» (обзор и видео доклада);
- «Наш опыт с Kubernetes в небольших проектах» (видео доклада, включающего в себя знакомство с техническим устройством Kubernetes).
Автор: aigrychev