Всем привет! Меня зовут Ренат Муфтеев, я DevOps-инженер компании «Флант». В этой статье я поделюсь опытом миграции операторного PostgreSQL из Kubernetes на «железные» серверы.

Выбор способа миграции

Ранее в работе мы использовали кластер PostgreSQL Patroni с двумя репликами, построенный на операторе Zalando в Kubernetes. Изначально всё было стабильно, но со временем приложения развивались, их становилось больше, а трафик рос. Из-за ограничений облачного провайдера мы «переросли» кластер PostgreSQL и приняли решение мигрировать на отдельные виртуальные машины.

Поскольку речь идет про рабочую production-среду, миграция должна проводиться с минимальным простоем. При этом базы настолько разрослись, что простой перенос дампа занял бы сутки.

Я рассматривал следующие варианты миграции:

  • Перенос данных с помощью pg_dump — остановка всех приложений, перенос дампа БД и загрузка дампа в новый кластер Patroni. Процесс долгий и предполагает длительный даунтайм для приложений.

  • Перенос данных с помощью pg_basebackup — процесс значительно быстрее, чем pg_dump, но переносит всю директорию базы данных. В операторном PostgreSQL все конфигурационные файлы хранятся внутри директории с базой. При прямом переносе этой директории на bare-metal-серверы PostgreSQL запускается с конфигами операторного PostgreSQL, и изменить это не удалось. Кроме того, изменение конфигураций в процессе миграции увеличивает время простоя.

  • Настройка standby-кластера — этот способ тоже требует времени, сравнимого с pg_basebackup, но его можно выполнить заранее без даунтайма для production. Сначала настраивается standby-кластер, а в нужный момент происходят его активация и переключение приложений. Поскольку это нативная технология Patroni, после переноса директорий с базой PostgreSQL перечитывает все конфигурационные файлы, включая /etc, и запускается с корректными настройками.

После анализа и тестирования разных вариантов переноса данных я принял решение использовать standby-кластер.

Условия задачи

Дано

У нас есть:

  • оператор PostgreSQL от Zalando версии v1.10.1, развёрнутый в Kubernetes-кластере;

  • PostgreSQL версии 14, также развёрнутый в Kubernetes, с двумя репликами — Leader и Replica;

  • pgbouncer, установленный в Kubernetes, для подключения приложений к операторному PostgreSQL.

Были созданы и настроены следующие компоненты, на которые будет происходить миграция:

  • отдельный кластер ETCD для нового Patroni;

  • отдельный pgbouncer для нового Patroni;

  • новый Patroni, установленный на двух отдельных виртуальных машинах, работающий с PostgreSQL версии 14 (важно, что версии базы совпадают).

Задача

Нам необходимо мигрировать кластер PostgreSQL, управляемый оператором в Kubernetes, на новый, недавно созданный кластер Patroni PostgreSQL. При этом требуется перенести данные, пользователей и сохранить минимальный простой сервисов.

Реализация

Мы настраиваем standby-кластер нового Patroni на основе старого (операторного) PostgreSQL и в нужный момент выполняем его промоушен в основной (primary) кластер. Такой подход значительно сокращает время даунтайма — до нескольких минут — и гарантирует полный перенос данных со старого PostgreSQL на новый.

Схема standby-кластера будет выглядеть так:

Краткий план миграции:

  1. Подготовка операторного PostgreSQL к настройке standby-кластера.

  2. Подготовка кластера Patroni на bare-metal-серверах.

  3. Настройка standby-кластера Patroni

  4. Отключение bare-metal-кластера Patroni от операторного и переключение приложений.

Шаг № 1. Подготовка операторного PostgreSQL к настройке standby-кластера

1. Создание LoadBalancer в Kubernetes.

Для обеспечения доступа нового кластера Patroni к старому операторному PostgreSQL в Kubernetes необходимо настроить LoadBalancer. Он должен направлять трафик именно на Leader кластера Patroni. Поскольку у нас две реплики, которые могут менять роли, создаётся копия существующего сервиса (svc), заданного оператором, с изменённым type на LoadBalancer.

2. Расширение дискового пространства у операторного PostgreSQL.

При инициализации standby-кластера новый Patroni будет копировать данные со старого с помощью pg_basebackup. При этом на старом кластере должен быть создан слот репликации для подключения нового кластера. Во время выполнения pg_basebackup этот слот будет накапливать WAL-журналы, чтобы не потерять данные, появляющиеся в процессе копирования. Если перенос занимает значительное время или нагрузка на базу высокая, объём данных в слоте репликации может существенно вырасти и заполнить весь доступный диск. Поэтому заранее необходимо расширить дисковое пространство на старом кластере, чтобы избежать проблем с дисковым пространством в процессе миграции.

3. Настройка доступа в pg_hba.conf.

Для подключения нового кластера Patroni к старому требуется разрешить доступ с IP-адресов физических серверов, на которых работает Patroni, для пользователей postgres и standby. Аналогичная настройка уже используется для узлов и подов PostgreSQL в операторе. Изменения в pg_hba.conf лучше вносить через Custom Resource (CR) postgresql, чтобы сохранить единый источник управления конфигурацией и избежать ручных правок.

Шаг № 2. Подготовка кластера Patroni на bare-metal-серверах

У нас есть недавно созданный и пустой кластер PostgreSQL, который необходимо дополнительно очистить. Для этого сначала останавливаем Patroni на всех серверах. Перед этим рекомендуется определить, какой сервер выполнял роль Leader:

