Релиз nxs-anomaly: отечественная альтернатива Grafana OnCall для управления дежурствами, маршрутизации оповещений и эскалации инцидентов. Доступен на GitHub
Все статьи

Использование универсальных Helm-чартов в проектах

Как nxs-universal-chart упрощает работу с Kubernetes

Время чтения
10 минут
Раздел
Инструменты
Использование универсальных Helm-чартов в проектах

Если у вас 1–2 проекта, а в каждом до 5 приложений, и вы раз в полгода создаёте новый чарт в Helm, можно просто копировать старые чарты или создавать новые с нуля. Но что делать, если у вас 5 проектов, в каждом по 20 микросервисов, и каждые две недели разработчики выкатывают новые и выводят из эксплуатации старые? При этом постоянно приходится что-то обновлять. А если проектов 20 или 50?

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

Мы столкнулись с такой ситуацией на одном из проектов, где нужно было запустить 15 микросервисов, и решили создать единый чарт, через который можно было бы запускать практически всё.

В этой статье расскажем о практическом применении универсального Helm-чарта НИКСИС. На теории останавливаться подробно не будем — сосредоточимся на практическом примере.

Универсальный Helm-чарт

Когда мы запустили проект с 15 микросервисами и решили использовать единый чарт, результат оказался удачным. Мы экономили время на подготовке к релизу, а также накапливали и переиспользовали в новых проектах решения, которые хорошо себя показали.

Это позволяло выполнять релизы практически любой сложности и конфигурации и быстро подготавливать микросервисы к развёртыванию в Kubernetes.

Основные преимущества подхода:

  • экономия времени при подготовке к релизам;
  • повторное использование готовых решений;
  • возможность быстро подготовить микросервисы к развёртыванию в Kubernetes;
  • хранение чарта отдельно от кода;
  • возможность размещать чарт в registry Nixys, чтобы потребители сразу получали новые версии и возможности;
  • возможность добавлять новые шаблоны и работать с их конструкциями непосредственно внутри values-файла.

В примере через опцию extraDeploy можно добавить шаблонный NetworkPolicy. При этом ограничений на тип добавляемых манифестов нет: таким способом можно подключать кастомные ресурсы, необходимые приложению, и практически любые другие Kubernetes-манифесты.

У такого подхода есть и особенности. Из-за гибкости шаблонов и values-файлов их структура достаточно сложная. Поэтому для работы с универсальным чартом требуется хорошее понимание Helm и навыки создания сложных Helm-чартов.

Кроме того, поскольку в values используются конструкции шаблонов, существуют ограничения при работе с Go templates. В частности, это ограничения на длину строки конструкции шаблона и использование символов, отличных от букв и цифр. Из-за этого иногда возникают ошибки, которые приходится находить и исправлять экспериментальным путём.

Несмотря на эти ограничения, преимуществ у подхода больше. Рассмотрим работу универсального чарта на практике.

Использование универсального чарта на практике

Откроем репозиторий с универсальным Helm-чартом. Там находятся сам проект и документация.

В качестве примера будем разворачивать проект на Python — связку Django и Celery. Django будет использоваться в качестве web-приложения, а Celery — в качестве асинхронного воркера для обработки задач.

Проект максимально простой, но при этом хорошо показывает, как работает асинхронное приложение.

В окружении уже развернуты Redis и PostgreSQL. Предполагаем, что базы данных уже доступны и с ними нет проблем.

Для полноты примера они также могут быть развернуты с помощью Helm. Соответствующие values-файлы находятся в репозитории проекта, в каталоге k8s.

Посмотрим, сколько времени займёт развёртывание такого проекта с использованием универсального чарта.

Для проверки работы приложения заранее подготовлены Kubernetes-манифесты и выполнено ручное развёртывание.

Создание values-файла

Начнём с создания пустого values-файла и его заполнения.

Если возникают вопросы по структуре, можно обращаться к документации и существующим манифестам.

Сначала укажем дефолтный image и дефолтный pull policy. Образ заранее собран и размещён в Docker Hub registry. Будем использовать лёгкий образ, поэтому defaultImagePullPolicy поменяем на Always.

