Делаю мир моей единой точкой отказа на PostgreSQL

CloudNativePG 1.24 умеет декларативный switchover между Kubernetes-кластерами в разных регионах через demotion/promotion-токены. Разбор на примере банка с двумя ЦОДами в Европе.
Опубликовано:

Оригинал: Making the world my single point of failure with PostgreSQL — 27 июня 2024 (обновлено 24 августа 2024).

В CloudNativePG 1.24 появилась прорывная фича — декларативное управление switchover между Kubernetes-кластерами в разных регионах. Без переклонирования бывшего primary.

CloudNativePG, который раньше жил в рамках одного Kubernetes-кластера, теперь поддерживает распределённые топологии PostgreSQL через несколько регионов. Декларативная координация promotion/demotion гарантирует, что primary в каждый момент только один.

Реальный сценарий

Не абстрактный «кластер на трёх континентах», а реалистичный кейс — банк в Европе. Два Kubernetes-кластера: в Центральной Европе и в Западной Европе, каждый по три AZ. У каждого — shared-nothing PostgreSQL. Каждые 3-6 месяцев primary-роль перебрасывается из одного дата-центра в другой — это и есть controlled / planned switchover.

Primary-дата-центр

Stateless-приложения работают рядом с локальным CloudNativePG. Кластер: primary + две hot-standby реплики. Одна реплика — синхронная. Continuous backup + WAL-архив идут в близкий object store. Доступ к приложениям — через DNS, Ingress / LoadBalancer.

DR-дата-центр

Зеркало primary в «симметричной» схеме. Цель — бесшовное переключение с минимумом простоя. Stateless-приложения можно скейлить в 0 в idle-периоды или направлять на DR read-only.

CloudNativePG вводит понятие replica cluster — Postgres-кластер в continuous recovery. В нём одна реплика — designated primary (готова стать primary при switchover), остальные синхронятся через cascading streaming replication.

Designated primary синхронится с источником через continuous backup storage — каждый PostgreSQL-кластер видит чужой object store в read-only. Типичный RPO — 5 минут, без необходимости постоянного streaming-канала между кластерами.

Первичная настройка: Центральная Европа

Primary-кластер pg16-eu-central:

YAML
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: pg16-eu-central
spec:
  imageCatalogRef:
    apiGroup: postgresql.cnpg.io
    kind: ImageCatalog
    name: postgresql
    major: 16

  instances: 3
  minSyncReplicas: 1
  maxSyncReplicas: 1

  storage:
    size: 100Gi
  walStorage:
    size: 50Gi

  backup:
    barmanObjectStore:
      destinationPath: s3://pg16-eu-central/

  externalClusters:
    - name: pg16-eu-central
      barmanObjectStore:
        destinationPath: s3://pg16-eu-central/
    - name: pg16-eu-western
      barmanObjectStore:
        destinationPath: s3://pg16-eu-western/
  replica:
    primary: pg16-eu-central
    source: pg16-eu-western
Нажмите, чтобы развернуть и увидеть больше

Используется новый image catalog для PostgreSQL 16. 3 инстанса с синхронной репликацией кворумом → RPO = 0 локально. PGDATA и WAL разнесены. Локальный object store для continuous backup.

replica.primary указывает, какой кластер сейчас является primary в распределённой топологии. Если metadata.name == replica.primary — этот кластер primary; иначе — replica cluster.

replica.source определяет источник, когда этот кластер перестаёт быть primary.

Best practice — сразу настроить расписание бэкапов:

YAML
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
  name: pg16-eu-central-backup
spec:
  schedule: '0 0 0 * * *'
  backupOwnerReference: self
  cluster:
    name: pg16-eu-central
  immediate: true
Нажмите, чтобы развернуть и увидеть больше

immediate: true снимает первый full backup сразу при создании.

Первичная настройка: Западная Европа

DR-кластер pg16-eu-western:

YAML
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: pg16-eu-western
spec:
  imageCatalogRef:
    apiGroup: postgresql.cnpg.io
    kind: ImageCatalog
    name: postgresql
    major: 16

  instances: 3
  minSyncReplicas: 1
  maxSyncReplicas: 1

  storage:
    size: 100Gi
  walStorage:
    size: 50Gi

  backup:
    barmanObjectStore:
      destinationPath: s3://pg16-eu-western/

  externalClusters:
    - name: pg16-eu-central
      barmanObjectStore:
        destinationPath: s3://pg16-eu-central/
    - name: pg16-eu-western
      barmanObjectStore:
        destinationPath: s3://pg16-eu-western/
  replica:
    primary: pg16-eu-central
    source: pg16-eu-central
