Connection pooling v1.27.1
EDB Postgres® AI for CloudNativePG™ Cluster provides native support for connection pooling with
PgBouncer, one of the most popular open source
connection poolers for PostgreSQL, through the Pooler custom resource definition (CRD).
In brief, a pooler in EDB Postgres® AI for CloudNativePG™ Cluster is a deployment of PgBouncer pods that sits
between your applications and a PostgreSQL service, for example, the rw
service. It creates a separate, scalable, configurable, and highly available
database access layer.
Architecture
The following diagram highlights how introducing a database access layer based on PgBouncer changes the architecture of EDB Postgres® AI for CloudNativePG™ Cluster. Instead of directly connecting to the PostgreSQL primary service, applications can connect to the equivalent service for PgBouncer. This ability enables reuse of existing connections for faster performance and better resource management on the PostgreSQL side.
Quick start
This example helps to show how EDB Postgres® AI for CloudNativePG™ Cluster implements a PgBouncer pooler:
apiVersion: postgresql.k8s.enterprisedb.io/v1 kind: Pooler metadata: name: pooler-example-rw spec: cluster: name: cluster-example instances: 3 type: rw pgbouncer: poolMode: session parameters: max_client_conn: "1000" default_pool_size: "10"
Important
The pooler name can't be the same as any cluster name in the same namespace.
This example creates a Pooler resource called pooler-example-rw
that's strictly associated with the Postgres Cluster resource called
cluster-example. It points to the primary, identified by the read/write
service (rw, therefore cluster-example-rw).
The Pooler resource must live in the same namespace as the Postgres cluster.
It consists of a Kubernetes deployment of 3 pods running the
latest stable image of PgBouncer,
configured with the session pooling mode
and accepting up to 1000 connections each. The default pool size is 10
user/database pairs toward PostgreSQL.
Important
The Pooler resource sets only the * fallback database in PgBouncer. This setting means that
that all parameters in the connection strings passed from the client are
relayed to the PostgreSQL server. For details, see "Section [databases]"
in the PgBouncer documentation.
EDB Postgres® AI for CloudNativePG™ Cluster also creates a secret with the same name as the pooler containing the configuration files used with PgBouncer.
API reference
For details, see PgBouncerSpec
in the API reference.
Pooler resource lifecycle
Pooler resources aren't cluster-managed resources. You create poolers
manually when they're needed. You can also deploy multiple poolers per
PostgreSQL cluster.
What's important is that the life cycles of the Cluster and the Pooler
resources are currently independent. Deleting the cluster doesn't imply the
deletion of the pooler, and vice versa.
Important
Once you know how a pooler works, you have full freedom in terms of possible architectures. You can have clusters without poolers, clusters with a single pooler, or clusters with several poolers, that is, one per application.
Important
When the operator is upgraded, the pooler pods will undergo a rolling upgrade. This is necessary to ensure that the instance manager within the pooler pods is also upgraded.
Security
Any PgBouncer pooler is transparently integrated with EDB Postgres® AI for CloudNativePG™ Cluster support for in-transit encryption by way of TLS connections, both on the client (application) and server (PostgreSQL) side of the pool.
Specifically, PgBouncer reuses the certificates of the PostgreSQL server. It
also uses TLS client certificate authentication to connect to the PostgreSQL
server to run the auth_query for clients' password authentication (see
Authentication).
Containers run as the pgbouncer system user, and access to the pgbouncer
database is allowed only by way of local connections, through peer authentication.
Certificates
By default, a PgBouncer pooler uses the same certificates that are used by the cluster. However, if you provide those certificates, the pooler accepts secrets with the following formats:
- Basic Auth
- TLS
- Opaque
In the Opaque case, it looks for the following specific keys that need to be used:
- tls.crt
- tls.key
So you can treat this secret as a TLS secret, and start from there.
Authentication
Password-based authentication is the only supported method for clients of PgBouncer in EDB Postgres® AI for CloudNativePG™ Cluster.
Internally, the implementation relies on PgBouncer's auth_user and
auth_query options. Specifically, the operator:
- Creates a standard user called
cnp_pooler_pgbouncerin the PostgreSQL server - Creates the lookup function in the
postgresdatabase and grants execution privileges to the cnp_pooler_pgbouncer user (PoLA) - Issues a TLS certificate for this user
- Sets
cnp_pooler_pgbounceras theauth_user - Configures PgBouncer to use the TLS certificate to authenticate
cnp_pooler_pgbounceragainst the PostgreSQL server - Removes all the above when it detects that a cluster doesn't have any pooler associated to it
Important
If you specify your own secrets, the operator doesn't automatically integrate the pooler.
To manually integrate the pooler, if you specified your own secrets, you must run the following queries from inside your cluster.
First, you must create the role:
CREATE ROLE cnp_pooler_pgbouncer WITH LOGIN;
Then, for each application database, grant the permission for
cnp_pooler_pgbouncer to connect to it:
GRANT CONNECT ON DATABASE { database name here } TO cnp_pooler_pgbouncer;
Finally, as a superuser connect in each application database, and then create the authentication function inside each of the application databases:
CREATE OR REPLACE FUNCTION public.user_search(uname TEXT) RETURNS TABLE (usename name, passwd text) LANGUAGE sql SECURITY DEFINER AS 'SELECT usename, passwd FROM pg_catalog.pg_shadow WHERE usename=$1;'; REVOKE ALL ON FUNCTION public.user_search(text) FROM public; GRANT EXECUTE ON FUNCTION public.user_search(text) TO cnp_pooler_pgbouncer;
Important
Given that user_search is a SECURITY DEFINER function, you need to
create it through a role with SUPERUSER privileges, such as the postgres
user.
Pod templates
You can take advantage of pod templates specification in the template
section of a Pooler resource. For details, see
PoolerSpec in the API reference.
Using templates, you can configure pods as you like, including fine control
over affinity and anti-affinity rules for pods and nodes. By default,
containers use images from quay.io/enterprisedb/pgbouncer.
This example shows Pooler specifying `PodAntiAffinity``:
apiVersion: postgresql.k8s.enterprisedb.io/v1 kind: Pooler metadata: name: pooler-example-rw spec: cluster: name: cluster-example instances: 3 type: rw template: metadata: labels: app: pooler spec: containers: [] affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - pooler topologyKey: "kubernetes.io/hostname"
Note
Explicitly set .spec.template.spec.containers to [] when not modified,
as it's a required field for a PodSpec. If .spec.template.spec.containers
isn't set, the Kubernetes api-server returns the following error when trying to
apply the manifest:error validating "pooler.yaml": error validating data:
ValidationError(Pooler.spec.template.spec): missing required field
"containers"
This example sets resources and changes the used image:
apiVersion: postgresql.k8s.enterprisedb.io/v1 kind: Pooler metadata: name: pooler-example-rw spec: cluster: name: cluster-example instances: 3 type: rw template: metadata: labels: app: pooler spec: containers: - name: pgbouncer image: my-pgbouncer:latest resources: requests: cpu: "0.1" memory: 100Mi limits: cpu: "0.5" memory: 500Mi
Service Template
Sometimes, your pooler will require some different labels, annotations, or even change
the type of the service, you can achieve that by using the serviceTemplate field: