Русский
Русский
English
Статистика
Реклама

Helm

KubeHelper -упростите множество повседневных задач с Kubernetes через веб-интерфейс

15.02.2021 18:22:10 | Автор: admin

KubeHelper - это продукт который упрощает решение многих ежедневных задач связанных с управлением Kubernetes через веб интерфейс. Поиск, анализ, запуск команд, cron jobs, репорты, фильтры, git синхронизация и многое другое.

KubeHelper это не ещё одна попытка отобразить Kubernetes API в графическом интерфейсе. Не попытка заменить Lens, официальный Dashboard или другие продукты. Это мой скромный вклад в Kubernetes Open Source сообщество. Проект не имеет какого-то узко специализированного направления и содержит довольно много различных функций которые будут полезны в ежедневной работе с Kubernetes.

Итак, го к описанию и возможностям.

Некоторые термины тяжело переводить, поэтому я вместо непонятного/корявого перевода буду писать английское слово.

Мотивация

Считаю Kubernetes замечательным и революционным продуктом. Много лет изучаю и пользуюсь, но очень часто возникала надобность иметь под рукой много различных функций и команд. Каждый раз набирать в командной строке длинные команды, искать в истории, прописывать aliases и так д. конечно же, можно, но очень часто нет возможности залогинится в консоль, или не сохранилась история, или новый хост. Или много других причин.

На некоторых фирмах жёсткие правила и очень часто чтоб залогинится в консоль нужно пробрасывать несколько ssh тоннелей. Или вообще очень ограниченный доступ к консоли.

Многие десктопные клиенты требуют сертификата для коннекта к серверу. И держать сертификат от кластера на локальной машине, не 100% безопасно.

А что если нет возможности проинсталлировать клиент на локальной машине, снова же надо создавать aliasesили копировать команды из сниппетов. Очень часто вижу, что люди ищут те или иные команды, выходят статьи с несколькими различными командами, но их сотни, а вариаций тысячи. Конечно же все эти задачи можно автоматизировать и наверняка каждый пользователь/администратор кластера уже тем или иным образом решил для себя эти задачи, но я уверен что есть и такие которе ещё этого сделалась не успели.

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

Также постарался на ресурсы посмотреть с другой стороны. Многие готовые решения показывают ресурсы по namespace, ещё несколько полезных функций и на этом гибкость веб интерфейсов заканчивается и чтоб сделать что-то отличное чем просто показать список ресурсов в namespace, снова же приходится обращаться к консоли. Например group labels, find selector, view RBACs и так д

Тут у меня и возникла идея помочь сообществу организовать много команд в едином интерфейсе, установить kubectl, плагины, утилиты и сделать для пользования командной строкой графический web интерфейс. Внедрить другие функции которые будут полезны в ежедневной работе с Kubernetes кластером. Внести свой скромный вклад в Open Source сообщество. Ну и конечно же чтоб еще лучше познать и узнать Kubernetes. Вот что из этого получилось.

Общие принципы и понятия

На данный момент я подготовил 3 возможности инсталляции: kubectl, Helm, Terraform. Посмотреть инсталляции и настроить их можно здесь. Позже добавлю в публичный Helm Chart Repository и в Terraform Registry.

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

По умолчанию KubeHelper устанавливается с правами на чтение и просмотр. (get, list). Поэтому с уверенностью можно сказать, что вы ничего не поломаете используя его у себя в кластере. Но при инсталляции вы можете изменить ClusterRole под свои нужды, аж до статуса cluster-admin. Подробная настройка в Wiki.

Конфигурация и команды хранятся в формате TOML. Как по мне, этот формат очень удобен для такого рода задач. Снова же, подробное описание и как всё работает, вы можете почитать в Wiki.

Возможности и интерфейс

Dashboard

Здесь собрана общая информация о кластере. Общее количество процессорного времени, хостов(нод), памяти и так д. Отображена общая техническая информация о хосте(ноде), количество, имена и размеры docker images.

Search

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

Принцип довольно прост и интуитивен. Выбираем namespace(all = all namespaces) в котором мы хотим найти ресурсы по ключевому слову. Выбираем ресурсы нажимаем поиск, применяем фильтры.Также при нажатии на кнопку в столбце "Raws" можно посмотреть ресурс в различных форматахJava POJO, YAML или JSON.

Подробное описание всех функций и принцип работы вы можете почитать в Wiki.

Ips and Ports

Название говорит само за себя, поиск IP и портов в подах и сервисах по всему кластеру. При клике на найденный ресурс будет показана детальная информация о ресурсе.

Security

Этот раздел состоит из 6 секций. Каждая секция имеет одни и те же кнопки для выбора namespace и поиска соответственных ресурсов в ней.

Также при нажатии на кнопку в столбце "Raws" можно посмотреть ресурс в различных форматахJava POJO, YAML или JSON.

Секция "Roles": Поиск ролей в отдельном namespace или во всех namespaces. Просмотр subjects и role rules для каждого verb.Фильтр и группировка результатов.

Секция"RBAC: Построение матрицы доступа к ресурсам на основании ролей и правил.Фильтр и группировка результатов.

Секция"Pod Security Contexts: ПоказатьPodSecurityContext объекты в отдельной namespace или во всех namespaces.

Секция"Container Security Contexts": Показать ContainerSecurityContext объекты в отдельной namespace или во всех namespaces.

Секция"Service Accounts": Показать"Service Accounts" в отдельном namespace или во всех namespaces.

Секция"Pod Security Policies": Показать"Pod Security Policies" в отдельном namespace или во всех namespaces.

Labels Annotations Selectors

Поискlabels, selectors и annotations по выделенным ресурсам. Фильтр и группирование результатов.

Вкладка Grouped: На основании поиска labels, selectors и annotations будут сагрегированы и сгруппированы

Commands

Идея секции Commands сделатьKubeHelper гибко настраиваемый для каждого пользователя. Каждый пользователь может исполнять свои kubectl и shell команды с различных консолей. Просматривать результаты исполнения. Экспортировать свои подборки команд с git репозитория и многое другое. KubeHelper уже имеет много готовых к использованию команд.

Секция "Commands": Окно разделено на 3 части, команды которые можно фильтровать и искать. Окно редактирования выделенной команды и окно вывода результатов исполнения.

Секция "Management": В этой секции вы имеете возможность редактировать и создавать свои команды, а также просматривать уже имеющиеся команды.

СекцияHistory: В этой секции вы можете посмотреть все исполненные команды и результат их исполнения. Для удобности, история сохраняется по дням. Присутствует несколько удобных фильтров.

Cron Jobs

Идея секции Cron Jobs: сделать исполнение регулярных задач, проверки безопасности, построение и просмотр репортов исполнения команд - более простым и тривиальным заданием.

Секция "Jobs": Окно разделено на 3 части, команды которые можно фильтровать и искать. Окно редактирования выделенной команды и поля для создания новой cron job. Таблица со списком активных и остановленных cron jobs, кнопки управления.

Секция"Reports": Для каждой "cron job создаётся папка в которую по дням будут сохраняться выполненияcron jobs.Присутствует несколько удобных фильтров.

Configurations

Идея секции Configurations дать возможность пользователю синхронизировать свой конфигурационный файл с git также быстрее определить или изменить нужные cron jobs. Здесь же находятся функции для управление пользовательским репозиторием. Подробное описане можно почитать в Wiki.

Versions

ВKubeHelper уже предустановлен kubectl, огромное количество плагинов, утилит, командных оболочек и так д.. Эта вкладка отображает весь список предустановленных программ, их версии и другую информацию.

Вопрос к дочитавшим:

Какие новые функции вы бы добавили в первую очередь? Какая новая функция сделает вашу ежедневную работу легче?

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

Достаточно оставить комментарий с приоритетом в виде номеров или своё предложение.

Для предложения новой функции есть соответствующий Issue.

P.S.1 Буду рад репосту, звёздочке на GitHub. Пользуйтесь, делитесь информацией с коллегами, друзьями, знакомыми.

P.S.2 Буду рад желающим помогать развивать проект. На начальных стадиях особенно людям которые помогут сделать дизайн красивее. В первую очередь я работал над функциональностью.

Всем спасибо!

Подробнее..

Перевод Автогенерация секретов в Helm

17.07.2020 10:14:14 | Автор: admin

Auto-Generated Helm Secrets

Команда Kubernetes aaS от Mail.ru перевела короткую заметку о том, как автоматически генерировать секреты Helm при обновлении. Далее текст от автора статьи технического директора Intoware, компании-разработчика SaaS-решений.

Контейнеры это круто. Сначала я был противником контейнеров (стыдно признаться), но теперь я полностью поддерживаю использование этой технологии.
Если вы читаете это, то, надеюсь, успешно плавали по морям Docker, осознали преимущества Kubernetes и сделали свою жизнь намного проще с Helm.

Тем не менее некоторые вещи явно сложнее, чем должны быть.

Как автоматически генерировать секреты при обновлении?

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

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

Создание секретов


Обычно, чтобы создать секрет в Helm, нужно:

  • описать секрет в файле значений;
  • переопределить его в процессе деплоя;
  • сослаться на него внутри деплоя/пода;
  • профит !

Обычно это выглядит примерно так:

apiVersion: v1kind: Secretmetadata:  name: my-super-awesome-api-keytype: OpaquestringData:  apiKey: {{ .Values.MyApiKeySecret | quote }}

Простой секрет Kubernetes, использующий значения из values.yml

Но, допустим, вы не хотите указывать свой секрет в файле значений.

Существует много вариантов, когда для развертывания требуется общий ключ, который должен быть сгенерирован во время установки.

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

Хуки


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

Для решения нашей проблемы добавления ключа, генерируемого при установке, идеально подходят предустановочные хуки. Но есть одна загвоздка: вы не можете автоматически сгенерировать секрет один раз при обновлении. Хуки будут работать при каждом обновлении.

Если вы сгенерировали свой секрет, и ваша первая установка еще не произошла, тогда прекратите чтение, предустановочный хук отлично подойдет для вас.

Но если секрет является частью обновления (возможно, новой функции, которой не было во время установки), то досадно, что невозможно создать предустановочный хук, который бы сработал всего один раз.

Функции


Функции Helm позволяют добавлять различные элементы скриптов в сценарии развертывания.

apiVersion: v1kind: Secretmetadata:  name: my-super-awesome-api-keytype: OpaquestringData:  apiKey: {{ uuidv4 | quote }} #Generate a new UUID and quote it

В этом примере показано, что значением секрета apiKey будет новый UUID, сгенерированный во время установки.

Helm включает в себя действительно обширную библиотеку функций, которая использует потрясающие функции шаблонов GO и библиотеку функций Sprig для создания настраиваемых развертываний.

Функция Lookup


В Helm 3.1 добавлена функция Lookup, которая позволяет запросить существующий деплой и:

  • проверить существование ресурсов;
  • вернуть значение существующего ресурса для последующего использования.

Используя обе эти возможности, мы можем создать одноразовый динамически сгенерированный секрет!

# 1. Запросить существование секрета и вернуть в переменной $secret{{- $secret := (lookup "v1" "Secret" .Release.Namespace "some-awesome-secret" -}}apiVersion: v1kind: Secretmetadata:  name: some-awesome-secrettype: Opaque# 2. Если секрет существует, взять его значение как apiKey (секрет использует кодирование Base64, так что используйте ключ "data"){{ if $secret -}}data:  apiKey: {{ $secret.data.apiKey }}# 3. Если секрет не существует  создать его (в этот раз используйте "stringData", так как будет обычное значение)!{{ else -}}stringData:  apiKey: {{ uuidv4 | quote }}{{ end }}

Всякий раз, когда новое обновление применяется к серверу, Helm будет либо генерировать новое значение секрета (если секрета еще нет), либо повторно использовать существующее значение.

Успехов!

Что еще почитать по теме:

  1. Три уровня автомасштабирования в Kubernetes и как их эффективно использовать.
  2. Kubernetes в духе пиратства с шаблоном по внедрению.
  3. Наш канал Вокруг Kubernetes в Телеграме.
Подробнее..

Практический пример подключения хранилища на базе Ceph в кластер Kubernetes

18.09.2020 14:13:41 | Автор: admin

Container Storage Interface (CSI) это унифицированный интерфейс взаимодействия Kubernetes и систем хранения данных. Вкратце о нём мы уже рассказывали, а сегодня подробнее рассмотрим связку CSI и Ceph: покажем, как подключить хранилище Ceph к кластеру Kubernetes.
В статье приведены реальные, хотя и немного упрощённые для удобства восприятия примеры. Установку и настройку кластеров Ceph и Kubernetes не рассматриваем.


Вам интересно, как это работает?



Итак, у вас под рукой есть кластер Kubernetes, развернутый, к примеру, kubespray. Рядом работает кластер Ceph его можно также поставить, например, вот этим набором плейбуков. Надеюсь, не нужно упоминать, что для продакшена между ними должна быть сеть с пропускной способностью не менее 10 Гбит/с.


Если всё это у вас есть, поехали!


Сначала зайдем на одну из нод кластера Ceph и проверяем, что всё в порядке:


ceph healthceph -s

Далее тут же создадим пул для RBD дисков:


ceph osd pool create kube 32ceph osd pool application enable kube rbd

Переходим в кластер Kubernetes. Там первым делом установим Ceph CSI драйвер для RBD. Ставить будем, как и положено, через Helm.
Добавляем репозиторий с чартом, получаем набор переменных чарта ceph-csi-rbd:


helm repo add ceph-csi https://ceph.github.io/csi-chartshelm inspect values ceph-csi/ceph-csi-rbd > cephrbd.yml

Теперь нужно заполнить файл cephrbd.yml. Для этого узнаем ID кластера и IP-адреса мониторов в Ceph:


ceph fsid  # так мы узнаем clusterIDceph mon dump  # а так увидим IP-адреса мониторов

Полученные значения заносим в файл cephrbd.yml. Попутно включаем создание политик PSP (Pod Security Policies). Опции в разделах nodeplugin и provisioner уже есть в файле, их можно исправить так, как показано ниже:


csiConfig:  - clusterID: "bcd0d202-fba8-4352-b25d-75c89258d5ab"    monitors:      - "v2:172.18.8.5:3300/0,v1:172.18.8.5:6789/0"      - "v2:172.18.8.6:3300/0,v1:172.18.8.6:6789/0"      - "v2:172.18.8.7:3300/0,v1:172.18.8.7:6789/0"nodeplugin:  podSecurityPolicy:    enabled: trueprovisioner:  podSecurityPolicy:    enabled: true

Далее всё что нам остаётся установить чарт в кластер Kubernetes.


helm upgrade -i ceph-csi-rbd ceph-csi/ceph-csi-rbd -f cephrbd.yml -n ceph-csi-rbd --create-namespace

Отлично, RBD драйвер работает!
Создадим в Kubernetes новый StorageClass. Для этого снова потребуется немного поработать с Ceph.


Создаём нового пользователя в Ceph и выдаём ему права на запись в пул kube:


ceph auth get-or-create client.rbdkube mon 'profile rbd' osd 'profile rbd pool=kube'

А теперь посмотрим ключ доступа всё там же:


ceph auth get-key client.rbdkube

Команда выдаст нечто подобное:


AQCO9NJbhYipKRAAMqZsnqqS/T8OYQX20xIa9A==

Занесём это значение в Secret в кластере Kubernetes туда, где нужен userKey:


---apiVersion: v1kind: Secretmetadata:  name: csi-rbd-secret  namespace: ceph-csi-rbdstringData:  # Значения ключей соответствуют имени пользователя и его ключу, как указано в  # кластере Ceph. ID юзера должен иметь доступ к пулу,  # указанному в storage class  userID: rbdkube  userKey: <user-key>

И создаём наш секрет:


kubectl apply -f secret.yaml

Далее нам нужен примерно такой манифест StorageClass:


---apiVersion: storage.k8s.io/v1kind: StorageClassmetadata:   name: csi-rbd-scprovisioner: rbd.csi.ceph.comparameters:   clusterID: <cluster-id>   pool: kube   imageFeatures: layering   # Эти секреты должны содержать данные для авторизации   # в ваш пул.   csi.storage.k8s.io/provisioner-secret-name: csi-rbd-secret   csi.storage.k8s.io/provisioner-secret-namespace: ceph-csi-rbd   csi.storage.k8s.io/controller-expand-secret-name: csi-rbd-secret   csi.storage.k8s.io/controller-expand-secret-namespace: ceph-csi-rbd   csi.storage.k8s.io/node-stage-secret-name: csi-rbd-secret   csi.storage.k8s.io/node-stage-secret-namespace: ceph-csi-rbd   csi.storage.k8s.io/fstype: ext4reclaimPolicy: DeleteallowVolumeExpansion: truemountOptions:  - discard

Нужно заполнить clusterID, который мы уже узнали командой ceph fsid, и применить этот манифест в кластере Kubernetes:


kubectl apply -f storageclass.yaml

Чтобы проверить работу кластеров в связке, создадим вот такой PVC (Persistent Volume Claim):


apiVersion: v1kind: PersistentVolumeClaimmetadata:  name: rbd-pvcspec:  accessModes:  - ReadWriteOnce  resources:    requests:      storage: 1Gi  storageClassName: csi-rbd-sc

Сразу посмотрим, как Kubernetes создал в Ceph запрошенный том:


kubectl get pvckubectl get pv

Вроде бы всё отлично! А как это выглядит на стороне Ceph?
Получаем список томов в пуле и просматриваем информацию о нашем томе:


rbd ls -p kuberbd -p kube info csi-vol-eb3d257d-8c6c-11ea-bff5-6235e7640653  # тут, конечно же, будет другой ID тома, который выдала предыдущая команда

Теперь давайте посмотрим, как работает изменение размера тома RBD.
Изменяем размер тома в манифесте pvc.yaml до 2Gi и применяем его:


kubectl apply -f pvc.yaml

Подождём, пока изменения вступят в силу, и ещё раз посмотрим на размер тома.


rbd -p kube info csi-vol-eb3d257d-8c6c-11ea-bff5-6235e7640653kubectl get pvkubectl get pvc

Видим, что размер у PVC не изменился. Чтобы узнать причину, можно запросить у Kubernetes описание PVC в формате YAML:


kubectl get pvc rbd-pvc -o yaml

А вот и проблема:


message: Waiting for user to (re-)start a pod to finish file system resize of volume on node. type: FileSystemResizePending


То есть диск увеличился, а файловая система на нём нет.
Чтобы увеличить файловую систему, надо смонтировать том. У нас же созданный PVC/PV сейчас никак не используется.


Можем создать тестовый Pod, например вот так:


---apiVersion: v1kind: Podmetadata:  name: csi-rbd-demo-podspec:  containers:    - name: web-server      image: nginx:1.17.6      volumeMounts:        - name: mypvc          mountPath: /data  volumes:    - name: mypvc      persistentVolumeClaim:        claimName: rbd-pvc        readOnly: false

И теперь посмотрим на PVC:


kubectl get pvc

Размер изменился, всё в порядке.


В первой части мы работали с блочным устройством RBD (оно так и расшифровывается Rados Block Device), но так нельзя делать, если требуется одновременная работа с этим диском разных микросервисов. Для работы с файлами, а не с образом диска, намного лучше подходит CephFS.
На примере кластеров Ceph и Kubernetes настроим CSI и остальные необходимые сущности для работы с CephFS.


Получим значения из нужного нам нового Helm-чарта:


helm inspect values ceph-csi/ceph-csi-cephfs > cephfs.yml

Снова нужно заполнить файл cephfs.yml. Как и ранее, помогут команды Ceph:


ceph fsidceph mon dump

Заполняем файл со значениями примерно так:


csiConfig:  - clusterID: "bcd0d202-fba8-4352-b25d-75c89258d5ab"    monitors:      - "172.18.8.5:6789"      - "172.18.8.6:6789"      - "172.18.8.7:6789"nodeplugin:  httpMetrics:    enabled: true    containerPort: 8091  podSecurityPolicy:    enabled: trueprovisioner:  replicaCount: 1  podSecurityPolicy:    enabled: true

Обратите внимание, что адреса мониторов указываются в простой форме address:port. Для монтирования cephfs на узле эти адреса передаются в модуль ядра, который ещё не умеет работать с протоколом мониторов v2.
Порт для httpMetrics (туда будет ходить Prometheus за метриками для мониторинга) мы меняем для того, чтобы он не конфликтовал с nginx-proxy, который устанавливается Kubesprayем. Вам это, возможно, не потребуется.


Устанавливаем Helm-чарт в кластер Kubernetes:


helm upgrade -i ceph-csi-cephfs ceph-csi/ceph-csi-cephfs -f cephfs.yml -n ceph-csi-cephfs --create-namespace

Переходим к хранилищу данных Ceph, чтобы создать там отдельного пользователя. В документации указано, что провизионеру CephFS необходимы права доступа администратора кластера. Но мы создадим отдельного пользователя fs с ограниченными правами:


ceph auth get-or-create client.fs mon 'allow r' mgr 'allow rw' mds 'allow rws' osd 'allow rw pool=cephfs_data, allow rw pool=cephfs_metadata'

И сразу же посмотрим его ключ доступа, он нам пригодится далее:


ceph auth get-key client.fs

Создадим отдельные Secret и StorageClass.
Ничего нового, мы это уже видели на примере RBD:


---apiVersion: v1kind: Secretmetadata:  name: csi-cephfs-secret  namespace: ceph-csi-cephfsstringData:  # Необходимо для динамически создаваемых томов  adminID: fs  adminKey: <вывод предыдущей команды>

Применяем манифест:


kubectl apply -f secret.yaml

А теперь отдельный StorageClass:


