Helm 2.10: расширенные хуки и улучшенная работа с CRD в Kubernetes
Helm 2.10 вышел с улучшенной поддержкой CRD и расширенными хуками жизненного цикла. Разбираем что изменилось на практике при rolling update баз данных.
Helm 2.10 - расширенные хуки жизненного цикла (pre/post-upgrade, crd-install) и улучшенная работа с CustomResourceDefinition при деплое в Kubernetes
Helm 2.10 вышел на прошлой неделе. Обновление минорное по номеру, но по содержанию - несколько вещей, которые нас лично касаются в работе с managed-кластерами. Особенно в части хуков и CRD. Разбираем.
Контекст: откуда мы пришли
Мы перевели CI/CD-пайплайны на Helm ещё в прошлом году. С тех пор Helm стал стандартной точкой входа для деплоя в наших Kubernetes-кластерах: helm upgrade --install на каждый сервис, ChartMuseum как внутренний репозиторий chart-ов, история ревизий вместо ручного отслеживания «что где задеплоено».
В целом работало нормально - пока не дошли до деплоя баз данных. А потом до деплоя операторов с кастомными ресурсами. И вот тут Helm 2.9 начинал скрипеть.
CRD: проблема порядка установки
Custom Resource Definition - это способ расширить API Kubernetes своими типами ресурсов. Операторы (etcd-operator, postgres-operator, vault-operator и другие) активно используют CRD: сначала устанавливаешь оператор, он регистрирует свои CRD в кластере, и после этого можно создавать ресурсы этих типов.
Проблема в том, что Helm 2.9 применял все манифесты chart-а параллельно или в слабо определённом порядке. Если в chart-е были и CRD, и ресурсы этих CRD одновременно - Kubernetes мог отклонить ресурс, потому что CRD ещё не успел зарегистрироваться. Получался race condition прямо во время helm install.
Обходили по-разному: выделяли CRD в отдельный chart, накатывали его первым, потом основной. Громоздко.
В 2.10 появился специальный тип хука - crd-install. Манифест с аннотацией "helm.sh/hook": crd-install устанавливается первым, до остальных ресурсов chart-а, и Helm ждёт пока CRD не будет зарегистрирован. Дальше идёт всё остальное. Порядок теперь детерминирован.
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: postgresclusters.acid.zalan.do
annotations:
"helm.sh/hook": crd-install
"helm.sh/hook-delete-policy": before-hook-creation
spec:
group: acid.zalan.do
names:
kind: PostgresCluster
plural: postgresclusters
scope: Namespaced
version: v1
Теперь весь chart в одном helm install, без ручной оркестрации. Мы проверили на кластере с postgres-оператором - работает как ожидалось.
Хуки жизненного цикла: что улучшили
Хуки в Helm существовали и до 2.10 - pre-install, post-install, pre-delete, post-delete. В 2.10 добавились pre-upgrade и post-upgrade, а также pre-rollback и post-rollback.
Это напрямую закрывает задачу, которая у нас давно висела в бэклоге: rolling update PostgreSQL с обязательной миграцией схемы.
Раньше схема работала примерно так: CI-пайплайн перед деплоем запускал отдельный job в кластере через kubectl, который накатывал миграции, потом делал helm upgrade. Две отдельные операции, слабо связанные между собой. Если helm upgrade падал после успешной миграции - ревизия Helm откатывалась, но схема БД уже изменилась. Неприятно.
С pre-upgrade хуком Job с миграцией становится частью самого Helm-релиза:
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-migrate"
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "-1"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["python", "manage.py", "migrate", "--noinput"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: "{{ .Release.Name }}-db"
key: url
Если Job с миграцией завершится с ошибкой - Helm не перейдёт к обновлению Deployment. Атомарность стала лучше, хотя и не абсолютной (миграции, которые уже применились к БД, не откатятся автоматически - это отдельная задача).
hook-weight и порядок
Хукам можно задавать вес через helm.sh/hook-weight. Хуки с меньшим весом выполняются раньше. Это позволяет выстроить последовательность в рамках одного этапа - например, сначала проверить что БД доступна, потом накатить миграции:
# Сначала - проверка доступности БД
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "-2"
# Потом - миграции
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "-1"
До 2.10 это тоже было, но поведение было менее предсказуемым. В 2.10 упорядочивание хуков по весу стало документированным и стабильным.
Про stable-чарты
Отдельно стоит упомянуть kubernetes/charts - официальный репозиторий stable-чартов. Параллельно с выходом 2.10 там обновились чарты для нескольких ключевых компонентов: postgresql, redis, nginx-ingress, cert-manager. Для нас важно что postgresql-чарт теперь использует как раз crd-install-хуки, что упрощает интеграцию с операторами.
Мы всё равно форкаем stable-чарты для своих нужд - там много generic-ного, что нам не нужно, и отсутствует специфика наших кластеров. Но отслеживать что там делается полезно: иногда видишь как они решили проблему, которую мы тоже пробовали решить.
Что остаётся проблемой
Главный скелет в шкафу Helm 2 - Tiller. Он по-прежнему живёт с правами cluster-admin, хранит историю релизов в ConfigMap-ах в kube-system, и является единой точкой отказа. Мы описывали это раньше, и с тех пор принципиально ничего не изменилось.
2.10 не решает архитектурную проблему Tiller-а. Зато делает работу с хуками и CRD существенно менее болезненной - и это тот слой, на котором мы чаще всего тратим время при деплое баз данных и операторов. Так что апгрейд с 2.9 на 2.10 в наших кластерах уже прошёл - вопросов не возникло.