Пользователь может создавать и настраивать PostgreSQL, если в кластере DKP администратором включена такая возможность.
Пользователь задаёт требуемую конфигурацию с помощью объекта Postgres, указывающего на конкретный класс сервиса (PostgresClass), который определяет доступные параметры и ограничения. PostgresClass создаёт и настраивает администратор кластера.
В руководстве используются два примера:
app-postgres— основной пример для создания и эксплуатации PostgreSQL: ресурсы, режимCluster, репликация, пользователи, базы данных, параметры PostgreSQL, TLS и наблюдаемость;snapshot-pg— отдельный пример для создания и восстановления снимков, поскольку для него требуется StorageClass с поддержкой CSI-снимков.
В примерах используются два worker-узла, чтобы экземпляры PostgreSQL в режиме Cluster могли размещаться на разных узлах. Кластер находится в одной зоне default.
Проверка доступных ресурсов
Перед созданием Postgres проверьте доступные ресурсы worker-узлов. Это позволяет подобрать значения CPU и памяти для примера с учётом реальной загрузки кластера.
Сначала посмотрите список узлов:
d8 k get nodes -o wide
В примере доступны два worker-узла.
Пример вывода:
NAME STATUS ROLES AGE VERSION
worker-1 Ready worker 25d v1.34.9
worker-2 Ready worker 43m v1.34.9
Проверьте занятые ресурсы первого worker-узла:
d8 k describe node worker-1 | grep -A 5 "Allocated resources"
Пример вывода:
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 1104m (28%) 500m (12%)
memory 4096854330 (53%) 390Mi (5%)
Проверьте второй worker-узел:
d8 k describe node worker-2 | grep -A 5 "Allocated resources"
Пример вывода:
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 472m (12%) 500m (12%)
memory 1004Mi (13%) 256Mi (3%)
Проверка хранилища
Перед созданием Postgres проверьте доступные StorageClass и выберите класс хранилища, в котором будут размещаться данные PostgreSQL:
d8 k get storageclass
Пример вывода тестового стенда:
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION
local csi.dvp.deckhouse.io Delete WaitForFirstConsumer true
replicated (default) csi.dvp.deckhouse.io Delete WaitForFirstConsumer true
Параметр spec.instance.persistentVolumeClaim.storageClassName задаётся только при создании Postgres. Изменить его после создания нельзя.
Основной пример: создание Postgres
Создайте неймспейс:
d8 k create namespace my-postgres
Для создания объекта Postgres необходимо выполнить следующие шаги:
- Выбрать PostgresClass;
- Настроить ресурсы;
- Выбрать режим работы;
- Настроить топологию и режим репликации;
- Создать логическую базу данных и пользователя;
- Настроить параметры PostgreSQL;
- Настроить TLS;
- Настроить наблюдаемость.
Ниже представлен типовой пример манифеста Postgres app-postgres, который можно применить сразу, а затем настроить под свои задачи, последовательно проходя каждый из этих шагов.
apiVersion: managed-services.deckhouse.io/v1alpha1
kind: Postgres
metadata:
name: app-postgres
namespace: my-postgres
spec:
postgresClassName: default
configuration:
maxConnections: 120
instance:
cpu:
cores: 1
coreFraction: 50
memory:
size: 1Gi
persistentVolumeClaim:
size: 10Gi
storageClassName: replicated
type: Cluster
cluster:
topology: Ignored
replication: Consistency
users:
- name: app-rw
role: rw
storeCredsToSecret: app-postgres-rw
databases:
- name: app
tls:
mode: K8s
observability: Enabled
Сохраните манифест в postgres.yaml и примените его:
d8 k apply -f postgres.yaml
Проверьте состояние созданного Postgres:
d8 k get postgres app-postgres -n my-postgres -o wide
После завершения развёртывания основные условия (status.conditions) должны перейти в True — что означает каждое условие, см. в разделе «Проверка состояния».
Пример вывода:
NAME AVAILABLE CONFIGURATIONVALID LASTVALIDCONFIGURATIONAPPLIED SCALEDTOLASTVALIDCONFIGURATION DATABASESSYNCED USERSSYNCED
app-postgres True True True True True True
Чтобы изменить параметр app-postgres, отредактируйте соответствующий фрагмент postgres.yaml и примените файл повторно.
Выбор PostgresClass
Параметр spec.postgresClassName определяет PostgresClass, который задаёт доступные параметры и ограничения для Postgres. Посмотреть доступные в кластере PostgresClass можно командой:
d8 k get postgresclass
Пример вывода:
NAME AGE
default 13d
В данном примере используется PostgresClass default со стандартными ограничениями. Если выбран другой PostgresClass, его ограничения можно посмотреть в конфигурации:
d8 k get postgresclass <CLASS_NAME> -o yaml
Где <CLASS_NAME> — имя выбранного PostgresClass.
При выборе PostgresClass учитывайте допустимые значения CPU, памяти и coreFraction, доступные топологии и параметры PostgreSQL, разрешённые для переопределения. Если конфигурация Postgres не соответствует ограничениям выбранного класса, API отклонит её при применении.
Настройки и ограничения PostgresClass описаны в разделе «Ограничение ресурсов CPU и памяти», разделе «Управление отказоустойчивостью через зоны доступности» и разделе «Автоматическая проверка настроек PostgreSQL».
Ограничения размещения
PostgresClass также может определять правила размещения экземпляров PostgreSQL с помощью nodeSelector, nodeAffinity и tolerations. Эти правила применяются автоматически при выборе класса и не указываются в объекте Postgres.
Настройка ресурсов
Для настройки ресурсов каждого экземпляра PostgreSQL — количества CPU, доли гарантированного CPU и объёма памяти — используется параметр spec.instance.
В примере за ресурсы и хранилище отвечает этот фрагмент:
spec:
instance:
cpu:
cores: 1
coreFraction: 50
memory:
size: 1Gi
В примере экземпляру выделяется одно ядро CPU и 1Gi памяти. Параметр coreFraction определяет отношение CPU request к CPU limit. Для cores: 1 и coreFraction: 50 модуль сформировал:
Пример вывода:
limits.cpu: 1
requests.cpu: 500m
Подробнее — в разделе «Ограничение ресурсов CPU и памяти».
Изменение ресурсов существующего Postgres
Ресурсы Postgres можно изменять повторным применением манифеста, если новые значения разрешены выбранным PostgresClass. Сначала узнайте текущие значения ресурсов командой:
d8 k get pods -n postgres \
-l managed-services.deckhouse.io/managed-service-name=app-postgres \
-o custom-columns='NAME:.metadata.name,CPU_REQUEST:.spec.containers[0].resources.requests.cpu,MEMORY_REQUEST:.spec.containers[0].resources.requests.memory'
Пример вывода:
NAME CPU_REQUEST MEMORY_REQUEST
d8ms-pg-app-postgres-1 500m 1Gi
d8ms-pg-app-postgres-2 500m 1Gi
Можно сразу применить изменения как для памяти, так и для CPU, но для наглядности сначала увеличьте память с 1Gi до 2Gi:
spec:
instance:
memory:
size: 2Gi
Примените изменённый манифест:
d8 k apply -f postgres.yaml
После завершения обновления CPU request останется 500m, а memory request экземпляров изменится на 2Gi.
Пример вывода:
NAME CPU_REQUEST MEMORY_REQUEST
d8ms-pg-app-postgres-1 500m 2Gi
d8ms-pg-app-postgres-2 500m 2Gi
Затем измените coreFraction с 50 на 100, оставив cores: 1:
spec:
instance:
cpu:
cores: 1
coreFraction: 100
Повторно примените манифест:
d8 k apply -f postgres.yaml
После завершения обновления CPU request экземпляров изменится с 500m на 1, а memory request останется 2Gi.
Пример вывода:
NAME CPU_REQUEST MEMORY_REQUEST
d8ms-pg-app-postgres-1 1 2Gi
d8ms-pg-app-postgres-2 1 2Gi
Таким образом, у работающего Postgres можно изменять память и coreFraction в пределах, разрешённых выбранным PostgresClass.
Проверка ограничения CPU и памяти через PostgresClass
Значения CPU и памяти должны соответствовать ограничениям выбранного PostgresClass. Если указанные ресурсы не соответствуют допустимым значениям или их сочетаниям, API отклонит конфигурацию.
PostgresClass default не подходит для наглядной проверки ограничений. Поэтому в этом примере используется отдельный PostgresClass check, который разрешает для 1–2 CPU память от 512Mi до 2Gi с шагом 512Mi.
При cores: 1 и coreFraction: 50 значение памяти 700Mi не соответствует установленному шагу, поэтому манифест будет отклонён:
spec:
postgresClassName: check
instance:
cpu:
cores: 1
coreFraction: 50
memory:
size: 700Mi
Примените манифест:
d8 k apply -f postgres.yaml
API отклонит запрос. Пример вывода:
spec.instance.memory.size: Invalid value: 734003200: memory setting does not fit Step 536870912 of the selected PostgresClass
Выбор режима работы
От выбора режима работы зависит состав экземпляров PostgreSQL: Cluster создаёт основной экземпляр и реплики, состав которых зависит от выбранного режима репликации. А Standalone — один экземпляр PostgreSQL без реплик.
Режим Cluster
Для работы с основным экземпляром и репликами используется режим Cluster, который задаётся параметром spec.type.
spec:
type: Cluster
cluster:
topology: Ignored
replication: Consistency
Режим репликации и его параметры настраиваются отдельно. Доступные режимы и примеры их использования описаны в разделе «Настройка репликации».
Режим Standalone
Режим Standalone используется для запуска PostgreSQL с одним экземпляром без репликации. В отличие от режима Cluster, для него не используются параметры топологии и репликации.
Чтобы использовать этот режим, укажите:
spec:
type: Standalone
После создания Postgres будет запущен один экземпляр PostgreSQL. Проверьте созданные экземпляры PostgreSQL:
d8 k get pods -n postgres \
-l managed-services.deckhouse.io/managed-service-name=app-postgres \
-o wide
Пример вывода:
NAME STATUS NODE
d8ms-pg-app-postgres-1 Running worker-1
Проверьте Service, созданные для подключения к PostgreSQL:
d8 k get svc -n my-postgres | grep app-postgres
Пример вывода:
d8ms-pg-app-postgres-r ClusterIP 10.223.234.52 <none> 5432/TCP
d8ms-pg-app-postgres-ro ClusterIP 10.223.70.248 <none> 5432/TCP
d8ms-pg-app-postgres-rw ClusterIP 10.223.120.250 <none> 5432/TCP
Проверьте, на какие экземпляры направлены Service, через эндпоинты:
d8 k get endpoints -n my-postgres | grep app-postgres
Пример вывода:
d8ms-pg-app-postgres-r 10.112.2.31:5432 42h
d8ms-pg-app-postgres-ro <none> 42h
d8ms-pg-app-postgres-rw 10.112.2.31:5432 42h
Сервисы с суффиксами -r и -rw направляют подключения на единственный экземпляр PostgreSQL. Сервис с суффиксом -ro также создаётся, но не имеет эндпоинта, поскольку в режиме Standalone отсутствуют реплики.
Настройка топологии и режима репликации
В режиме Cluster пользователь может управлять отказоустойчивостью экземпляров PostgreSQL, задавая их размещение по узлам и зонам доступности в параметре spec.cluster.topology.
Настройка топологии
Поддерживаются следующие значения:
Ignored— размещение выполняется по стандартным правилам планирования Kubernetes с разнесением экземпляров по разным узлам;Zonal— экземпляры размещаются в пределах одной из разрешённых зон;TransZonal— экземпляры размещаются в разных зонах доступности.
Доступные значения топологии и зоны определяются выбранным PostgresClass. Для Zonal и TransZonal инфраструктура кластера должна предоставлять соответствующие зоны доступности. Подробнее — в разделе «Управление отказоустойчивостью через зоны доступности».
Размещение без выбора зоны
При topology: Ignored размещением экземпляров управляет планировщик Kubernetes. Режим обеспечивает разнесение экземпляров по разным узлам без дополнительных настроек со стороны пользователя. В основном примере используется этот режим:
spec:
cluster:
topology: Ignored
Проверьте размещение экземпляров командой:
d8 k get pods -n my-postgres \
-l managed-services.deckhouse.io/managed-service-name=app-postgres \
-o wide
Экземпляры должны находиться на разных узлах.
Пример вывода:
NAME STATUS NODE
d8ms-pg-app-postgres-1 Running worker-1
d8ms-pg-app-postgres-2 Running worker-2
Размещение в одной зоне
При topology: Zonal для размещения Postgres выбирается одна из зон, разрешённых выбранным PostgresClass. Все экземпляры кластера размещаются в пределах этой зоны.
spec:
cluster:
topology: Zonal
Для использования Zonal узлы должны иметь лейбл topology.kubernetes.io/zone со значением соответствующей зоны. Зона должна быть разрешена выбранным PostgresClass.
Например, если два доступных узла относятся к зоне default:
Пример вывода:
NAME ZONE
worker-1 default
worker-2 default
Экземпляры могут быть размещены следующим образом.
Пример вывода:
NAME STATUS NODE
d8ms-pg-app-postgres-1 Running worker-1
d8ms-pg-app-postgres-2 Running worker-2
В этом примере оба экземпляра размещены в зоне default.
Настройка репликации
В режиме Cluster репликация обеспечивает передачу данных с основного экземпляра PostgreSQL на реплики. Режим репликации задаётся в spec.cluster.replication.
Поддерживаются следующие режимы:
Availability— основной экземпляр и одна асинхронная реплика;Consistency— основной экземпляр и одна синхронная реплика;ConsistencyAndAvailability— основной экземпляр, одна синхронная и одна асинхронная реплика.
Проверка режима репликации
Состояние репликации проверяется через представление pg_stat_replication на основном экземпляре. Эту процедуру используйте для любого режима Cluster, в том числе после смены spec.cluster.replication.
Сначала определите текущий основной экземпляр:
PRIMARY="$(d8 k get clusters.cnpg.internal.managed.deckhouse.io d8ms-pg-app-postgres \
-n my-postgres \
-o jsonpath='{.status.targetPrimary}')"
Затем выполните запрос:
d8 k exec -n my-postgres "$PRIMARY" -- \
psql -U postgres -d postgres -c \
"SELECT application_name, state, sync_state FROM pg_stat_replication;"
Ожидаемые значения sync_state зависят от режима:
| Режим | Число реплик | Ожидаемый sync_state |
|---|---|---|
Availability |
1 | async |
Consistency |
1 | quorum |
ConsistencyAndAvailability |
2 | quorum и async |
Значение state: streaming означает, что реплика получает изменения от основного экземпляра. Во время смены режима роли экземпляров могут меняться, поэтому каждый раз определяйте основной экземпляр заново через status.targetPrimary, а не по номеру Pod.
Режим Availability
Режим Availability создаёт основной экземпляр PostgreSQL и одну асинхронную реплику.
Чтобы использовать этот режим, укажите:
spec:
type: Cluster
cluster:
topology: Zonal
replication: Availability
После создания Postgres запускаются два экземпляра PostgreSQL:
d8 k get pods -n my-postgres \
-l managed-services.deckhouse.io/managed-service-name=app-postgres \
-o wide
Пример вывода:
NAME READY STATUS
d8ms-pg-app-postgres-1 1/1 Running
d8ms-pg-app-postgres-2 1/1 Running
Проверьте режим репликации, как описано в разделе «Проверка режима репликации». Для Availability ожидается одна реплика со sync_state = async:
application_name | state | sync_state
---------------------------+-----------+------------
d8ms-pg-app-postgres-2 | streaming | async
(1 row)
Распределение Service между основным экземпляром и репликой можно проверить через EndpointSlice:
d8 k get endpointslice -n my-postgres | grep app-postgres
Пример вывода:
d8ms-pg-app-postgres-r-v8kcv IPv4 5432 10.112.2.249,10.112.2.155
d8ms-pg-app-postgres-ro-696bp IPv4 5432 10.112.2.155
d8ms-pg-app-postgres-rw-8nx8s IPv4 5432 10.112.2.249
Service -rw направляет подключения на основной экземпляр, -ro — на реплику, а -r — на оба экземпляра.
Режим Consistency
Режим Consistency, используемый в основном примере, создаёт основной экземпляр PostgreSQL и одну синхронную реплику:
spec:
type: Cluster
cluster:
topology: Ignored
replication: Consistency
После создания Postgres запускаются два экземпляра PostgreSQL:
d8 k get pods -n my-postgres \
-l managed-services.deckhouse.io/managed-service-name=app-postgres \
-o wide
Пример вывода:
NAME STATUS NODE
d8ms-pg-app-postgres-1 Running worker-1
d8ms-pg-app-postgres-2 Running worker-2
Проверьте режим репликации, как описано в разделе «Проверка режима репликации». Для Consistency ожидается одна реплика со sync_state = quorum:
application_name | state | sync_state
----------------------------+-----------+------------
d8ms-pg-app-postgres-2 | streaming | quorum
(1 row)
Дополнительно работу синхронной репликации можно проверить по фактической передаче данных. Определите основной экземпляр так же, как в разделе «Проверка режима репликации»:
PRIMARY="$(d8 k get clusters.cnpg.internal.managed.deckhouse.io d8ms-pg-app-postgres \
-n my-postgres \
-o jsonpath='{.status.targetPrimary}')"
Создайте на основном экземпляре контрольную таблицу и добавьте запись:
d8 k exec -n my-postgres "$PRIMARY" -- \
psql -U postgres -d postgres -c "
CREATE TABLE consistency_check (
id integer PRIMARY KEY,
value text
);
INSERT INTO consistency_check VALUES (1, 'replicated');
"
Определите реплику:
REPLICA="$(d8 k get pods -n my-postgres \
-l managed-services.deckhouse.io/managed-service-name=app-postgres \
-o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' | \
grep -v "^${PRIMARY}$" | head -n1)"
Проверьте наличие записи непосредственно на реплике:
d8 k exec -n my-postgres "$REPLICA" -- \
psql -U postgres -d postgres -c \
"SELECT pg_is_in_recovery(), * FROM consistency_check;"
Пример вывода:
pg_is_in_recovery | id | value
-------------------+----+------------
t | 1 | replicated
(1 row)
Значение pg_is_in_recovery() = t показывает, что запрос выполнен на реплике. Наличие строки replicated подтверждает передачу данных с основного экземпляра на синхронную реплику.
Режим ConsistencyAndAvailability
Режим ConsistencyAndAvailability создаёт основной экземпляр PostgreSQL, одну синхронную и одну асинхронную реплику.
Чтобы использовать этот режим, укажите:
spec:
type: Cluster
cluster:
topology: Zonal
replication: ConsistencyAndAvailability
После создания Postgres запускаются три экземпляра PostgreSQL:
d8 k get pods -n my-postgres \
-l managed-services.deckhouse.io/managed-service-name=app-postgres \
-o wide
Пример вывода:
NAME READY STATUS
d8ms-pg-app-postgres-1 1/1 Running
d8ms-pg-app-postgres-2 1/1 Running
d8ms-pg-app-postgres-3 1/1 Running
Проверьте режим репликации, как описано в разделе «Проверка режима репликации». Для ConsistencyAndAvailability ожидаются две реплики — со sync_state = quorum и sync_state = async:
application_name | state | sync_state
---------------------------+-----------+------------
d8ms-pg-app-postgres-2 | streaming | quorum
d8ms-pg-app-postgres-3 | streaming | async
(2 rows)
Изменение режима репликации существующего кластера
Режим репликации можно изменить у уже существующего Postgres в режиме Cluster. Для этого измените spec.cluster.replication в манифесте app-postgres и примените его повторно.
Например, чтобы перейти с Availability на Consistency, укажите:
spec:
cluster:
replication: Consistency
Примените изменения:
d8 k apply -f postgres.yaml
Во время обновления ScaledToLastValidConfiguration может временно перейти в False. После завершения обновления условия объекта (status.conditions) должны вернуться в True.
Проверьте новый режим, как описано в разделе «Проверка режима репликации». После перехода на Consistency реплика должна работать в синхронном режиме:
d8ms-pg-app-postgres-1 | streaming | quorum
При обратном переходе на Availability та же проверка должна показывать асинхронную репликацию:
d8ms-pg-app-postgres-2 | streaming | async
При переходе на ConsistencyAndAvailability число экземпляров увеличивается с двух до трёх. Проверьте запущенные экземпляры:
d8 k get pods -n my-postgres \
-l managed-services.deckhouse.io/managed-service-name=app-postgres \
-o wide
После завершения обновления проверка pg_stat_replication должна показывать синхронную и асинхронную реплики:
application_name | state | sync_state
---------------------------+-----------+------------
d8ms-pg-app-postgres-3 | streaming | async
d8ms-pg-app-postgres-2 | streaming | quorum
(2 rows)
При обратном переходе с ConsistencyAndAvailability на Consistency число экземпляров уменьшается с трёх до двух, а оставшаяся реплика работает в режиме streaming | quorum.
Создание логической базы данных и пользователя
В основном примере создаются пользователь app-rw и логическая база данных app:
spec:
users:
- name: app-rw
role: rw
storeCredsToSecret: app-postgres-rw
databases:
- name: app
После применения манифеста дождитесь синхронизации пользователей и баз данных. Состояния USERSSYNCED и DATABASESSYNCED должны иметь значение True:
d8 k get postgres app-postgres -n my-postgres -o wide
Пользователь PostgreSQL
Учётные данные пользователя сохраняются в Secret, указанном в storeCredsToSecret.
Проверьте созданный Secret:
d8 k get secret app-postgres-rw -n my-postgres
Пример вывода:
NAME TYPE DATA
app-postgres-rw kubernetes.io/basic-auth 4
Secret содержит параметры, необходимые для подключения:
app-dsn
host
password
username
Получите параметры подключения следующим образом:
echo "host: $(d8 k get secret app-postgres-rw -n my-postgres -o jsonpath='{.data.host}' | base64 --decode)"
echo "username: $(d8 k get secret app-postgres-rw -n my-postgres -o jsonpath='{.data.username}' | base64 --decode)"
echo "password: $(d8 k get secret app-postgres-rw -n my-postgres -o jsonpath='{.data.password}' | base64 --decode)"
echo "app-dsn: $(d8 k get secret app-postgres-rw -n my-postgres -o jsonpath='{.data.app-dsn}' | base64 --decode)"
Пример вывода:
host: d8ms-pg-app-postgres-rw
username: app-rw
password: <PASSWORD>
app-dsn: postgresql://app-rw:<PASSWORD>@d8ms-pg-app-postgres-rw:5432/app
Где <PASSWORD> — пароль пользователя из Secret.
Значения из Secret можно использовать для настройки подключения приложения или PostgreSQL-клиента.
Для подключения приложений используйте Secret, имя которого указано в storeCredsToSecret. Внутренние Secret с именами вида d8ms-pg-... для этого использовать не следует.
Декларативное управление пользователями
Список пользователей в spec.users описывает требуемое состояние PostgreSQL. При изменении списка модуль синхронизирует роли пользователей и связанные с ними Secret.
Например, удалите пользователя app-rw из манифеста:
spec:
users: []
Примените изменённый манифест:
d8 k apply -f postgres.yaml
После завершения синхронизации условие USERSSYNCED должно вернуться в True:
d8 k get postgres app-postgres -n my-postgres -o wide
Проверьте отсутствие роли непосредственно в PostgreSQL:
PRIMARY="$(d8 k get clusters.cnpg.internal.managed.deckhouse.io d8ms-pg-app-postgres \
-n my-postgres \
-o jsonpath='{.status.targetPrimary}')"
d8 k exec -n my-postgres "$PRIMARY" -- \
psql -U postgres -d postgres -Atc \
"SELECT rolname FROM pg_roles WHERE rolname = 'app-rw';"
Команда не должна вернуть имя роли.
При выполнении d8 k exec может отображаться служебное сообщение о выборе контейнера:
Defaulted container "postgres" out of: postgres, bootstrap-controller (init)
Логическая база данных app, оставшаяся в spec.databases, при удалении пользователя не удаляется. Проверьте её наличие командой:
d8 k exec -n my-postgres "$PRIMARY" -- \
psql -U postgres -d postgres -Atc \
"SELECT datname FROM pg_database WHERE datname = 'app';"
Ожидаемый вывод:
app
Удаление пользователя из spec.users приводит к удалению соответствующей роли PostgreSQL. Перед удалением убедитесь, что пользователь больше не используется приложениями.
Чтобы создать пользователя заново, добавьте его обратно в spec.users:
spec:
users:
- name: app-rw
role: rw
storeCredsToSecret: app-postgres-rw
После повторного применения манифеста модуль создаст роль PostgreSQL и Secret заново.
Логическая база данных
Логические базы данных, которые должен создать и поддерживать модуль, задаются в spec.databases:
spec:
databases:
- name: app
После создания базы условие DATABASESSYNCED должно иметь значение True.
Удаление базы данных из spec.databases приводит к удалению соответствующей логической базы данных PostgreSQL вместе с её данными.
Подключение к PostgreSQL
После создания Postgres модуль создаёт Service -r, -ro и -rw, которые используются для подключения к экземплярам PostgreSQL в зависимости от их роли:
-rw— основной экземпляр;-ro— реплики;-r— все доступные экземпляры.
Для наглядности в примере app-postgres создаются следующие Service:
NAME TYPE PORT(S)
d8ms-pg-app-postgres-r ClusterIP 5432/TCP
d8ms-pg-app-postgres-ro ClusterIP 5432/TCP
d8ms-pg-app-postgres-rw ClusterIP 5432/TCP
По умолчанию эти Service имеют тип ClusterIP и доступны внутри кластера. Учётные данные и параметры подключения пользователя сохраняются в Secret, указанном в storeCredsToSecret.
Подключаться к PostgreSQL можно как из приложений внутри кластера, так и из внешней сети. Для внешнего подключения соответствующий Service необходимо дополнительно опубликовать.
Подключение из кластера
Для подключения из кластера используйте соответствующий Service и учётные данные из Secret пользователя. В основном примере приложение с правами на запись подключается к Service d8ms-pg-app-postgres-rw от имени пользователя app-rw к базе данных app.
Для проверки подключения не требуется устанавливать psql на control-plane-узел. Для этого можно использовать временный клиентский Pod:
d8 k run postgres-client \
-n my-postgres \
--rm -it \
--restart=Never \
--image=postgres:17 \
--env="PGPASSWORD=$(d8 k get secret app-postgres-rw -n my-postgres -o jsonpath='{.data.password}' | base64 --decode)" \
-- \
psql \
-h d8ms-pg-app-postgres-rw \
-U app-rw \
-d app \
-c 'SELECT current_database(), session_user, current_user;'
Пример вывода:
current_database | session_user | current_user
------------------+--------------+--------------
app | app-rw | rw
(1 row)
session_user показывает пользователя, от имени которого выполнено подключение (app-rw), а current_user — действующую роль прав (rw).
Внешнее подключение к PostgreSQL
Для работы с PostgreSQL из внешней сети можно использовать графические клиенты и другие приложения, поддерживающие подключение к PostgreSQL. Для этого Service, через который публикуется PostgreSQL, должен быть доступен извне кластера, а клиенту необходимо указать адрес сервера, порт, базу данных и учётные данные пользователя.
В этом разделе внешнее подключение рассматривается на примере DBeaver. Аналогичным образом можно использовать другие PostgreSQL-клиенты и приложения.
В примере подключение выполняется к созданной ранее базе данных app от имени пользователя app-rw в Postgres app-postgres.
Публикация PostgreSQL для внешнего доступа
Способ публикации зависит от сетевой инфраструктуры кластера. В этом примере внешний балансировщик нагрузки принимает подключения к <EXTERNAL_IP>:5432 и перенаправляет их на NodePort 30001 узла кластера. Отдельный Service направляет этот трафик на основной экземпляр PostgreSQL.
Не изменяйте созданный модулем Service d8ms-pg-app-postgres-rw. Создайте отдельный Service для внешнего доступа:
apiVersion: v1
kind: Service
metadata:
name: app-postgres-external
namespace: my-postgres
spec:
type: NodePort
selector:
cnpg.internal.managed.deckhouse.io/cluster: d8ms-pg-app-postgres
cnpg.internal.managed.deckhouse.io/instanceRole: primary
ports:
- name: postgres
protocol: TCP
port: 5432
targetPort: 5432
nodePort: 30001
Примените манифест:
d8 k apply -f app-postgres-external.yaml
Проверьте созданный Service:
d8 k get svc app-postgres-external -n my-postgres -o wide
Пример вывода:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
app-postgres-external NodePort 10.223.111.45 <none> 5432:30001/TCP 4s cnpg.internal.managed.deckhouse.io/cluster=d8ms-pg-app-postgres,cnpg.internal.managed.deckhouse.io/instanceRole=primary
На внешнем балансировщике нагрузки настройте приём TCP-соединений на порту 5432 и перенаправление на NodePort 30001 узла кластера. В текущем примере создана такая схема:
<EXTERNAL_IP>:5432
|
внешний балансировщик нагрузки
|
<NODE_IP>:30001
|
NodePort
|
primary PostgreSQL :5432
При публикации PostgreSQL во внешней сети убедитесь, что доступ к порту базы данных ограничен только доверенными источниками. Для этого могут использоваться файрвол, списки разрешённых IP-адресов, VPN и другие средства сетевой инфраструктуры. Не рекомендуется оставлять PostgreSQL доступным из Интернета без ограничений.
Перед проверкой внешнего подключения можно убедиться, что созданный NodePort направляет трафик на основной экземпляр PostgreSQL. Для этого получите пароль пользователя:
PGPASSWORD="$(d8 k get secret app-postgres-rw -n my-postgres \
-o jsonpath='{.data.password}' | base64 --decode)"
Запустите временный клиентский Pod и подключитесь через IP узла и NodePort:
d8 k run nodeport-test \
-n my-postgres \
--rm -i \
--restart=Never \
--image=postgres:17 \
--env="PGPASSWORD=$PGPASSWORD" \
-- \
psql \
-h <NODE_IP> \
-p 30001 \
-U app-rw \
-d app \
-c "SELECT current_database(), pg_is_in_recovery(), inet_server_addr();"
Пример успешного результата:
current_database | pg_is_in_recovery | inet_server_addr
------------------+-------------------+------------------
app | f | <POD_IP>
(1 row)
Значение pg_is_in_recovery = f подтверждает, что соединение направлено на основной экземпляр PostgreSQL.
Подключение по IP-адресу
Подключение непосредственно по IP-адресу технически возможно и может использоваться, например, для проверки внешней доступности PostgreSQL. Для постоянного подключения рекомендуется использовать DNS-имя и TLS с проверкой сертификата сервера, как описано далее.
Получите пароль пользователя:
d8 k get secret app-postgres-rw -n my-postgres \
-o jsonpath='{.data.password}' | base64 --decode; echo
Создайте в DBeaver подключение PostgreSQL и укажите:
Host: <EXTERNAL_IP>
Port: 5432
Database: app
Username: app-rw
Password: <PASSWORD>
Где <PASSWORD> — пароль из Secret app-postgres-rw.
После подключения откройте SQL Editor и выполните:
SELECT
current_database(),
session_user,
inet_server_addr(),
inet_server_port(),
pg_is_in_recovery();
На проверенном стенде запрос вернул:
current_database | session_user | inet_server_addr | inet_server_port | pg_is_in_recovery
------------------+--------------+------------------+------------------+-------------------
app | app-rw | <POD_IP> | 5432 | f
Значение pg_is_in_recovery = f подтверждает подключение к основному экземпляру PostgreSQL.
Подключение с проверкой TLS
Для постоянного внешнего подключения рекомендуется использовать TLS с проверкой сертификата сервера.
В примере для app-postgres используется режим K8s, поэтому TLS-сертификаты PostgreSQL выпускаются автоматически. Серверный сертификат подписан cluster-selfsigned-ca.
Сохраните автоматически созданный серверный сертификат в файл, чтобы определить DNS-имя из SAN:
d8 k get secret d8ms-pg-app-postgres-server-cert \
-n my-postgres \
-o jsonpath='{.data.tls\.crt}' | \
base64 --decode > /tmp/app-postgres-server.crt
Посмотрите сведения о сертификате и его Subject Alternative Name (SAN):
openssl x509 \
-in /tmp/app-postgres-server.crt \
-noout \
-subject -issuer -dates -ext subjectAltName
Для app-postgres сертификат содержит DNS-имя Service -rw:
d8ms-pg-app-postgres-postgres-rw.<EXTERNAL_IP>.sslip.io
При использовании режима verify-full клиент проверяет соответствие имени сервера сертификату, поэтому для подключения используйте DNS-имя из SAN.
Получите CA-сертификат:
d8 k get secret selfsigned-ca-key-pair \
-n d8-cert-manager \
-o jsonpath='{.data.tls\.crt}' | \
base64 --decode > /tmp/app-postgres-ca.crt
Проверьте цепочку доверия:
openssl verify \
-CAfile /tmp/app-postgres-ca.crt \
/tmp/app-postgres-server.crt
Пример успешного результата:
/tmp/app-postgres-server.crt: OK
Перенесите CA-сертификат на компьютер, с которого выполняется подключение. Например, если к узлу кластера доступен SSH, скопируйте сертификат с помощью scp:
scp user@<NODE_IP>:/tmp/app-postgres-ca.crt ~/app-postgres-ca.crt
В DBeaver укажите параметры подключения:
Host: d8ms-pg-app-postgres-postgres-rw.<EXTERNAL_IP>.sslip.io
Port: 5432
Database: app
Username: app-rw
Password: <PASSWORD>
Где <PASSWORD> — пароль из Secret app-postgres-rw.
В настройках SSL укажите CA-сертификат и режим verify-full:
CA Certificate: <CA_CERT_PATH>
SSL mode: verify-full
Где <CA_CERT_PATH> — путь к файлу app-postgres-ca.crt.
После подключения выполните:
SELECT
current_database(),
session_user,
inet_server_addr(),
inet_server_port(),
pg_is_in_recovery();
Успешное выполнение запроса и значение pg_is_in_recovery = f подтверждают подключение к основному экземпляру PostgreSQL.
Если при использовании verify-full вместо DNS-имени из SAN указать IP-адрес <EXTERNAL_IP>, проверка имени сервера завершится ошибкой:
The hostname <EXTERNAL_IP> could not be verified by hostnameverifier PgjdbcHostnameVerifier.
Таким образом, для подключения с verify-full используйте DNS-имя, указанное в SAN серверного сертификата.
Настройка параметров PostgreSQL
Параметры PostgreSQL можно изменять через spec.configuration, если выбранный PostgresClass разрешает их переопределение.
Возможность изменения параметра определяется настройками PostgresClass:
- параметр должен быть разрешён для переопределения;
- значение параметра должно соответствовать установленным правилам проверки.
Если параметр запрещён для изменения или его значение не соответствует ограничениям, API отклонит запрос.
Изменение разрешённого параметра
В основном примере app-postgres использует PostgresClass default, который разрешает изменять параметр maxConnections.
Измените значение:
spec:
configuration:
maxConnections: 100
Примените изменения:
d8 k apply -f postgres.yaml
После завершения обновления проверьте применённое значение непосредственно в PostgreSQL:
PRIMARY="$(d8 k get clusters.cnpg.internal.managed.deckhouse.io d8ms-pg-app-postgres \
-n my-postgres \
-o jsonpath='{.status.targetPrimary}')"
d8 k exec -n my-postgres "$PRIMARY" -- \
psql -U postgres -d postgres -c \
"SHOW max_connections;"
Пример вывода:
max_connections
-----------------
100
Параметр был изменён, так как он разрешён для переопределения выбранным PostgresClass.
Ограничения на переопределение параметров
PostgresClass может ограничивать список параметров PostgreSQL, которые пользователь может изменять через spec.configuration.
Например, если PostgresClass разрешает переопределять только:
overridableConfiguration:
- maxConnections
- sharedBuffers
- walKeepSize
попытка изменить параметр, отсутствующий в этом списке, будет отклонена.
Примените, например:
spec:
configuration:
workMem: 16Mi
d8 k apply -f postgres.yaml
API вернёт ошибку. Пример вывода:
Configuration field workmem restricted to override by administrator in selected postgresClass
В этом случае конфигурация Postgres не изменится, так как параметр запрещён выбранным PostgresClass.
Проверка значений параметров
Помимо списка разрешённых параметров, PostgresClass может задавать правила проверки допустимых значений.
Например, если для maxConnections установлено ограничение:
configuration.maxConnections >= 100
следующее изменение будет отклонено:
spec:
configuration:
maxConnections: 50
Примените манифест:
d8 k apply -f postgres.yaml
API вернёт ошибку. Пример вывода:
Rule: configuration.maxConnections >= 100
Существующий Postgres продолжит работать с последней успешно применённой конфигурацией.
Настройка TLS
Для управления TLS-сертификатами PostgreSQL используется параметр spec.tls. Поддерживаются режимы CertManager, CustomCertificate и K8s.
Для использования сертификатов, выпускаемых cert-manager, укажите режим CertManager:
spec:
tls:
mode: CertManager
certManager:
clusterIssuerName: postgres-ca
Соответствующий Issuer или ClusterIssuer должен быть предварительно подготовлен. Административные зависимости описаны в разделе «Зависимости для отдельных функций».
Чтобы использовать существующие сертификаты из Secret, выберите режим CustomCertificate:
spec:
tls:
mode: CustomCertificate
customCertificate:
serverCASecret: postgres-ca
serverTLSSecret: postgres-tls
Режим K8s
В режиме K8s сертификаты для PostgreSQL выпускаются автоматически:
spec:
tls:
mode: K8s
После перехода объекта в готовое состояние модуль создаёт Secret с CA, сертификаты сервера и репликации.
Проверьте использование TLS на стороне PostgreSQL через представление pg_stat_ssl. Определите основной экземпляр так же, как в разделе «Проверка режима репликации», и выполните запрос:
PRIMARY="$(d8 k get clusters.cnpg.internal.managed.deckhouse.io d8ms-pg-app-postgres \
-n my-postgres \
-o jsonpath='{.status.targetPrimary}')"
d8 k exec -n my-postgres "$PRIMARY" -- \
psql -U postgres -d postgres -c "
SELECT
a.pid,
a.usename,
a.client_addr,
a.client_port,
s.ssl,
s.version,
s.cipher
FROM pg_stat_activity a
LEFT JOIN pg_stat_ssl s USING (pid)
WHERE a.usename = 'app-rw';
"
Для TLS-соединения поле ssl имеет значение t, а в version и cipher отображаются используемые версия TLS и шифр.
Настройка клиентского подключения с проверкой сертификата сервера описана в разделе «Подключение с проверкой TLS».
Мониторинг и алерты
Для Postgres можно включить мониторинг с алертами, полностью отключить мониторинг или оставить мониторинг без алертов. Режим наблюдаемости задаётся параметром spec.observability.
В основном примере включены мониторинг и алерты:
spec:
observability: Enabled
Чтобы полностью отключить мониторинг, используйте:
spec:
observability: Disabled
Чтобы сохранить мониторинг, но отключить алерты, используйте:
spec:
observability: EnabledWithoutAlerts
Проверьте применённый режим по лейблам Pod:
d8 k get pod -n my-postgres \
-l managed-services.deckhouse.io/managed-service-name=app-postgres \
-o json | \
jq '.items[].metadata.labels | with_entries(select(.key | test("observability|prometheus")))'
Значение лейбла observability.deckhouse.io/servicemonitoring зависит от выбранного режима:
Enabled → enabled
Disabled → disabled
EnabledWithoutAlerts → no-alerts
При включённом мониторинге вывод также содержит лейбл:
"prometheus.deckhouse.io/custom-target": "managed-postgres"
Резервное копирование и восстановление
Для создания снимков используется объект PostgresSnapshot. StorageClass, в котором размещён Postgres, должен использовать CSI-драйвер с поддержкой snapshots, а в кластере должен быть доступен соответствующий VolumeSnapshotClass.
В основном примере используется StorageClass replicated, для которого провайдер в рассматриваемой конфигурации не поддерживает создание снимков. Поэтому для демонстрации используется отдельный StorageClass snapshot-local на sds-local-volume с LVM Thin.
Проверьте доступные классы снимков:
d8 k get volumesnapshotclass
Для snapshot-local доступен следующий класс снимков:
NAME DRIVER DELETIONPOLICY
sds-local-volume-snapshot-class local.csi.storage.deckhouse.io Delete
Создание снимка
Для проверки создайте отдельный Postgres snapshot-pg в StorageClass snapshot-local:
apiVersion: managed-services.deckhouse.io/v1alpha1
kind: Postgres
metadata:
name: snapshot-pg
namespace: my-postgres
spec:
postgresClassName: default
instance:
cpu:
cores: 1
coreFraction: 50
memory:
size: 1Gi
persistentVolumeClaim:
size: 2Gi
storageClassName: snapshot-local
type: Standalone
users:
- name: snapshot-rw
role: rw
storeCredsToSecret: snapshot-pg-rw
databases:
- name: snapshotdb
Чтобы наглядно проверить восстановление данных на момент создания снимка, используйте контрольную таблицу: строку BEFORE_SNAPSHOT добавьте до создания снимка, а AFTER_SNAPSHOT — после.
Создайте контрольную таблицу и запишите первую строку:
PGPASSWORD="$(d8 k get secret snapshot-pg-rw -n my-postgres \
-o jsonpath='{.data.password}' | base64 --decode)"
d8 k run snapshot-client \
-n my-postgres \
--rm -i \
--restart=Never \
--image=postgres:17 \
--env="PGPASSWORD=$PGPASSWORD" \
-- \
psql \
-h d8ms-pg-snapshot-pg-rw \
-U snapshot-rw \
-d snapshotdb \
-c "
CREATE TABLE snapshot_check (
id integer PRIMARY KEY,
value text NOT NULL
);
INSERT INTO snapshot_check VALUES (1, 'BEFORE_SNAPSHOT');
SELECT * FROM snapshot_check;
"
Пример вывода:
id | value
----+-----------------
1 | BEFORE_SNAPSHOT
(1 row)
Создайте объект PostgresSnapshot:
apiVersion: managed-services.deckhouse.io/v1alpha1
kind: PostgresSnapshot
metadata:
name: snapshot-pg-backup
namespace: my-postgres
spec:
postgresName: snapshot-pg
Примените манифест:
d8 k apply -f snapshot-pg-backup.yaml
Проверьте состояние снимка:
d8 k get postgressnapshot snapshot-pg-backup -n my-postgres \
-o jsonpath='{.status.phase}{"\n"}'
После успешного создания снимка команда возвращает:
completed
Проверьте созданный VolumeSnapshot:
d8 k get volumesnapshot -n my-postgres
Пример вывода:
NAME READYTOUSE SOURCEPVC RESTORESIZE SNAPSHOTCLASS
d8ms-pg-snapshot-pg-backup true d8ms-pg-snapshot-pg-1 2Gi sds-local-volume-snapshot-class
READYTOUSE=true подтверждает готовность снимка к восстановлению.
После завершения создания снимка добавьте в исходную базу вторую контрольную строку:
PGPASSWORD="$(d8 k get secret snapshot-pg-rw -n my-postgres \
-o jsonpath='{.data.password}' | base64 --decode)"
d8 k run snapshot-client \
-n my-postgres \
--rm -i \
--restart=Never \
--image=postgres:17 \
--env="PGPASSWORD=$PGPASSWORD" \
-- \
psql \
-h d8ms-pg-snapshot-pg-rw \
-U snapshot-rw \
-d snapshotdb \
-c "
INSERT INTO snapshot_check VALUES (2, 'AFTER_SNAPSHOT');
SELECT * FROM snapshot_check ORDER BY id;
"
Пример вывода:
id | value
----+-----------------
1 | BEFORE_SNAPSHOT
2 | AFTER_SNAPSHOT
(2 rows)
Восстановление из PostgresSnapshot
Для восстановления создайте новый объект Postgres и укажите созданный PostgresSnapshot в spec.dataSource.objectRef. Исходный Postgres удалять не требуется:
apiVersion: managed-services.deckhouse.io/v1alpha1
kind: Postgres
metadata:
name: snapshot-pg-restored
namespace: my-postgres
spec:
dataSource:
objectRef:
kind: PostgresSnapshot
name: snapshot-pg-backup
type: Standalone
instance:
cpu:
cores: 1
coreFraction: 50
memory:
size: 1Gi
persistentVolumeClaim:
size: 2Gi
storageClassName: snapshot-local
Поля type и instance необходимо указывать явно — они не наследуются от исходного Postgres. После этого примените манифест:
d8 k apply -f snapshot-pg-restored.yaml
Дождитесь готовности восстановленного Postgres:
d8 k get postgres snapshot-pg-restored -n my-postgres -o wide -w
После запуска восстановленного PostgreSQL проверьте контрольную таблицу:
PGPASSWORD="$(d8 k get secret snapshot-pg-rw -n my-postgres \
-o jsonpath='{.data.password}' | base64 --decode)"
d8 k run snapshot-restore-check \
-n my-postgres \
--rm -i \
--restart=Never \
--image=postgres:17 \
--env="PGPASSWORD=$PGPASSWORD" \
-- \
psql \
-h d8ms-pg-snapshot-pg-restored-rw \
-U snapshot-rw \
-d snapshotdb \
-c "SELECT * FROM snapshot_check ORDER BY id;"
Пример вывода:
id | value
----+-----------------
1 | BEFORE_SNAPSHOT
(1 row)
Наличие только BEFORE_SNAPSHOT подтверждает, что восстановлено состояние базы данных на момент создания снимка.
Проверка состояния
Текущее состояние Postgres отражается в status.conditions объекта.
Для краткой проверки используйте:
d8 k get postgres app-postgres -n my-postgres -o wide
Основные условия (status.conditions):
| Условие | Что показывает |
|---|---|
ConfigurationValid |
Конфигурация прошла проверки связанного PostgresClass |
LastValidConfigurationApplied |
Последняя валидная конфигурация применена |
ScaledToLastValidConfiguration |
Экземпляры соответствуют последней валидной конфигурации |
Available |
Postgres доступен |
UsersSynced |
Пользователи синхронизированы |
DatabasesSynced |
Логические базы данных синхронизированы |
Во время изменения ресурсов или параметров PostgreSQL часть условий может временно иметь значение False, в то время как Available остаётся True.
Для наблюдения за изменением состояния:
d8 k get postgres app-postgres -n my-postgres -o wide -w
Для просмотра подробностей:
d8 k get postgres app-postgres -n my-postgres -o yaml
Если Postgres не переходит в готовое состояние, диагностика — в разделе «Частые вопросы».