---apiVersion: storage.k8s.io/v1kind: StorageClassmetadata:  name: csi-cephfs-scprovisioner: cephfs.csi.ceph.comparameters:  clusterID: <cluster-id>  # Имя файловой системы CephFS, в которой будет создан том  fsName: cephfs  # (необязательно) Пул Ceph, в котором будут храниться данные тома  # pool: cephfs_data  # (необязательно) Разделенные запятыми опции монтирования для Ceph-fuse  # например:  # fuseMountOptions: debug  # (необязательно) Разделенные запятыми опции монтирования CephFS для ядра  # См. man mount.ceph чтобы узнать список этих опций. Например:  # kernelMountOptions: readdir_max_bytes=1048576,norbytes  # Секреты должны содержать доступы для админа и/или юзера Ceph.  csi.storage.k8s.io/provisioner-secret-name: csi-cephfs-secret  csi.storage.k8s.io/provisioner-secret-namespace: ceph-csi-cephfs  csi.storage.k8s.io/controller-expand-secret-name: csi-cephfs-secret  csi.storage.k8s.io/controller-expand-secret-namespace: ceph-csi-cephfs  csi.storage.k8s.io/node-stage-secret-name: csi-cephfs-secret  csi.storage.k8s.io/node-stage-secret-namespace: ceph-csi-cephfs  # (необязательно) Драйвер может использовать либо ceph-fuse (fuse),   # либо ceph kernelclient (kernel).  # Если не указано, будет использоваться монтирование томов по умолчанию,  # это определяется поиском ceph-fuse и mount.ceph  # mounter: kernelreclaimPolicy: DeleteallowVolumeExpansion: truemountOptions:  - debug

Заполним тут clusterID и применим в Kubernetes:


kubectl apply -f storageclass.yaml

Проверка


Для проверки, как и в прошлом примере, создадим PVC:


---apiVersion: v1kind: PersistentVolumeClaimmetadata:  name: csi-cephfs-pvcspec:  accessModes:    - ReadWriteMany  resources:    requests:      storage: 5Gi  storageClassName: csi-cephfs-sc

И проверим наличие PVC/PV:


kubectl get pvckubectl get pv

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


Сходим на одну из нод кластера Ceph и выполним такие действия:


# Точка монтированияmkdir -p /mnt/cephfs# Создаём файл с ключом администратораceph auth get-key client.admin >/etc/ceph/secret.key# Добавляем запись в /etc/fstab# !! Изменяем ip адрес на адрес нашего узлаecho "172.18.8.6:6789:/ /mnt/cephfs ceph name=admin,secretfile=/etc/ceph/secret.key,noatime,_netdev    0       2" >> /etc/fstabmount /mnt/cephfs

Конечно же, вот такое монтирование FS на ноде Ceph подходит исключительно для целей обучения, чем мы и занимаемся на наших курсах Слёрм. Не думаю, что кто-то станет это делать в продакшене, велик риск случайно затереть важные файлы.


Ну и напоследок давайте проверим, как в случае с CephFS обстоят дела с изменением размеров тома. Возвращаемся в Kubernetes и отредактируем наш манифест для PVC увеличим там размер, к примеру, до 7Gi.


Применим отредактированный файл:


kubectl apply -f pvc.yaml

Посмотрим на примонтированном каталоге, как изменилась квота:


getfattr -n ceph.quota.max_bytes <каталог-с-данными>

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


Глаза боятся, а руки делают


С виду все эти заклинания и длинные манифесты YAML кажутся сложными, но на практике студенты Слёрма разбираются с ними довольно быстро.
В этой статье мы не углублялись в дебри для этого есть официальная документация. Если вас интересуют детали настройки хранилища Ceph совместно с кластером Kubernetes, помогут вот эти ссылки:


Общие принципы работы Kubernetes c томами
Документация по RBD
Интеграция RBD и Kubernetes с точки зрения Ceph
Интеграция RBD и Kubernetes с точки зрения CSI
Общая документация по CephFS
Интеграция CephFS и Kubernetes с точки зрения CSI


На курсе Слёрм Kubernetes База вы можете пойти ещё чуть дальше и развернуть в Kubernetes реальное приложение, которое будет использовать CephFS в качестве хранилища для файлов. Посредством GET/POST запросов вы сможете передавать файлы и получать их из Ceph.


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


Автор статьи: Александр Швалов, практикующий инженер Southbridge, Certified Kubernetes Administrator, автор и разработчик курсов Слёрм.

Подробнее..

Перевод Автоскейлинг приложений Kubernetes при помощи Prometheus и KEDA

28.09.2020 18:04:45 | Автор: admin
Balloon Man by Cimuanos

Масштабируемость ключевое требование для облачных приложений. С Kubernetes масштабировать приложение так же просто, как и увеличить количество реплик для соответствующего развертывания или ReplicaSet но это ручной процесс.

Kubernetes позволяет автоматически масштабировать приложения (то есть Pod в развертывании или ReplicaSet) декларативным образом с использованием спецификации Horizontal Pod Autoscaler. По умолчанию критерий для автоматического масштабирования метрики использования CPU (метрики ресурсов), но можно интегрировать пользовательские метрики и метрики, предоставляемые извне.

Команда Kubernetes aaS от Mail.ru перевела статью о том, как использовать внешние метрики для автоматического масштабирования приложения Kubernetes. Чтобы показать, как все работает, автор использует метрики запросов HTTP-доступа, они собираются с помощью Prometheus.

Вместо горизонтального автомасштабирования подов, применяется Kubernetes Event Driven Autoscaling (KEDA) оператор Kubernetes с открытым исходным кодом. Он изначально интегрируется с Horizontal Pod Autoscaler, чтобы обеспечить плавное автомасштабирование (в том числе до/от нуля) для управляемых событиями рабочих нагрузок. Код доступен на GitHub.

Краткий обзор работы системы




На схеме краткое описание того, как все работает:

  1. Приложение предоставляет метрики количества обращений к HTTP в формате Prometheus.
  2. Prometheus настроен на сбор этих показателей.
  3. Скейлер Prometheus в KEDA настроен на автоматическое масштабирование приложения на основе количества обращений к HTTP.

Теперь подробно расскажу о каждом элементе.

KEDA и Prometheus


Prometheus набор инструментов для мониторинга и оповещения систем с открытым исходным кодом, часть Cloud Native Computing Foundation. Собирает метрики из разных источников и сохраняет в виде данных временных рядов. Для визуализации данных можно использовать Grafana или другие инструменты визуализации, работающие с API Kubernetes.

KEDA поддерживает концепцию скейлера он действует как мост между KEDA и внешней системой. Реализация скейлера специфична для каждой целевой системы и извлекает из нее данные. Затем KEDA использует их для управления автоматическим масштабированием.

Скейлеры поддерживают нескольких источников данных, например, Kafka, Redis, Prometheus. То есть KEDA можно применять для автоматического масштабирования развертываний Kubernetes, используя в качестве критериев метрики Prometheus.

Тестовое приложение


Тестовое Golang-приложение предоставляет доступ по HTTP и выполняет две важные функции:

  1. Использует клиентскую библиотеку Prometheus Go для инструментирования приложения и предоставления метрики http_requests, которая содержит счетчик обращений. Конечная точка, по которой доступны метрики Prometheus, расположена по URI /metrics.

    var httpRequestsCounter = promauto.NewCounter(prometheus.CounterOpts{       Name: "http_requests",       Help: "number of http requests",   })
    
  2. В ответ на запрос GET приложение увеличивает значение ключа (access_count) в Redis. Это простой способ выполнить работу как часть обработчика HTTP, а также проверить метрики Prometheus. Значение метрики должно быть таким же, как значение access_count в Redis.

    func main() {       http.Handle("/metrics", promhttp.Handler())       http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {           defer httpRequestsCounter.Inc()           count, err := client.Incr(redisCounterName).Result()           if err != nil {               fmt.Println("Unable to increment redis counter", err)               os.Exit(1)           }           resp := "Accessed on " + time.Now().String() + "\nAccess count " + strconv.Itoa(int(count))           w.Write([]byte(resp))       })       http.ListenAndServe(":8080", nil)   }
    

Приложение развертывается в Kubernetes через Deployment. Также создается служба ClusterIP, она позволяет серверу Prometheus получать метрики приложения.

Вот манифест развертывания для приложения.

Сервер Prometheus


Манифест развертывания Prometheus состоит из:

  • ConfigMap для передачи конфига Prometheus;
  • Deployment для развертывания Prometheus в Kubernetes-кластере;
  • ClusterIP сервис для доступа к UI Prometheus;
  • ClusterRole, ClusterRoleBinding и ServiceAccount для работы автоопределения сервисов в Kubernetes (Auto-discovery).

Вот манифест для запуска Prometheus.

KEDA Prometheus ScaledObject


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

ScaledObject содержит информацию о масштабировании развертывания, метаданные об источнике события (например, секреты для подключения, имя очереди), интервал опроса, период восстановления и другие данные. Он приводит к соответствующему ресурсу автомасштабирования (определение HPA) для масштабирования развертывания.

Когда объект ScaledObject удаляется, соответствующее ему определение HPA очищается.

Вот определение ScaledObject для нашего примера, в нем используется скейлер Prometheus:

apiVersion: keda.k8s.io/v1alpha1kind: ScaledObjectmetadata: name: prometheus-scaledobject namespace: default labels:   deploymentName: go-prom-appspec: scaleTargetRef:   deploymentName: go-prom-app pollingInterval: 15 cooldownPeriod:  30 minReplicaCount: 1 maxReplicaCount: 10 triggers: - type: prometheus   metadata:     serverAddress: http://prometheus-service.default.svc.cluster.local:9090     metricName: access_frequency     threshold: '3'     query: sum(rate(http_requests[2m]))

Учтите следующие моменты:

  1. Он указывает на Deployment с именем go-prom-app.
  2. Тип триггера Prometheus. Адрес сервера Prometheus упоминается вместе с именем метрики, пороговым значением и запросом PromQL, который будет использоваться. Запрос PromQL sum(rate(http_requests[2m])).
  3. Согласно pollingInterval, KEDA запрашивает цель у Prometheus каждые пятнадцать секунд. Поддерживается минимум один под (minReplicaCount), а максимальное количество подов не превышает maxReplicaCount (в данном примере десять).

Можно установить minReplicaCount равным нулю. В этом случае KEDA активирует развертывание с нуля до единицы, а затем предоставляет HPA для дальнейшего автоматического масштабирования. Возможен и обратный порядок, то есть масштабирование от единицы до нуля. В примере мы не выбрали ноль, поскольку это HTTP-сервис, а не система по запросу.

Магия внутри автомасштабирования


Пороговое значение используют в качестве триггера для масштабирования развертывания. В нашем примере запрос PromQL sum(rate (http_requests [2m])) возвращает агрегированное значение скорости HTTP-запросов (количество запросов в секунду), ее измеряют за последние две минуты.

Поскольку пороговое значение равно трем, значит, будет один под, пока значение sum(rate (http_requests [2m])) меньше трех. Если же значение возрастает, добавляется дополнительный под каждый раз, когда sum(rate (http_requests [2m])) увеличивается на три. Например, если значение от 12 до 14, то количество подов четыре.

Теперь давайте попробуем настроить!

Предварительная настройка


Всё, что вам нужно кластер Kubernetes и настроенная утилита kubectl. В этом примере используется кластер minikube, но вы можете взять любой другой. Для установки кластера есть руководство.

Установить последнюю версию на Mac:

curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 \&& chmod +x minikubesudo mkdir -p /usr/local/bin/sudo install minikube /usr/local/bin/

Установите kubectl, чтобы получить доступ к кластеру Kubernetes.

Поставить последнюю версию на Mac:

curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -shttps://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl"chmod +x ./kubectlsudo mv ./kubectl /usr/local/bin/kubectlkubectl version

Установка KEDA


Вы можете развернуть KEDA несколькими способами, они перечислены в документации. Я использую монолитный YAML:

kubectl apply -fhttps://raw.githubusercontent.com/kedacore/keda/master/deploy/KedaScaleController.yaml

KEDA и ее компоненты устанавливаются в пространство имен keda. Команда для проверки:

kubectl get pods -n keda

Дождитесь, когда под KEDA Operator стартует перейдет в Running State. И после этого продолжайте.

Установка Redis при помощи Helm


Если у вас не установлен Helm, воспользуйтесь этим руководством. Команда для установки на Mac:

brew install kubernetes-helmhelm init --history-max 200

helm init инициализирует локальный интерфейс командной строки, а также устанавливает Tiller в кластер Kubernetes.

kubectl get pods -n kube-system | grep tiller

Дождитесь перехода пода Tiller в состояние Running.

Примечание переводчика: Автор использует Helm@2, который требует установки серверного компонента Tiller. Сейчас актуален Helm@3, для него серверная часть не нужна.

После установки Helm для запуска Redis достаточно одной команды:

helm install --name redis-server --set cluster.enabled=false --set usePassword=false stable/redis

Убедиться, что Redis успешно запустился:

kubectl get pods/redis-server-master-0

Дождитесь, когда под Redis перейдет в состояние Running.

Развертывание приложения


Команда для развертывания:

kubectl apply -f go-app.yaml//outputdeployment.apps/go-prom-app createdservice/go-prom-app-service created

Проверить, что все запустилось:

kubectl get pods -l=app=go-prom-app

Дождитесь перехода Redis в состояние Running.

Развертывание сервера Prometheus


Манифест Prometheus использует Kubernetes Service Discovery для Prometheus. Он позволяет динамически обнаруживать поды приложения на основе метки службы.

kubernetes_sd_configs:   - role: service   relabel_configs:   - source_labels: [__meta_kubernetes_service_label_run]     regex: go-prom-app-service     action: keep

Для развертывания:

kubectl apply -f prometheus.yaml//outputclusterrole.rbac.authorization.k8s.io/prometheus createdserviceaccount/default configuredclusterrolebinding.rbac.authorization.k8s.io/prometheus createdconfigmap/prom-conf createddeployment.extensions/prometheus-deployment createdservice/prometheus-service created

Проверить, что все запустилось:

kubectl get pods -l=app=prometheus-server

Дождитесь, пока под Prometheus перейдет в состояние Running.

Используйте kubectl port-forward для доступа к пользовательскому интерфейсу Prometheus (или серверу API) по адресу http://localhost:9090.

kubectl port-forward service/prometheus-service 9090

Развертывание конфигурации автомасштабирования KEDA


Команда для создания ScaledObject:

kubectl apply -f keda-prometheus-scaledobject.yaml

Проверьте логи оператора KEDA:

KEDA_POD_NAME=$(kubectl get pods -n keda -o=jsonpath='{.items[0].metadata.name}')kubectl logs $KEDA_POD_NAME -n keda

Результат выглядит примерно так:

time="2019-10-15T09:38:28Z" level=info msg="Watching ScaledObject:default/prometheus-scaledobject"time="2019-10-15T09:38:28Z" level=info msg="Created HPA with namespace default and name keda-hpa-go-prom-app"

Проверьте под приложения. Должен быть запущен один экземпляр, поскольку minReplicaCount равно 1:

kubectl get pods -l=app=go-prom-app

Проверьте, что ресурс HPA успешно создан:

kubectl get hpa

Вы должны увидеть что-то вроде:

NAME                   REFERENCE                TARGETS     MINPODS   MAXPODS   REPLICAS   AGEkeda-hpa-go-prom-app   Deployment/go-prom-app   0/3 (avg)   1         10        1          45s

Проверка работоспособности: доступ к приложению


Чтобы получить доступ к конечной точке REST нашего приложения, запустите:

kubectl port-forward service/go-prom-app-service 8080

Теперь вы можете получить доступ к приложению Go, используя адрес http://localhost:8080. Для этого выполните команду:

curl http://localhost:8080/test

Результат выглядит примерно так:

Accessed on 2019-10-21 11:29:10.560385986 +0000 UTC m=+406004.817901246Access count 1

На этом этапе также проверьте Redis. Вы увидите, что ключ access_count увеличен до 1:

kubectl exec -it redis-server-master-0 -- redis-cli get access_count//output"1"

Убедитесь, что значение метрики http_requests такое же:

curl http://localhost:8080/metrics | grep http_requests//output# HELP http_requests number of http requests# TYPE http_requests counterhttp_requests 1

Создание нагрузки


Мы будем использовать hey утилиту для генерации нагрузки:

curl -o hey https://storage.googleapis.com/hey-release/hey_darwin_amd64 && chmod a+x hey

Вы можете также скачать утилиту для Linux или Windows.

Запустите ее:

./hey http://localhost:8080/test

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

curl http://localhost:8080/metrics | grep http_requests//output# HELP http_requests number of http requests# TYPE http_requests counterhttp_requests 201kubectl exec -it redis-server-master-0 -- redis-cli get access_count//output201

Подтвердите значение фактической метрики (возвращенной запросом PromQL):

curl -g 'http://localhost:9090/api/v1/query?query=sum(rate(http_requests[2m]))'//output{"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1571734214.228,"1.686057971014493"]}]}}

В этом случае фактический результат равен 1,686057971014493 и отображается в поле value. Этого недостаточно для масштабирования, поскольку установленный нами порог равен 3.

Больше нагрузки!


В новом терминале следите за количеством подов приложения:

kubectl get pods -l=app=go-prom-app -w

Давайте увеличим нагрузку с помощью команды:

./hey -n 2000 http://localhost:8080/test

Через некоторое время вы увидите, что HPA масштабирует развертывание и запускает новые поды. Проверьте HPA, чтобы в этом убедиться:

kubectl get hpaNAME                   REFERENCE                TARGETS         MINPODS   MAXPODS   REPLICAS   AGEkeda-hpa-go-prom-app   Deployment/go-prom-app   1830m/3 (avg)   1         10        6          4m22s

Если нагрузка непостоянна, развертывание уменьшится до точки, при которой работает только один под. Если хотите проверить фактическую метрику (возвращенную запросом PromQL), то используйте команду:

curl -g 'http://localhost:9090/api/v1/query?query=sum(rate(http_requests[2m]))'

Очистка


//Delete KEDAkubectl delete namespace keda//Delete the app, Prometheus server and KEDA scaled objectkubectl delete -f .//Delete Redishelm del --purge redis-server

Заключение


KEDA позволяет автоматически масштабировать ваши развертывания Kubernetes (до/от нуля) на основе данных из внешних метрик. Например, на основе метрик Prometheus, длины очереди в Redis, задержки потребителя в теме Kafka.

KEDA выполняет интеграцию с внешним источником, а также предоставляет его метрики через Metrics Server для Horizontal Pod Autoscaler.

Успехов!

Что еще почитать:

  1. Лучшие практики и рекомендации для запуска контейнеров и Kubernetes в производственных средах.
  2. 90+ полезных инструментов для Kubernetes: развертывание, управление, мониторинг, безопасность и не только.
  3. Наш канал Вокруг Kubernetes в Телеграме.
Подробнее..

АйТиБорода Контейнеризация понятным языком. Интервью с System Engineers из Southbridge

02.10.2020 16:05:32 | Автор: admin

Сегодня вас ожидает путешествие в мир системных инженеров aka DevOps-инженеров: выпуск про виртуализацию, контейнеризацию, оркестрацию с помощью kubernetes, и настройку конфигоd через. Docker, kubernetes, ansible, рулбуки, кублеты, хельм, докерсворм, kubectl, чарты, поды -мощная теория для чёткой практики.


В гостях System Engineers из учебного центра Слёрм и одновременно компании Southbridge Николай Месропян и Марсель Ибраев. Так что, заваривайте чаинский/кофеинский и приготовьтесь к погружению



ДОПОЛНИТЕЛЬНО:




НАВИГАЦИЯ:


0:00 Вступление
1:00 Коля о себе
5:02 Марсель о себе
11:54 О виртуализации
13:50 Отличие контейнеризации от виртуализации
17:54 Почему контейнеры работают быстро
19:05 Аналоги контейнеризации
20:35 Почему Docker захватил рынок
21:30 Про дебаг и логи в контейнера
23:18 Контейнеризация в винде
25:37 Почему нет нативного докера для винды
27:20 WSL
27:58 Про оркестрацию
30:30 Типичные примеры применения оркестратора
32:18 Кубернетис это только про контейнеры?
33:43 Конкуренты Docker
34:45 Как работает kubernetes
47:35 Опять про дебаг и кублеты
50:08 Для каких архитектурных мощностей хорош кубик
50:34 Про поды
51:51 Что с базами данных
1:00:45 Helm & чарты
1:05:11 Statefull-приложения и их разворачивание
1:07:30 Безопасность кубика
1:15:35 Навыки для работы с кубиком
1:16:32 Когда кубик использовать не стоит
1:18:02 Разница между ансиблом и кубиком
1:19:26 Что такое ansible и зачем это нужно
1:22:38 Как работает ансибл
1:26:15 Из чего состоит ансибл
1:33:20 Тесты конфигов
1:37:04 Нужно ли программирование, что бы работать с ансиблом
1:39:20 Навыки для работы с ансиблом
1:42:51 Про Слёрм и ламповый формат образования
1:53:48 Онлайн и корона сказался на качестве получения знаний?
1:57:35 Кто клиент Слёрма и какой порог входа на курсы
1:59:53 КОНКУРС


May the Kubernetes be with you!

Подробнее..

Прости, OpenShift, мы недостаточно ценили тебя и принимали как должное

15.10.2020 12:13:13 | Автор: admin
Этот пост написан поскольку у наших сотрудников было довольно много разговоров с клиентами о разработке приложений на Kubernetes и о специфике такой разработки на OpenShift.



Начинаем мы обычно с тезиса, что Kubernetes это просто Kubernetes, а OpenShift это уже Kubernetes-платформа, как Microsoft AKS или Amazon EKS. У каждой из этих платформ есть свои плюсы, ориентированные на ту или иную целевую аудиторию. И после этого разговор уже перетекает в сравнение сильных и слабых сторон конкретных платформ.

В общем, мы думали написать этот пост с выводом типа Слушайте, да без разницы, где запускать код, на OpenShift или на AKS, на EKS, на каком-то кастомном Kubernetes, да на каком-угодно-Kubernetes (для краткости назовем его КУК) это реально просто, и там, и там.

Затем мы планировали взять простейший Hello World и на его примере показать, что общего и в чем различия между КУК и Red Hat OpenShift Container Platform (далее, OCP или просто OpenShift).

