Цели
Настроить мониторинг managed PostgreSQL Yandex Cloud;
Деплой в k8s;
Сервис дисковеринг (экспортёр самостоятельно обнаруживает кластера и хосты БД);
Минимизировать нагрузку от экспортёра на БД и Victoria Metrics (собирать только нужные метрики с заданной частотой - т.к. не все метрики нужно пересчитывать при каждом скрейпе);
Избежать шумного поведения экспортёра (большой packet-rate на ноде).

Важно отметить, что цель предлагаемого подхода — получить управляемый и предсказуемый мониторинг, который:
минимизирует влияние на хосты PostgreSQL;
минимизирует трафик и потребление ресурсов внутри кластера Kubernetes;
сам актуализирует список хостов;
не генерирует шум в системе хранения метрик.
Поэтому в дальнейшем акцент сделан на кеширование, контроль частоты сбора и разделение метрик по «важности».
Предполагается, что у вас в k8s уже установлен victoria-metrics-k8s-stack
Пространство имён k8s
Деплой будем осуществлять в pgscv namespace. Создайте файл namespace.yaml
---
apiVersion: v1
kind: Namespace
metadata:
name: pgscv
$ kubectl apply -f namespase.yaml
namespace/pgscv created
Подготовка PostgreSQL
В кластерах создайте пользователя pgscv c доступом к пользовательским БД и правами mdb_monitor. Пароль пользователя должен быть одинаковый на уровне folder в облаке.
В managed PostgreSQL от Yandex Cloud роль mdb_monitor даёт достаточные права для чтения статистики, при этом не позволяет выполнять DDL читать или изменять данные.
Примерное описание ресурса terraform
resource "yandex_mdb_postgresql_user" "pgscv" {
cluster_id = yandex_mdb_postgresql_cluster.postgres.id
name = "pgscv"
password = "exporter_password"
conn_limit = 15
grants = ["mdb_monitor"]
settings = {
pool_mode = "transaction"
}
permission {
database_name = yandex_mdb_postgresql_cluster.db.name
}
}
Ресурс приведён для иллюстрации
Пароль от пользователя pgscv отправим в секрет (измените my-password на реальный пароль)
postgresql-password.yaml:
---
apiVersion: v1
kind: Secret
stringData:
PGPASSWORD: "my-password"
metadata:
name: pgscv-exporter-postgresql-password
namespace: pgscv
type: Opaque
kubectl apply -f postgresql-password.yaml
secret/pgscv-exporter-postgresql-password created
Сервисная учётная запись (SA)
Создайте сервисную учётную запись с правами managed-postgresql.viewer и ключ авторизации.
Использование сервисного аккаунта и API облака позволяет отказаться от статического описания хостов PostgreSQL.
Это особенно важно для managed-сервисов, где:
хосты могут пересоздаваться;
добавляться или удаляться реплики;
меняться topology без участия пользователя.
pgSCV в этом сценарии выполняет роль service discovery, самостоятельно отслеживая актуальный состав кластеров и баз данных.
main.tf
terraform {
required_providers {
yandex = {
source = "yandex-cloud/yandex"
}
}
required_version = ">= 0.13"
}
provider "yandex" {
zone = "ru-central1-a"
}
locals {
folder_id = "укажите идентификатор"
}
resource "yandex_iam_service_account" "pgscv_exporter" {
folder_id = local.folder_id
name = "pgscv-exporter"
}
resource "yandex_iam_service_account_key" "pgscv_exporter_sa_auth_key" {
service_account_id = yandex_iam_service_account.pgscv_exporter.id
description = "SA for pgSCV exporter"
key_algorithm = "RSA_2048"
}
resource "yandex_resourcemanager_folder_iam_member" "permissions" {
depends_on = [yandex_iam_service_account.pgscv_exporter]
folder_id = local.folder_id
role = "managed-postgresql.viewer"
member = "serviceAccount:${yandex_iam_service_account.pgscv_exporter.id}"
}
После выполнения terraform init && terraform apply в стейте будет содержаться ключ авторизации.
terraform.tfstate
{
"mode": "managed",
"type": "yandex_iam_service_account_key",
"name": "pgscv_exporter_sa_auth_key",
"provider": "provider[\"registry.terraform.io/yandex-cloud/yandex\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"created_at": "2026-01-05T06:57:45Z",
"description": "SA for pgSCV exporter",
"encrypted_private_key": null,
"format": "PEM_FILE",
"id": "xxxxxyyyyyzzzzz",
"key_algorithm": "RSA_2048",
"key_fingerprint": null,
"output_to_lockbox": [],
"output_to_lockbox_version_id": null,
"pgp_key": null,
"private_key": "PLEASE DO NOT REMOVE THIS LINE! Yandex.Cloud SA Key ID \u003ID\u003e\n-----BEGIN PRIVATE KEY-----\n\n-----END PRIVATE KEY-----\n",
"public_key": "-----BEGIN PUBLIC KEY-----\n\n-----END PUBLIC KEY-----\n",
"service_account_id": "xxxxxyyyyyzzzzz"
},
"sensitive_attributes": [
[
{
"type": "get_attr",
"value": "private_key"
}
]
],
"identity_schema_version": 0,
"private": "encoded-private-key==",
"dependencies": [
"yandex_iam_service_account.pgscv_exporter"
]
}
]
}Для формирования секрета в k8s нам понадобиться 6 полей:
created_at
id
key_algorithm
private_key
public_key
service_account_id
Создайте файл pgscv-exporter-cloud-sa.yaml
---
apiVersion: v1
kind: Secret
stringData:
authorized_key.json: |
{
"created_at": "2026-01-05T06:57:45Z",
"id": "",
"key_algorithm": "RSA_2048",
"private_key": "",
"public_key": "",
"service_account_id": ""
}
metadata:
name: pgscv-exporter-cloud-sa
namespace: pgscv
type: Opaque
$ kubectl apply -f pgscv-exporter-cloud-sa.yaml
secret/pgscv-exporter-cloud-sa created
Memcached
pgSCV собирает часть метрик через достаточно тяжёлые SQL-запросы к системным каталогам и функциям PostgreSQL.
Если выполнять их при каждом scrape-запросе Prometheus, это приведёт к:
росту нагрузки на БД;
увеличению времени ответа экспортёра;
росту packet-rate внутри кластера.
Использование memcached позволяет:
разделить частоту scrape и частоту реального опроса БД;
настраивать частоту реального сбора метрик на уровне коллекторов (подсистем мониторинга);
получить общий кеш для подов экспортёра;
снизить нагрузку как на PostgreSQL, так и на систему мониторинга.
Создадим деплоймент memcached и проверим, что под создался.
memcached-deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: pgscv-cache
namespace: pgscv
labels:
app: pgscv-cache
spec:
replicas: 1
selector:
matchLabels:
app: pgscv-cache
template:
metadata:
labels:
app: pgscv-cache
spec:
nodeSelector: {}
affinity: {}
tolerations: []
containers:
- name: memcached
image: memcached:1.6.39-alpine3.22
imagePullPolicy: IfNotPresent
ports:
- containerPort: 11211
args:
- --conn-limit=1024
- --memory-limit=64
- --threads=4
resources: {}
$ kubectl apply -f memcached-deployment.yaml
deployment.apps/pgscv-cache created
$ deploy kubectl -n pgscv get pods
NAME READY STATUS RESTARTS AGE
pgscv-cache-56b7fbb479-l98wj 1/1 Running 0 16s
Под в состоянии READY, отлично, проверим работу memcached.
В первом терминале прокинем порт
$ kubectl -n pgscv port-forward pods/pgscv-cache-56b7fbb479-l98wj 11211:11211
Forwarding from 127.0.0.1:11211 -> 11211
Forwarding from [::1]:11211 -> 11211
а во втором терминале в него постучимся
$ echo stats | netcat localhost 11211
STAT pid 1
STAT uptime 56
STAT time 1767606355
STAT version 1.6.39
STAT libevent 2.1.12-stable
STAT pointer_size 64
....
Создадим сервис
memcached-service.yaml
---
apiVersion: v1
kind: Service
metadata:
name: pgscv-cache
namespace: pgscv
labels:
app: pgscv-cache
spec:
type: ClusterIP
ports:
- name: cache
targetPort: 11211
protocol: TCP
port: 11211
selector:
app: pgscv-cache
$ kubectl apply -f memcached-service.yaml
service/pgscv-cache created
Memcached-exporter
Экспортёр для memcached не является обязательным компонентом схемы, но он позволяет:
наблюдать реальное потребление памяти кешем;
контролировать hit/miss ratio;
корректно подобрать лимиты памяти для memcached.
Это особенно полезно на этапе настройки, когда ещё не понятно, какие TTL и объёмы кеша оптимальны.
memcached-exporter-deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: pgscv-cache-exporter
namespace: pgscv
labels:
app: pgscv-cache-exporter
spec:
replicas: 1
selector:
matchLabels:
app: pgscv-cache-exporter
template:
metadata:
labels:
app: pgscv-cache-exporter
spec:
nodeSelector: {}
affinity: {}
tolerations: []
containers:
- name: memcached-exporter
image: quay.io/prometheus/memcached-exporter:v0.15.3
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9150
args:
- "--memcached.address=pgscv-cache:11211"
resources: {}
memcached-exporter-service.yaml
---
apiVersion: v1
kind: Service
metadata:
name: pgscv-cache-exporter
namespace: pgscv
labels:
app: pgscv-cache-exporter
spec:
type: ClusterIP
ports:
- name: metrics
targetPort: 9150
protocol: TCP
port: 9150
selector:
app: pgscv-cache-exporter
$ kubectl apply -f memcached-exporter-service.yaml
service/pgscv-cache-exporter created
$ kubectl apply -f memcached-exporter-service.yaml
service/pgscv-cache-exporter configured
Чтобы стек мониторинга начал скрейпить метрики, создадим service monitor. Можно сократить количество манифестов, если использовать pod monitor.
memcached-exporter-service-monitor.yaml
---
apiVersion: operator.victoriametrics.com/v1beta1
kind: VMServiceScrape
metadata:
name: pgscv-cache-exporter
namespace: pgscv
labels:
app: pgscv-cache-exporter
spec:
endpoints:
- attach_metadata: {}
port: metrics
path: "/metrics"
interval: "30s"
selector:
app: pgscv-cache-exporter
namespaceSelector:
matchNames:
- pgscv
Экспортёр pgSCV
Документация на параметры экспортёра можно найти в вики на GitHub.
Конфигурация pgSCV в данном примере построена вокруг нескольких принципов:
отключение неиспользуемых коллекторов (в managed решении коллекторы system и postgres/logs бесполезны);
ограничение количества соединений с БД (раздел pool);
агрессивное кеширование метрик, не требующих высокой актуальности, частый сбор которых нагружает БД;
отключено получение полных текстов запросов в лейблы метрик (
no_track_mode: true).сбор top-k метрик по таблицам, индексам и запросам (параметры
collect_top_XXXX) .
Немного подробностей про top-k метрик:
-
collect_top_queryДля каждого запроса из
pg_stat_statementsвычисляются независимые ранги (ROW_NUMBER()) по следующим показателям:количество вызовов (
calls);количество обработанных строк (
rows);суммарное время выполнения (
total_time);время чтения и записи блоков (
blk_read_time,blk_write_time);-
shared buffers:
попадания,
чтения,
модификации,
записи;
статистика по local buffers;
использование временных файлов (
temp_blks_read,temp_blks_written).
Если запрос попадает в Top хотя бы по одному из этих критериев, он помечается как
visibleи включается в дальнейшую обработку. -
collect_top_tableДля каждой пользовательской таблицы вычисляются независимые ранги (
row_number()) по следующим группам метрик:Активность индексов и доступа
количество сканирований по индексам (
idx_scan);количество строк, полученных через индексы (
idx_tup_fetch).
Изменение данных
количество вставок (
n_tup_ins);количество обновлений (
n_tup_upd);количество HOT-обновлений (
n_tup_hot_upd);количество удалений (
n_tup_del).
Размер и «живость» данных
количество живых строк (
n_live_tup);количество мёртвых строк (
n_dead_tup);приблизительное количество строк (
reltuples);физический размер таблицы (
pg_table_size(relid)).
Статистика и обслуживание
количество изменений с последнего
ANALYZE(n_mod_since_analyze);количество запусков
VACUUM(vacuum_count);количество запусков
AUTOVACUUM(autovacuum_count);количество запусков
ANALYZE(analyze_count).
I/O-нагрузка
чтения heap-блоков (
heap_blks_read);попадания по индексам (
idx_blks_hit);чтения TOAST-блоков (
toast_blks_read);попадания TOAST-блоков в кеш (
toast_blks_hit).
Если таблица попадает в Top по любому из этих показателей, она помечается как
visibleи включается в дальнейшую обработку. -
collect_top_indexДля каждого пользовательского индекса вычисляются независимые ранги (
row_number()) по следующим показателям:Использование индекса
количество сканирований по индексу (
idx_scan);количество строк, прочитанных через индекс (
idx_tup_read);количество строк, фактически возвращённых индексом (
idx_tup_fetch).
I/O-нагрузка
количество чтений индексных блоков с диска (
idx_blks_read);количество попаданий индексных блоков в кеш (
idx_blks_hit).
Размер индекса
физический размер индекса (
pg_relation_size(indexrelid)).
Если индекс попадает в Top по любому из этих показателей, он помечается как
visibleи включается в дальнейшую обработку.
Такой подход позволяет использовать pgSCV даже в окружениях с сотнями кластеров PostgreSQL без заметного влияния на производительность.
Если вы по какой то причине не будете использовать nginx proxy для кеша, то уберите параметр url_prefix
pgscv-configmap.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
name: pgscv-configmap
namespace: pgscv
data:
pgscv.yaml: |
listen_address: '0.0.0.0:9890'
conn_timeout: 10
no_track_mode: true
collect_top_query: 15
collect_top_table: 15
collect_top_index: 15
skip_conn_error_mode: true
disable_collectors:
- system
- postgres/logs
pooler:
max_conns: 5
min_conns: 1
min_idle_conns: 1
url_prefix: http://nginx-pgscv.pgscv:9890
cache:
type: "memcached"
server: pgscv-cache:11211
ttl: 30s
collectors:
postgres/indexes:
ttl: 10m
postgres/tables:
ttl: 10m
postgres/statements:
ttl: 5m
discovery:
yandex_mdb:
type: yandex-mdb
config:
- authorized_key: /app/secret/authorized_key.json
folder_id: "идентификатор папки в облаке"
password_from_env: "PGPASSWORD"
user: "pgscv"
refresh_interval: 1
clusters:
- name: ".*"
db:
exclude_name:
exclude_db:
Согласуйте параметры: GOMEMLIMIT должен быть примерно 90% от resources.limits.memory
pgscv-deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: pgscv
namespace: pgscv
labels:
app: pgscv
spec:
replicas: 2
selector:
matchLabels:
app: pgscv
template:
metadata:
labels:
app: pgscv
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
nodeSelector: {}
affinity: {}
tolerations: []
volumes:
- name: pgscv-config
configMap:
name: pgscv-configmap
items:
- key: pgscv.yaml
path: pgscv.yaml
- name: cloud-service-account-auth-key
secret:
secretName: pgscv-exporter-cloud-sa
containers:
- name: pgscv
image: ghcr.io/cherts/pgscv:v1.0-master-6f67d4b-20260111-beta
imagePullPolicy: IfNotPresent
securityContext:
runAsNonRoot: true
runAsUser: 1000
resources:
limits:
cpu: 2
memory: 1500Mi
requests:
cpu: 1
memory: 1000Mi
ports:
- containerPort: 9890
name: http
protocol: TCP
args:
- --config-file=/app/conf/pgscv.yaml
volumeMounts:
- name: pgscv-config
mountPath: /app/conf/
- name: cloud-service-account-auth-key
mountPath: /app/secret/
env:
- name: GOMEMLIMIT
value: 1350MiB
- name: LOG_LEVEL
value: info
envFrom:
- secretRef:
name: pgscv-exporter-postgresql-password
pgscv-service.yaml
---
apiVersion: v1
kind: Service
metadata:
name: pgscv
namespace: pgscv
labels:
app: pgscv
spec:
type: ClusterIP
ports:
- name: http
targetPort: 9890
protocol: TCP
appProtocol: http
port: 9890
selector:
app: pgscv
задеплоим, в configmap не забудьте поставить корректный folder_id
$ kubectl apply -f pgscv-configmap.yaml
configmap/pgscv-configmap created
$ deploy kubectl apply -f pgscv-deployment.yaml
deployment.apps/pgscv created
$ kubectl apply -f pgscv-service.yaml
service/pgscv created
Проверим, прокинув порт
$ kubectl -n pgscv port-forward services/pgscv 9890:9890
Forwarding from 127.0.0.1:9890 -> 9890
Forwarding from [::1]:9890 -> 9890
откроем в браузере, убедимся что всё хорошо. Через некоторое время по ручке /targets будут возвращаться все хосты Managed PostgreSQL.

