Цели
Настроить мониторинг 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



