# CloudNativePG

Use the `cnpg` provider when PostgreSQL runs under the CloudNativePG operator.  Set
`type = "cnpg"` and name the Cluster.  The collector asks Kubernetes which pod is the primary, which
pods are standbys, and where their metrics ports are.  It keeps that answer current, so a failover
requires no config change and does not lose history.

The chart, the namespace, secrets, and the install command work as described on the
[Kubernetes](/docs/connect/kubernetes/) page.  That page also works against a CloudNativePG cluster
with `type = "self_hosted"` pointed at the `-rw` service.  The `cnpg` provider has three differences:

- Failover keeps the same component.  The component's identity is the namespace and Cluster name,
  so when CloudNativePG promotes a standby, the collector picks up the new primary on its next
  discovery pass and history continues under the same component.
- It scrapes every instance rather than only the primary.  Each PostgreSQL pod exposes metrics on
  port 9187 through the operator's instance manager, and the collector reads all of them, so
  per-standby replication lag arrives as its own series.
- It leaves out PgBouncer pods.  Pods created by a `Pooler` carry the same `cnpg.io/cluster` label
  as the database pods.  The provider filters on `cnpg.io/podRole=instance`, so those pods are not
  scraped.

## Before you start

The requirements in [Before you start](/docs/connect/kubernetes/#before-you-start) on the Kubernetes
page apply here, along with the following:

- The collector runs inside the same Kubernetes cluster as the CloudNativePG Cluster.  It reaches
  the Kubernetes API with its own ServiceAccount token and dials pod IPs directly for the metrics
  scrape.  Neither works from outside.
- A read-only Role in the namespace that holds the Cluster.  The chart creates it when you ask.  See
  [Kubernetes read access](#kubernetes-read-access).
- `pg_stat_statements` loaded through the Cluster spec.  See
  [pg_stat_statements](#pg_stat_statements-under-cloudnativepg).
- The same `dbg_readonly` login as everywhere else.  See [Grants](#grants).

### Kubernetes read access

The provider reads three kinds of object: the `Cluster` itself, to learn the current primary and
whether monitoring is served over TLS; the Cluster's pods, to find instance addresses; and pod
metrics.  It gets three verbs, `get`, `list` and `watch`.  No writes, no `exec`, and no access to
Secrets.  Nothing the collector is granted can change a database or a Kubernetes object.

The Role the chart creates allows reading more than the provider uses.  With the same three verbs it
also covers `backups`, `scheduledbackups`, `poolers`, `databases`, `publications`,
`subscriptions`, `failoverquorums` and `imagecatalogs`, Barman `objectstores`, and
`persistentvolumeclaims`.

The chart renders this when `rbac.create` is on.  The only decision is where to bind it:

```yaml
rbac:
  create: true
  scope: namespaced
  namespaces:
    - prod-db          # the namespace that HOLDS the Cluster
```

Without `rbac.create`, discovery uses metrics only.

:::caution[`namespaces` is where the Cluster lives, not where the collector runs]

The two are often different namespaces.  A binding in the collector's own namespace renders and
installs cleanly, but grants nothing.  Discovery then gets a 403, and in the default `auto` mode
that becomes a metrics-only install with no error.  List the namespace the `Cluster` object is in.
Leaving the list empty with `scope: namespaced` fails the render.
:::

`scope: cluster` binds the same permissions across every namespace.  Use it only when you discover
Clusters across the whole cluster.

#### Pod CPU, memory and disk

`rbac.nodeMetrics` is on by default and is separate from `scope`.  The collector reads each
database pod's CPU, memory, and volume usage from the kubelet on the node that runs it.  The objects
that authorize that read, `nodes`, `nodes/stats`, and `nodes/pods`, are cluster scoped, so this
binding is always a ClusterRoleBinding, whatever `scope` says.  It reads node statistics and grants
nothing on any database object.

Set it to `false` and you lose those three metrics for your database pods.  The collector checks the
grant at startup and skips the pipeline when it is missing.  Nothing else is affected.

#### Choosing `kubernetes.mode`

| Mode | What it does | Use it when |
|---|---|---|
| `auto` (default) | Tries the API.  Full mode if it can read, metrics-only if it cannot. | You want an install that never fails on permissions |
| `enabled` | Requires the API.  Fails with an error if the Role is missing. | You want to know the moment the Role is wrong |
| `disabled` | Never calls the API.  No Kubernetes permissions needed. | You cannot grant read access at all |

In `auto` mode a missing binding does not fail the install.  The collector runs and reports fewer
metrics, with no error.  If you have granted the Role and want proof it works, set
`mode = "enabled"` and read the logs.

:::note[What the provider reads from the Cluster]

The primary, the monitoring TLS setting, and the pod addresses, in every mode.  Backup and WAL
archiving state stay with CloudNativePG and its own tooling.
:::

### pg_stat_statements under CloudNativePG

Do not run `ALTER SYSTEM` on an operator-managed cluster.  Declare the library in the `Cluster`
spec and CloudNativePG restarts the instances for you:

```yaml
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: app-db
  namespace: prod-db
spec:
  postgresql:
    shared_preload_libraries:
      - pg_stat_statements
    parameters:
      shared_buffers: 256MB
```

:::caution[`shared_preload_libraries` sits beside `parameters`, not inside it]

Under `parameters`, CloudNativePG accepts the manifest and never loads the library.  There is no
error, and nothing in the logs says why query statistics are missing.
:::

Connect to each database you will list under `databases`:

```sql
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
```

Adding a library to `shared_preload_libraries` triggers a rolling restart of the instances.  Plan
it like any restart.  It is the only disruptive step in this install.

### Grants

The provider adds no SQL requirement of its own.  Create `dbg_readonly` exactly as in
[Prepare PostgreSQL](/docs/connect/prepare-postgresql/#database-grants), connected to the primary
through the `-rw` service.  Repeat the per-database `USAGE` and `SELECT` grants in every database
you list.

Here, members come from the Kubernetes API, so the topology stays correct without `pg_monitor`.  On
the generic Kubernetes path, a missing `pg_monitor` grant collapses a cluster of three nodes to one
node, because members are discovered through `pg_stat_replication`. `pg_monitor` is still required: the collector reads
`pg_stat_activity` and `pg_stat_statements` through it, and every query-level feature rests on
that.

## Install

### How the collector connects

SQL goes to the `-rw` service only.  Each standby is scraped on its metrics port and never gets a
SQL connection.

CloudNativePG issues one server certificate per
Cluster, shared by every instance, whose names cover the `-rw`, `-ro`, and `-r` services and
nothing else: no pod names or IP addresses.  The collector verifies certificates by default, so a
SQL connection to a pod can never verify.  A metrics scrape can dial a pod IP while checking the
certificate against the `-rw` name, so that is how per-instance data is read.

The primary is the SQL source and one metrics target.  Each standby is a metrics target only.  In the
app, a standby shows as a member you cannot run a query against.  That is expected.

Point `host` at the short service name when the collector runs in the same namespace, or the
fully qualified one otherwise.  Both are on the certificate:

```toml
host = "app-db-rw"                              # same namespace
host = "app-db-rw.prod-db.svc.cluster.local"    # anywhere in the cluster
```

Metrics on 9187 are plain HTTP unless the Cluster sets `.spec.monitoring.tls.enabled`.  When it
does, the collector switches the scrape to TLS and verifies against the Cluster CA on its own.  You
do not configure that.

### Run the install

`collector.toml`, one `[[component]]` per Cluster:

```toml
[dbgorilla]
agent_id  = "<agent id from the app>"
tenant_id = "<tenant id from the app>"
secret    = { env = "DBG_SERVER_SECRET" }

[topology]
interval = "60s"

[commands]
enabled = true
allowed = ["explain", "collect_statistics"]   # see https://www.dbgorilla.com/docs/reference/collector-commands/#database-commands

[[component]]
name   = "prod-cnpg"
engine = "postgres"

[component.provider]
type      = "cnpg"
namespace = "prod-db"      # required
cluster   = "app-db"       # required

[component.provider.kubernetes]
mode = "auto"

[component.provider.metrics]
port = 9187

[component.auth]
method   = "password"
user     = "dbg_readonly"
password = { env = "APP_DB_PASSWORD" }

[component.connect]
host      = "app-db-rw"
port      = 5432
ssl_mode  = "verify-full"
databases = ["app"]
```

`values.yaml`:

```yaml
rbac:
  create: true
  scope: namespaced
  namespaces:
    - prod-db

secrets:
  serverSecret: "<secret from the app>"
  databasePasswords:
    APP_DB_PASSWORD: "<the dbg_readonly password>"
```

Then:

```bash
helm install dbg-collector oci://dbgorillapublic.azurecr.io/charts/dbg-collector \
  --namespace prod-db \
  --values ./values.yaml \
  --set-file config.inline=./collector.toml
```

This config has no `ca_cert` and no mounted CA, unlike the Kubernetes page.  SQL goes to the `-rw`
name on the Cluster certificate, and the metrics scrape carries its own trust.  The config also
omits a `node_exporter` block because the `cnpg` provider has none: there is no host to probe.

:::note[You supply the password yourself, from CloudNativePG's application Secret]

CloudNativePG creates `<cluster>-app` with `username` and `password` keys.  The collector never
reads Secrets, and the chart injects passwords as environment variables named by you.  Either put
the `dbg_readonly` password under `secrets.databasePasswords`, or supply `secrets.existingSecret`
and inject the key you chose through `extraEnv` with a `secretKeyRef`.
:::

### Several Clusters, one collector

Add a `[[component]]` block per Cluster, each with its own `namespace` and `cluster`, and list every
namespace under `rbac.namespaces`.  One stanza is always one Cluster.  Discovery runs inside a single
namespace, so that a component's identity is settled before it starts.

## Check it is working

Follow [Check it worked](/docs/connect/check-it-is-working/#cloudnativepg) for CloudNativePG.

## Upgrade

See [Upgrading and removing](/docs/administer/manage-collectors/#cloudnativepg) in Manage
collectors.

## Remove

`helm uninstall` and what it removes are under
[Upgrading and removing](/docs/administer/manage-collectors/#cloudnativepg) in Manage collectors.

## Next

To manage this install from Terraform, see
[Deploying from Terraform](/docs/connect/infrastructure-as-code/#cloudnativepg).