5757The pooler name can't be the same as any cluster name in the same namespace.
5858!!!
5959
60+ !!!warning
61+
62+ The ` spec.cluster` field is immutable after creation. To point a pooler at
63+ a different `Cluster`, create a new `Pooler` resource instead of updating
64+ an existing one.
65+ !!!
66+
6067This example creates a `Pooler` resource called `pooler-example-rw`
6168that's strictly associated with the Postgres `Cluster` resource called
6269` cluster-example` . It points to the primary, identified by the read/write
@@ -190,15 +197,27 @@ GRANT CONNECT ON DATABASE postgres TO cnp_pooler_pgbouncer;
190197
191198Create the lookup function for password verification. This function is created
192199in the `postgres` database with `SECURITY DEFINER` privileges and is used by
193- PgBouncer’s `auth_query` option :
200+ PgBouncer’s `auth_query` option. Because it runs as the function owner, its
201+ ` search_path` is pinned to `pg_catalog, pg_temp` so that the function body
202+ cannot resolve operators or objects through a caller- or
203+ tenant-controlled `search_path` :
194204
195205` ` ` sql
196206CREATE OR REPLACE FUNCTION public.user_search(uname TEXT)
197207 RETURNS TABLE (usename name, passwd text)
198- LANGUAGE sql SECURITY DEFINER AS
208+ LANGUAGE sql SECURITY DEFINER
209+ SET search_path = pg_catalog, pg_temp AS
199210 'SELECT usename, passwd FROM pg_catalog.pg_shadow WHERE usename=$1;';
200211` ` `
201212
213+ !!!note
214+
215+ Clusters created with an earlier version of {{name.ln}} carry a
216+ ` user_search` function without the pinned `search_path`. The operator
217+ recreates the function with the `SET search_path` clause automatically
218+ during reconciliation when the cluster is upgraded.
219+ !!!
220+
202221Restrict and grant permissions on the lookup function :
203222
204223` ` ` sql
@@ -280,10 +299,89 @@ spec:
280299 topologyKey: "kubernetes.io/hostname"
281300` ` `
282301
283- # ### Custom image and resource limits
302+ # ### Resource limits
303+
304+ You can define resource requests and limits by adding a container named
305+ `pgbouncer` to the `template` section :
306+
307+ ` ` ` yaml
308+ # ...
309+ template:
310+ metadata:
311+ # ...
312+ spec:
313+ containers:
314+ # This name MUST be "pgbouncer"
315+ - name: pgbouncer
316+ resources:
317+ requests:
318+ cpu: "0.1"
319+ memory: 100Mi
320+ limits:
321+ cpu: "0.5"
322+ memory: 500Mi
323+ ` ` `
324+
325+ # # PgBouncer image
326+
327+ By default, {{name.ln}} deploys the
328+ [latest stable PgBouncer image](https://docker.enterprisedb.com/k8s/pgbouncer)
329+ the operator was built against. You can override that default in three ways.
330+ When more than one is set, the sources are evaluated top-down — the first
331+ match in the list below is used; if none match, the operator's built-in
332+ default applies :
333+
334+ 1. An explicit image set on the `pgbouncer` container inside
335+ ` spec.template.spec.containers` (escape hatch — see
336+ [Pod-template override](#pod-template-override) below).
337+ 2. `spec.pgbouncer.image` — an image reference set directly on the `Pooler`.
338+ 3. `spec.pgbouncer.imageCatalogRef` — a reference to an entry in an
339+ ` ImageCatalog` or `ClusterImageCatalog`.
340+
341+ ` spec.pgbouncer.image` and `spec.pgbouncer.imageCatalogRef` are mutually
342+ exclusive — set at most one.
343+
344+ !!!warning Policy gating
345+
346+ If you enforce admission policies that restrict which PgBouncer images
347+ may run, those policies **must gate all three** image sources :
348+ ` spec.pgbouncer.image` , `spec.pgbouncer.imageCatalogRef`, and the
349+ ` image` field on a `pgbouncer` container inside
350+ ` spec.template.spec.containers` . A policy that covers only the first
351+ two leaves the pod-template override as an unguarded escape hatch.
352+ The same consideration applies to `Cluster.spec.imageName` and
353+ ` Cluster.spec.imageCatalogRef` .
354+ !!!
355+
356+ # ## Setting an explicit image
357+
358+ Use `spec.pgbouncer.image` to pin a specific PgBouncer version or pull from
359+ a private registry :
360+
361+ ` ` ` yaml
362+ apiVersion: postgresql.k8s.enterprisedb.io/v1
363+ kind: Pooler
364+ metadata:
365+ name: pooler-example-rw
366+ spec:
367+ cluster:
368+ name: cluster-example
369+ instances: 3
370+ type: rw
371+ pgbouncer:
372+ poolMode: session
373+ image: docker.enterprisedb.com/k8s/pgbouncer:1.25.1-ubi9
374+ ` ` `
375+
376+ # ## Using an image catalog
284377
285- You can specify a custom image and define resource requests/limits. Note that
286- the container name is explicitly set to `pgbouncer`.
378+ The `Pooler` resource can manage the PgBouncer container image centrally via
379+ an `ImageCatalog` or `ClusterImageCatalog`, following the same pattern as
380+ ` Cluster` resources (see [Image Catalog](image_catalog.md)). The catalog
381+ entry is selected by the `key` defined in the catalog's `componentImages`
382+ list.
383+
384+ Reference a catalog entry with `spec.pgbouncer.imageCatalogRef` :
287385
288386` ` ` yaml
289387apiVersion: postgresql.k8s.enterprisedb.io/v1
@@ -295,29 +393,68 @@ spec:
295393 name: cluster-example
296394 instances: 3
297395 type: rw
396+ pgbouncer:
397+ poolMode: session
398+ imageCatalogRef:
399+ apiGroup: postgresql.k8s.enterprisedb.io
400+ kind: ImageCatalog
401+ name: my-catalog
402+ key: pgbouncer
403+ ` ` `
404+
405+ To use a cluster-wide catalog instead, set `kind : ClusterImageCatalog` and
406+ point `name` at the corresponding resource — the rest of the spec is
407+ identical.
408+
409+ When a catalog entry is updated, the operator automatically reconciles all
410+ poolers referencing it and rolls out the new image without any change to the
411+ ` Pooler` spec.
298412
413+ # ## Pod-template override
414+
415+ The pod template can also carry an `image` on the `pgbouncer` container, in
416+ which case it overrides every other source (including `spec.pgbouncer.image`
417+ and `spec.pgbouncer.imageCatalogRef`). Treat this as an **escape hatch** —
418+ use it only when you need to customize other container-level settings
419+ (resources, environment, security context) and happen to want to pin the
420+ image in the same place. For routine image changes, prefer
421+ `spec.pgbouncer.image` or an image catalog : those fields are validated,
422+ mutually exclusive, and visible to admission policies that gate the
423+ PgBouncer image (see [Pod templates](#pod-templates) for the broader
424+ template mechanics) :
425+
426+ ` ` ` yaml
427+ # ...
299428 template:
300- metadata:
301- labels:
302- app: pooler
303429 spec:
304430 containers:
305- # This name MUST be "pgbouncer"
306431 - name: pgbouncer
307432 image: my-pgbouncer:latest
308- resources:
309- requests:
310- cpu: "0.1"
311- memory: 100Mi
312- limits:
313- cpu: "0.5"
314- memory: 500Mi
315433` ` `
316434
435+ # ## Monitoring the resolved image
436+
437+ The operator stores the resolved image in `status.image` and reflects the
438+ outcome in `status.phase`, one of `active`, `paused`, `inactive`, or
439+ ` failed` . On `failed`, `status.phaseReason` describes the cause (for
440+ example, if the catalog or key does not exist). You can inspect the
441+ current state with :
442+
443+ ` ` ` shell
444+ kubectl get pooler pooler-example-rw -o jsonpath='{.status.image}'
445+ ` ` `
446+
447+ !!!note API reference
448+
449+ For details, see [`PgBouncerSpec`](pg4k.v1.md#pgbouncerspec)
450+ in the API reference.
451+ !!!
452+
317453# # Service Template
318454
319- Sometimes, your pooler will require some different labels, annotations, or even change
320- the type of the service, you can achieve that by using the `serviceTemplate` field :
455+ Sometimes, your pooler will require some different labels, annotations, or
456+ even a different Service type. You can achieve that by using the
457+ `serviceTemplate` field :
321458
322459` ` ` yaml
323460apiVersion: postgresql.k8s.enterprisedb.io/v1
@@ -668,6 +805,42 @@ spec:
668805 - port: metrics
669806` ` `
670807
808+ # ## TLS for the Metrics Endpoint
809+
810+ Set `.spec.monitoring.tls.enabled : true` to serve the metrics endpoint over
811+ HTTPS. By default, the cluster's server certificate is being used.
812+ The certificate is reloaded on every TLS handshake, so rotations are
813+ picked up without restarting the pod.
814+
815+ ` ` ` yaml
816+ spec:
817+ monitoring:
818+ tls:
819+ enabled: true
820+ ` ` `
821+
822+ When `.spec.pgbouncer.clientTLSSecret` is set, the metrics server presents
823+ that certificate instead.
824+
825+ ` ` ` yaml
826+ spec:
827+ pgbouncer:
828+ clientTLSSecret:
829+ name: <CLIENT_TLS_SECRET>
830+ monitoring:
831+ tls:
832+ enabled: true
833+ ` ` `
834+
835+ The generated `PodMonitor` scrapes with `insecureSkipVerify=true` because
836+ Prometheus scrapes pods by IP and the certificate's SANs do not generally
837+ cover the pod IP.
838+
839+ If you need strict verification, set `.spec.monitoring.enablePodMonitor : false`
840+ and manage the `PodMonitor` yourself : the operator-generated one is hardcoded
841+ to `insecureSkipVerify=true` and overwrites its spec on every reconcile, so a
842+ manual patch on the generated `PodMonitor` would not survive.
843+
671844# ## Deprecation of Automatic `PodMonitor` Creation
672845
673846!!!warning Feature Deprecation Notice
@@ -704,7 +877,7 @@ following example:
704877# # Pausing connections
705878
706879The `Pooler` specification allows you to take advantage of PgBouncer's `PAUSE`
707- and `RESUME` commands, using only declarative configuration. You can ado this
880+ and `RESUME` commands, using only declarative configuration. You can do this
708881using the `paused` option, which by default is set to `false`. When set to
709882` true` , the operator internally invokes the `PAUSE` command in PgBouncer,
710883which :
0 commit comments