Однако по ходу написания этого поста, мы поняли, что так давно и сильно привыкли использовать OpenShift, что просто не осознаем, как он вырос и превратился в потрясающую платформу, которая стала гораздо больше, чем просто Kubernetes-дистрибутив. Мы привыкли воспринимать зрелость и простоту OpenShift как должное, упуская из виду его великолепие.

В общем, пришла пора деятельного раскаяния, и сейчас мы пошагово сравним ввод в строй своего Hello World на КУК и на OpenShift, и сделаем это максимально объективно (ну разве что выказывая иногда личное отношение к предмету). Если вам интересно сугубо субъективное мнение по этому вопросу, то его можно прочитать здесь (EN). А в этом посте мы будем придерживаться фактов и только фактов.

Кластеры


Итак, для нашего Hello World нужны кластеры. Сразу скажем нет всяким публичным облакам, чтобы не платить за сервера, реестры, сети, передачу данных и т.д. Соответственно, мы выбираем простой одноузловой кластер на Minikube (для КУК) и Code Ready Containers (для кластера OpenShift). Оба этих варианта реально просты в установке, но потребуют довольно много ресурсов на вашем ноуте.



Сборка на КУК-е


Итак, поехали.

Шаг 1 собираем наш контейнерный образ


Начнем я с того, что развернем наш Hello World на minikube. Для этого потребуется:

  1. 1. Установленный Docker.
  2. 2. Установленный Git.
  3. 3. Установленный Maven (вообще-то в этом проекте используется mvnw-бинарник, так что можно обойтись и без этого).
  4. 4. Собственно, сам исходник, т.е. клон репозитория github.com/gcolman/quarkus-hello-world.git

Первым делом надо создать проект Quarkus. Не пугайтесь, если никогда не работали с сайтом Quarkus.io это легко. Просто выбираете компоненты, которые хотите использовать в проекте (RestEasy, Hibernate, Amazon SQS, Camel и т.д.), а дальше Quarkus уже сам, без какого-либо вашего участия, настраивает архетип maven и выкладывает всё на github. То есть буквально один клик мышью и готово. За это мы и любим Quarkus.



Самый простой способ собрать наш Hello World в контейнерный образ использовать расширения quarkus-maven для Dockerа, которые и проделают всю необходимую работу. С появлением Quarkus это стало действительно легко и просто: добавляете расширение container-image-docker и можете создавать образы командами maven.

./mvnw quarkus:add-extension -Dextensions=container-image-docker

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



./mvnw -X clean package -Dquarkus.container-image.build=true

Вот, собственно, и всё, теперь можно запускать контейнер командой docker run, подмапив наш сервис на порт 8080, чтобы к нему можно было обращаться.

docker run -i  rm -p 8080:8080 gcolman/quarkus-hello-world




После того, как контейнерный инстанс запустился, остается только проверить командой curl, что наш сервис работает:



Итак, всё работает, и это было действительно легко и просто.

Шаг 2 отправляем наш контейнер в репозиторий контейнерных образов


Пока что созданный нами образ хранится локально, в нашем локальном контейнерном хранилище. Если мы хотим использовать этот образ в своей среде КУК, то его надо положить в какой-то другой репозиторий. В Kubernetes нет таких функций, поэтому мы будем использовать dockerhub. Потому что, во-первых, он бесплатный, а во-вторых, (почти) все так делают.

Это тоже очень просто, и нужен здесь только аккаунт на dockerhub.

Итак, ставим dockerhub и отправляем туда наш образ.



Шаг 3 запускаем Kubernetes


Есть много способов собрать конфигурацию kubernetes для запуска нашего Hello World, но мы будем использовать наипростейший из них, уж такие мы люди

Для начала запускаем кластер minikube:

minikube start

Шаг 4 развертываем наш контейнерный образ


Теперь надо преобразовать наш код и контейнерный образ в конфигурации kubernetes. Иначе говоря, нам нужен pod и deployment definition с указанием на наш контейнерный образ на dockerhub. Один из самых простых способов это сделать запустить команду create deployment, указав на наш образ:



kubectl create deployment hello-quarkus  image =gcolman/quarkus-hello-world:1.0.0-SNAPSHOT

Этой командной мы сказали нашей КУК создать deployment configuration, которая должна содержать спецификацию podа для нашего контейнерного образа. Эта команда также применит эту конфигурацию к нашему кластеру minikube, и создаст deployment, который скачает наш контейнерный образ и запустит pod в кластере.

Шаг 5 открываем доступ к нашему сервису


Теперь, когда у нас есть развернутый контейнерный образ, пора подумать, как сконфигурировать внешний доступ к этому Restful-сервису, который, собственно, и запрограммирован в нашем коде.

Тут есть много способов. Например, можно использовать команду expose, чтобы автоматически создавать соответствующие Kubernetes-компоненты, такие как services и endpoints. Собственно, так мы и сделаем, выполнив команду expose для нашего deployment-объекта:

kubectl expose deployment hello-quarkus  type=NodePort  port=8080

Давайте на минутку остановимся на опции type команды expose.

Когда мы делаем expose и создаем компоненты, необходимые для запуска нашего сервиса, нам, среди прочего, надо, чтобы снаружи можно было подключиться к службе hello-quarkus, которая сидит внутри нашей программно-определяемой сети. И параметр type позволяет нам создать и подключить такие вещи, как балансировщики нагрузки, чтобы маршрутизировать трафик в эту сеть.

Например, прописав type=LoadBalancer, мы автоматически инициализируем балансировщик нагрузки в публичном облаке, чтобы подключаться к нашему кластеру Kubernetes. Это, конечно, замечательно, но надо понимать, что такая конфигурация будет жестко привязана к конкретному публичному облаку и ее будет сложнее переносить между Kubernetes-инстансам в разных средах.

В нашем примере type=NodePort, то есть обращение к нашему сервису идет по IP-адресу узла и номеру порта. Такой вариант позволяет не использовать никакие публичные облака, но требует ряд дополнительных шагов. Во-первых, нужен свой балансировщик нагрузки, поэтому мы развернем в своем кластере балансирощик нагрузки NGINX.

Шаг 6 ставим балансировщик нагрузки


У minikube есть ряд платформенных функций, облегчающих создание необходимых для доступа извне компонентов, вроде ingress-контроллеров. Minikube идет в комплекте с ingress-контроллером Nginx, и нам остается только включить его и настроить.

minikube addons enable ingress

Теперь мы всего одной командой создам ingress-контроллер Nginx, который будет работать внутри нашего кластера minikube:

ingress-nginx-controller-69ccf5d9d8-j5gs9 1/1 Running 1 33m

Шаг 7 Настраиваем ingress


Теперь нам надо настроить ingress-контроллер Nginx, чтобы он воспринимал запросы hello-quarkus.





И, наконец, нам надо применить эту конфигурацию.



kubectl apply -f ingress.yml




Поскольку мы делаем все это на своем компе, то просто добавляем IP-адрес своей ноды в файл /etc/ hosts, чтобы направлять http-запросы к нашему minikube на балансировщик нагрузки NGINX.

192.168.99.100 hello-quarkus.info

Всё, теперь наш сервис minikube доступен снаружи через ingress-контроллер Nginx.



Ну что, это же было легко, да? Или не очень?





Запуск на OpenShift (Code Ready Containers)


А теперь посмотрим, как это все делается на Red Hat OpenShift Container Platform (OCP).

Как в случае с minikube, мы выбираем схему с одноузловым кластером OpenShift в форме Code Ready Containers (CRC). Раньше это называлось minishift и базировалось на проекте OpenShift Origin, а теперь это CRC и построено на Red Hatовской OpenShift Container Platform.

Здесь мы, извините, не можем сдержаться и не сказать: OpenShift прекрасен!

Изначально мы думали написать, что разработка на OpenShift ничем не отличается от разработки на Kubernetes. И по сути так оно и есть. Но в процессе написания этого поста мы вспомнили, сколько лишних движений приходится совершать, когда у вас нет OpenShift, и поэтому он, повторимся, прекрасен. Мы любим, когда всё делается легко, и то, как легко по сравнению с minikube наш пример развертывается и запускается на OpenShift, собственно, и подвигло нас написать этот пост.

Давайте пробежимся по процессу и посмотрим, что нам потребуется сделать.

Итак, в примере с minikube мы начинали с Docker Стоп, нам больше не надо, чтобы на машине был установлен Docker.

И локальный git нам не нужен.
И Maven не нужен.
И не надо руками создавать контейнерный образ.
И не надо искать какой-нибудь репозиторий контейнерных образов.
И не надо устанавливать ingress-контроллер.
И конфигурировать ingress тоже не надо.


Вы поняли, да? Чтобы развернуть и запустить наше приложение на OpenShift, не нужно ничего из вышеперечисленного. А сам процесс выглядит следующим образом.

Шаг 1 Запускаем свой кластер OpenShift


Мы используем Code Ready Containers от Red Hat, который по сути есть тот же Minikube, но только с полноценным одноузловым кластером Openshift.

crc start

Шаг 2 Выполняем сборку и развертывание приложения в кластере OpenShift


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

OpenShift всегда строился как платформа для создания и запуска контейнерных приложений. Сборка контейнеров всегда была неотъемлемой частью этой платформы, поэтому здесь есть куча дополнительных Kubernetes-ресурсов для соответствующих задач.

Мы будем использовать OpenShift'овский процесс Source 2 Image (S2I), у которого есть несколько различных способов для того, чтобы взять наш исходник (код или двоичные файлы) и превратить его в контейнерный образ, запускаемый в кластере OpenShift.

Для этого нам понадобятся две вещи:

  • Наш исходный код в репозитории git
  • Builder-образ, на основании которого будет выполняться сборка.

Существует множество таких образов, сопровождаемых как силами Red Hat, так и на уровне сообщества, и мы воспользуемся образом OpenJDK, ну поскольку я же собираю Java-приложение.

Запустить S2I-сборку можно, как и из графической консоли OpenShift Developer, так и из командной строки. Мы воспользуемся командой new-app, указав ей, где брать builder-образ и наш исходный код.



oc new-app registry.access.redhat.com/ubi8/openjdk-11:latest~https://github.com/gcolman/quarkus-hello-world.git

Всё, наше приложение создано. При этом процесс S2I выполнил следующие вещи:

  • Создал служебный build-pod для всяких вещей, связанных со сборкой приложения.
  • Создал конфиг OpenShift Build.
  • Скачал builder-образ во внутренний docker-реестр OpenShift.
  • Клонировал Hello World в локальный репозиторий.
  • Увидел, что там есть maven pom, и поэтому скомпилировал приложение с помощью maven.
  • Создал новый контейнерный образ, содержащий скомпилированное Java-приложение, и положил этот образ во внутренний реестр контейнеров.
  • Создал Kubernetes Deployment со спецификациями podа, сервиса и т.д.
  • Запустил deploy контейнерного образа.
  • Удалил служебный build-pod.

В этом списке много всего, но главное, что вся сборка происходит исключительно внутри OpenShift, внутренний Docker-реестр находится внутри OpenShift, а процесс сборки создает все компоненты Kubernetes и запускает их в кластере.

Если визуально отслеживать запуск S2I в консоли, то можно видно, как при выполнении сборки запускается build pod.



А теперь взглянем логи builder podа: во-первых, там видно, как maven делает свою работу и скачивает зависимости для сборки нашего java-приложения.



После того, как закончена maven-сборка, запускается сборка контейнерного образа, и затем этот собранный образ отправляется во внутренний репозиторий.



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

oc get service




Вот и всё. И всего одна команда. Нам остается только сделать expose этого сервиса для доступа извне.

Шаг 3 делаем expose сервиса для доступа извне


Как и в случае КУК, на платформе OpenShift нашему Hello World тоже нужен роутер, чтобы направлять внешний трафик на сервис внутри кластера. В OpenShift это делает очень просто. Во-первых, в кластере по умолчанию установлен компонент маршрутизации HAProxy (его можно поменять на тот же NGINX). Во-вторых, здесь есть специальные и допускающие широкие возможности настройки ресурсы, которые называются Routes и напоминают Ingress-объекты в старом добром Kubernetes (на самом деле OpenShiftовкие Routes сильно повлияли на дизайн Ingress-объектов, которые теперь можно использовать и в OpenShift), но для нашего Hello World, да и почти во всех остальных случаях, нам хватит стандартного Route без дополнительной настройки.

Чтобы создать для Hello World маршрутизируемый FQDN (да, в OpenShiift есть свой DNS для маршрутизации по именам сервисов), мы просто выполним expose для нашего сервиса:



oc expose service quarkus-hello-world

Если посмотреть только что созданный Route, то там можно найти FQDN и другие сведения по маршрутизации:

oc get route




И наконец, обращаемся к нашему сервису из браузера:



А вот теперь это было действительно легко!


Мы любим Kubernetes и всё, что позволяет делать эта технология, а также мы любим простоту и легкость. Kubernetes создавался, чтобы невероятно упростить эксплуатацию распределенных масштабируемых контейнеров, но вот для ввода в строй приложений его простоты сегодня уже недостаточно. И здесь в игру вступает OpenShift, который идет в ногу со временем и предлагает Kubernetes, ориентированный в первую очередь на разработчика. Была вложена масса усилий, чтобы заточить платформу OpenShift именно под разработчика, включая создание таких инструментов, как S2I, ODI, Developer Portal, OpenShift Operator Framework, интеграция с IDE, Developer Catalogues, интеграция с Helm, мониторинг и многие другие.

Надеемся, что эта статься была для вас интересной и полезной. А найти дополнительные ресурсы, материалы и другие полезные для разработки на платформе OpenShift вещи можно на портале Red Hat Developers.

Перевод, источник: itnext.io/im-sorry-openshift-i-ve-taken-you-for-granted-the-evidence-dd7a7d471fa1
Подробнее..

Kubernetes мониторинг c помощью Prometheus

29.10.2020 14:07:14 | Автор: admin


В этои статье я постарался показать, как можно использовать Prometheus в качестве системы мониторинга для микросервиснои архитектуры. Подробно рассмотрел архитектуру Prometheus и взаимодеиствие его компонентов. Обозначил ключевые характеристики благодаря чему эта система получила такое широкое распространение в средах использующих контеинеризацию. Предупреждаю сразу: статья получилась довольно объемной. Эта статься будет полезна для начинающих DevOps специалистов, которые планируют или уже используют в своеи работе Docker, Kubernetes. Итак, начнем!



Что такое Prometheus?


Prometheus система мониторинга, разработанная специально для динамически изменяющейся среды. Кроме того, она может использоваться для традиционной инфраструктуры, например, на физических серверах с приложениями, развернутыми непосредственно на ОС. На сегодняшний день Prometheus занимает лидирующую позицию среди инструментов, применяемых в мире микросервисов. Чтобы понять, почему Prometheus пользуется такой популярностью, давайте рассмотрим несколько примеров.


В качестве среды для разворачивания системы будем использовать Kubernetes.


Цель настроить в Prometheus мониторинг для Redis-кластера в Kubernetes. Графический интерфейс Grafana. Для оповещения задействуем email и Slack.


Основные компоненты Prometheus


"

В центре Prometheus сервер, который выполняет работу по мониторингу. Он состоит из следующих частей:


  • Time Series Data Base (TSDB) база данных, в которой хранятся метрики, полученные от целевых объектов. Например, CPU usage, Memory utilization или количество запросов к сервису

  • Retrieval worker отвечает за получение этих метрик с целевых ресурсов и размещение данных в TSDB

  • HTTP server API для выполнения запросов к сохраненным в TSDB данным. Используется для отображения данных на дашборде в Prometheus или сторонних системах визуализации таких, как Grafana.

От теории к практике


Давайте развернем наш сервер Prometheus с помощью пакетного менеджера Helm.



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

Для этого потребуются рабочий кластер Kubernetes и настроенный kubectl. Можно попробовать использовать кластер Kubernetes в MCS. Сервер Prometheus устанавливается достаточно просто:


#Добавляем репозиторийhelm repo add stable https://kubernetes-charts.storage.googleapis.com#Обновляемhelm repo update#Создаем namespacekubectl create namespace monitoring#Устанавливаем helm c именем my-prometheushelm install my-prometheus stable/prometheus -n monitoring

В результате выполнения мы получаем:


The Prometheus PushGateway can be accessed via port 9091 on the following DNS name from within your cluster:my-prometheus-pushgateway.monitoring.svc.cluster.localGet the PushGateway URL by running these commands in the same shell:  export POD_NAME=$(kubectl get pods --namespace monitoring -l "app=prometheus,component=pushgateway" -o jsonpath="{.items[0].metadata.name}")  kubectl --namespace monitoring port-forward $POD_NAME 9091For more information on running Prometheus, visit:https://prometheus.io/

Проверяем, все ли поды запущены:


kubectl get pods -n monitoringNAME                                               READY   STATUS    RESTARTS   AGEmy-prometheus-alertmanager-86986878b5-5w2vw        2/2     Running   0          76smy-prometheus-kube-state-metrics-dc694d6d7-xk85n   1/1     Running   0          76smy-prometheus-node-exporter-grgqw                  1/1     Running   0          76smy-prometheus-node-exporter-njksq                  1/1     Running   0          76smy-prometheus-node-exporter-pcmgv                  1/1     Running   0          76smy-prometheus-pushgateway-6694855f-n6xwt           1/1     Running   0          76smy-prometheus-server-77ff45bc6-shrmd               2/2     Running   0          76s

При выводе Helm chart предлагает выполнить port-forward для pushgateway, чтобы получить доступ к интерфейсу Prometheus. Меняем component на server и выполняем:


#получаем название контейнера и сохраняем его в переменную POD_NAMEexport POD_NAME=$(kubectl get pods --namespace monitoring -l "app=prometheus,component=server" -o jsonpath="{.items[0].metadata.name}")#пробрасываем портkubectl --namespace monitoring port-forward $POD_NAME 9090

Теперь открываем http://127.0.0.1:9090/ в браузере и видим интерфейс Prometheus:



Итак, у нас есть сервер Prometheus, развернутый внутри кластера Kubernetes. Теперь давайте развернем Redis, который впоследствии и поставим на мониторинг в Prometheus. Для этого также используем Helm:


#добавляем репозиторийhelm repo add bitnami https://charts.bitnami.com/bitnami#Обновляемhelm repo update#Создаем namespacekubectl create namespace redis#Устанавливаем helm c именем redishelm install redis bitnami/redis -n redis --set cluster.enabled=true --set cluster.slaveCount=2 --set master.persistence.enabled=false --set slave.persistence.enabled=false

Параметры cluster.enabled и cluster.slaveCount определяют, что Redis будет развернут в режиме кластер, и в этом кластере два пода будут работать как slave. В параметрах указываем: не использовать persistent volume для master и slave (persistence.enabled=false). Сейчас это нужно, чтобы продемонстрировать работу Redis. В продакшене будет необходимо сделать настройку persistent volume.

Проверяем, что все поды redis запущены:


kubectl get pod -n redis NAME             READY   STATUS    RESTARTS   AGEredis-master-0   1/1     Running   0          65sredis-slave-0    1/1     Running   0          65sredis-slave-1    1/1     Running   0          28s

Теперь, когда у нас развернуты Prometheus и Redis, нужно настроить их взаимодействие.



Targets и metrics


Prometheus-сервер может мониторить самые разные объекты к примеру, Linux- и Windows-серверы. Это может быть база данных или приложение, которое предоставляет информацию о своем состоянии. Такие объекты в Prometheus называются targets. Каждый объект имеет так называемые единицы мониторинга. Для Linux-сервера это может быть текущая утилизация CPU, использование memory и диска. Для приложения количество ошибок, количество запросов и время их выполнения. Эти единицы называются metrics и хранятся в TSDB.



Exporters


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


 scrape_configs:      job_name: prometheus       static_configs:          targets:            localhost:9090

Указываем серверу Prometheus забирать метрики из конечной точки: localhost:9090/metrics.


Для сервисов, которые не могут самостоятельно предоставлять метрики в формате Prometheus, нужно установить дополнительный компонент exporters. Обычно exporters скрипт или сервис, который получает метрики от цели, конвертирует их формат, который понимает Prometheus, и предоставляет эти данные серверу по пути /metrics. Prometheus имеет большой набор готовых exporters для разных сервисов эти компоненты можно использовать для HAProxy, Linux system, облачных платформ и др.


Redis и exporter



Давайте подключим exporter для нашего Redis-кластера. Сначала потребуется создать файл values.yaml со следующим содержанием:


cluster: enabled: true slaveCount: 2 #### Redis Master parameters##master: persistence:   enabled: false extraFlags:    "--maxmemory 256mb" slave: persistence:   enabled: false extraFlags:    "--maxmemory 256mb"  ## Prometheus Exporter / Metrics##metrics: enabled: true  image:   registry: docker.io   repository: bitnami/redis-exporter   tag: 1.4.0-debian-10-r3   pullPolicy: IfNotPresent  ## Metrics exporter pod Annotation and Labels podAnnotations:   prometheus.io/scrape: "true"   prometheus.io/port: "9121"

Здесь параметры, которые использовались в командной строке для настройки Redis в режиме cluster, перенесены в yaml-файл. Для сбора метрик добавлено подключение redis-exporter.


Обновляем Redis с новыми параметрами:


helm upgrade redis -f redis/values.yaml bitnami/redis -n redis

Проверяем, что поды Redis запущены:


kubectl get pods -n redisredis-master-0   2/2     Running   0          3m40sredis-slave-0    2/2     Running   0          2m4sredis-slave-1    2/2     Running   0          2m16s

Теперь к каждому pod привязан дополнительный контейнер redis-exporter, который предоставляет доступ к метрикам Redis.


Добавлять снятие метрик для развернутых контейнеров redis-exporter в конфигурацию Prometheus не нужно, так как он по умолчанию содержит kubernetes_sd_configs (листинг представлен ниже). За счёт этого динамически подключается сбор метрик для вновь появившихся подов. Подробнее об этом можно прочитать в документации.

   job_name: 'kubernetes-pods'       kubernetes_sd_configs:          role: pod       relabel_configs:          source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]           action: keep           regex: true          source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]           action: replace           target_label: __metrics_path__           regex: (.+)          source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]           action: replace           regex: ([^:]+)(?::\d+)?;(\d+)           replacement: $1:$2           target_label: __address__          action: labelmap           regex: __meta_kubernetes_pod_label_(.+)          source_labels: [__meta_kubernetes_namespace]           action: replace           target_label: kubernetes_namespace          source_labels: [__meta_kubernetes_pod_name]           action: replace           target_label: kubernetes_pod_name