Nginx proxy cache
Nginx в данной схеме используется как дополнительный уровень кеширования, он сглаживает шквал запросов от VMAgent (может быть сетап когда количество скрейпов кратно количеству VMAgent).
От излишних запросов в PostgreSQL нас уже защищает memcached, но он кеширует только результаты SQL запросов, экспортёру pgSCV всё равно бы пришлось собирать метрики (нагружая CPU подов).
Размер кеша задаётся параметром max_size=1G
Время кеширования указывается последним параметром в директиве proxy_cache_valid
nginx-configmap.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-configmap
namespace: pgscv
data:
nginx.conf: |
worker_processes 1;
events {
worker_connections 1024;
}
http {
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=metrics_cache:10m max_size=1G inactive=60m use_temp_path=off;
server {
listen 80;
location / {
proxy_pass http://pgscv:9890;
proxy_cache metrics_cache;
proxy_cache_valid 200 30s;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
add_header X-Proxy-Cache $upstream_cache_status;
}
}
}
nginx-deployment.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: pgscv
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
nodeSelector: {}
affinity: {}
tolerations: []
containers:
- name: nginx
image: nginx:1.22
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
resources:
limits:
cpu: 1
memory: 150Mi
requests:
memory: 100Mi
volumes:
- name: nginx-config
configMap:
name: nginx-configmap
nginx-service.yaml
---
apiVersion: v1
kind: Service
metadata:
name: nginx-pgscv
namespace: pgscv
labels:
app: nginx
spec:
type: ClusterIP
ports:
- name: http
targetPort: 80
protocol: TCP
appProtocol: http
port: 9890
selector:
app: nginx
Задеплоим
$ kubectl apply -f nginx-configmap.yaml
configmap/nginx-configmap created
$ kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx created
$ kubectl apply -f nginx-service.yaml
service/nginx-pgscv created
VictoriaMetrics scrape config
Использование httpSDConfigs позволяет VictoriaMetrics динамически получать список целей от pgSCV, не требуя ручного обновления конфигурации при изменении состава кластеров PostgreSQL.
pgscv-scrape-config.yaml
---
apiVersion: operator.victoriametrics.com/v1beta1
kind: VMScrapeConfig
metadata:
name: pgscv
namespace: victoria-metrics
spec:
httpSDConfigs:
- url: "http://nginx-pgscv.pgscv:9890/targets"
metricRelabelConfigs:
- sourceLabels: [host]
targetLabel: shorthost
regex: (.*)\.mdb\.yandexcloud\.net
replacement: "$1"
Выводы
pgSCV в сочетании с кешированием и service discovery позволяет выстроить мониторинг, который:
даёт достаточную наблюдаемость;
не создаёт дополнительной нагрузки;
остаётся управляемым по мере роста инфраструктуры.
Пример утилизации ресурсов подами pgSCV + Memcached + Nginx + memcached-exporter
На графиках утилизация ресурсов k8s для стека, который мониторит 170 трехнодовых кластера PostgreSQL