systemctl stop patroni.service

Удаляем наш кластер из ETCD:

patronictl remove production # Имя кластера из patronictl list.

И очищаем директорию с БД:

rm -rf /var/lib/postgresql/14/main/*

Также нам в pg_hba.conf на всех bare-metal-серверах нужно добавить таких же пользователей, которые используется в операторном PostgreSQL:

postgresql:
  authentication:
    replication:
      username: standby
      password: standby_password
    superuser:
      username: postgres
      password: postgres_password

И внести необходимые изменения в pg_hba.

Шаг № 3. Настройка standby-кластера Patroni

Для настройки bare-metal-кластера в роли Standby от операторного PostgreSQL необходимо в конфиг patroni.yml на бывшем Leader добавить в конфигурацию bootstrap параметры для standby_cluster. Добавляем примерно следующее:

bootstrap:
  dcs:
    standby_cluster:
      host: 10.10.10.10 # Откуда доступен мастер PostgreSQL в K8s.
      port: 5432
      primary_slot_name: patroni # Имя слота в zalando.
      create_replica_methods:
      - basebackup

В Kubernetes создаём слот patroni путём добавления в CR postgresql:

 slots:
      patroni: # Новый слот.
        type: physical

И запускаем Patroni на бывшем bare-metal Leader:

systemctl start patroni.service

После всех проделанных работ мы сможем увидеть на bare-metal Leader, что он начинает стягивать данные из операторного PostgreSQL:

# patronictl list
+ Cluster: production (initializing) -+------------------+----+-----------+
| Member      | Host        | Role    | State            | TL | Lag in MB |
+-------------+-------------+---------+------------------+----+-----------+
| postgresql1 | 10.10.10.11 | Replica | creating replica |    |   unknown |
+-------------+-------------+---------+------------------+----+-----------+

И в операторном PostgreSQL будет отображаться как созданный нами слот, так и слот pg_basebackup_*, с помощью которого осуществляется копирование данных:

postgres=# SELECT slot_name, pg_size_pretty(pg_current_wal_lsn() - restart_lsn), active_pid, catalog_xmin, restart_lsn, confirmed_flush_lsn
FROM pg_replication_slots;
         slot_name          | pg_size_pretty | active_pid | catalog_xmin | restart_lsn  | confirmed_flush_lsn 
----------------------------+----------------+------------+--------------+--------------+---------------------
 patroni                    | 16 MB          |            |              | 1A0/5B01EA88 | 
 pg_basebackup_13141        | 145 kB         |      13141 |              | 1A0/5C000000 | 
(2 rows)

Осталось дождаться, пока bare-metal Leader перейдёт в статус Standby Leader в patronictl list. После этого можно запускать Patroni на втором bare-metal-сервере (обращу внимание, что в его секцию bootstrap ничего добавлять не нужно). Второй сервер начнет «стягивать» данные со Standby Leader:

systemctl start patroni.service

В результате мы получим bare-metal standby-кластер PostgreSQL, управляемый Patroni, где Standby Leader реплицируется от операторного Patroni, а все bare-metal-реплики уже реплицируются с него.

Шаг № 4. Отключение bare-metal-кластера Patroni от операторного и переключение приложений

Нужно любым удобным способом полностью остановить трафик на операторный PostgreSQL. В моём случае все приложения подключались через pgbouncer, поэтому достаточно было просто масштабировать его реплики до нуля. Но если это не так, потребуется поскейлить в ноль реплик все приложения.

После того как трафик на операторном PostgreSQL в Kubernetes полностью остановлен, можно промоутить bare-metal-кластер в самостоятельный. Для этого нужно выполнить команду patronictl edit-config и удалить из конфигурации секцию standby_cluster.

По завершении миграции остаётся лишь переключить трафик приложений на новые серверы с PostgreSQL. В моём случае я также мигрировал на новый pgbouncer. Чтобы не ждать обновления строк подключения во всех приложениях (а их было более 10) и снизить простой, я изменил Kubernetes Service старого pgbouncer, сделав его тип ExternalName, который теперь перенаправляет запросы на новый pgbouncer. В итоге Service стал выглядеть так:

apiVersion: v1
kind: Service
metadata:
  name: pgbouncer
  namespace: infra-production
spec:
  externalName: pgbouncer-production.infra-production.svc.cluster.local # svc для нового pgbouncer
  sessionAffinity: None
  type: ExternalName

На этом мой процесс переключения завершился. При хорошей подготовке его можно выполнить с даунтаймом всего в несколько минут, что значительно быстрее по сравнению с другими способами переноса данных.

Заключение

Standby-кластер используется для настройки репликации между двумя независимыми кластерами Patroni. Его можно использовать для множества целей, и в данной статье рассмотрена одна из них — миграция данных без простоя из одного кластера Patroni в другой. Такой способ позволяет сократить даунтайм до минимума, так как все подготовительные действия, в том числе и перенос данных, можно сделать заранее без влияния на работающие приложения и базу данных для них. Это руководство помогает осуществить подобную миграцию. Надеюсь, оно будет полезным.

P. S.

Читайте также в нашем блоге:

Комментарии (1)


  1. iwram
    18.08.2025 07:28

    Странно, почему так мало причин по уходу постгреса в кубернетесь на bare metal или укажите по ресурсам в кубере и какие железяки стали целевыми. Графики по latency до и после будут?