[Перевод] Пакетный менеджер для Kubernetes — Helm: прошлое, настоящее, будущее

Прим. перев.: Этой статьёй мы открываем цикл публикаций про пакетный менеджер для Kubernetes, который активно используем в повседневной работе, — Helm. Оригинальным автором материала является Matt Butcher — один из основателей проекта Helm, работающий над Open Source-проектами в Microsoft и написавший 8 технических книг (в частности, «Go in Practice»). Однако статья дополнена нашими (местами — обширными) комментариями, а в скором времени будет ещё больше расширена новыми заметками по Helm более практической направленности.

ff1xjsnvdlnfinirlwb-0p42tlo.png

В июне 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-скрипты.

Шаблон простого веб-приложения мог выглядеть следующим образом:

YAML
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 состоит из трёх компонентов. Следующая диаграмма иллюстрирует отношения между ними:

66f8l_blo_rfzohvo1wpobchuzg.png

  1. Manager выполняет функцию веб-сервера (общение с клиентами происходит по REST API), управляет развёртываниями в кластере Kubernetes и используется как хранилище данных.
  2. Компонент expandybird приводит пользовательские конфигурации к плоской форме, т.е. применяет Jinja-шаблоны и выполняет Python-скрипты.
  3. Получив плоскую конфигурацию, resourcifier выполняет необходимые вызовы kubectl и возвращает в manager статус и сообщения об ошибках, если таковые имеются.


Для понимания возможностей первой версии Helm приведу справку по команде dm:

Вывод help от dm
Usage: ./dm []  [( |  | ( [...]))]
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, добавив в него следующее:

  1. шаблоны чартов («чарт» — аналог пакета в экосистеме Helm — прим. перев.) для кастомизации;
  2. управление внутри кластера для команд;
  3. полноценный репозиторий чартов;
  4. стабильный и подписываемый формат пакетов;
  5. твёрдая приверженность семантическому версионированию и сохранению обратной совместимости от версии к версии.


Чтобы достичь этих целей, в экосистему 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 было четыре преимущества:

  1. библиотека встроена в Go;
  2. шаблоны исполняются в жёстко ограниченном песочницей окружении;
  3. мы могли вставлять в движок произвольные функции и объекты;
  4. они хорошо работали с YAML.


Хотя мы и сохранили в Helm интерфейс для поддержки других движков шаблонов, шаблоны Go стали нашим стандартом по умолчанию. И последующие несколько лет опыта показали, как инженеры из многих компаний создавали тысячи чартов, используя шаблоны Go.

И мы узнали об их разочарованиях:

  1. Синтаксис сложен в чтении и плохо документирован.
  2. Проблемы языка, такие как неизменные переменные, запутанные типы данных и ограничительные правила в области видимости, превратили простые вещи в сложные.
  3. Отсутствие возможности определять функции внутри шаблонов ещё больше усложнили создание повторно используемых библиотек.


Самое важное — используя язык шаблонов, мы существенно «обрезали» объекты 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, для поддержания состояния различных релизов внутри кластера нам требуется новый способ взаимодействия всех клиентов (по управлению релизами).

Для этого мы представили две новые записи:

  1. Release — для конкретной инсталляции конкретного чарта. Если мы выполним helm install my-wordpress stable/wordpress, будет создан релиз с названием my-wordpress и поддерживаться на протяжении всей жизни этой инсталляции WordPress.
  2. 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. от переводчика


Читайте также в нашем блоге:

© Habrahabr.ru