Подготовка переменных окружения

У чарта есть две встроенные сущности для работы с переменными окружения:

  • Из envs генерируется ConfigMap. В качестве префикса используется название релиза приложения, которое мы запускаем, а постфикс — envs.
  • В secretEnvs создаётся Secret. В качестве префикса используется название релиза, а постфикс — secretEnvs.

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

Для CELERY_BROKER из уже проверенного манифеста забираем Redis. POSTGRES_USER у нас дефолтный, а пароль сгенерирован. База будет называться Demo, порт используем стандартный.

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

Поскольку приложение работает на Django, а Django умеет обрабатывать бизнес-логику, но не предназначен для отдачи статики, будем использовать nginx. Он будет отдавать статику и проксировать запросы к Django.

Для этого создадим ConfigMap, в который положим конфигурацию nginx. В ней будет указано, что сервер на 80-м порту слушает все запросы, отдаёт статику из нужного каталога и проксирует остальные запросы на Django-сервер. Этот конфиг будем монтировать внутрь контейнера с nginx.

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

Django использует миграции, которые будем запускать с помощью hook. Для этого потребуется само приложение и дефолтный контейнер. У него минимум настроек и параметров: название контейнера и команды, с помощью которых выполняется миграция. Добавим переменную окружения для подключения к базе данных.

Чтобы сразу подключить все переменные окружения к контейнеру, воспользуемся envConfigMap и envSecret. В этот параметр передаётся список сущностей, создаваемых чартом и содержащих переменные окружения.

По умолчанию всегда создаются ConfigMap-envs и секрет secret-envs, где префиксом является название релиза.

Если отдельным блоком добавить секреты или ConfigMap, в которых также содержатся переменные окружения, их тоже можно подключить с помощью этих опций.

На этом настройка hook для миграции закончена.

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

Деплойментов всего три:

  • web-приложение;
  • celery-воркер;
  • небольшой дашборд для Celery Flower, который позволяет видеть работающие воркеры, количество выполненных ими задач и их текущий статус.

Настроим каждый из них:

1. Web-приложение

Так как мы работаем с несколькими деплойментами в рамках одного приложения, потребуется дополнительный селектор лейблов, чтобы корректно работала маршрутизация. Укажем для каждого приложения, к какому компоненту оно относится, и настроим контейнеры.

Запустим Django. В используемом образе Django стартует по умолчанию, поэтому дополнительно указывать команду не нужно. При этом потребуются переменные окружения, которые подключим с помощью опций envConfigMap и envSecret.

Вторым контейнером будет nginx, который будет проксировать запросы и отдавать статику.тДля запуска nginx укажем образ и тег. В качестве образа используем Alpine, поскольку он небольшой и лёгкий. Для nginx не потребуются переменные окружения, но нужны конфигурация и статика.

Подключим volumeMounts с конфигурацией. Конфиг будет подключаться в стандартный путь, при этом необходимо заменить один файл. Статику будем отдавать из каталога, указанного в конфигурации nginx.

Также нужно объявить volumes, которые будем использовать. В чарте есть стандартные volumes, но через блок volumes можно добавить специальную сущность. Это типизированный volume, который позволяет подключить конкретную сущность — ConfigMap, Secret или PersistentVolumeClaim — к контейнеру.

Если требуется подключить нативный Kubernetes volume, для этого есть опция extraVolumes. 

В нашем случае создадим emptyDir. При запуске статика будет распаковываться в этот volume, а после завершения работы удаляться.

Volume с конфигурацией nginx подключим из ConfigMap.

Из-за использования subPath и отличия путей потребуется указать items. Внутри ConfigMap находится файл nginx.conf, но доступен он должен быть как default.conf.

Осталось добавить init-контейнер, в котором будет распаковываться статика. Здесь выполняется команда Python, которую также можно взять из подготовленного YAML-файла.

После этого всё необходимое для запуска основного web-приложения готово.

2. Celery-воркер

Укажем, что хотим получить на старте несколько реплик. Контейнер будет похож на тот, который использовали ранее, но команда будет отличаться. Назовём контейнер воркером, добавим команду и переменные окружения, которые используются и в основном приложении.