Давайте убедимся, что мы получаем данные о состоянии Redis. Для этого можно открыть интерфейс и ввести PromQL-запрос redis_up:


"

В отличие от других систем мониторинга, Prometheus не получает данные от целевых сервисов, а самостоятельно забирает с указанных в конфигурации endpoints это одна из его важнейших характеристик. Когда вы работаете с большим количеством микросервисов, и каждый из них отправляет данные в систему мониторинга, вы можете столкнуться с риском отправки слишком большого количества данных на сервер Prometheus, а это может привести его выходу из строя. Prometheus предоставляет централизованное управление сбором метрик, т.е. вы самостоятельно решаете, откуда и как часто забирать данные. Еще одно преимущество использования Prometheus возможность динамически получать источники данных с помощью функции service discovery, работа которой была продемонстрирована выше на примере Redis-подов.


Но бывает случаи, когда необходимо получать данные от источника временно, и у Prometheus нет необходимости забирать их с сервиса постоянно (например, запланированные задания по крону, снятие бэкапов и т.д.). Для таких случаев Prometheus предлагает pushgateway, чтобы сервисы могли отправлять свои метрики в базу данных Prometheus. Использование pushgateway скорее исключение, чем правило, но о его возможности не стоит забывать.


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


Alertmanager и Alerting rules


За отправку предупреждений в Prometheus отвечает компонент AlertManager. В качестве каналов оповещения могут выступать: email, slack и другие клиенты. Для настройки оповещения необходимо обновить конфигурацию файла alertmanager.yml.


Создадим файл prometheus/values.yaml со следующим содержимым:


## alertmanager ConfigMap entries##alertmanagerFiles: alertmanager.yml:   global:     slack_api_url: <secret>    route:     receiver: slack-alert     group_by:        redis_group     repeat_interval: 30m     routes:        match:           severity: critical         receiver: slack-alert    receivers:      name: slack-alert       slack_configs:          channel: 'general'           send_resolved: true           color: '{{ if eq .Status "firing" }}danger{{ else }}good{{ end }}'           title: '[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing             | len }}{{ end }}] {{ .CommonAnnotations.summary }}'           text: |-             {{ range .Alerts }}               *Alert:* {{ .Annotations.summary }}  *{{ .Labels.severity | toUpper }}* on {{ .Labels.instance }}               *Description:* {{ .Annotations.description }}               *Details:*               {{ range .Labels.SortedPairs }}  *{{ .Name }}:* `{{ .Value }}`               {{ end }}             {{ end }}

Slack_api_url должен содержать ключ, который можно получить на сайте Slack.


В поле channel указывается канал, на который будут приходить оповещения. В нашем случае это general. Остальные параметры отвечают за формат уведомлений.


Конфигурацию, описанную далее, можно найти в репозитории.


Обновляем my-prometheus:


helm upgrade my-prometheus -f prometheus/values.yaml stable/prometheus -n monitoring

Для того, чтобы получить доступ к интерфейсу Alertmanager, выполним следующее:


export POD_NAME=$(kubectl get pods --namespace monitoring -l "app=prometheus,component=alertmanager" -o jsonpath="{.items[0].metadata.name}")kubectl --namespace monitoring port-forward $POD_NAME 9093

Теперь в интерфейсе Alertmanager можно убедиться, что появилась конфигурация для отправки оповещения в канал Slack http://127.0.0.1:9093/#/status:



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


Правила представляют собой значения метрик или их совокупности, объединенные логическим условием. При выходе значения метрики за допустимые пределы Prometheus обращается к Alertmanager, чтобы отправить нотификации по определенным в нем каналам. Например, если метрика redis_up вернет значение 0, сработает уведомление о недоступности того или иного узла кластера.


Добавляем в файл prometheus/values.yaml стандартные правила для сигнализации о проблемах в Redis:


serverFiles:  ## Alerts configuration ## Ref: https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/ alerting_rules.yml:   groups:      name: redis_group       rules:          alert: redis_is_running           expr: redis_up == 0           for: 30s           labels:             severity: critical           annotations:             summary: "Critical: Redis is down on the host {{ $labels.instance }}."             description: "Redis has been down for more than 30 seconds"          alert: redis_memory_usage           expr:  redis_memory_used_bytes / redis_memory_max_bytes * 100 > 40           for: 5m           labels:             severity: warning           annotations:             description: "Warning: Redis high memory(>40%) usage on the host {{ $labels.instance }} for more than 5 minutes"             summary: "Redis memory usage {{ humanize $value}}% of the host memory"          alert: redis_master           expr: redis_connected_clients{instance!~"server1.mydomain.com.+"} > 50           for: 5m           labels:             severity: warning           annotations:             description: "Warning: Redis has many connections on the host {{ $labels.instance }} for more than 5 minutes"             summary: "Redis number of connections {{ $value }}"          alert: redis_rejected_connections           expr: increase(redis_rejected_connections_total[1m]) > 0           for: 30s           labels:             severity: critical           annotations:             description: "Critical: Redis rejected connections on the host {{ $labels.instance }}"             summary: "Redis rejected connections are {{ $value }}"          alert: redis_evicted_keys           expr: increase(redis_evicted_keys_total[1m]) > 0           for: 30s           labels:             severity: critical           annotations:             description: "Critical: Redis evicted keys on the host {{ $labels.instance }}"             summary: "Redis evicted keys are {{ $value }}"

Обновляем my-prometheus:


helm upgrade my-prometheus -f prometheus/values.yaml stable/prometheus -n monitoring

Для проверки работы нотификации в Slack, изменим правило алерта redis_memory_usage:


 expr:  redis_memory_used_bytes / redis_memory_max_bytes * 100 < 40

Снова обновляем my-prometheus:


helm upgrade my-prometheus -f prometheus/values.yaml stable/prometheus -n monitoring

Переходим на страницу http://127.0.0.1:9090/alerts:



Redis_memory_usage перешел в статус pending. Значение выражения для всех трех подов чуть больше 0.72. Далее уведомление проходит в Slack:



Теперь добавляем нотификацию по email. При этом конфигурация alermanager.yml изменится так:


alertmanagerFiles: alertmanager.yml:   global:     slack_api_url: <secret>    route:     receiver: slack-alert     group_by:        redis_group     repeat_interval: 30m     routes:        match:           severity: critical         receiver: slack-alert         continue: true        match:           severity: critical         receiver: email-alert    receivers:      name: slack-alert       slack_configs:          channel: 'general'           send_resolved: true           color: '{{ if eq .Status "firing" }}danger{{ else }}good{{ end }}'           title: '[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing             | len }}{{ end }}] {{ .CommonAnnotations.summary }}'           text: |-             {{ range .Alerts }}               *Alert:* {{ .Annotations.summary }}  *{{ .Labels.severity | toUpper }}* on {{ .Labels.instance }}               *Description:* {{ .Annotations.description }}               *Details:*               {{ range .Labels.SortedPairs }}  *{{ .Name }}:* `{{ .Value }}`               {{ end }}             {{ end }}      name: email-alert       email_configs:        to: alert@agima.ru         send_resolved: true         require_tls: false         from: alert@agima.ru         smarthost: smtp.agima.ru:465         auth_username: "alert@agima.ru"         auth_identity: "alert@agima.ru"         auth_password: <secret>

В блок routes добавляем еще один путь для нотификации: receiver: email-alert. Ниже описываем параметры для email. В этом случае при том или ином событии мы получим уведомления одновременно в Slack и на email:




Blackbox exporter


Теперь рассмотрим, как в Prometheus добавляются targets на примере контейнера blackbox exporter, который позволяет организовать мониторинг внешних сервисов по протоколам HTTP(s), DNS, TCP, ICMP.


Для установки blackbox exporter используем Helm:


helm install blackbox-exporter stable/prometheus-blackbox-exporter --namespace monitoring 

Проверяем, что поды blackbox exporter запущены:


kubectl get pods -n monitoring | grep blackboxblackbox-exporter-prometheus-blackbox-exporter-df9f6d679-tvhrp   1/1     Running   0          20s

Добавляем в файл prometheus/values.yaml следующую конфигурацию:


extraScrapeConfigs: |  job_name: 'prometheus-blackbox-exporter'   metrics_path: /probe   params:     module: [http_2xx]   static_configs:      targets:        https://example.org    relabel_configs:      source_labels: [__address__]       target_label: __param_target      source_labels: [__param_target]       target_label: instance      target_label: __address__       replacement: blackbox-exporter-prometheus-blackbox-exporter.monitoring.svc.cluster.local:9115

Указываем, откуда собирать метрики:


blackbox-exporter-prometheus-blackbox-exporter.monitoring.svc.cluster.local:9115/probe. В качестве targets указываем URL сервиса для мониторинга: https://example.org. Для проверки используется модуль http_2xx, который по умолчанию устанавливается в blackbox exporter. Конфигурация проверки:


secretConfig: falseconfig: modules:   http_2xx:     prober: http     timeout: 5s     http:       valid_http_versions: ["HTTP/1.1", "HTTP/2"]       no_follow_redirects: false       preferred_ip_protocol: "ip4"

Обновляем конфигурацию my-prometheus:


helm upgrade my-prometheus -f prometheus/values.yaml stable/prometheus -n monitoring

В интерфейсе Prometheus http://127.0.0.1:9090/targets проверяем, что у нас появилась конечная точка для сбора метрик:



Чтобы расширить область проверки, добавляем http_2xx_check-модуль, который, помимо валидации версии и статуса 200 http, будет проверять наличие заданного текста в теле ответа:


secretConfig: falseconfig: modules:   http_2xx:     prober: http     timeout: 5s     http:       valid_http_versions: ["HTTP/1.1", "HTTP/2"]       no_follow_redirects: false       preferred_ip_protocol: "ip4"       valid_status_codes: [200]   http_2xx_check:     prober: http     timeout: 5s     http:       method: GET       fail_if_body_not_matches_regexp:        "Example Domain"       fail_if_not_ssl: true       preferred_ip_protocol: ip4       valid_http_versions: ["HTTP/1.1", "HTTP/2"]       valid_status_codes: [200]

Обновляем конфигурацию blackbox-exporter/values.yaml:


helm upgrade blackbox-exporter -f blackbox-exporter/values.yaml stable/prometheus-blackbox-exporter --namespace monitoring

Изменяем в файле prometheus/values.yaml модуль http_2xx на http_2xx_check:


extraScrapeConfigs: |  job_name: 'prometheus-blackbox-exporter'   metrics_path: /probe   params:     module: [http_2xx]

Описания проверок, которые можно делать с blackbox exporter, приведены в документации.


Теперь добавим правила для сигнализации в Prometheus в файл prometheus/values.yaml:


   name: http_probe       rules:          alert: example.org_down           expr: probe_success{instance="http://personeltest.ru/aways/example.org",job="prometheus-blackbox-exporter"} == 0           for: 5s           labels:             severity: critical           annotations:             description: '{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 1 minutes.'             summary: 'Instance {{ $labels.instance }} down'

И обновляем конфигурацию my-prometheus:


helm upgrade my-prometheus -f prometheus/values.yaml stable/prometheus -n monitoring

Для проверки можно изменить в blackbox-exporter/values.yaml значение текста для модуля http_2xx_check, который ищется в теле ответа, и обновить blackbox exporter. Должна сработать нотификация в Slack и Email.



Grafana


Настала очередь визуализации. Для отображения графиков будем использовать
Grafana.


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


helm install my-grafana bitnami/grafana -n monitoring --set=persistence.enabled=false

В параметрах указываем не использовать persistent volume (--set=persistence.enabled=false), чтобы только продемонстрировать работу grafana. В продакшн- среде нужно настроить хранилище, так как поды по своей природе эфемерны, и есть риск потерять настройки Grafana.


Должен получиться вот такой вывод:


2. Get the admin credentials:    echo "User: admin"    echo "Password: $(kubectl get secret my-grafana-admin --namespace monitoring -o jsonpath="{.data.GF_SECURITY_ADMIN_PASSWORD}" | base64 --decode)"

Проверяем, что под Grafana запущен:


$kubectl get pods -n monitoring | grep grafanaNAME                                               READY   STATUS    RESTARTS   AGEmy-grafana-67c9776d7-nwbqj                         1/1     Running   0          55s

Перед тем как открыть интерфейс Grafana, нужно получить пароль от пользователя admin, сделать это можно так:


$echo "Password: $(kubectl get secret my-grafana-admin --namespace monitoring -o jsonpath="{.data.GF_SECURITY_ADMIN_PASSWORD}" | base64 --decode)"

Затем пробрасываем порт Grafana:


kubectl port-forward -n monitoring svc/my-grafana 8080:3000

Открываем http://127.0.0.1:9090/ в браузере и авторизуемся:



Для того чтобы Grafana могла получать значения метрик, хранящихся в базе данных Prometheus, необходимо подключить его. Переходим на http://127.0.0.1:8080/datasources и добавляем data source. В качестве TSDB выбираем Prometheus, который доступен в нашем кластере по адресу my-prometheus-server.monitoring.svc.cluster.local.


Должно получиться примерно так:



После добавления data source нужно добавить dashboard в Grafana чтобы состояния показателей Redis-кластера отображались на графиках. Переходим на http://127.0.0.1:8080/dashboard/import и добавляем id = 763. Итог:



После импорта получаем следующий dashboard с виджетами, которые отображают собранные метрики c кластера Redis:



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


Вот, в принципе, и всё. Надеюсь, что сумел рассказать вам что-то новое. А главное убедил, что пользоваться Prometheus просто и удобно!

Подробнее..

HelmWave v0.5.0 GitOps для твоего Kubernetes

14.12.2020 22:19:31 | Автор: admin

preview


Helm, как и Docker стал де-факто стандартом в индустрии. Когда мы обсуждаем Kubernetes (52%). И новость, что Docker is deprecated вызвало волну обсуждений в сообществе. Настолько все привыкли к Docker.


Для Docker есть замечательный по своей простоте docker-compose, в котором мы можем декларативно описать, что мы хотим от Docker.


Для Kubernetes набор yaml-tpl файлов упаковывается в архив. И затем этот архив называется Helm-чартом. Но как это часто бывает приложение не может быть описано лишь одним Helm чартом. Требуется как-то управлять/композить/настраивать/шаблонизировать такие сеты.


Одним из подходов по управлению является Umbrella Chart. Это helm chart который объединяет в себе все другие чарты.


Очевидные минусы данного решения:


  • Требуется поддерживать дополнительный чарт
  • Новый слой согласования имен values переменных.
  • Umbrella-chart это все тот же чарт, поэтому о шаблонизации values и декларативном разделении на контуры (Окружения) не может быть и речи.
  • Когда обновляется саб-чарт, нужно идти в umbrella и обновлять еще версию umbrella чарта.

Helmwave возник, как инструмент для декларативного описания всех чартов в одном yaml.
Этот пост покажет как можно решить основные проблемы (use-cases) с помощью helmwave.


Что такое HelmWave?


  • Это бинарь, который устанавливает helm release из helmwave.yml.
  • Кладешь helmwave.yml в git и применяешь его через CI.
  • Можно шаблонизировать все c помощью (Go template), начиная от helmwave.yml до values.
  • Helmwave понимает какие helm-repositories ему понадобятся для деплоя. И вытесняет лишние.

Порядок комманд



graph TD;    Start(helmwave.yml.tpl) --render--> helmwave.yml;    helmwave.yml --planfile--> .helmwave;    .helmwave --sync--> Finish(Releases have been deployed!)

Быстрый старт


helmwave.yml.tpl имеет следующий вид


project: my-projectversion: 0.5.0repositories:  - name: bitnami    url: https://charts.bitnami.com/bitnami.options: &options  install: true  namespace: my-namespacereleases:  - name: redis-a    chart: bitnami/redis    options:      <<: *options  - name: redis-b    chart: bitnami/redis    options:      <<: *options

$ helmwave deploy

Поздравляю, вы задеплоили с помощью helmwave!


$ helm list -n my-namespaceNAME       NAMESPACE       REVISION     STATUS      CHART             APP VERSIONredis-a    my-namespace    1            deployed    redis-11.2.3      6.0.9      redis-b    my-namespace    1            deployed    redis-11.2.3      6.0.9  $ k get po -n my-namespace                                                                                                                         NAME               READY   STATUS    RESTARTS   AGEredis-a-master-0   1/1     Running   0          64sredis-a-slave-0    1/1     Running   0          31sredis-a-slave-1    1/1     Running   0          62sredis-b-master-0   1/1     Running   0          59sredis-b-slave-0    1/1     Running   0          32sredis-b-slave-1    1/1     Running   0          51s

Переменные окружения


$ helmwave help

  • $HELMWAVE_TPL_FILE отвечает за путь к входному файлу для шаблонизации (helmwave.yml.tpl).
  • $HELMWAVE_FILE указывает путь выходного файла после операции шаблонизации (helmwave.yml).
  • $HELMWAVE_PLAN_DIR указывает путь к папке, в которой хранится или будет хранится план (.helmwave/).
  • $HELMWAVE_TAGS массив строк, на основании которого будет проводится планирование.
  • $HELMWAVE_PARALLEL включает/выключает многопоточность (рекомендуется включать).
  • $HELMWAVE_LOG_FORMAT позволяет выбрать один из предустановленных форматов вывода.
  • $HELMWAVE_LOG_LEVEL позволяет управлять детализацией вывода.
  • $HELMWAVE_LOG_COLOR включает/выключает цвета для вывода.

Use-Cases


Примеры будут производиться, опираясь на gitlab-ci. Но это не помешает вам встроить helmwave в любой другой CI-инструмент.


Чем ниже, тем сложнее будут примеры.


Git tag > Docker tag


Допустим вы написали какой-то helm чарт для нашего приложения. Его values.yaml по умолчанию имеет вид:


image:  repository: registry.gitlab.local/example/app  tag: master

Необходимо чтобы image.tag брался из переменной CI


Приступим, создадим 2 файла.


. helmwave.yml.tpl values.yml

helmwave.yml.tpl


project: my-project # Имя проектаversion: 0.5.0 # Версия helmwavereleases:  - name: my-release    chart: my-chart-repo/my-app    values:      - values.yml    options:      install: true      namespace: my-namespace

values.yml


image:  tag: {{ env "CI_COMMIT_TAG" }}

Git commit --> PodAnnotations


Требуется чтобы deployment обновлялся только если у нас есть новый коммит.


deployment имеет примерно этот вид:


    ...    metadata:        {{- with .Values.podAnnotations }}        annotations:          {{- toYaml . | nindent 8 }}        {{- end }}    ...

Поэтому мы можем легко расширить предыдущий пример values.yml


image:  tag: {{ requiredEnv "CI_COMMIT_TAG" }}podAnnotations:    gitCommit: {{ requiredEnv "CI_COMMIT_SHORT_SHA" | quote }}

Контуры, окружения, environments


Структура каталога


. helmwave.yml.tpl values     _.yml     prod.yml     stage.yml

helmwave.yml.tpl


project: my-project  version: 0.5.0  releases:    - name: my-release      chart: my-chart-repo/my-app      values:        # Default        - values/_.yml        # For specific ENVIRONMENT        - values/{{ env "CI_ENVIRONMENT_NAME" }}.yml      options:        install: true        namespace: {{ env "CI_ENVIRONMENT_NAME" }}

values/_.yml Будет запускаться для любого окружения


image:  tag: {{ requiredEnv "CI_COMMIT_TAG" }}podAnnotations:    gitCommit: {{ requiredEnv "CI_COMMIT_SHORT_SHA" | quote }}

values/prod.yml Будет запускаться только для prod


replicaCount: 6

values/stage.yml Будет запускаться только для stage


replicaCount: 2

Используем внешний yaml и .Release.Store


Store это просто хранилище, которое можно задавать в helmwave.yml и передавать дальше в шаблонизацию values.


Допустим мы хотим связать путь к секрету в vault и путь к проекту в gitlab или вы хотите переопределять путь к image.repository. Это можно удобно сделать через Store.


. helmwave.yml.tpl values    _.yml vars     my-list.yaml

values/_.yml


vault: secret/{{ .Release.Store.path  }}/{{ requiredEnv "CI_ENVIRONMENT_NAME"  }}image:  repository: {{ env "CI_REGISTRY" | default "localhost:5000" }}/{{ .Release.Store.path }}

Добавим произвольный yaml файл.


vars/my-list.yaml


releases:  - name: adm-api    path: main/product/adm/api  - name: api    path: main/product/api

helmwave.yml.tpl


project: my-projectversion: 0.5.0.options: &options  install: true  wait: true  timeout: 5mreleases:  {{- with readFile "vars/my-list.yaml" | fromYaml | get "releases" }}  {{- range $v := . }}  - name: {{ $v | get "name" }}    chart: my-project/{{ $v | get "name" }}    options:      <<: *options    store:      path: {{ $v | get "path" }} # Set .Release.Store.path    tags:      - {{ $v | get "name" }}      - my    values:        # Default        - values/_.yml        # For specific ENVIRONMENT        - values/{{ env "CI_ENVIRONMENT_NAME" }}.yml  {{ end }}  {{- end }}

Запускаем!


$ CI_ENVIRONMENT_NAME=stage helmwave planfile

Появится helmwave.yml и папка .helmwave


$ tree .helmwave.helmwave planfile values     _.yml.adm-api@.plan     _.yml.api@.plan$ cat .helmwave/values/_.yml.api@.plan                            vault: secret/main/product/api/stage                                                               image:  repository: localhost:5000/main/product/api$ cat .helmwave/values/_.yml.adm-api@.plan                                  vault: secret/main/product/adm/api/stageimage:  repository: localhost:5000/main/product/adm/api

helmwave.yml


