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-е одного из клиентов. Если всё пойдёт нормально, появится пост с подробностями по репликации.