Нажмите, чтобы развернуть и увидеть больше

Большая часть конфига идентична. Отличия — имя кластера, локальный object store и replica.source: pg16-eu-central.

Поскольку replica.primarymetadata.name, CloudNativePG считает этот кластер replica cluster и тянет изменения из WAL-архива в Центральной Европе.

Поток: приложения пишут в Центральную Европу → WAL льётся локально → архивируется в S3 каждые 5 минут или по заполнению → designated primary в Западной Европе подтягивает WAL через restore command → реплеит → архивирует в свой S3. Получается continuous backup на двух регионах, без зависимости от вендора storage.

Demotion бывшего primary

В каждый момент primary только один. Перед сменой — старый должен сложить с себя полномочия.

Меняем replica.primary в pg16-eu-central на pg16-eu-western:

YAML
replica:
  primary: pg16-eu-western
  source: pg16-eu-western
Нажмите, чтобы развернуть и увидеть больше

CloudNativePG видит, что pg16-eu-central больше не primary, и понижает его до replica cluster, ожидающего изменений.

Этот шаг = downtime по записи: распределённого primary в этот момент нет.

При demotion CloudNativePG делает две важные операции:

  1. Архивирует REDO-WAL контрольной точки (включая shutdown-checkpoint) в виде .partial-файла.
  2. Генерирует demotionToken в статусе кластера — base64-JSON из pg_controldata (system identifier, latest checkpoint TimeLineID, REDO location, REDO WAL file, время чекпоинта).

Designated primary в Центральной Европе ждёт нового WAL из S3 Западной Европы. Но Западная Европа пока ещё реплика — нового WAL нет.

Promotion бывшей реплики

Берём токен:

BASH
kubectl get cluster pg16-eu-central \
  -o jsonpath='{.status.demotionToken}'
Нажмите, чтобы развернуть и увидеть больше

Посмотреть содержимое:

BASH
kubectl get cluster pg16-eu-central \
  -o jsonpath='{.status.demotionToken}' | base64 -d | jq
Нажмите, чтобы развернуть и увидеть больше

Обновляем pg16-eu-western:

YAML
replica:
  primary: pg16-eu-western
  source: pg16-eu-central
  promotionToken: <DEMOTION_TOKEN_VALUE>
Нажмите, чтобы развернуть и увидеть больше

⚠️ Менять primary и promotionToken нужно одновременно. Без токена будет failover, и старый primary потом придётся пересобрать с нуля.

CloudNativePG аккуратно дотягивает designated primary до LSN из токена и только потом делает promotion: меняет timeline, архивирует history-file и новый WAL → разблокирует репликацию в Центральной Европе (теперь она в роли replica).

Расширения

То, что описано — лишь часть возможного. Можно добавить третий кластер и сделать круговую топологию:

YAML
externalClusters:
  - name: pg16-eu-central
    barmanObjectStore:
      destinationPath: s3://pg16-eu-central/
  - name: pg16-eu-western
    barmanObjectStore:
      destinationPath: s3://pg16-eu-western/
  - name: pg16-eu-south
    barmanObjectStore:
      destinationPath: s3://pg16-eu-south/
Нажмите, чтобы развернуть и увидеть больше

replica.source:

Дополнительно к WAL-архиву можно поднять streaming replication между внешними кластерами — для лучшей устойчивости.

Выводы

CloudNativePG — первый инструмент, способный сделать controlled switchover между ЦОДами полностью декларативно, опираясь только на WAL-архив.

Алгоритм сводится к трём шагам:

  1. Обновить конфиг текущего primary, понизив его и указав нового primary в распределённой топологии.
  2. Снять demotionToken из статуса понижённого кластера.
  3. Обновить конфиг новой реплики, повысив её и передав promotionToken.

Всё ложится в GitOps / IaC. Это решение сразу годится не только для public/private cloud, но и для hybrid/multi-cloud.

Начать поиск

Введите ключевые слова для поиска статей

↑↓
ESC
⌘K Горячая клавиша