project: my-projectversion: 0.5.0.options: &options  install: true  wait: true  timeout: 5mreleases:  - name: adm-api    chart: my/adm-api    options:      <<: *options    store:      path: main/product/adm/api    tags:      - adm-api      - my    values:        # Default        - values/_.yml        # For specific ENVIRONMENT        - values/stage.yml  - name: api    chart: my/api    options:      <<: *options    store:      path: main/product/api    tags:      - api      - my    values:        # Default        - values/_.yml        # For specific ENVIRONMENT        - values/stage.yml

Отделяем продукты от инфраструктуры


Структура проекта


Создадим в папке values 2 папки


  • product здесь будут values для продуктов
  • infrastructure здесь будет инфарструктурные values

values/infrastructure


  • adminer веб морда для подключения к базе, полезна в основном только в dev-контурах
  • postgresql база данных
  • ns-ready здесь LimitRange, ResourcseQuota, Secrets, NetworkPolicy, etc
  • rabbitmq общая шина между chat и api

values/product
Приложение состоит из 3 микросервисов


  • api
  • chat
  • frontend

И еще нам понадобятся 2 отдельных файла описывающие массив product и массив infrastructure.


Структура проекта:


. helmwave.yml.tpl values    infrastructure       adminer          _.yml          dev.yml          stage.yml       ns-ready          _.yml       postgresql          _.yml          dev.yml       rabbitmq           _.yml           dev.yml           stage.yml    product        _           _.yml           dev.yml           prod.yml           stage.yml        api           _.yml           dev.yml           prod.yml           stage.yml        chat           _.yml        frontend            _.yml            dev.yml            prod.yml            stage.yml vars     infrastructure.yaml     products.yaml

vars/infrastructure.yaml


releases:  - name: postgresql    repo: bitnami    version: 8.6.13  - name: adminer    repo: cetic    version: 0.1.5  - name: rabbitmq    repo: bitnami    version: 7.6.6  - name: ns-ready    repo: my-project    version: 0.1.1

vars/products.yaml


releases:  - name: adm-api    path: rdw/sbs/adm/api  - name: frontend    path: my-project/internal/frontend  - name: api    path: my-project/internal/api  - name: chat    path: my-project/internal/chat

helmwave.yml.tpl


project: my-projectversion: 0.5.0repositories:  - name: bitnami    url: https://charts.bitnami.com/bitnami  - name: cetic    url: https://cetic.github.io/helm-charts.options: &options  install: true  wait: true  timeout: 5m  atomic: false  maxhistory: 10  namespace: {{ requiredEnv "HELM_NS" }}releases:  {{- with readFile "vars/products.yaml" | fromYaml | get "releases" }}  {{- range $v := . }}  - name: {{ $v | get "name" }}    chart: my-project/{{ $v | get "name" }}    options:      <<: *options    store:      path: {{ $v | get "path" }}    tags:      - {{ $v | get "name" }}      - product    values:      # all products & all envs      - values/product/_/_.yml      # all products & an env      - values/product/_/{{ requiredEnv "CI_ENVIRONMENT" }}.yml      # a product & all envs      - values/product/{{ $v | get "name" }}/_.yml      # a product & an env      - values/product/{{ $v | get "name" }}/{{ requiredEnv "CI_ENVIRONMENT" }}.yml  {{ end }}  {{- end }}  {{- with readFile "vars/infrastructure.yaml" | fromYaml | get "releases" }}  {{- range $v := . }}  - name: {{ $v | get "name" }}    chart: {{ $v | get "repo" }}/{{ $v | get "name" }}    options:      <<: *options      chartpathoptions:        version: {{ $v | get "version" }}    tags:      - {{ $v | get "name" }}      - infrastructure    values:      # a svc & all envs      - values/infrastructure/{{ $v | get "name" }}/_.yml      # a svc & an env      - values/infrastructure/{{ $v | get "name" }}/{{ requiredEnv "CI_ENVIRONMENT" }}.yml  {{ end }}  {{- end }}

Контуры в Store


Допустим у нас есть 2 окружения dev и prod.
И в prod'e нам не нужна база данных


vars/infrastructure.yaml


releases:  - name: rabbitmq    repo: stable    version: 6.18.2    envs:      - _ # all environments    tags:      - queue  - name: postgresql    repo: bitnami    version: 8.6.13    envs:      - dev # only dev    tags:      - db

# vim: set filetype=yaml:{{- $env := requiredEnv "CI_ENVIRONMENT" }} # Look at this firstproject: insiderversion: 0.5.0repositories:  - name: stable    url: https://kubernetes-charts.storage.googleapis.com  - name: bitnami    url: https://charts.bitnami.com/bitnami.options: &options  install: true  wait: true  force: false  timeout: 5m  atomic: false  maxhistory: 10  namespace: {{ requiredEnv "HELM_NS" }}releases:  {{- with readFile "vars/infrastructure.yaml" | fromYaml | get "releases" }}  {{- range $v := . }}  {{- $envs := $v | get "envs" }}  {{- if or (has "_" $envs) (has $env $envs) }}  - name: {{ $v | get "name" }}    chart: {{ $v | get "repo" }}/{{ $v | get "name" }}    options:      <<: *options      chartpathoptions:        version: {{ $v | get "version" }}    tags:      - {{ $v | get "name" }}      - infrastructure      {{- if $v | hasKey "tags" }}      - {{ $v | get "tags" | toYaml }}      {{- end }}    values:      # a svc & all envs      - values/infrastructure/{{ $v | get "name" }}/_.yml      # a svc & an env      - values/infrastructure/{{ $v | get "name" }}/{{ $env }}.yml  {{ end }}  {{- end }}  {{- end }}

База по умолчанию выключена


$ helmwave planfile

Чтобы postgresql включился


$ CI_ENVIRONMENT=dev helmwave planfile

Giltab-CI Pipelines


Рассмотрим шаблон gitlab-ci с использованием helmwave из проекта g-ci


variables:  HELMWAVE_LOG_LEVEL: debug.helmwave-deploy:  stage: deploy  environment:    name: ref/$CI_COMMIT_REF_SLUG  image:    name: diamon/helmwave:0.5.0    entrypoint: [""]  script:    - helmwave deployhelmwave deploy:  extends: .helmwave-deploy

С использованием include


include: https://gitlab.com/g-ci/deploy/-/raw/master/helmwave.ymlhelmwave deploy:  environment:    name: prod

P.S.


Helmwave source: https://github.com/zhilyaev/helmwave/
G-CI: https://gitlab.com/g-ci
Приходите к нам в telegram с любыми вопросами!

Подробнее..
Категории: Kubernetes , Git , Devops , Helm , Gitops

Перевод Основы работы с Helm чартами и темплейтами Часть 2

24.03.2021 14:22:50 | Автор: admin

В этом руководстве мы кратко обсудим, как Helm может помочь упростить управление приложениями Kubernetes, и узнаем, как использовать Helm для создания базового чарта.


В этом руководстве объясняется, как шаблон Helm deployment.yaml преобразуется из шаблона в манифест YAML.


Статья не соответствует традиционному справочному руководству по программированию: операторы и функции перечислены в алфавитном порядке.


Статья объясняет синтаксис шаблона по мере необходимости, чтобы объяснить deployment.yaml от начала до конца.


Использование значений в шаблонах


Вот полный файл deployment.yaml для справки:


apiVersion: apps/v1kind: Deploymentmetadata:  name: {{ include "myhelm1.fullname" . }}  labels:    app.kubernetes.io/name: {{ include "myhelm1.name" . }}    helm.sh/chart: {{ include "myhelm1.chart" . }}    app.kubernetes.io/instance: {{ .Release.Name }}    app.kubernetes.io/managed-by: {{ .Release.Service }}spec:  replicas: {{ .Values.replicaCount }}  selector:    matchLabels:      app.kubernetes.io/name: {{ include "myhelm1.name" . }}      app.kubernetes.io/instance: {{ .Release.Name }}  template:    metadata:      labels:        app.kubernetes.io/name: {{ include "myhelm1.name" . }}        app.kubernetes.io/instance: {{ .Release.Name }}    spec:      containers:        - name: {{ .Chart.Name }}          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"          imagePullPolicy: {{ .Values.image.pullPolicy }}          terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }}          command: ['sh', '-c', 'sleep 60']      {{- with .Values.nodeSelector }}      nodeSelector:        {{- toYaml . | nindent 8 }}      {{- end }}    {{- with .Values.affinity }}      affinity:        {{- toYaml . | nindent 8 }}    {{- end }}    {{- with .Values.tolerations }}      tolerations:        {{- toYaml . | nindent 8 }}    {{- end }}

Выдержка из этого deployment.yaml:


    app.kubernetes.io/instance: {{ .Release.Name }}    app.kubernetes.io/managed-by: {{ .Release.Service }}  replicas: {{ .Values.replicaCount }}      app.kubernetes.io/instance: {{ .Release.Name }}        app.kubernetes.io/instance: {{ .Release.Name }}        - name: {{ .Chart.Name }}          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"          imagePullPolicy: {{ .Values.image.pullPolicy }}

.Release.Name и .Release.Service встроены в объекты полный список на https://docs.helm.sh/chart_template_guide/ Встроенные объекты. К сожалению, нет ссылок на определенные части их ОЧЕНЬ длинных веб-страниц вам нужно прокрутить вниз и найти.


Ниже показано, как отображается финальный файл deployment.yaml, если вы используете:


helm install .\myhelm1\ --name test5 --dry-run --debug

В первой половине руководства вам не надо выполнять никаких команд, просто прочтите. (Даже при отладке выводится слишком много, если все, что нам нужно, это исследовать синтаксис трехстрочного шаблона.)


(После того, как вы прочитали полный учебник, вы можете захотеть прочитать его еще раз, но на этот раз отредактировав файлы шаблонов и значений и запустив helm install с отладкой для воспроизведения / обучения)


    app.kubernetes.io/instance: test5    app.kubernetes.io/managed-by: Tiller      app.kubernetes.io/instance: test5        - name: myhelm1          image: "radial/busyboxplus:base"          imagePullPolicy: IfNotPresent

Эти 3 значения ниже взяты из файла values.yaml:


          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"          imagePullPolicy: {{ .Values.image.pullPolicy }}

Извлекаем values.yaml


image:  repository: radial/busyboxplus  tag: base  pullPolicy: IfNotPresent

name: {{ .Chart.Name }} из файла Chart.yaml. Его содержимое показано ниже.


apiVersion: v1appVersion: "1.0"description: A Helm chart for Kubernetesname: myhelm1version: 0.1.0

Вероятно, половина всех шаблонов это как раз такие простые замены значений.


Вы видели, что в файлах YAML есть директивы шаблонов, встроенные в {{ and }}.


У вас должно быть пустое пространство после открытия {{ и пустое пространство перед закрытием }}.


Значения, которые передаются в шаблон, можно рассматривать как объекты с пространством имен, где точка (.) разделяет каждый элемент с пространством имен.


Первая точка перед чартом указывает на то, что мы начинаем с самого верхнего пространства имен. Прочтите .Chart.name как начните с верхнего пространства имен, найдите объект Chart, затем посмотрите внутри него на предмет с именем name.


With


**values.yaml** extract: nodeSelector:    disktype: ssd    gpu: Nvidia

**deployment.yaml** extract:       {{- with .Values.nodeSelector }}      nodeSelector:        {{- toYaml . | nindent 8 }}      {{- end }}

Отображается как:


      nodeSelector:        disktype: ssd        gpu: Nvidia

ОЧЕНЬ важно: пробелы имеют синтаксическое значение в YAML.


Перед визуализированными двумя селекторами стоит 8 пробелов, поскольку в deployment.yaml есть {{- toYaml. | nindent 8}}


nindent 8 делает отступ на 8 пробелов.


with конструкция цикла. Со значениями в .Values.nodeSelector: преобразуйте его в Yaml (toYaml).
Точка после toYaml это текущее значение .Values.nodeSelector в цикле. Это должно быть там.
Считайте его похожим на sum(1,34,454) как toYaml(.) это значение переданного параметра.


Символ | работает так же, если вы знакомы с оболочкой Linux.


affinity: и tolerations: with работают точно так же.


К сожалению, эти примеры не показывают, как with также является текущим модификатором области видимости. Это хорошо объясняется в разделе MODIFYING SCOPE USING WITH


За исключением include, вы теперь полностью понимаете, как выполняется рендеринг всего deployment.yaml с использованием значений из values.yaml, Chart.yaml и встроенных объектов.


Полный service.yaml ниже:


Теперь вы тоже это полностью понимаете.


apiVersion: v1kind: Servicemetadata:  name: {{ include "myhelm1.fullname" . }}  labels:    app.kubernetes.io/name: {{ include "myhelm1.name" . }}    helm.sh/chart: {{ include "myhelm1.chart" . }}    app.kubernetes.io/instance: {{ .Release.Name }}    app.kubernetes.io/managed-by: {{ .Release.Service }}spec:  type: {{ .Values.service.type }}  ports:    - port: {{ .Values.service.port }}      targetPort: http      protocol: TCP      name: http  selector:    app.kubernetes.io/name: {{ include "myhelm1.name" . }}    app.kubernetes.io/instance: {{ .Release.Name }}

Переменные и range (диапазон) Helm


Извлечение первых и последних 3 строк из ingress.yaml


{{- if .Values.ingress.enabled -}}{{- $fullName := include "myhelm1.fullname" . -}}{{- $ingressPaths := .Values.ingress.paths -}}... rest of yaml ....    {{- end }}  {{- end }}{{- end }}

**values.yaml** extract: ingress:  enabled: false

Все содержимое ingress.yaml заключено в большой if ..., начиная со строки 1 и заканчивая самой последней строкой. Если вход включен false, содержимое yaml не создается как мы этого хотим.


Строки 2 и 3 демонстрируют, как объявлять переменные шаблона Helm.


Обратите внимание на дефис в {{ и }}


Эти дефисы / тире съедают символы пробела. {{- съедает все пробелы слева


-}} означает, что пробелы справа должны быть удалены включая новую строку строка полностью удаляется.


Извлечение values.yaml


ingress:  enabled: false  hosts:    - chart-example.local  tls:    - secretName: chart-example-tls      hosts:        - chart-example.local-1        - chart-example.local-2        - chart-example.local-3

Извлечение deployment.yaml :


{{- if .Values.ingress.tls }}  tls:  {{- range .Values.ingress.tls }}    - hosts:      {{- range .hosts }}        - {{ . | quote }}      {{- end }}      secretName: {{ .secretName }}  {{- end }}{{- end }}

Отображается как:


spec:  tls:    - hosts:        - "chart-example.local-1"        - "chart-example.local-2"        - "chart-example.local-3"      secretName: chart-example-tls

Обратите внимание, как цикл диапазона (range) генерирует список хостов. quote окружает каждого хоста кавычками.


Также существует цикл range .Values.ingress.tls, который выполняется только один раз. Присвоение этому циклу 3 значений продемонстрирует, как он будет колебаться в пределах значений.


Extract of **values.yaml** ingress:  enabled: false  hosts:    - chart-example.local  tls:    - secretName: chart-example-tls-a      hosts:        - chart-example.local-1-a        - chart-example.local-2-a        - chart-example.local-3-a    - secretName: chart-example-tls-b      hosts:        - chart-example.local-1-b        - chart-example.local-2-b    - secretName: chart-example-tls-c      hosts:        - chart-example.local-1-c        - chart-example.local-2-c        - chart-example.local-3-c        - chart-example.local-4-c

Отображается как:


  tls:    - hosts:        - "chart-example.local-1-a"        - "chart-example.local-2-a"        - "chart-example.local-3-a"      secretName: chart-example-tls-a    - hosts:        - "chart-example.local-1-b"        - "chart-example.local-2-b"      secretName: chart-example-tls-b    - hosts:        - "chart-example.local-1-c"        - "chart-example.local-2-c"        - "chart-example.local-3-c"        - "chart-example.local-4-c"      secretName: chart-example-tls-c

importance of -


Оригинальный шаблон с дефисами.


{{- if .Values.ingress.tls }}  tls:  {{- range .Values.ingress.tls }}    - hosts:      {{- range .hosts }}        - {{ . | quote }}      {{- end }}      secretName: {{ .secretName }}  {{- end }}{{- end }}

шаблон с удаленными дефисами:


{{ if .Values.ingress.tls }}  tls:  {{ range .Values.ingress.tls }}    - hosts:      {{ range .hosts }}        - {{ . | quote }}      {{ end }}      secretName: {{ .secretName }}  {{ end }}{{ end }}

Отображается как:


  tls:    - hosts:        - "chart-example.local-1-a"        - "chart-example.local-2-a"        - "chart-example.local-3-a"      secretName: chart-example-tls-a    - hosts:        - "chart-example.local-1-b"        - "chart-example.local-2-b"      secretName: chart-example-tls-b    - hosts:        - "chart-example.local-1-c"        - "chart-example.local-2-c"        - "chart-example.local-3-c"        - "chart-example.local-4-c"      secretName: chart-example-tls-c

У вас должны быть дефисы на конце строки и пробела.


_helpers.tpl


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


Одно исключение: name: {{include "myhelm1.fullname". }} включение.


Теперь мы исследуем include


Мы используем include для включения других шаблонов в наши YAML-шаблоны.


Файл _helpers.tpl это стандартный способ определения нескольких коротких фрагментов шаблона, которые мы хотим включить в другие шаблоны.


Вот первый именованный шаблон в _helpers.tpl:


{{- define "myhelm1.name" -}}{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}{{- end -}}

В первой строке мы даем имя фрагменту шаблона. Затем мы ссылаемся на это имя, чтобы включить его.


Вторая строка дает myhelm1.name значение по умолчанию: .Chart.Name.


Если значение по умолчанию не существует, myhelm1.name получает значение .Values.nameOverride.


trunc 63 обрезает его до 63 символов.


trimSuffix "-" удаляет ОДИН завершающий - если он существует.
но


trimSuffix "-" удаляет только два завершающих - если они есть.


(Это не работает, как в некоторых языках программирования, где обрезка удаляет все завершающие символы)


app.kubernetes.io/name: {{ include "myhelm1.name" . }}


рендерится как


app.kubernetes.io/name: myhelm1


Далее: код шаблона


helm.sh/chart: {{ include "myhelm1.chart" . }}


рендерится как


helm.sh/chart: myhelm1-0.1.0


Это функция шаблона:


{{- define "myhelm1.chart" -}}{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}{{- end -}}

printf "%s-%s" .Chart.Name .Chart.Version объединяет .Chart.Name и .Chart.Version плюс ставит дефис между ними.


replace "+" "_" заменяет символы плюса на символы подчеркивания.


Теперь, когда вы понимаете эти две однострочные функции, вы должны легко понять 10-строчное определение myhelm1.fullname.


Если у вас есть опыт программирования, вы увидите, что if / else работает должным образом:


if condition do somethingelse do something elseend

Единственное отличие это синтаксис шаблона {{ и }}.


Быстрое изучение синтаксиса шаблона Helm


Официальная документация Helm содержит подробную справочную информацию о чартах и шаблонах.


The Chart Developer's Guide: https://helm.sh/docs/topics/charts/


The Chart Template Developer's Guide: https://docs.helm.sh/chart_template_guide/


Чтобы полностью изучить всю информацию понадобится не меньше дня. Лучший способ изучить всю информацию интерактивное использование.


В этой части руководства объясняется, как изучить Helm в интерактивном режиме.


Изучение включает:


редактировать как можно меньше файлов
показывать только как можно меньше визуализированных строк шаблона
не делайте живых установок. Отлаживайте пробные запуски


Давайте как можно быстрее сконвертируем наши текущие файлы диаграмм в это помните, что это некрасиво, а взлом БСТРЙ.


Отредактируйте файл values.yaml, чтобы он выглядел так, как показано ниже:


replicaCount: 1terminationGracePeriodSeconds: 30image:  repository: radial/busyboxplus  tag: base  pullPolicy: IfNotPresent

Убедитесь, что ./myhelm1/.helmignore содержит эти строки, показанные ниже:


NOTES.txttest-connection.yamlservice.yamlingress.yaml

Сделайте содержимое deployment.yaml, как показано ниже:


apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app.kubernetes.io/name: {{ include "myhelm1.name" . }}    helm.sh/chart: {{ include "myhelm1.chart" . }}    app.kubernetes.io/instance: {{ .Release.Name }}spec:  replicas: {{ .Values.replicaCount }}  template:    spec:      containers:        - name: {{ .Chart.Name }}          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"          imagePullPolicy: {{ .Values.image.pullPolicy }}          terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }}

ВСЕ, что нам нужно, это yaml (даже НЕПРАВИЛЬНЙ), чтобы его запустить.


Сделайте пробный запуск:


helm install .\myhelm1\  --name test5 --dry-run --debug

Получается слишком длинный вывод, как показано ниже:


PS C:\k8> helm install .\myhelm1\  --name test5 --dry-run --debug[debug] Created tunnel using local port: '50327'[debug] SERVER: "127.0.0.1:50327"[debug] Original chart version: ""[debug] CHART PATH: C:\k8\myhelm1NAME:   test5REVISION: 1RELEASED: Fri Feb 15 13:47:49 2019CHART: myhelm1-0.1.0USER-SUPPLIED VALUES:{}COMPUTED VALUES:image:  pullPolicy: IfNotPresent  repository: radial/busyboxplus  tag: basereplicaCount: 1terminationGracePeriodSeconds: 30HOOKS:MANIFEST:---# Source: myhelm1/templates/deployment.yamlapiVersion: apps/v1kind: Deploymentmetadata:  labels:    app.kubernetes.io/name: myhelm1    helm.sh/chart: myhelm1-0.1.0    app.kubernetes.io/instance: test5spec:  replicas: 1  template:    spec:      containers:        - name: myhelm1          image: "radial/busyboxplus:base"          imagePullPolicy: IfNotPresent          terminationGracePeriodSeconds: 30

Избавьтесь от первых нескольких бесполезных строк с помощью grep.


helm install .\myhelm1\  --name test5 --dry-run --debug | grep -vE 'debug]|NAME|REVIS|RELEA|ART:|OKS:|FEST:'

