Skip to content

Kubernetes

Last updated

View as Markdown

If your PostgreSQL runs in Kubernetes, under CloudNativePG, Crunchy, Zalando, or a StatefulSet you wrote yourself, run the collector there too, with the official Helm chart.

For a database on your laptop see Local development with Docker. For RDS or Aurora see AWS RDS and Aurora; the AWS path there deploys a Fargate task and does not involve Kubernetes.

  • Helm 3.8 or newer. The chart is distributed as an OCI artifact, which older Helm cannot pull.
  • Kubernetes 1.25 or newer.
  • Collector credentials. In the DBGorilla app, open Collectors → New Collector. It issues an agent ID, a tenant ID and a secret. The secret is shown once and cannot be retrieved again.
  • A database login with the right grants. See Grants below.
  • A namespace. Not an arbitrary choice; see Choosing a namespace.

The collector opens outbound connections only. It needs your databases on 5432, and otlp.dbgorilla.com plus auth.dbgorilla.com on 443. The chart renders no Service and no inbound ports.

The chart models nothing about your database

Section titled “The chart models nothing about your database”

You supply a complete collector.toml and the chart writes it to a ConfigMap verbatim, mounted at /etc/dbg-collector/collector.toml. The chart owns the image, the pod spec, the ServiceAccount and secret injection, and nothing else. Anything under config: other than inline is rejected at install rather than quietly ignored.

Create the dbg_readonly login on each database server the collector watches, exactly as in Collector Installation.

USAGE and SELECT are per-database. The example below watches app and orders, so those two grants have to be run twice, once connected to each, and once per schema within them. Run them in postgres only and the collector reports metrics normally while capturing nothing.

Address the read-write service, not a read-only or round-robin one. For a CloudNativePG cluster named app-pg, that is app-pg-rw, not app-pg-ro or app-pg-r.

Two things break otherwise. pg_stat_replication is empty on a standby, so the collector discovers no cluster members. And a node in recovery has frozen statistics. Replicas apply changes through WAL replay, which never moves a counter, so the collector will not use one as its source of truth.

The collector finds the replicas itself, from the primary. You do not list them.

Operator-managed PostgreSQL almost always presents a certificate from the operator’s own authority. Mount that CA into the collector and point ca_cert at it:

extraVolumes:
- name: pg-ca
secret:
secretName: app-pg-ca # CloudNativePG names it <cluster>-ca
extraVolumeMounts:
- name: pg-ca
mountPath: /etc/dbg-collector-certs
readOnly: true
ssl_mode = "verify-full"
ca_cert = "/etc/dbg-collector-certs/ca.crt"

If you would rather not manage a CA bundle, ssl_mode = "require" is the documented fallback: encrypted, unverified, and reasonable inside a cluster you control.

Two constraints usually decide this for you, and both are easier to satisfy than to work around.

A Secret cannot be mounted across namespaces. If you mount the database CA, the collector must run in the namespace holding it, or you copy that Secret, which then drifts when the operator rotates its CA.

NetworkPolicy. If your database namespace runs a default-deny policy, the collector must be admitted by it. Many policies admit by namespace; some admit only pods carrying a particular label.

Running the collector in the same namespace as the database satisfies both constraints at once, and is the shortest path for a single cluster.

Turn off node_exporter for containerized databases

Section titled “Turn off node_exporter for containerized databases”

A database in a pod has nothing listening on port 9100, and the default auto mode spends the full probe timeout on every node on every discovery pass. Turn it off unless your databases run on hosts you have put node_exporter on:

[component.provider.node_exporter]
mode = "disabled"

collector.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 = false
[[component]]
name = "app-pg"
engine = "postgres"
[component.provider]
type = "self_hosted"
[component.provider.node_exporter]
mode = "disabled"
[component.auth]
method = "password"
user = "dbg_readonly"
password = { env = "APP_PG_PASSWORD" }
[component.connect]
host = "app-pg-rw.databases.svc.cluster.local"
port = 5432
ssl_mode = "verify-full"
ca_cert = "/etc/dbg-collector-certs/ca.crt"
databases = ["app", "orders"]

values.yaml:

secrets:
serverSecret: "<secret from the app>"
databasePasswords:
APP_PG_PASSWORD: "<the dbg_readonly password>"
extraVolumes:
- name: pg-ca
secret:
secretName: app-pg-ca
extraVolumeMounts:
- name: pg-ca
mountPath: /etc/dbg-collector-certs
readOnly: true

Then:

Terminal window
helm install dbg-collector oci://dbgorillapublic.azurecr.io/charts/dbg-collector \
--namespace databases \
--values ./values.yaml \
--set-file config.inline=./collector.toml

values.yaml holds live secrets. collector.toml does not, as long as you kept the { env = … } references above rather than pasting literals into it. Keep values.yaml out of version control, or supply secrets.existingSecret and manage the Secret yourself, with an ExternalSecret, for instance. If you do, it must contain the DBG_SERVER_SECRET key plus every database password key your TOML references; the chart cannot read your config, so it cannot derive those names for you.

The image comes from dbgorillapublic.azurecr.io, which allows anonymous pulls. No image pull secret is needed.

Terminal window
kubectl -n databases logs -f deploy/dbg-collector

Four lines say it is healthy:

opamp websocket connected
opamp handshake complete
otelcol started
discovered component

Then look at what it found. For a three-node cluster you want three nodes: one writer and two readers. One node where you expect three points at the pg_monitor grant.

This warning means one node cannot be reached:

no instance baseline and a node is unreachable; deferring topology post until all nodes are probed

The collector holds back the whole component until every node answers, so a single unreachable replica means no data at all, including from the primary. Usual causes are a NetworkPolicy that admits the collector to the primary’s service but not to individual pods, or a ca_cert path that does not exist in the container.

Add a [[component]] block per database server. One collector can watch as many as it can reach, each with its own credentials and TLS settings:

[[component]]
name = "app-pg"
# …
[[component]]
name = "billing-pg"
# …

Give each component’s password its own environment variable name and list them all under secrets.databasePasswords.

Terminal window
helm upgrade dbg-collector oci://dbgorillapublic.azurecr.io/charts/dbg-collector \
--namespace databases \
--values ./values.yaml \
--set-file config.inline=./collector.toml
helm uninstall dbg-collector --namespace databases

helm uninstall removes the workload but does not deprovision the collector’s identity in DBGorilla. To retire a collector permanently, delete it in the app as well, and note that a new install always means a new collector, never the old one resumed.