В зависимости от проекта может потребоваться работа с volumes. Например, статику можно разместить в PersistentVolume и добавлять туда пользовательские файлы. В нашем примере воркеру для работы больше ничего не требуется.

3. Dash

Dash также будет состоять из одного контейнера, в котором запустим Flower. Для работы ему нужна всего одна переменная окружения — broker. Подключим её с помощью envFrom и ConfigMap или Secret. Укажем имя envs и перечислим, какие переменные окружения должны быть доступны контейнеру.

В данном случае это broker, но в нашем ConfigMap она называется иначе. Поэтому укажем, по какому имени искать нужную переменную.

Деплоймент настроен. Теперь нужно добавить Service и Ingress, чтобы получить доступ к web-приложению извне.

Service и Ingress

Для настройки сервисов обратимся к дефолтному values-файлу и посмотрим, как добавить необходимые сущности.

Нам потребуются два сервиса:

  • Web, который будет работать на 80-м порту и обращаться к tier: Web;
  • Dash, который будет обращаться к дашборду Flower.

Для Dash укажем порт 5555, а extraSelectorLabels для этого workload — dash.

Осталось настроить Ingress. В качестве ключа в названии используем доменное имя. Понадобится одна аннотация. В кластере, с которым мы работаем, уже развёрнут cert-manager и есть cluster-issuer.

Настроим сервисы для Ingress. Дефолтный корневой путь будет указывать на сервис web, а servicePort — на http. Также добавим дашборд, доступный по пути /flower. Он будет обращаться к serviceName: dash, а servicePort у него будет 5555.

Важно учитывать версию Kubernetes и Ingress-контроллера. В используемом кластере Kubernetes и Ingress-контроллер поддерживают Ingress Class, поэтому необходимо указать IngressClassName. Это позволит избежать ошибки при доставке манифестов в Kubernetes.

На этом настройка закончена. Осталось проверить результат.

Тестирование и отладка

Мы создали небольшой values-файл, который описывает развёртывание приложения. 

Для проверки корректности работы укажем файл, из которого будем подключать values, и посмотрим, корректно ли универсальный чарт генерирует Kubernetes-манифесты.

При первой проверке получили ошибку и внесли необходимые правки.

Чтобы убедиться, что всё работает, заменим команду на install и запустим развёртывание.

Сначала не стартует миграция, потому что потерялись секреты. Причина оказалась в ручном вводе: вместо secret-envs было указано secrets-env. После исправления всё заработало, и начали создаваться контейнеры приложения.

Теперь перейдём в web-приложение, введём название домена и увидим, что не работает статика. Причина в том, что при запуске init-контейнера мы не подключили volume, в который должна распаковываться статика. Исправляем конфигурацию.

После этого проверим Flower. Дашборд доступен, мы видим воркеры, но UI не работает. Причиной снова стала ошибка ручного ввода. Мы не видели init-контейнер, потому что его не было в конфигурации. Блок называется initContainer, а чарт ожидает initContainers. После исправления проблема была устранена.

Теперь UI появился, а все основные компоненты приложения работают.

Что даёт использование универсального Helm-чарта

Универсальный Helm-чарт позволяет сократить время подготовки приложений к развёртыванию, переиспользовать уже проверенные решения и стандартизировать работу с Kubernetes.

При большом количестве проектов и микросервисов такой подход позволяет не создавать отдельный набор однотипных Helm-чартов для каждого приложения, а описывать необходимые параметры через values.

При этом гибкость чарта позволяет добавлять дополнительные Kubernetes-ресурсы, настраивать переменные окружения, секреты, volumes, init-контейнеры, сервисы, Ingress и другие компоненты.

Главное — внимательно работать с документацией и структурой values-файла. Это позволяет избежать значительной части ошибок при подготовке конфигурации и быстрее запускать новые приложения.

Оставьте заявку, чтобы обсудить проект

Ваши контакты
О вашем проекте

Нажав на кнопку, соглашаюсь на обработку персональных данных, Ознакомлен со всеми документами, размещенными на сайте

См. Политику обработки персональных данных
См. Пользовательское соглашение