ADG Оставить заявку
Блог DevOps 4 мин чтения

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 в наших кластерах уже прошёл - вопросов не возникло.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.