From 35281f1d3f0e93d76e4c1a5790b9941704a15ca2 Mon Sep 17 00:00:00 2001 From: Maxime Beauchamp <15185355+baktun14@users.noreply.github.com> Date: Sat, 31 Oct 2026 22:27:12 -0700 Subject: [PATCH] feat(console-api): add an optional pgbouncer in front of the database Every API, bg-jobs, stats and cron pod opens its own pools against Cloud SQL, so connections grow with pod count. Managed connection pooling needs Cloud SQL Enterprise Plus, so the pooler runs in the cluster instead: PgBouncer 1.26 in transaction mode in front of cloudsql-proxy-console-api, off by default, with its userlist read from a PGBOUNCER_USERLIST key in the release's Doppler secret. Refs CON-1354 --- charts/console-api/Chart.yaml | 2 +- .../templates/pgbouncer-configmap.yaml | 24 +++++++ .../templates/pgbouncer-deployment.yaml | 68 +++++++++++++++++++ .../console-api/templates/pgbouncer-pdb.yaml | 12 ++++ .../templates/pgbouncer-service.yaml | 16 +++++ charts/console-api/values.yaml | 19 ++++++ 6 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 charts/console-api/templates/pgbouncer-configmap.yaml create mode 100644 charts/console-api/templates/pgbouncer-deployment.yaml create mode 100644 charts/console-api/templates/pgbouncer-pdb.yaml create mode 100644 charts/console-api/templates/pgbouncer-service.yaml diff --git a/charts/console-api/Chart.yaml b/charts/console-api/Chart.yaml index d24df689..bb84d537 100644 --- a/charts/console-api/Chart.yaml +++ b/charts/console-api/Chart.yaml @@ -14,7 +14,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.11.2 +version: 0.12.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. diff --git a/charts/console-api/templates/pgbouncer-configmap.yaml b/charts/console-api/templates/pgbouncer-configmap.yaml new file mode 100644 index 00000000..7c84e608 --- /dev/null +++ b/charts/console-api/templates/pgbouncer-configmap.yaml @@ -0,0 +1,24 @@ +{{- if .Values.pgbouncer.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Chart.Name }}-{{ .Values.chain }}-pgbouncer-config + namespace: {{ .Release.Namespace }} +data: + pgbouncer.ini: | + [databases] + * = host={{ .Values.pgbouncer.upstream.host }} port={{ .Values.pgbouncer.upstream.port }} + + [pgbouncer] + listen_addr = 0.0.0.0 + listen_port = 5432 + auth_type = scram-sha-256 + auth_file = /etc/pgbouncer/auth/userlist.txt + pool_mode = transaction + max_prepared_statements = 200 + default_pool_size = {{ .Values.pgbouncer.defaultPoolSize }} + max_client_conn = {{ .Values.pgbouncer.maxClientConn }} + ignore_startup_parameters = extra_float_digits + log_connections = 0 + log_disconnections = 0 +{{- end }} diff --git a/charts/console-api/templates/pgbouncer-deployment.yaml b/charts/console-api/templates/pgbouncer-deployment.yaml new file mode 100644 index 00000000..1c2f316a --- /dev/null +++ b/charts/console-api/templates/pgbouncer-deployment.yaml @@ -0,0 +1,68 @@ +{{- if .Values.pgbouncer.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Chart.Name }}-{{ .Values.chain }}-pgbouncer + namespace: {{ .Release.Namespace }} + labels: + app: {{ .Chart.Name }}-{{ .Values.chain }}-pgbouncer + annotations: + secrets.doppler.com/reload: 'true' +spec: + replicas: {{ .Values.pgbouncer.replicas }} + selector: + matchLabels: + app: {{ .Chart.Name }}-{{ .Values.chain }}-pgbouncer + template: + metadata: + labels: + app: {{ .Chart.Name }}-{{ .Values.chain }}-pgbouncer + annotations: + checksum/config: {{ include (print $.Template.BasePath "/pgbouncer-configmap.yaml") . | sha256sum }} + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + topologyKey: kubernetes.io/hostname + labelSelector: + matchLabels: + app: {{ .Chart.Name }}-{{ .Values.chain }}-pgbouncer + containers: + - name: pgbouncer + image: {{ .Values.pgbouncer.image }} + command: ["/usr/bin/pgbouncer", "/etc/pgbouncer/conf/pgbouncer.ini"] + ports: + - containerPort: 5432 + name: pgbouncer + protocol: TCP + volumeMounts: + - name: config + mountPath: /etc/pgbouncer/conf + readOnly: true + - name: userlist + mountPath: /etc/pgbouncer/auth + readOnly: true + resources: + {{- toYaml .Values.pgbouncer.resources | nindent 12 }} + readinessProbe: + tcpSocket: + port: pgbouncer + periodSeconds: 10 + livenessProbe: + tcpSocket: + port: pgbouncer + periodSeconds: 10 + failureThreshold: 6 + volumes: + - name: config + configMap: + name: {{ .Chart.Name }}-{{ .Values.chain }}-pgbouncer-config + - name: userlist + secret: + secretName: {{ .Chart.Name }}-{{ .Values.chain }}-secret + items: + - key: PGBOUNCER_USERLIST + path: userlist.txt +{{- end }} diff --git a/charts/console-api/templates/pgbouncer-pdb.yaml b/charts/console-api/templates/pgbouncer-pdb.yaml new file mode 100644 index 00000000..489cb902 --- /dev/null +++ b/charts/console-api/templates/pgbouncer-pdb.yaml @@ -0,0 +1,12 @@ +{{- if .Values.pgbouncer.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ .Chart.Name }}-{{ .Values.chain }}-pgbouncer + namespace: {{ .Release.Namespace }} +spec: + maxUnavailable: 1 + selector: + matchLabels: + app: {{ .Chart.Name }}-{{ .Values.chain }}-pgbouncer +{{- end }} diff --git a/charts/console-api/templates/pgbouncer-service.yaml b/charts/console-api/templates/pgbouncer-service.yaml new file mode 100644 index 00000000..c6e6aa54 --- /dev/null +++ b/charts/console-api/templates/pgbouncer-service.yaml @@ -0,0 +1,16 @@ +{{- if .Values.pgbouncer.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }}-{{ .Values.chain }}-pgbouncer + namespace: {{ .Release.Namespace }} +spec: + selector: + app: {{ .Chart.Name }}-{{ .Values.chain }}-pgbouncer + ports: + - protocol: TCP + port: 5432 + targetPort: pgbouncer + name: pgbouncer + type: ClusterIP +{{- end }} diff --git a/charts/console-api/values.yaml b/charts/console-api/values.yaml index a19ebb6c..1ddab95f 100644 --- a/charts/console-api/values.yaml +++ b/charts/console-api/values.yaml @@ -59,3 +59,22 @@ jobDefaults: cpu: 50m ephemeral-storage: 1Gi memory: 256Mi + +# Transaction-mode pooler in front of the database. Needs a PGBOUNCER_USERLIST key in the release's Doppler secret, one `"user" "password"` line per database user. +pgbouncer: + enabled: false + replicas: 2 + image: edoburu/pgbouncer:v1.26.1-p0 + upstream: + host: cloudsql-proxy-console-api + port: 5432 + # Server connections per database and user, on each replica. + defaultPoolSize: 20 + maxClientConn: 2000 + resources: + limits: + cpu: 1 + memory: 256Mi + requests: + cpu: 50m + memory: 64Mi