Смотрите что ниже. Это все, что нам нужно: некоторые значения для игры и некоторый контент шаблона yaml для игры с синтаксисом шаблона.


USER-SUPPLIED VALUES:{}COMPUTED VALUES:image:  pullPolicy: IfNotPresent  repository: radial/busyboxplus  tag: basereplicaCount: 1terminationGracePeriodSeconds: 30---# Source: myhelm1/templates/deployment.yamlapiVersion: apps/v1kind: Deploymentmetadata:  labels:    app.kubernetes.io/name: myhelm1    helm.sh/chart: myhelm1-0.1.0    app.kubernetes.io/instance: test5spec:  replicas: 1  template:    spec:      containers:        - name: myhelm1          image: "radial/busyboxplus:base"          imagePullPolicy: IfNotPresent          terminationGracePeriodSeconds: 30

А теперь займитесь изучением синтаксиса:
Смотрите ниже deployment.yaml


apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app.kubernetes.io/name: {{ include "myhelm1.name" . }}    helm.sh/chart: {{ include "myhelm1.chart" . }}    app.kubernetes.io/instance: {{ .Release.Name }}spec: #-------------------->> learn spacing << ------------------------  replicas1: {{ .Values.replicaCount }}  replicas2:   {{ .Values.replicaCount }}  replicas3:    {{ .Values.replicaCount }}  replicas4: '{{ .Values.replicaCount }}'  replicas5: "{{ .Values.replicaCount }}"  replicas6: "{{    .Values.replicaCount }}"  replicas7: "{{    .Values.replicaCount       }}"  replicas: "{{    .Values.replicaCount       }}'  replicas: '{{    .Values.replicaCount       }}"  replicas: {{    .Values.replicaCount       }}"  replicas: "{{    .Values.replicaCount       }}  template:    spec:      containers:        - name: {{ .Chart.Name }}          image1: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"          image2: "{{ .Values.image.repository }} {{ .Values.image.tag }}"          image3: "{{ .Values.image.repository }}{{ .Values.image.tag }}"          image4: {{ .Values.image.repository }}{{ .Values.image.tag }}          imagePullPolicy1: {{ .Values.image.pullPolicy }}          imagePullPolicy2: {{ .Values.image.pullPolicyzzz }}          imagePullPolicy3: {{ .Values.image.pullPolicyeeeeeeeeeee }}          terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }}

На выходе:


spec: #-------------------->> learn spacing << ------------------------  replicas1: 1  replicas2:   1  replicas3:    1  replicas4: '1'  replicas5: "1"  replicas6: "1"  replicas7: "1"  template:    spec:      containers:        - name: myhelm1          image1: "radial/busyboxplus:base"          image2: "radial/busyboxplus base"          image3: "radial/busyboxplusbase"          image4: radial/busyboxplusbase          imagePullPolicy1: IfNotPresent          imagePullPolicy2:          imagePullPolicy3:          terminationGracePeriodSeconds: 30

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


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


Отредактируйте deployment.yaml


apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app.kubernetes.io/name: {{ include "myhelm1.name" . }}    helm.sh/chart: {{ include "myhelm1.chart" . }}    app.kubernetes.io/instance: {{ .Release.Name }}spec: #  replicas1: {{ .Values.replicaCount }}  template:    spec:      containers:        - name: {{ .Chart.Name }}          image1: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"          imagePullPolicy-correct: {{ .Values.image.pullPolicy }}          imagePullPolicy1: {{ Values.image.pullPolicy }}          imagePullPolicy2: {{ .Valu.image.pullPolicyzzz }}          imagePullPolicy3: {{ ..Values.image.pullPolicyeeeeeeeeeee }}

Сделайте пробный запуск:


helm install .\myhelm1\  --name test5 --dry-run --debug | grep -vE 'debug]|NAME|REVIS|RELEA|ART:|OKS:|FEST:'

Error: parse error in "myhelm1/templates/deployment.yaml": template: myhelm1/templates/deployment.yaml:19: function "Values" not defined

ПРОЧИТАЙТЕ, поймите и исправьте ошибку, отправьте повторно.


Error: parse error in "myhelm1/templates/deployment.yaml": template: myhelm1/templates/deployment.yaml:21: unexpected . after term "."

ПРОЧИТАЙТЕ, поймите и исправьте ошибку, отправьте повторно.


Error: render error in "myhelm1/templates/deployment.yaml": template: myhelm1/templates/deployment.yaml:20:36: executing "myhelm1/templates/deployment.yaml" at <.Valu.image.pullPoli...>: can't evaluate field image in type interface {}

helm install .\myhelm1\  --name test5 --dry-run --debug | grep -vE 'debug]|NAME|REVIS|RELEA|ART:|OKS:|FEST:'

Ниже приведены несколько различных синтаксических экспериментов:


apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app.kubernetes.io/name: {{ include "myhelm1.name" . }}    helm.sh/chart: {{ include "myhelm1.chart" . }}    app.kubernetes.io/instance: {{ .Release.Name }}spec: #  replicas1: {{ .Values.replicaCount }}  template:    spec:      containers:        - name: {{ .Chart.Name }}          image1: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"          imagePullPolicy1: {{ quote .Values.image.pullPolicy }}          imagePullPolicy2: {{ .Values.image.pullPolicy | quote }}          imagePullPolicy3: "{{ .Values.image.pullPolicy }}"          imagePullPolicy4: {{ .Values.image.pullPolicy | upper }}          imagePullPolicy5: {{ .Values.image.pullPolicy | lower }}{{ $variable := 123 }}          variable: $variable                     variable: {{ $variable }}

См. Дополнительные 3 строки внизу use those -символы, чтобы удалить их. Удалите все 3 строки.


Helm не такой интерактивный, как Python, но таким образом вы почти можете это сделать.


Отображается как:


      containers:        - name: myhelm1          image1: "radial/busyboxplus:base"          imagePullPolicy1: "IfNotPresent"          imagePullPolicy2: "IfNotPresent"          imagePullPolicy3: "IfNotPresent"          imagePullPolicy4: IFNOTPRESENT          imagePullPolicy5: ifnotpresent          variable: $variable          variable: 123     

Еще одна уловка. Смотрите, imagePullPolicy с 1 по 3 выглядит одинаково. Что мы сделали? Вы можете заменить уродливые названия вот так:


deployment.yaml


apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app.kubernetes.io/name: {{ include "myhelm1.name" . }}    helm.sh/chart: {{ include "myhelm1.chart" . }}    app.kubernetes.io/instance: {{ .Release.Name }}spec: #  replicas1: {{ .Values.replicaCount }}  template:    spec:      containers:        - name: {{ .Chart.Name }}          image1: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"#          imagePullPolicy1: {{ quote .Values.image.pullPolicy }}          imagePullPolicy1: {{ quote .Values.image.pullPolicy }}#          imagePullPolicy2: {{ .Values.image.pullPolicy | quote }}          imagePullPolicy2: {{ .Values.image.pullPolicy | quote }}          imagePullPolicy3: " .Values.image.pullPolicy "          imagePullPolicy3: "{{ .Values.image.pullPolicy }}"          imagePullPolicy4:  .Values.image.pullPolicy | upper           imagePullPolicy4: {{ .Values.image.pullPolicy | upper }}          imagePullPolicy5:  .Values.image.pullPolicy | lower           imagePullPolicy5: {{ .Values.image.pullPolicy | lower }}{{ $variable := 123 }}          variable: $variable                     variable: {{ $variable }}

helm install .\myhelm1\  --name test5 --dry-run --debug | grep -vE 'debug]|NAME|REVIS|RELEA|ART:|OKS:|FEST:'

На выходе получается:


        - name: myhelm1          image1: "radial/busyboxplus:base"#          imagePullPolicy1: "IfNotPresent"          imagePullPolicy1: "IfNotPresent"#          imagePullPolicy2: "IfNotPresent"          imagePullPolicy2: "IfNotPresent"          imagePullPolicy3: " .Values.image.pullPolicy "          imagePullPolicy3: "IfNotPresent"          imagePullPolicy4:  .Values.image.pullPolicy | upper          imagePullPolicy4: IFNOTPRESENT          imagePullPolicy5:  .Values.image.pullPolicy | lower          imagePullPolicy5: ifnotpresent

Комментарии к заметкам не помогают. Интерпретируется синтаксис шаблона внутри комментариев.


Удаление {{ and }} всегда работает. Затем вы можете просмотреть исходный код шаблона и результат в следующей строке выходных данных.


