PostgreSQL
CloudNativePG itself is an infrastructure-layer component
(infrastructure/base/cloudnative-pg*/), not part of observability/base/ —
but every SQLInstance claim it backs feeds the same VictoriaMetrics,
VictoriaLogs, and Grafana covered on the rest of this lane. This page is
that integration, re-verified against SPEC-010 (the Barman Cloud plugin
migration) rather than carried forward from the pre-migration draft.
Metrics
CloudNativePG’s built-in exporter surfaces pg_stat_statements as
Prometheus-format metrics, scraped like any other VMServiceScrape target.
Confirmed live metric names, pulled directly from the two Grafana dashboards
that query them:
cnpg_pg_stat_statements_callscnpg_pg_stat_statements_mean_exec_time/_max_exec_timecnpg_pg_stat_statements_rowscnpg_pg_stat_statements_shared_blks_hit/_shared_blks_read
(An earlier draft of this documentation used _calls_total and similar
_total-suffixed names — those don’t match what the dashboards actually
query and have been dropped.)
Two dashboards, both in the databases Grafana folder
(infrastructure/base/cloudnative-pg/):
databases-cnpg-query-performance— per-query mean execution time, call volume, cache-hit ratio, aggregated acrossdatabase/query_id.databases-cnpg-query-plan-correlation— drills into onequery_id: execution stats plus everyauto_explainplan captured for it (see Logs below), with a button that copies the latest plan JSON to the clipboard and opens a private PEV2 instance (Postgres Explain Visualizer 2) to paste it into.
postgresql.conf parameters behind these metrics — pg_stat_statements.track,
shared_preload_libraries, compute_query_id, and the auto_explain.*
thresholds — are set by the SQLInstance composition itself, which lives in
Smana/crossplane-configuration,
not in this repo. The metric names above are verified live (they’re queried
by dashboards that exist and render); the exact server-side settings that
produce them are not verifiable from this repository alone.No CloudNativePG-specific VMRule is deployed anywhere in this repo —
victoria-metrics-k8s-stack/vmrules/ holds rules for karpenter, openbao,
and runlore only. Connection-saturation, replication-lag, or
query-regression alerting for CloudNativePG doesn’t exist yet.
Logs: the auto_explain pipeline
victoria-logs’s Vector configuration (observability/base/victoria-logs/helmrelease-vlsingle.yaml)
carries a purpose-built pipeline, separate from general Kubernetes log
collection, that exists only to parse CloudNativePG’s auto_explain output:
k8s (kubernetes_logs source)
→ parse_pg_json (unwraps CloudNativePG's JSON log envelope)
→ filter_pg_auto_explain (keeps only postgres containers, auto_explain lines)
→ parse_pg_auto_explain (extracts plan JSON, query_id, timings)
→ sinks:
victorialogs_pg_plans (successfully parsed plans)
victorialogs_pg_parse_failures (anything that failed to parse — kept, not dropped)
vlogs-0 (everything else, general k8s logs)The plans sink streams on cluster_name,namespace,database,query_id and
keeps a fixed, small field set (only_fields); the parse-failures sink is
deliberately not dropped silently — it exists so a parsing regression
shows up as its own queryable stream instead of vanishing. Six Vector unit
tests ship alongside the config (valid plan, missing duration, malformed
JSON, non-Postgres filtering, and two more).
query_id is the correlation key end to end: it’s a pg_stat_statements
label on the metrics side and a stream field here, which is what lets the
query-plan-correlation dashboard join “this query is slow” to “here are its
actual plans.”
Backups: Barman Cloud CNPG-I plugin
CloudNativePG’s in-tree barmanObjectStore backup config was removed in
operator 1.31.0; this repo migrated to the Barman Cloud CNPG-I plugin model
ahead of that bump (SPEC-010, done). Confirmed live in
infrastructure/base/cloudnative-pg-barman-plugin/:
- A
barman-cloudcontrollerDeployment, installed via a FluxKustomizationsourcing the plugin’s upstreamkubernetes/overlay at a pinned tag (no Helm chart exists upstream) into theinfrastructurenamespace — co-located with the CNPG operator itself, since the plugin must share its namespace. - Its own
CiliumNetworkPolicy(network-policy.yaml): DNS with L7 inspection, Kubernetes API access, S3 egress on 443, and the EKS Pod Identity Agent (169.254.170.23:80, classified as thehostentity —toCIDRalone wouldn’t match it). - The
ObjectStoreCRD, backed by an S3Bucket(cnpg-backups,infrastructure/base/cloudnative-pg/s3-bucket.yaml) withmanagementPoliciesdeliberately excludingDelete— backups must outlive any individual cluster’s teardown.
A claim’s SQLInstance spec (spec.backup.schedule, spec.backup.bucketName,
spec.objectStoreRecovery) is unchanged by the migration; only the rendered
Cluster’s internals moved from barmanObjectStore to
spec.plugins: [{name: barman-cloud.cloudnative-pg.io}]. This is live on the
cluster today, not just shipped code — Zitadel’s SQLInstance
(security/base/zitadel/sqlinstance.yaml) documents an actual
promotion/recovery cycle against it:
objectStoreRecovery:
bucketName: "eu-west-3-ogenki-cnpg-backups"
path: "zitadel-20260719" # frozen dated snapshot, not the live prefix
backup:
schedule: "0 0 * * *"
bucketName: "eu-west-3-ogenki-cnpg-backups"Recovery deliberately reads from a frozen, dated snapshot prefix
(zitadel-20260719), not the live cluster’s own accruing backup prefix — a
bad day on the live database (corruption, an accidental DROP) can’t
cascade into a poisoned recovery source, at the cost of manually promoting a
new snapshot when the schema or data changes meaningfully. Credentials
default to EKS Pod Identity: per SPEC-010’s refined credential-mechanism
clarification (docs/specs/done/2026-Q3/010-cnpg-barman-cloud-plugin/clarifications.md:125),
the rendered ObjectStore sets s3Credentials.inheritFromIAMRole: true — a
verbatim carry-over of the pre-migration in-tree config — rather than
omitting the block; ambient credentials still come from Pod Identity via the
AWS SDK default chain, and no new Kubernetes Secret is created. A claim can
opt into access-key credentials sourced from OpenBao via ExternalSecret
instead, but that’s not the default path.