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

Kubernetes 1.3 и StatefulSets: пробуем запустить PostgreSQL в контейнере по-человечески

Kubernetes 1.3 анонсировал StatefulSets (ex-PetSets): стабильные имена подов и PVC решают главные проблемы stateful workloads в k8s.

Контекст момента

Kubernetes 1.3 готовится к выходу в июле 2016: ключевое нововведение - StatefulSets (ранее PetSets) для управления stateful-приложениями в контейнерах

Kubernetes мы используем для stateless-сервисов - веб-приложения, API-серверы, фоновые воркеры. Всё это хорошо ложится на модель «pod умер - появился новый, данных нет, ничего страшного». Базы данных в этот сценарий не вписываются: PostgreSQL с каждым перезапуском не должен терять данные, а реплика должна знать, что она реплика, а не мастер.

Официальный совет сообщества k8s до недавнего времени был примерно такой: stateful-приложения запускайте вне кластера. Kubernetes 1.3, который выходит в июле, меняет позицию - вводит StatefulSets (они же PetSets в альфа-версии 1.2). Мы потрогали бету на нашем managed-сопровождении и хотим рассказать что там к чему.

Зачем вообще StatefulSet, если есть Deployment

С обычным Deployment pod-ы взаимозаменяемы и безымянны. Планировщик создаёт app-7f9d4b-xkrjp, перезапускает - получается app-7f9d4b-mnqrt. Разные имена, разные IP, разный hostname. Для stateless это нормально. Для PostgreSQL - нет по нескольким причинам:

  • Persistent storage. PVC через Deployment можно прикрепить, но к конкретному pod-у. При пересоздании нового pod-а старый PVC не гарантированно переприкрепится. Была возможность потерять связку «pod - данные».
  • Идентификация. Реплика PostgreSQL должна знать своё имя и адрес мастера. С рандомными именами pod-ов это либо через переменные окружения, либо через side-car, либо через боль.
  • Порядок запуска. При инициализации кластера БД надо сначала поднять мастер, потом реплики. Deployment запускает реплики параллельно без гарантий порядка.

StatefulSet решает все три проблемы в одном примитиве.

Что даёт StatefulSet

Ключевые гарантии, которые отличают его от Deployment:

Стабильные имена. Pod-ы получают предсказуемые имена: pg-0, pg-1, pg-2. После пересоздания имя сохраняется. У каждого pod-а стабильный DNS-запись вида pg-0.postgres-svc.default.svc.cluster.local через Headless Service.

Sticky PVC. Каждый pod получает свой PVC через volumeClaimTemplates, и этот PVC не удаляется при пересоздании pod-а. pg-0 всегда получает data-pg-0, pg-1 - data-pg-1. Данные на месте.

Упорядоченный запуск и остановка. Pod-ы стартуют строго по порядку: сначала pg-0, потом pg-1. Остановка - в обратном порядке. Это важно для репликации: мастер поднимается раньше реплик.

Манифест выглядит примерно так:

apiVersion: apps/v1alpha1
kind: StatefulSet
metadata:
  name: pg
spec:
  serviceName: "postgres-svc"
  replicas: 2
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:9.5
        ports:
        - containerPort: 5432
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 20Gi

Headless Service нужен отдельно - именно он создаёт DNS-записи для pod-ов:

apiVersion: v1
kind: Service
metadata:
  name: postgres-svc
spec:
  clusterIP: None
  selector:
    app: postgres
  ports:
  - port: 5432

Что мы попробовали

Подняли standalone PostgreSQL 9.5 в StatefulSet на тестовом кластере. Проверили базовые сценарии: pod упал - данные на месте, pod пересоздался с тем же именем, PVC переприкрепился корректно. Это работает.

Дальше проверили что будет если убить ноду целиком. Kubernetes пометил pod как недоступный через несколько минут (таймаут node-monitor-grace-period), пересоздал его на другой ноде. PVC переехал вместе с pod-ом - если storage-класс поддерживает remount (у нас Ceph через rbd).

Репликацию PostgreSQL мы пока не конфигурировали автоматически. Initdb-скрипт и настройка recovery.conf для реплики требуют либо отдельного init-контейнера, либо собственного образа с логикой «если я pg-1, то я реплика, вот адрес мастера». Технически реализуемо, но не из коробки.

Где мы сейчас

Для production-баз данных мы StatefulSets пока не используем. Главная причина - не сама технология, а отсутствие наработанных операционных процедур: как делать мажорный апгрейд PostgreSQL внутри StatefulSet, как делать point-in-time recovery, как работать с резервными копиями когда хранилище внутри кластера. Всё это решаемо, но требует времени на обкатку.

Для non-critical баз - dev/staging окружения, внутренние инструменты - StatefulSets уже смотрятся разумно. Проще чем держать отдельные VM, надёжнее чем обычный Deployment с emptyDir.

Один момент, который стоит иметь в виду: StatefulSet в 1.3 всё ещё beta API (apps/v1alpha1 на момент тестирования, в релизе ожидается apps/v1beta1). Это не значит «нельзя использовать», но значит что API может меняться между минорными версиями. Манифесты, написанные сейчас, могут потребовать правки.

Следующий шаг - попробовать схему с одним мастером и одной read-реплик под реальным трафиком на staging-е одного из клиентов. Если всё пойдёт нормально, появится пост с подробностями по репликации.

Контакт

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

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