Лучший способ показать заголовки того, что было протестировано, это использовать { { and { } вместо {{ and }} политики 1 ниже.


{ and } также работает в заголовках и очень похож на синтаксис чтения {{ and }}


          imagePullPolicy1: { { quote .Values.image.pullPolicy } }          imagePullPolicy1: {{ quote .Values.image.pullPolicy }}          imagePullPolicy2: { .Values.image.pullPolicy | quote }          imagePullPolicy2: {{ .Values.image.pullPolicy | quote }}

Отобразится как:


          imagePullPolicy1: { { quote .Values.image.pullPolicy } }          imagePullPolicy1: "IfNotPresent"          imagePullPolicy2: { .Values.image.pullPolicy | quote }          imagePullPolicy2: "IfNotPresent"

helm install .\myhelm1\ --set replicaCount={1,2,3}  --name test5 --dry-run --debug | grep -vE 'debug]|NAME|REVIS|RELEA|ART:|OKS:|FEST:'

Обучение на других шаблонах


В официальном репозитории Helm по адресу https://github.com/helm/charts есть почти 300 превосходных примеров диаграмм и шаблонов Helm.


Вы хотите учиться у лучших: у этих людей.


Вы увидите, что всего после этих двух руководств вы уже сможете понять более 80% всего кодирования шаблонов. (Но эти 2 руководства охватывают примерно 10 процентов синтаксиса).


Теперь у вас есть 4 независимых разных способа изучения чартов и шаблонов:


официальные справочные документы Helm
эти 2 практических урока
300 превосходных примеров диаграмм Helm
метод быстрого взлома, который вы можете использовать для вырезания и быстрого изучения этих 3 источников с помощью быстрых интерактивных упражнений


Справочная документация Helm сосредоточена на деталях на низком уровне.


Ищите идеи для проектирования структурных диаграмм, просматривая репозиторий диаграмм Helm.


Из https://github.com/helm/charts/blob/master/stable/lamp/templates/NOTES.txt


Посмотрите, как на чарте LAMP отображается справочный текст только для определенных примечаний .Values.ingress.enabled


{{- if .Values.ingress.enabled }}INGRESS:      Please make sure that you have an ingress controller instance {{ if .Values.ingress.ssl }}and a lego instance      {{- end -}} running      and that you have configured the A Records of {{ template "lamp.domain" . }} and its      subdomains to point to your ingress controllers ip address.{{- else }}

Большинство других чартов используют эту идею, поскольку чарты бесконечно гибки: многие функции можно включить или отключить.


Другой пример использования отображения NOTES.txt в зависимости от того, что активировал пользователь: https://github.com/helm/charts/blob/master/stable/lamp/templates/NOTES.txt


1. You can now connect to the following services:      {{- if not .Values.ingress.enabled }}      export CHARTIP=$(kubectl get svc {{ template "lamp.fullname" . }} --output=jsonpath={.status.loadBalancer.ingress..ip})      {{- end }}      Main Site:        {{- if .Values.ingress.enabled }}        http{{ if .Values.ingress.ssl }}s{{ end }}://{{ template "lamp.domain" . }}        {{- else }}        http://$CHARTIP        {{- end }}      {{- if .Values.phpmyadmin.enabled }}      PHPMyAdmin:      {{- if .Values.ingress.enabled }}        http{{ if .Values.ingress.ssl }}s{{ end }}://{{ .Values.phpmyadmin.subdomain }}.{{ template "lamp.domain" . }}      {{- else }}        http://$CHARTIP:{{ .Values.phpmyadmin.port }}      {{- end }}      {{- end }}

Другой часто используемый метод предупредить пользователей, если чарт настроен неправильно или небезопасно: https://github.com/helm/charts/blob/master/stable/mongodb/templates/NOTES.txt


{{- if contains .Values.service.type "LoadBalancer" }}{{- if not .Values.mongodbRootPassword }}------------------------------------------------------------------------------- WARNING    By specifying "service.type=LoadBalancer" and not specifying "mongodbRootPassword"    you have most  likely exposed the MongoDB service externally without any    authentication mechanism.    For security reasons, we strongly suggest that you switch to "ClusterIP" or    "NodePort". As alternative, you can also specify a valid password on the    "mongodbRootPassword" parameter.-------------------------------------------------------------------------------{{- end }}{{- end }}

Прекрасный пример того, как обращаться с .Values.service.type "NodePort", "LoadBalancer" или "ClusterIP": https://github.com/helm/charts/blob/master/stable/mongodb/templates/NOTES.txt


To connect to your database from outside the cluster execute the following commands:{{- if contains "NodePort" .Values.service.type }}    export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")    export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "mongodb.fullname" . }})    mongo --host $NODE_IP --port $NODE_PORT {{- if .Values.usePassword }} --authenticationDatabase admin -p $MONGODB_ROOT_PASSWORD{{- end }}{{- else if contains "LoadBalancer" .Values.service.type }}  NOTE: It may take a few minutes for the LoadBalancer IP to be available.        Watch the status with: 'kubectl get svc --namespace {{ .Release.Namespace }} -w {{ template "mongodb.fullname" . }}'    export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "mongodb.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")    mongo --host $SERVICE_IP --port {{ .Values.service.nodePort }} {{- if .Values.usePassword }} --authenticationDatabase admin -p $MONGODB_ROOT_PASSWORD{{- end }}{{- else if contains "ClusterIP" .Values.service.type }}    kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ template "mongodb.fullname" . }} 27017:27017 &    mongo --host 127.0.0.1 {{- if .Values.usePassword }} --authenticationDatabase admin -p $MONGODB_ROOT_PASSWORD{{- end }}{{- end }}

Благодаря этим примерам вы потратили менее 3 минут на изучение некоторых примеров чартов. Вы можете спокойно провести там день (и оно того стоит если вы будете делать заметки).


Как использовать .Files.Get


На https://docs.helm.sh/chart_template_guide/ Доступ к файлам внутри шаблонов (нельзя напрямую ссылаться на этот абзац) есть несколько примеров того, как включать файлы в шаблоны.


В репозитории Helm есть 80 примеров использования .Files.Get.
https://github.com/helm/charts/search?utf8=%E2%9C%93&q=.Files.Get&type=
В первых 10 результатах я обнаружил 5 различных вариантов использования .Files.Get.
Чтобы узнать больше о Helm, посетите https://github.com/helm/charts/tree/master/stable.

Подробнее..
Категории: Kubernetes , Devops , Helm

Эксплуатация MongoDB в Kubernetes решения, их плюсы и минусы

26.03.2021 10:08:08 | Автор: admin

MongoDB одна из самых популярных NoSQL/документоориентированных баз данных в мире веб-разработки, поэтому многие наши клиенты используют её в своих продуктах, в том числе и в production. Значительная их часть функционирует в Kubernetes, так что хотелось бы поделиться накопленным опытом: какие варианты для запуска Mongo в K8s существуют? В чем их особенности? Как мы сами подошли к этому вопросу?

Ведь не секрет: несмотря на то, что Kubernetes предоставляет большое количество преимуществ в масштабировании и администрировании приложений, если делать это без должного планирования и внимательности, можно получить больше неприятностей, чем пользы. То же самое касается и MongoDB в Kubernetes.

Главные вызовы

В частности, при размещении Mongo в кластере важно учитывать:

  1. Хранилище. Для гибкой работы в Kubernetes для Mongo лучше всего подойдут удаленные хранилища, которые можно переключать между узлами, если понадобится переместить Mongo при обновлении узлов кластера или их удалении. Однако удаленные диски обычно предоставляются с более низким показателем iops (в сравнении с локальными). Если база является высоконагруженной и требуются хорошие показания по latency, то на это стоит обратить внимание в первую очередь.

  2. Правильные requests и limits на podах с репликами Mongo (и соседствующих с ними podами на узле). Если не настроить их правильно, то поскольку Kubernetes более приветлив к stateless-приложениям можно получить нежелательное поведение, когда при внезапно возросшей нагрузке на узле Kubernetes начнет убивать podы с репликами Mongo и переносить их на соседние, менее загруженные. Это вдвойне неприятно по той причине, что перед тем, как pod с Mongo поднимется на другом узле, может пройти значительное время. Всё становится совсем плохо, если упавшая реплика была primary, т.к. это приведет к перевыборам: вся запись встанет, а приложение должно быть к этому готово и/или будет простаивать.

  3. В дополнение к предыдущему пункту: даже если случился пик нагрузки, в Kubernetes есть возможность быстро отмасштабировать узлы и перенести Mongo на узлы с большими ресурсами. Потому не стоит забывать про podDisruptionBudget, что не позволит удалять или переносить podы разом, старательно поддерживая указанное количество реплик в рабочем состоянии.

Решив эти вызовы для своего случая, вы получите быстро масштабируемую вертикально и горизонтально базу, которая будет находиться в одной среде с приложениями и удобно управляться общими механизмами Kubernetes. В плане надежности все зависит лишь от того, насколько хорошо спланировано размещение базы внутри кластера с учетом основных негативных сценариев при её использовании.

К счастью, на данный момент практически любой провайдер может предоставить любой тип хранилища на ваш выбор: от сетевых дисков до локальных с внушительным запасом по iops. Для динамического расширения кластера MongoDB подойдут только сетевые диски, но мы должны учитывать, что они всё же проигрывают в производительности локальным. Пример из Google Cloud:

А также они могут зависеть от дополнительных факторов:

В AWS картина чуть лучше, но всё ещё далека от производительности, что мы видим для локального варианта:

В нашем практике ещё ни разу не понадобилось добиваться такой производительности для MongoDB: в большинстве случаев хватает того, что предоставляют провайдеры.

Каким образом можно поднять MongoDB в Kubernetes?

Очевидно, что в любой ситуации (и Mongo здесь не будет исключением) можно обойтись самописными решениями, подготовив несколько манифестов со StatefulSet и init-скриптом. Но далее мы рассмотрим уже существующие подходы, которые давно придумали за нас.

1. Helm-чарт от Bitnami

И первое, что привлекает внимание, это Helm-чарт от Bitnami. Он довольно популярен, создан и поддерживается значительно долгое время.

Чарт позволяет запускать MongoDB несколькими способами:

  1. standalone;

  2. Replica Set (здесь и далее по умолчанию подразумевается терминология MongoDB; если речь пойдет про ReplicaSet в Kubernetes, на это будет явное указание);

  3. Replica Set + Arbiter.

Используется свой (т.е. неофициальный) образ.

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

Минимальная конфигурация, которая понадобится для поднятия, это:

1. Указать архитектуру (Values.yaml#L58-L60). По умолчанию это standalone, но нас интересует replicaset:

...architecture: replicaset...

2. Указать тип и размер хранилища (Values.yaml#L442-L463):

...persistence:  enabled: true  storageClass: "gp2" # у нас это general purpose 2 из AWS  accessModes:    - ReadWriteOnce  size: 120Gi...

После этого через helm install мы получаем готовый кластер MongoDB с инструкцией, как к нему подключиться из Kubernetes:

NAME: mongobitnamiLAST DEPLOYED: Fri Feb 26 09:00:04 2021NAMESPACE: mongodbSTATUS: deployedREVISION: 1TEST SUITE: NoneNOTES:** Please be patient while the chart is being deployed **MongoDB(R) can be accessed on the following DNS name(s) and ports from within your cluster:    mongobitnami-mongodb-0.mongobitnami-mongodb-headless.mongodb.svc.cluster.local:27017    mongobitnami-mongodb-1.mongobitnami-mongodb-headless.mongodb.svc.cluster.local:27017    mongobitnami-mongodb-2.mongobitnami-mongodb-headless.mongodb.svc.cluster.local:27017To get the root password run:    export MONGODB_ROOT_PASSWORD=$(kubectl get secret --namespace mongodb mongobitnami-mongodb -o jsonpath="{.data.mongodb-root-password}" | base64 --decode)To connect to your database, create a MongoDB(R) client container:    kubectl run --namespace mongodb mongobitnami-mongodb-client --rm --tty -i --restart='Never' --env="MONGODB_ROOT_PASSWORD=$MONGODB_ROOT_PASSWORD" --image docker.io/bitnami/mongodb:4.4.4-debian-10-r0 --command -- bashThen, run the following command:    mongo admin --host "mongobitnami-mongodb-0.mongobitnami-mongodb-headless.mongodb.svc.cluster.local:27017,mongobitnami-mongodb-1.mongobitnami-mongodb-headless.mongodb.svc.cluster.local:27017,mongobitnami-mongodb-2.mongobitnami-mongodb-headless.mongodb.svc.cluster.local:27017" --authenticationDatabase admin -u root -p $MONGODB_ROOT_PASSWORD

В пространстве имен увидим готовый кластер с арбитром (он enabled в чарте по умолчанию):

Но такая минимальная конфигурация не отвечает главным вызовам, перечисленным в начале статьи. Поэтому советую включить в неё следующее:

1. Установить PDB (по умолчанию он выключен). Мы не хотим терять кластер в случае drainа узлов можем позволить себе недоступность максимум 1 узла (Values.yaml#L430-L437):

...pdb:  create: true  maxUnavailable: 1...

2. Установить requests и limits (Values.yaml#L350-L360):

...resources:  limits:    memory: 8Gi  requests:     cpu: 4    memory: 4Gi...

В дополнение к этому можно повысить приоритет у podов с базой относительно других podов (Values.yaml#L326).

3. По умолчанию чарт создает нежесткое anti-affinity для podов кластера. Это означает, что scheduler будет стараться не назначать podы на одни и те же узлы, но если выбора не будет, то начнет размещать туда, где есть место.

Если у нас достаточно узлов и ресурсов, стоит сделать так, чтобы ни в коем случае не выносить две реплики кластера на один и тот же узел (Values.yaml#L270):

...podAntiAffinityPreset: hard...

Сам же запуск кластера в чарте происходит по следующему алгоритму:

  1. Запускаем StatefulSet с нужным числом реплик и двумя init-контейнерами: volume-permissions и auto-discovery.

  2. Volume-permissions создает директорию для данных и выставляет права на неё.

  3. Auto-discovery ждёт, пока появятся все сервисы, и пишет их адреса в shared_file, который является точкой передачи конфигурации между init-контейнером и основным контейнером.

  4. Запускается основной контейнер с подменой command, определяются переменные для entrypointа и run.sh.

  5. Запускается entrypoint.sh, который вызывает каскад из вложенных друг в друга Bash-скриптов с вызовом описанных в них функций.

  6. В конечном итоге инициализируется MongoDB через такую функцию:

      mongodb_initialize() {        local persisted=false        info "Initializing MongoDB..."        rm -f "$MONGODB_PID_FILE"        mongodb_copy_mounted_config        mongodb_set_net_conf        mongodb_set_log_conf        mongodb_set_storage_conf        if is_dir_empty "$MONGODB_DATA_DIR/db"; then                info "Deploying MongoDB from scratch..."                ensure_dir_exists "$MONGODB_DATA_DIR/db"                am_i_root && chown -R "$MONGODB_DAEMON_USER" "$MONGODB_DATA_DIR/db"                mongodb_start_bg                mongodb_create_users                if [[ -n "$MONGODB_REPLICA_SET_MODE" ]]; then                if [[ -n "$MONGODB_REPLICA_SET_KEY" ]]; then                        mongodb_create_keyfile "$MONGODB_REPLICA_SET_KEY"                        mongodb_set_keyfile_conf                fi                mongodb_set_replicasetmode_conf                mongodb_set_listen_all_conf                mongodb_configure_replica_set                fi                mongodb_stop        else                persisted=true                mongodb_set_auth_conf                info "Deploying MongoDB with persisted data..."                if [[ -n "$MONGODB_REPLICA_SET_MODE" ]]; then                if [[ -n "$MONGODB_REPLICA_SET_KEY" ]]; then                        mongodb_create_keyfile "$MONGODB_REPLICA_SET_KEY"                        mongodb_set_keyfile_conf                fi                if [[ "$MONGODB_REPLICA_SET_MODE" = "dynamic" ]]; then                        mongodb_ensure_dynamic_mode_consistency                fi                mongodb_set_replicasetmode_conf                fi        fi        mongodb_set_auth_conf        }

2. Устаревший чарт

Если поискать чуть глубже, можно обнаружить еще и старый чарт в главном репозитории Helm. Ныне он deprecated (в связи с выходом Helm 3 подробности см. здесь), но продолжает поддерживаться и использоваться различными организациями независимо друг от друга в своих репозиториях например, здесь им занимается норвежский университет UiB.

Этот чарт не умеет запускать Replica Set + Arbiter и использует маленький сторонний образ в init-контейнерах, но в остальном достаточно прост и отлично выполняет задачу деплоя небольшого кластера.

Мы стали использовать его в своих проектах задолго до того, как он стал deprecated (а это произошло не так давно 10 сентября 2020 года). За минувшее время чарт сильно изменился, однако в то же время сохранил основную логику работы. Для своих задач мы сильно урезали чарт, сделав его максимально лаконичным и убрав всё лишнее: шаблонизацию и функции, которые неактуальны для наших задач.

Минимальная конфигурация сильно схожа с предыдущим чартом, поэтому подробно останавливаться на ней не буду только отмечу, что affinity придется задавать вручную (Values.yaml#L108):

      affinity:        podAntiAffinity:          requiredDuringSchedulingIgnoredDuringExecution:          - labelSelector:              matchLabels:               app: mongodb-replicaset

Алгоритм его работы схож с чартом от Bitnami, но менее нагружен (нет такого нагромождения маленьких скриптов с функциями):

1. Init-контейнер copyconfig копирует конфиг из configdb-readonly (ConfigMap) и ключ из секрета в директорию для конфигов (emptyDir, который будет смонтирован в основной контейнер).

2. Секретный образ unguiculus/mongodb-install копирует исполнительный файл peer-finder в work-dir.

3. Init-контейнер bootstrap запускает peer-finder с параметром /init/on-start.sh этот скрипт занимается поиском поднятых узлов кластера MongoDB и добавлением их в конфигурационный файл Mongo.

4. Скрипт /init/on-start.sh отрабатывает в зависимости от конфигурации, передаваемой ему через переменные окружения (аутентификация, добавление дополнительных пользователей, генерация SSL-сертификатов), плюс может исполнять дополнительные кастомные скрипты, которые мы хотим запускать перед стартом базы.

5. Список пиров получают как:

          args:            - -on-start=/init/on-start.sh            - "-service=mongodb"log "Reading standard input..."while read -ra line; do    if [[ "${line}" == *"${my_hostname}"* ]]; then        service_name="$line"    fi    peers=("${peers[@]}" "$line")done

6. Выполняется проверка по списку пиров: кто из них primary, а кто master.

  • Если не primary, то пир добавляется к primary в кластер.

  • Если это самый первый пир, он инициализирует себя и объявляется мастером.

7. Конфигурируются пользователи с правами администратора.

8. Запускается сам процесс MongoDB.

3. Официальный оператор

В 2020 году вышел в свет официальный Kubernetes-оператор community-версии MongoDB. Он позволяет легко разворачивать, обновлять и масштабировать кластер MongoDB. Кроме того, оператор гораздо проще чартов в первичной настройке.

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

Архитектура оператора:

В отличие от обычной установки через Helm в данном случае понадобится установить сам оператор и CRD (CustomResourceDefinition), что будет использоваться для создания объектов в Kubernetes.

Установка кластера оператором выглядит следующим образом:

  1. Оператор создает StatefulSet, содержащий podы с контейнерами MongoDB. Каждый из них член ReplicaSetа в Kubernetes.

  2. Создается и обновляется конфиг для sidecar-контейнера агента, который будет конфигурировать MongoDB в каждом podе. Конфиг хранится в Kubernetes-секрете.

  3. Создается pod с одним init-контейнером и двумя основными.

    1. Init-контейнер копирует бинарный файл хука, проверяющего версию MongoDB, в общий empty-dir volume (для его передачи в основной контейнер).

    2. Контейнер для агента MongoDB выполняет управление основным контейнером с базой: конфигурация, остановка, рестарт и внесение изменений в конфигурацию.

  4. Далее контейнер с агентом на основе конфигурации, указанной в Custom Resource для кластера, генерирует конфиг для самой MongoDB.

Вся установка кластера укладывается в:

---apiVersion: mongodb.com/v1kind: MongoDBCommunitymetadata:  name: example-mongodbspec:  members: 3  type: ReplicaSet  version: "4.2.6"  security:    authentication:      modes: ["SCRAM"]  users:    - name: my-user      db: admin      passwordSecretRef: # ссылка на секрет ниже для генерации пароля юзера        name: my-user-password      roles:        - name: clusterAdmin          db: admin        - name: userAdminAnyDatabase          db: admin      scramCredentialsSecretName: my-scram# учетная запись пользователя генерируется из этого секрета# после того, как она будет создана, секрет больше не потребуется---apiVersion: v1kind: Secretmetadata:  name: my-user-passwordtype: OpaquestringData:  password: 58LObjiMpxcjP1sMDW

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

Но в то же время он уступает предыдущим вариантам тем, что у него нет встроенной возможности отдачи метрик в Prometheus, а вариант запуска только один Replica Set (нельзя создать арбитра). Кроме того, данный способ развертывания не получится сильно кастомизировать, т.к. практически все параметры регулируются через кастомную сущность для поднятия кластера, а сама она ограничена.

На момент написания статьи community-версия оператора имеет очень краткую документацию, не описывающую конфигурацию в подробностях, и это вызывает множество проблем при дебаге тех или иных случаев.

Как уже упоминалось, существует и enterprise-версия оператора, которая предоставляет большие возможности в том числе, установку не только Replica Setов, но и shared-кластеров с настройками шардирования, конфигурации для доступа извне кластера (с указанием имен, по которым он будет доступен извне), дополнительные способы аутентификации т.д. И, конечно же, документация к нему описана гораздо лучше.

Заключение

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

У разных вариантов запуска MongoDB есть разные плюсы. Чарты легко модифицировать под ваши нужды, но вы столкнетесь с проблемами при обновлении MongoDB или при добавлении узлов, т.к. всё равно потребуются ручные операции с кластером. Способ с оператором в этом смысле лучше, но ограничен по другим параметрам (по крайней мере, в своей community-редакции). Также мы не нашли ни в одном из описанных вариантов возможность из коробки запускать скрытые реплики.

Наконец, не стоит забывать, что есть и managed-решения для Mongo, однако мы в своей практике стараемся не привязываться к определенным провайдерам и предпочитаем варианты для чистого Kubernetes. Мы также не рассматривали Percona Kubernetes Operator for PSMDB, потому что он ориентирован на вариацию MongoDB от одноимённой компании (Percona Server for MongoDB).

P.S.

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

Подробнее..

Werf vs. Helm корректно ли их вообще сравнивать?

29.04.2021 10:23:13 | Автор: admin

Эта статья развернутый ответ на вопрос, который нам периодически задают: чем werf отличается от Helm? На первый взгляд можно предположить, что задача у них примерно одинаковая: автоматизировать деплой приложений в Kubernetes. Но всё, конечно, немного сложнее

Роль в CI/CD

Если упрощенно показать утилиты в рамках полного цикла CI/CD, то их функции значительно отличаются:

Helm

werf

Сборка приложения (в Docker-образ)

Публикация образов в container registry и их автоматическая очистка со временем

Деплой в Kubernetes

Деплой в Kubernetes (на базе Helm), расширенный трекингом ресурсов, интеграцией с образами, встроенной поддержкой Giterminism и другими фичами

Как видно, в рамках CI/CD-пайплайна werf делает гораздо больше, участвуя в полном жизненном цикле приложения, от сборки до выката в Kubernetes.

Helm хороший инструмент, но довольно низкоуровневый. Для реального использования в CI/CD он требует надстроек, интеграции с другими инструментами в общем, заметного усложнения инфраструктуры и процессов.

Поэтому мы говорим, что werf это следующий уровень доставки приложений в Kubernetes. Утилита использует Helm как один из компонентов и интегрирует его с другими стандартными инструментами: Git, Docker и Kubernetes. Благодаря этому werf выступает в роли клея, который упрощает, унифицирует организацию CI/CD-пайплайнов на базе специализированных инструментов, уже ставших стандартом в индустрии, и выбранной вами CI-системы.

Что умеет werf (и не умеет Helm)

Мы упомянули, что werf это утилита, которая не только про деплой. Но даже если посмотреть только на данный этап, то и здесь у werf есть ряд доработок:

Возможности

Helm

werf

Ожидание готовности ресурсов во время деплоя

+

+

Трекинг ресурсов и обнаружение ошибок

+

Fail-fast во время деплоя

+

Защита от параллельных запусков деплоя одного и того же релиза

+

Интеграция с собираемыми образами

+

Поддержка автодобавления аннотаций и лейблов во все ресурсы релиза

+ /

+

Публикация файлов конфигурации и образов приложения в container registry (бандлы)

+ /

+

Базовая поддержка секретных values

+

Поддержка Giterminism и GitOps

+ /

+

Подробнее мы разберем каждый пункт таблицы ниже (см. Детальное сравнение werf и Helm ниже). Но сначала об истоках, которые привели к таким следствиям. Расскажем о том, к чему мы стремились, создавая werf.

Четыре благородные истины werf

1. werf должна использоваться в CI/CD-пайплайне как единый инструмент

В утилите необходима поддержка работы в любой существующей CI/CD-системе и легкой интеграции. Сейчас werf работает из коробки с GitLab CI/CD и GitHub Actions. Другие CI-системы тоже поддерживаются для интеграции достаточно написать скрипт, следуя инструкции.

2. werf должна оптимальным образом доставлять приложения в Kubernetes

В процессе доставки собираются только недостающие образы из container registry или недостающие слои для этих образов, а всё старое берется из прошлых сборок или кэша. Так экономится и время сборки, и место для хранения всех образов.

После доставки [обновлённого приложения] текущее состояние ресурсов в Kubernetes приводится к новому требуемому состоянию, которое определено в Git (мы назвали это словом гитерминизм, от слов Git + детерминизм) и здесь снова речь идёт о том, что для такой синхронизации вычисляются изменения и применяются только они.

3. werf должна давать четкую обратную связь

Итоговый отчет werf в идеале должен быть достаточным для диагностики проблемы. Если что-то пошло не так в процессе доставки и развертывания, пользователю не нужно запускать kubectl и искать информацию в кластере. werf сразу показывает и постоянно обновляет актуальный статус процесса деплоя и даёт достаточно информации для решения проблемы без привлечения системного администратора.

4. werf должна поддерживать GitOps-подход

GitOps в общем виде это подход, при котором для развертывания приложений в Kubernetes используется единственный источник правды Git-репозиторий. Через декларативные действия в Git вы управляете реальным состоянием инфраструктуры, запущенной в Kubernetes. Этот паттерн из коробки работает в werf.

У нас есть собственный взгляд на то, как должен быть реализован GitOps для CI/CD (уже упомянутый Giterminism). GitOps в werf поддерживает не только хранение Kubernetes-манифестов в Git, но и версионирование собираемых образов, связанное с Git. Откат до предыдущей версии приложения выполняется без сборки выкатывавшихся ранее образов (при условии, что версия учитывается политиками очистки). Другие существующие реализации GitOps не дают этой гарантии.

Зачем и как werf использует Helm

werf использует и расширяет Helm, чтобы следовать вышеприведенным принципам.

Helm популярный и проверенный инструмент. У него есть собственный шаблонизатор, чарты и т.п. Мы не стали переизобретать то, что уже отлично работает. Вместо этого сфокусировались на фичах, которые помогают оптимизировать CI/CD.

С точки зрения совместимости важно, что кодовая база Helm вкомпилирована в werf. Обновления из upstream приходят регулярно, руками Helm обновлять не нужно. (Как, впрочем, и у самой werf, у которой есть встроенный version manager multiwerf с 5 каналами стабильности и поддержкой автообновления.)

Мы даже стараемся по возможности участвовать в улучшении Helm через upstream. Один из примеров нашего вклада аннотация helm.sh/hook-delete-policy=before-hook-creation (Удалить предыдущий ресурс перед запуском нового хука), которая пришла в Helm из werf.

Плюсы и минусы Helm

У Helm есть ряд преимуществ:

  • Это стандартный package manager в K8s. Helm используют [практически] все, кто работает с Kubernetes; он стал стандартом индустрии.

  • Шаблонизация. Шаблоны Helm удобны для тиражирования манифестов при работе с несколькими окружениями. (Пусть и не все согласятся, что Go-templates удобный шаблонизатор, но это уже другой вопрос...)

  • Удобное управление чартами. Общепринятый формат описания ресурсов и объединения их в чарты (charts это пакеты для Helm).

  • Переиспользование чартов. Helm поддерживает публикацию переиспользуемых чартов в Chart Repository или container registry.

  • Удобное управление жизненным циклом релизов. В Helm легко управлять релизами и откатываться на нужные версии.

Что до минусов, то основной в том, что Helm это маленькое звено в CI/CD-цепи, которое мало или совсем не связано с другими важными звеньями.

CI/CD приложения предполагает непрерывное слияние изменений в основную кодовую базу проекта и непрерывную доставку этих изменений до пользователя. Это включает периодическую сборку новых образов приложения с обновлениями, тестирование этих образов и выкат новой версии приложения. В CI/CD у нас обычно несколько окружений (production, staging, development и т. д.). И конфигурация приложения может отличаться для разных окружений.

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

Детальное сравнение werf иHelm

А теперь вернемся к таблице и разберем подробнее каждый её пункт.

1. Трекинг ресурсов и обнаружение ошибок

И werf, и Helm умеют ждать готовности ресурсов в процессе развертывания. Но werf дает обратную связь и более проактивна. Три главных отличия:

1. В процессе деплоя werf выводит логи по выкатываемым ресурсам. Для отдельных ресурсов логирование отключается автоматически по мере их перехода в состояние готовности. Можно отключить трекинг конкретных контейнеров или логировать вывод только по определенным контейнерам ресурса, отключив остальные; можно включать и выключать вывод сервисной информации Kubernetes. Всё это настраивается с помощью аннотаций, которые объявляются в шаблонах чарта.

2. Как только werf замечает проблему в одном из ресурсов, он завершается с ошибкой и ненулевым кодом выхода. werf следует принципу fail-fast. Он выдает наиболее полезную информацию по ошибке, чтобы пользователь сразу мог понять, в чём проблема, по выводу в CI/CD job.

Helm, в отличие от werf, в случае проблем с конфигурацией ждет истечения таймаута. А такие проблемы распространенная ситуация в CI/CD. Выяснить, в чём их причина, можно только после подключения к кластеру через kubectl. Это неудобно:

  • нужно настраивать права доступа к kubectl;

  • kubectl требует знаний и умений: куда смотреть, что искать и как это интерпретировать.

В идеале разработчик по выводу werf сможет понять причину проблемы и пофиксить ее, не привлекая админа. После этого достаточно создать в Git новый коммит с исправлением.

3. Защита от параллельных запусков деплоя одного и того же релиза. Helm может упасть с ошибкой, если будут работать одновременно два выката одного и того же релиза. werf автоматически предотвращает одновременный выкат: эти процессы работают по очереди.

2. Интеграция со сборкой образов

В Helm приходится явно передавать полные имена образов через values и заботиться об актуализации имен при изменениях.

werf решает эту проблему за пользователя: не нужно думать о способах передачи имен образов, т. к. они передаются через те же values автоматически. Более того, werf оптимальным образом решает проблему именования образов так, чтобы в процессе деплоя менялись имена только у изменившихся образов.

К тому же, werf дает возможность откатиться на старую версию приложения со старыми образами без повторной пересборки этих образов. Часто при использовании Helm в CI/CD через values для упрощения передаются статические имена образов (вроде registry.example.com/myproject:production) в этом случае имя образа ссылается только на последнюю собранную версию образа. При такой схеме тегирования приходиться пересобирать старый образ, чтобы откатиться до предыдущей версии. werf использует схему с content-based-тегами, которые связаны с историей Git. Помимо прочего, такая схема тегирования полностью решает вопрос с откатом на старую версию.

Что дает интеграция с собранными образами:

  • werf может использовать уже существующие Dockerfile'ы в своей конфигурации.

  • werf автоматически и оптимально именует собираемые образы, чтобы имена зависели от контента внутри образа и обновлялись только при его изменении.

  • Нет лишних перевыкатов компонентов приложения в Kubernetes и, как следствие, лишнего простоя компонентов. Перевыкат происходит быстро и только для измененных компонентов, а не приложения целиком.

  • Со стороны конфигурации Helm остается только использовать имена образов, которые werf предоставляет через специальные values.

  • Можно откатиться на образы старой версии приложения.

3. Добавление аннотаций и лейблов в ресурсы релиза

В Helm есть механизм post rendering, чтобы дописывать в ресурсы аннотации или лейблы и менять другие поля. Однако в Helm нет простой встроенной функции для добавления конкретно лейблов и аннотаций.

werf добавляет во все ресурсы релиза автоматические аннотации вроде ссылки на CI/CD job, из которого ресурс был в последний раз выкачен, или ссылки на Git-коммит.

Также в werf через CLI-опции можно указать произвольные аннотации или лейблы, которые будут добавлены во все ресурсы релиза. Пример такой команды:

werf converge --add-annotation pipeline-id=$CI_PIPELINE_ID --add-annotation git-branch=$CI_COMMIT_REF_NAME

Это удобно:

  • для интроспекции, когда надо понять, с каким CI/CD job связана версия ресурса;

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

4. Бандлы: публикация файлов конфигурации и образов приложения в container registry

Helm поддерживает публикацию чартов в OCI container registry либо Chart Repository, однако не отвечает за образы, которые нужны этому чарту для выката. Такие образы должны быть отдельно собраны и правильно протегированы, а опубликованный чарт должен быть настроен на использование образов по правильным именам. Эти проблемы пользователю Helm приходиться решать самостоятельно.

werf поддерживает так называемые бандлы, которые предполагают публикацию чартов и образов, собранных специально для текущего чарта как единой сущности в container registry.

Пользователь не думает об именах образов, публикуемых вместе с чартом: werf делает это автоматически и оптимальным способом. Неизменные образы будут переиспользованы. Публикуются лишь те слои, которые требуются для текущего коммита. Пользователю werf достаточно выбрать версию бандла и обеспечить его публикацию в требуемом Git-коммите.

5. Встроенная базовая поддержка значений секретов

В Helm поддержка секретов возможна через подключение сторонних плагинов. Это усложняет установку Helm на новые хосты.

werf из коробки дает возможность закодировать значения values через алгоритмы AES-128, AES-192 и AES-256. Ключи шифрования можно менять.

6. Поддержка Giterminism и GitOps

Helm не регулирует привязку используемых конфигурационных файлов к Git-коммитам.

werf (с версии v1.2) форсирует использование конфигурации из текущего Git-коммита и реализует режим гитерминизма, в том числе и для конфигурации Helm.

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

Подытожим

Прямое противопоставление werf vs Helm не совсем корректно, потому что утилита werf реализует не только деплой, а нацелена на поддержку полного жизненного цикла доставки приложений. Сам по себе Helm хороший инструмент для деплоя в Kubernetes, поэтому он (с некоторыми улучшениями) встроен в werf для решения этой задачи. Однако и в контексте деплоя у werf есть ряд преимуществ, общий смысл которых сводится к более полной и удобной интеграции с CI/CD-системами.

P.S.

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

Подробнее..

Как готовить Helm правильно несколько полезных рецептов

16.05.2021 18:12:28 | Автор: admin
(источник фото -https://unsplash.com/photos/XWNbUhUINB8(источник фото -https://unsplash.com/photos/XWNbUhUINB8

Когда-то давно, когда ножей не знали,х@#$ говядину рубили... ой нет, это другая сказка, простите. Вот, правильная эта. Когда-то давно, деды сказывают, для обновления сервиса в среде инженерам приходилось самим писать скрипты для деплоймента, а некоторые даже запускали их сами из консоли, руками. Были даже причудливые инструменты вроде ClusterSSH, чтобы делать сеанс одновременной игры на всех нодах кластера.

Но тотальная контейнеризация, ввод в обиход универсального понятия "workload", и оркестризация привели к появлению Kubernetes, на котором сейчас работает примерно 3/4 мировых production сред.

Kubernetes ввёл в обиход концепцию управления конфигурацией при помощи дескрипторов - kubectl apply что в переводе означает "я не хочу объяснять что делать, я говорю как хочу чтобы в среде всё стало - а ты делай что должен, блестящий металлический зад!".

Как с любой инновацией, выход на базовый уровень оркестрации привёл к осознанию того, что дескрипторы должен делать не человек руками, а другой "металлический зад", которому задачу ставить можно на ещё более высоком уровне - "накати обновление сервиса на среду Ъ, согласно правилам подготовки конфигурации среды".

Поэтому Helm стал де-факто стандартом в автоматизированных деплойментах Kubernetes, добавляя новые интересные возможности, которые идут гораздо дальше простой альтернативы выполнению kubectl apply -f ...

Ниже мы расссмотрим несколько примеров того, что можно получить от Helm по сравнению с простой командой kubectl apply, с чем разработчик сталкивается в реальной жизни. Мы покажем, как организовать доставку новых версий сервисов в разные среды, описывая параметры так, что не требуется копипастных повторений, которые ведут к ошибкам. Мы также увидим несколько других полезных приёмов - для удобного управления секретами и организации надёжных пайплайнов.

Итак, напомним, что Helm в принципе может делать три основные вещи:

  • сделать дескрипторы по шаблонам

  • накатить дескрипторы на кластер

  • использовать репозитории charts - взять шаблоны дескрипторов из центральной среды

В данном тексте мы фокусируемся на первых двух моментах, шаблонах и состоянии кластера, не затрагивая репозитории.

Шаблонизатор Helm работает примерно так:

Другими словами, Helm берёт исходники:

  • файлы, текстовые и бинарные

  • шаблоны .yaml

  • значения из одного или нескольких .yaml файлов, и, возможно, командной строки

и производит файлы .yaml для того, чтобы применить их на кластер Kubernetes.

При этом файлы вставляются в Secrets и ConfigMaps, а значения занимают свои места в шаблонах.

Получившиеся шаблоны можно скормить в кластер непосредственно через kubectl, или же (что открывает новые возможности) наложить их через Helm. Helm имеет встроенные средства управления состоянием кластера - несколько последних сделанных деплойментов сохраняются в отдельном Secret, что даёт возможность полного и точного отката при ошибке - то есть обеспечивает атомарность операции и целостность кластера.

Рассмотрим некоторые полезные техники Helm для решения типичных задач, с которыми девопс инженер сталкивается за пределами простого, понятного, нежно-розового мира hello world:

  • управление конфигурацией множества сред

  • использование секретов

  • повышение стабильности пайплайнов

Конфигурация множества сред

Скорее всего, ваши сервисы работают в нескольких средах. Простейшим разделением может быть Test и Production, но на практике всё бывает чуть сложнее. Тестовые среды бывают разных видов: некоторые из них одноразовые, нестабильные и экспериментальные, выполняющие код из новых пулл реквестов, который может часто ломаться.

Другие тестовые среды предназначены, например, для приёмочного тестирования реальными пользователями (по уровню ответственности не Production - но весьма похожие). Такие среды обычно используются несколькими командами, поэтому все ожидают от них стабильности.

Production-среды имеют общие моменты, характерные для production, например реальный секретный ключ для платёжной системы. Но, тем не менее, могут различаться в других аспектах: например по региону, среда для "канареечных" релизов для тестов на малой группе, или по регионам. Или же может быть несколько production сред для каждого клиента или их группы, если ваш сервис больше похож на SaaS.

12 заповедей

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

Однако в скрижалях с 12 заповедями не сообщается подробностей - как именно передать эти значения в среды прозрачным, безопасным и контролируемым образом и где все это хранить. Особенно когда вам нужно поддерживать много сред, похожих по одним аспектам, но разных по другим. И как быть с некоторыми значениями конфигурации, которые лучше держать в секрете? Чтобы успешно справиться со всей этой сложностью с помощью Helm, необходимо внести некоторые улучшения в стандартную компоновку chart.

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

Код с конфигурацией - как мясное с молочным? Код с конфигурацией - как мясное с молочным?

Они решают, что заповедь запрещает мешать молочное с мясным хранить конфигурацию приложения в том же гит-репозитории что и его код.
Истинно говорю вам, они заблуждаются! Третья заповедь - про то, что артефакт сервиса (результат build) должен быть один и тот же, и не содержать конфигурации, которая считывается при запуске из среды - в коей среде она должна присутствовать.

Каждый новый билд порождает неизменяемые (immutable) контейнер(ы) с кодом, и Helm chart, который можно применить для любой среды - от локального docker-desktop до production кластера в AWS.

При этом код конфигурации, в нашем случае - шаблоны и файлы Helm chart, с полным набором настроек для всех сред, за исключением секретных значений, проще хранить вместе с кодом сервиса. В особенности, если вам всё-таки удалось сделать гармоничную (микро)сервисную архитектуру, и у каждого сервиса есть возможность обновляться независимо. Если такой возможности нет - не льстите себе, у вас распределённый монолит, а не (микро)сервисы.

Чтобы сделать всё красиво и надёжно, мы прежде всего хотим устранить избыточность и дублирование схожих элементов конфигурации и шаблонов и сделать их такими же простыми, как приложение, работающее на ноутбуке разработчика.

Следовательно:

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

  • ... ведь в подавляющем большинстве случаев все, что нужно сервису - это просто набор из нескольких файлов конфигурации в одном, доступном ему каталоге (conf или properties)

  • а ещё больше мы не хотим ничего копипастить - это ведёт к визуальному мусору, глупым ошибкам, повышению затрат на поддержку - поэтому

    • каждое значение конфигурации и/или файл должны храниться в одном и только в одном месте,

    • если значения или файлы различаются в зависимости от среды, в каждой среде сохраняются только отличающиеся части,

    • добавление файла в конфигурацию должно требовать именно этого - добавление файла: никаких изменений в пицот других yaml-ов!

Мы будем хранить конфигурации всех наших сред в диаграмме Helm как часть chart.

Пример ниже, дополнительные каталоги выделены (+).

/env             // (+) значения для сред /<chart-name>    // chart    /files         // (+) конфигурационные файлы и их шаблоны     /templates     // шаблоны Helm   Chart.yaml     // заглавный файл chart  values.yaml    // значения по умолчанию

Файлы со значениями для сред

Давайте посмотрим подробнее на структуру ваших сред.

Допустим у вас есть два типа, TEST и PROD для тестовых и продакшен сред, соответственно. тип TEST делится на две разновидности -STABLE и -PR (пул реквест, нестабильная среда), а для типа PROD у вас разновидности это регионы, EU и US.

Получается примерно такая структура для /env (значения для сред) и /files (файлы конфигурации):

/env                  TEST.yaml            // общие значения для всех тестовых сред  TEST-PR.yaml         // только для PR/нестабильной  TEST-STABLE.yaml     // только для стабильной     PROD.yaml            // общие значения для всех продакшен сред  PROD-EU.yaml         // продакшен EU     PROD-US.yaml         // продакшен US /files                     /TEST                // общие файлы для всех тестовых сред  /TEST-PR             // ...  /TEST-STABLE         // ...    /PROD                // общие файоы для всех продакшен сред   /PROD-EU             // ...  /PROD-US             // ...  /shared              // файлы общие для всех средvalues.yaml             // значения общие для всех сред

Теперь посмотрим что внутри /files - текстовые и бинарные файлы конфигурации.

.../PROD    /binary    /text/PROD-EU    /binary    /text .../shared    /binary    /text    /secret-text

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

Не все типы файлов представлены на всех уровнях, но всему есть причина, давайте подробнее рассмотрим.

1. Мы предполагаем что секретные файлы только текстовые а не бинарные. Почему? Потому что так проще. Секретный двоичный файл (например .p12 сертификат) становится несекретным, если зашифровать его секретным ключом с достаточной (скажем 120+ бит) энтропией - и можно хранить его просто в гите, даже если он размеров в несколько (десятков) килобайт. С точки зрения безопасности - это просто рандомный набор битов. А вот ключ, отпирающий этот бинарник, мы будем хранить в строгом секрете - как и все другие-остальные пароли.

2. С другой стороны, вряд ли будут секретные текстовые файлы, которые при этом не общие (shared) для всех сред. Обычно такой файл - это шаблон (например конфигурация пула соединений к базе данных), в который надо вставить несколько секретных значений, например пароль пользователя. Ниже мы чуть подробнее раскроем тему секретов в шаблонах.

Итак, когда структура выше подготовлена, всё работает примерно так - значения из нескольких файлов сначала превращаются в один плоский yaml, накладываясь друг на друга, в порядке уменьшающегося приоритета:

  • среда - разновидность (TEST-STABLE.yaml)

  • среда - тип (TEST.yaml)

  • общие значения (values.yaml)

Helm позволяет это сделать, указав файлы со значениями как последовательность опций --values:

helm upgrade --install <chart> path/to/<chart> --strict \     --values env/<env>.yaml --values env/<env>-<flavour>.yaml

В .yaml-файлах разновидности и типа среды содержатся атрибуты, например TEST-STABLE.yaml среди прочего содержит:

envFlavour: STABLE

а TEST.yaml содержит

envClass: TEST

В плоском финальном наборе значений всё это объединяется, для того чтобы, как мы увидим в следующем разделе, можно было удобно используем эти значения, чтобы взять нужные файлы из /files.

Один ConfigMap и один Secret для всех файлов

Смотрим внимательно - вот одна, единая конфигурация для одного единственного ConfigMap и одного Secret, куда поместятся все ваши файлы. Бинарные файлы и текстовые несекретные файлы пойдут в ConfigMap, а секретные текстовые файлы в Secret.

# это нужно для вложенных range{{- $self := . -}} # список директорий, откуда берутся файлы - среда-разновидность, среда-тип и потом общие файлы из shared{{ $sources := (list "shared" .Values.envClass (printf "%s-%s" .Values.envFlavour .Values.envClass ) }}apiVersion: v1kind: ConfigMapmetadata:  name: myapp# вставить несекретные текстовые файлы как шаблоныdata:{{ range $env := $sources }}{{ range $path, $bytes := $self.Files.Glob (printf "files/%s/text/*" $env) }}  {{ base $path }}: {{ tpl ($self.Files.Get $path) $ | quote }}{{ end }}{{ end }}# вставить двоичные файлыbinaryData:{{ range $env := $sources }}{{ range $path, $bytes := $self.Files.Glob (printf "files/%s/binary/*" $env) }}  {{ base $path }}: {{ $self.Files.Get $path | b64enc | quote }}{{ end }}{{ end }}---apiVersion: v1kind: Secretmetadata:  name: myapp  labels:type: Opaquedata:# вставить секретные текстовые файлы как шаблоны{{ range $env := $sources }}{{ range $path, $bytes := $self.Files.Glob (printf "files/%s/secret-text/*" $env) }}  {{ base $path }}: {{ tpl ($self.Files.Get $path) $ | b64enc | quote }}{{ end }}{{ end }}

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

Тотальная шаблонизация

Имеющие опыт с Helm, возможно, задаются вопросом, к чему все эти хитрые танцы, если у Helm уже есть набор функций для ConfigMaps и Secrets?

А вот почему. Заметили маленькую функцию tpl, применяемую на каждый файл? Правильно, именно поэтому она нужна, чтобы каждый текстовый файл обрабатывался как отдельный шаблон - вы можете вставлять ссылки на значения как {{ .Values.myValue }}в любое место ваших текстовых файлов конфигурации.

Шаблон может использоваться для любого типа конфигурации, .properties, yaml, HOCON, например:

akka {  persistence {    cassandra {        keyspace = {{ .Values.cassandra.keyspace | quote }}        table = "{{ .Values.cassandra.tablePrefix }}messages"

Коварные кавычки

Кавычки в значении могут оказаться как лишними, так и наоборот, в зависимости от формата файла.

Каким-то значениям, содержащим скобки, запятые и прочее, что может смутить парсер конфигурации, полезно быть взятым в кавычки:

databasePassword: {{ .Values.databasePassword | quote }}

Для других значений, например в файле .properties, кавычки наоборот, вредны, поэтому их можно убрать вот так:

param/username={{ .Values.username | trimAll "\"" }}

Проецируемые тома (projected volumes)

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

Для этого в Kubernetes удачно завезли projected volumes - проецируемые тома, которые можно собирать из нескольких ConfigMaps и Secrets.

volumes:  - name: properties    projected:      defaultMode: 0640      sources:        - configMap:            name: myapp        - secret:            name: myapp

Очень просто смонтировать такой "сборный" том в директорию/confвашего приложения.

Линтер

Не вредно бывает запускать линтер для проверки chart на вменяемость, чтобы при ошибке пайплайн падал не в момент деплоймента, а существенно раньше.

У Helm есть для этого команды lint и template:

helm lint --debug path/to/<chart> --strict --values env/<env>.yaml \  --values env/<env>-<flavour>.yaml  helm template <chart> path/to/<chart> --strict --values env/<env>.yaml \  --values env/<env>-<flavour>.yaml

В процессе подготовки chart в пайплайне эти команды должны выполниться для всех валидных комбинаций типа и разновидности сред.

Но совершенству нет пределов (почти) - можно пойти ещё на шаг дальше и использовать yamllint для валидации yaml-дескрипторов. Это позволит отловить проблемы, которые иначе не получается ловить - например два файла с одним и тем же именем, которые оказались в PROD и PROD-EU, будут дважды вставлены в ConfigMap, что приведёт к ошибке при деплойменте.

Управление секретами

Теперь немного про то, как обращаться с секретами - например ключами платных API, паролями для баз данных и паролями от TLS-сертификатов, которые мы не хотим показывать никому, кроме самого сервиса.

Обычно секретные значения хранятся в отдельном сервисе, типа Heroku Vault, Azure Vault, Google Cloud KMS и подобных. В Helm даже есть плагин для управления секретами,но в большинстве компаний управление секретами централизовано, и инженерам не позволено, да и не нужно, трогать секреты из production.

Когда пайплайн запускает обновление сервиса в подобной среде, то сам пайплайн имеет возможность считать секреты и вставить их в конфигурационные файлы. В большинстве случаев (Gitlab, Circle, Azure, ...) секреты становятся переменными среды, и всё что требуется - это вставить их в шаблон в самом начале.

Для этого мы добавим секцию в values.yaml

# secretsdatabase_username: "${UserNameSecret}"database_password: "${DatabasePasswordSecret}"

И в нашем пайплайне испольуемenvsubstили нечто похожее:

cat <chart>/values.yaml | envsubst > <chart>/values-injected.yamlmv <chart>/values-injected.yaml <chart>/values.yaml

Такой подход позволяет ссылаться на секреты просто как {{ .Value.xxx }} в любом шаблоне, вставляя их туда куда им положено быть.

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

Чтобы решить этот вопрос, можно ввести конвенцию называть секреты как XXXSecret, и добавить что-то типа такого:

EXPOSED_SECRETS=$(grep Secret <chart>/files | grep -v secret-files | wc -l)if [ $EXPOSED_SECRETS -gt 0 ]; then fail "Secrets are exposed"; fi

Это добавит уровень защиты против случайной потери секрета.

Атомарные обновления сред

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

Предположим, вы только что развернули новую версию приложения или новый набор значений конфигурации. Теперь вы определенно хотите знать, не поломалось ли что-то? Если все в порядке, вы можете продолжить работу над другими задачами вашего спринта, но если что-то не так - то вы скорее всего очень хотите, чтобы среда была автоматически восстановлена до последнего известного исправного состояния, без вашего вмешательства и всех этих надоедливых сообщений фрустрированных коллег: Почему я снова получаю ошибку 5xx?.

Это может показаться сложным - особенно если вы хотите реализовать это самостоятельно, вооружившись простым kubectl apply -f ... Но, к счастью, Helm многое делает "прямо из коробки".

Флаг --atomic

Просто добавьте флаг --atomicдля автоматической отмены текущего обновления, если поды не запустились по любой причине:

helm upgrade --install my-chart some/path/to/my-chart \    --atomic --debug --timeout 300s

Helm откатит обновление, если проверки проб health/readiness не дали положительного результата в указанный срок. Хорошо, но можно лучше.

Hooks

Можно, и нужно, добавить KubernetesJobв ваш дескриптор, чтобы запускать тесты "на дым" после каждого обновления. Обычно это не полный набор тестов, но несколько базовых сценариев, которые затрагивают все ключевые внешние системы, к которым сервис обращается в среде - например базу данных, внешний кеш, какие-то другие сервисы и тп.

И разумеется, сигнализировать Helm откатить обновление, если тест не прошёл.

apiVersion: batch/v1kind: Jobmetadata:  name: myapp  labels:  annotations:     "helm.sh/hook": post-install,post-upgrade     "helm.sh/hook-delete-policy": before-hook-creation,hook-succeededspec:  template:    metadata:      name: myapp-smoke-test    spec:      restartPolicy: Never      containers:        - name: tests          image: test-image:           command: ['/bin/sh',                    '-c',                    '/test/run-test.sh']

Если вы используете--atomicфлаг и post-upgrade/post-install hook для тестов в пайплайне, вы можете со значительной долей уверенности считать пайплайн надёжным. Он будет иметь "зелёный" статус тогда, и только тогда, когда сервис был успешно обновлён, запущен и прошёл базовые тесты.

А если что-то пошло не так - Helm возвратит среду в то состояние, которое было до этого обновление, включая все конфигурации и дескрипторы.

Автоматически!

Заключение

Helm это бесплатный инструмент с открытым кодом для управления обновлениями сервисов и их конфигураций в кластерах Kubernetes.

Используя шаблоны и hooks, можно добиться того, что ваш continuous delivery плавен и надёжен настолько, что его практически никто не замечает.

***

Этот текст является авторским переводом, оригинал статьи этого же автора
https://jacum.medium.com/advanced-helm-practices-for-perfect-kubernetes-deployments-7fc4e00cc41c

Подробно об авторе - https://www.linkedin.com/in/tim-evdokimov/

Подробнее..

Установка простого приложения (например Superset) в microk8s используя metallb и helm

03.03.2021 10:13:29 | Автор: admin

Snaps являются кросс-дистрибутивными, независимыми и простыми в установке приложениями, упакованными со всеми их зависимостями для запуска во всех основных дистрибутивах Linux. Snaps безопасны они ограничены и не ставят под угрозу всю систему. Они работают под разными уровнями содержания (то есть степень изоляции от базовой системы и друг от друга).


MicroK8s это CNCF-сертифицированное развертывание локального кластера Kubernetes, он предназначен для быстрой и легкой установки потока Kubernetes, изолированной от вашей локальной среды. В качестве оснастки он запускает все службы Kubernetes (т.е. без виртуальных машин), упаковывая при этом весь необходимый набор библиотек и файлов. Эта изоляция достигается за счет упаковки всех двоичных файлов для Kubernetes, Docker.io, iptables и CNI в единый пакет Snap.


Преимущества microk8s:


  • использует только необходимые ему ресурсы
  • кластеризация нескольких microk8s
  • легкая и простая установка в Ubuntu через snap, хваленая изоляция snap пакетов, возможность легкого отката на предыдущую версию
  • наличие аддонов

Apache Superset это веб-приложение для поиска и визуализации данных.


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


MetalLB это реализация балансировщика нагрузки для кластеров Kubernetes на голом железе со стандартными протоколами маршрутизации. Он выделяет службы с отдельными выделенными адресами IP, выделенными из пула.


Операционная система: Ubuntu 18.04


Устанавливаем Snapd, git


sudo apt-get update && sudo apt-get install -y snapd git

Устанавливаем microk8s версии 1.18


sudo snap install microk8s --classic --channel=1.18/stable && sudo snap install helm --classic

Стартуем microk8s


sudo microk8s.start

Добавляем текущего пользователя в группу microk8s


sudo usermod -a -G microk8s $USER

Меняем права директории .kube в домашней директории текущего пользователя


sudo chown -f -R $USER ~/.kube

Выходим из сессии и заходим снова


exit

Делаем алиал kubectl на microk8s.kubectl


alias kubectl=microk8s.kubectl

Активируем дополнения microk8s. В опциях дополнения metallb указываем список IP с ваших сетевых карточек. Если у вас 1 сервер, то это два одинаковых IP адреса. Кластеризацию microk8s я не проверял, но по идее нужно указывать IP адреса обоих серверов. Для этого обязательна кластеризация microk8s. IP на сетевой карте 192.168.22.7. У вас он будет другой.


microk8s enable dns ingress storage metallb:192.168.22.7-192.168.22.7 

Смотрим что все поды у нас Running


kubectl get all --all-namespaces

Скачиваем репозиторий superset


git clone https://github.com/apache/superset.git

Переходим в директорию где хранится helm для superset


cd superset/helm/superset

Скачиваем зависимиости для текущего helm


helm dependency update

Сохраняем конфиг для подключения к Kubernetes


sudo microk8s.kubectl config view --raw > $HOME/.kube/config

Запускаем установку superset с помощью helm используя конфиги в текущей директории


helm install --set persistence.enabled=true,service.type=LoadBalancer,ingress.enabled=true,ingress.hosts[0]=superset.192.168.22.7.xip.io  superset ./

Если вы перейдет по ссылке superset.192.168.22.7.xip.io то увидите вот такой экран.



Логин и пароль по умолчанию admin/admin. Superset настроен. Можно пользоваться.



Если нужно удалить весь кластер Kubernetes в microk8s, то можно воспользоваться командой reset


microk8s reset --destroy-storage
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru