From 5158bc956a934332ab184b2d33566a282109978d Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Wed, 22 Apr 2026 17:26:33 -0400 Subject: [PATCH 01/23] feat: enable connection pooling (use correct image, pass through variable for CR) --- bundle/uds-config.yaml | 2 -- chart/templates/postgres-minimal.yaml | 10 ++++++++++ values/upstream-values.yaml | 2 +- zarf.yaml | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/bundle/uds-config.yaml b/bundle/uds-config.yaml index 2067cc5..e69de29 100644 --- a/bundle/uds-config.yaml +++ b/bundle/uds-config.yaml @@ -1,2 +0,0 @@ -# Copyright 2024 Defense Unicorns -# SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial diff --git a/chart/templates/postgres-minimal.yaml b/chart/templates/postgres-minimal.yaml index e3f3cad..efa474b 100644 --- a/chart/templates/postgres-minimal.yaml +++ b/chart/templates/postgres-minimal.yaml @@ -14,6 +14,16 @@ spec: volume: size: {{ .Values.postgresql.volume.size | quote }} numberOfInstances: {{ .Values.postgresql.numberOfInstances }} + {{- if hasKey .Values.postgresql "enableConnectionPooler" }} + enableConnectionPooler: {{ .Values.postgresql.enableConnectionPooler }} + {{- end }} + {{- if hasKey .Values.postgresql "enableReplicaConnectionPooler" }} + enableReplicaConnectionPooler: {{ .Values.postgresql.enableReplicaConnectionPooler }} + {{- end }} + {{- with .Values.postgresql.connectionPooler }} + connectionPooler: + {{- toYaml . | nindent 4 }} + {{- end }} users: {{- toYaml .Values.postgresql.users | nindent 4 }} # database owner databases: diff --git a/values/upstream-values.yaml b/values/upstream-values.yaml index df44cfe..53f206a 100644 --- a/values/upstream-values.yaml +++ b/values/upstream-values.yaml @@ -6,7 +6,7 @@ image: repository: zalando/postgres-operator tag: v1.15.1 configConnectionPooler: - connection_pooler_image: "ghcr.io/cloudnative-pg/pgbouncer:1.24.1-23" + connection_pooler_image: "registry.opensource.zalan.do/acid/pgbouncer:master-32" configLogicalBackup: logical_backup_docker_image: "ghcr.io/zalando/postgres-operator/logical-backup:v1.15.1" configGeneral: diff --git a/zarf.yaml b/zarf.yaml index b874dc9..89e00be 100644 --- a/zarf.yaml +++ b/zarf.yaml @@ -66,7 +66,7 @@ components: images: - ghcr.io/zalando/postgres-operator:v1.15.1 - ghcr.io/zalando/postgres-operator/logical-backup:v1.15.1 - - ghcr.io/cloudnative-pg/pgbouncer:1.24.1-23 + - registry.opensource.zalan.do/acid/pgbouncer:master-32 # Docker image that provides PostgreSQL and Patroni bundled together for PostgreSQL HA - ghcr.io/zalando/spilo-17:4.0-p3 # Container image that provides the postgres-exporter sidecar to create a metrics endpoint From bf0a5c8983fca3a72d40168e7bc727d33195f7d0 Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Wed, 22 Apr 2026 17:26:33 -0400 Subject: [PATCH 02/23] feat: enable connection pooling (use correct image, pass through variable for CR) --- bundle/uds-config.yaml | 2 -- chart/templates/postgres-minimal.yaml | 10 ++++++++++ values/upstream-values.yaml | 2 +- zarf.yaml | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/bundle/uds-config.yaml b/bundle/uds-config.yaml index 2067cc5..e69de29 100644 --- a/bundle/uds-config.yaml +++ b/bundle/uds-config.yaml @@ -1,2 +0,0 @@ -# Copyright 2024 Defense Unicorns -# SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial diff --git a/chart/templates/postgres-minimal.yaml b/chart/templates/postgres-minimal.yaml index e3f3cad..efa474b 100644 --- a/chart/templates/postgres-minimal.yaml +++ b/chart/templates/postgres-minimal.yaml @@ -14,6 +14,16 @@ spec: volume: size: {{ .Values.postgresql.volume.size | quote }} numberOfInstances: {{ .Values.postgresql.numberOfInstances }} + {{- if hasKey .Values.postgresql "enableConnectionPooler" }} + enableConnectionPooler: {{ .Values.postgresql.enableConnectionPooler }} + {{- end }} + {{- if hasKey .Values.postgresql "enableReplicaConnectionPooler" }} + enableReplicaConnectionPooler: {{ .Values.postgresql.enableReplicaConnectionPooler }} + {{- end }} + {{- with .Values.postgresql.connectionPooler }} + connectionPooler: + {{- toYaml . | nindent 4 }} + {{- end }} users: {{- toYaml .Values.postgresql.users | nindent 4 }} # database owner databases: diff --git a/values/upstream-values.yaml b/values/upstream-values.yaml index df44cfe..53f206a 100644 --- a/values/upstream-values.yaml +++ b/values/upstream-values.yaml @@ -6,7 +6,7 @@ image: repository: zalando/postgres-operator tag: v1.15.1 configConnectionPooler: - connection_pooler_image: "ghcr.io/cloudnative-pg/pgbouncer:1.24.1-23" + connection_pooler_image: "registry.opensource.zalan.do/acid/pgbouncer:master-32" configLogicalBackup: logical_backup_docker_image: "ghcr.io/zalando/postgres-operator/logical-backup:v1.15.1" configGeneral: diff --git a/zarf.yaml b/zarf.yaml index b874dc9..89e00be 100644 --- a/zarf.yaml +++ b/zarf.yaml @@ -66,7 +66,7 @@ components: images: - ghcr.io/zalando/postgres-operator:v1.15.1 - ghcr.io/zalando/postgres-operator/logical-backup:v1.15.1 - - ghcr.io/cloudnative-pg/pgbouncer:1.24.1-23 + - registry.opensource.zalan.do/acid/pgbouncer:master-32 # Docker image that provides PostgreSQL and Patroni bundled together for PostgreSQL HA - ghcr.io/zalando/spilo-17:4.0-p3 # Container image that provides the postgres-exporter sidecar to create a metrics endpoint From 436239885bebef87ea1111f7fd03909f079aabb7 Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Wed, 22 Apr 2026 17:37:25 -0400 Subject: [PATCH 03/23] fix: accidentally removed uds-config.yaml file --- bundle/uds-config.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bundle/uds-config.yaml b/bundle/uds-config.yaml index e69de29..2067cc5 100644 --- a/bundle/uds-config.yaml +++ b/bundle/uds-config.yaml @@ -0,0 +1,2 @@ +# Copyright 2024 Defense Unicorns +# SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial From a4b1d2b1462a70678a4144263ded6076ca08d759 Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Thu, 23 Apr 2026 13:09:28 -0400 Subject: [PATCH 04/23] feat: enhance connection pooling configuration and update task options --- chart/templates/postgres-minimal.yaml | 13 +++++++------ chart/values.yaml | 6 ++++++ tasks.yaml | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/chart/templates/postgres-minimal.yaml b/chart/templates/postgres-minimal.yaml index efa474b..cf50e2e 100644 --- a/chart/templates/postgres-minimal.yaml +++ b/chart/templates/postgres-minimal.yaml @@ -14,16 +14,17 @@ spec: volume: size: {{ .Values.postgresql.volume.size | quote }} numberOfInstances: {{ .Values.postgresql.numberOfInstances }} - {{- if hasKey .Values.postgresql "enableConnectionPooler" }} - enableConnectionPooler: {{ .Values.postgresql.enableConnectionPooler }} - {{- end }} - {{- if hasKey .Values.postgresql "enableReplicaConnectionPooler" }} - enableReplicaConnectionPooler: {{ .Values.postgresql.enableReplicaConnectionPooler }} - {{- end }} + + enableConnectionPooler: {{ .Values.postgresql.enableConnectionPooler | default false }} + enableReplicaConnectionPooler: {{ .Values.postgresql.enableReplicaConnectionPooler | default false }} + + {{- if and (hasKey .Values.postgresql "connectionPooler") (not (empty .Values.postgresql.connectionPooler)) }} {{- with .Values.postgresql.connectionPooler }} connectionPooler: {{- toYaml . | nindent 4 }} {{- end }} + {{- end }} + users: {{- toYaml .Values.postgresql.users | nindent 4 }} # database owner databases: diff --git a/chart/values.yaml b/chart/values.yaml index 28ac2de..9cc51e8 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -17,6 +17,12 @@ postgresql: additionalVolumes: [] env: [] +# Connection pooler options +# Ref: https://opensource.zalando.com/postgres-operator/docs/user.html#connection-pooler +enableConnectionPooler: false +enableReplicaConnectionPooler: false +connectionPooler: {} + # Example values for postgresql # # postgresql: diff --git a/tasks.yaml b/tasks.yaml index 057bac4..27670ab 100644 --- a/tasks.yaml +++ b/tasks.yaml @@ -26,7 +26,7 @@ tasks: actions: - task: create:package with: - options: "--skip-sbom" + options: "--skip-sbom --flavor=registry1" - name: create-deploy-test-bundle description: Test and validate cluster is deployed with Postgres Operator From b6619fdc64f4875491f9c419d4c2ac56425b09e1 Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Thu, 23 Apr 2026 14:25:54 -0400 Subject: [PATCH 05/23] fix: removed setting flavor in tasks.yaml file --- tasks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.yaml b/tasks.yaml index 27670ab..057bac4 100644 --- a/tasks.yaml +++ b/tasks.yaml @@ -26,7 +26,7 @@ tasks: actions: - task: create:package with: - options: "--skip-sbom --flavor=registry1" + options: "--skip-sbom" - name: create-deploy-test-bundle description: Test and validate cluster is deployed with Postgres Operator From 634a689506dec7da2964c1a0fb41b6e46ac373b3 Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Thu, 23 Apr 2026 16:20:08 -0400 Subject: [PATCH 06/23] feat: added _helpers.tpl for setting proper netpols; updated both uds-package.*.yaml templates accordingly --- bundle/uds-bundle.yaml | 2 ++ chart/templates/_helpers.tpl | 24 +++++++++++++++++ chart/templates/uds-package-postgres.yaml | 33 ++++++++++++++++------- chart/templates/uds-package.yaml | 9 +++++++ 4 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 chart/templates/_helpers.tpl diff --git a/bundle/uds-bundle.yaml b/bundle/uds-bundle.yaml index ccb256b..5a711bc 100644 --- a/bundle/uds-bundle.yaml +++ b/bundle/uds-bundle.yaml @@ -24,6 +24,8 @@ packages: volume: size: "10Gi" numberOfInstances: 2 + enableConnectionPooler: true + enableReplicaConnectionPooler: true users: gitlab.gitlab: [] # database owner sonarqube.sonarqube: [] # database owner diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl new file mode 100644 index 0000000..642bdb2 --- /dev/null +++ b/chart/templates/_helpers.tpl @@ -0,0 +1,24 @@ +{{/* +Copyright 2024 Defense Unicorns +SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial +*/}} + +{{/* +Network Polcies for Postgres Operator, specifically related to the addition of Connection Pooler. +*/}} +{{- define "uds-postgres.ingressRules" -}} +{{- $selector := .selector -}} +{{- if kindIs "slice" .ingress -}} +{{- range .ingress }} +- direction: Ingress + selector: + {{- $selector | toYaml | nindent 4 }} + {{- . | toYaml | nindent 2 }} +{{- end }} +{{- else }} +- direction: Ingress + selector: + {{- $selector | toYaml | nindent 4 }} + {{- .ingress | toYaml | nindent 2 }} +{{- end }} +{{- end -}} diff --git a/chart/templates/uds-package-postgres.yaml b/chart/templates/uds-package-postgres.yaml index 042b628..1365de2 100644 --- a/chart/templates/uds-package-postgres.yaml +++ b/chart/templates/uds-package-postgres.yaml @@ -25,29 +25,42 @@ spec: - direction: Egress remoteGenerated: IntraNamespace - {{- if kindIs "slice" .Values.postgresql.ingress -}} - {{- range .Values.postgresql.ingress }} + {{- include "uds-postgres.ingressRules" (dict + "ingress" .Values.postgresql.ingress + "selector" (dict "cluster-name" "pg-cluster") + ) | nindent 6 }} + - direction: Ingress selector: cluster-name: pg-cluster - {{ . | toYaml | nindent 8 }} - {{- end }} - {{- else }} - - direction: Ingress + remoteNamespace: {{ .Release.Namespace }} + remoteSelector: + app.kubernetes.io/name: postgres-operator + + - direction: Egress selector: cluster-name: pg-cluster - {{- .Values.postgresql.ingress | toYaml | nindent 8 }} - {{- end }} + remoteGenerated: KubeAPI + + {{- /* Pooler (pgbouncer) traffic. Zalando labels pooler pods with + `application=db-connection-pooler`. IntraNamespace rules above + already cover pooler <-> spilo traffic within this namespace. */}} + {{- if or (.Values.postgresql.enableConnectionPooler | default false) (.Values.postgresql.enableReplicaConnectionPooler | default false) }} + {{- include "uds-postgres.ingressRules" (dict + "ingress" .Values.postgresql.ingress + "selector" (dict "application" "db-connection-pooler") + ) | nindent 6 }} - direction: Ingress selector: - cluster-name: pg-cluster + application: db-connection-pooler remoteNamespace: {{ .Release.Namespace }} remoteSelector: app.kubernetes.io/name: postgres-operator - direction: Egress selector: - cluster-name: pg-cluster + application: db-connection-pooler remoteGenerated: KubeAPI + {{- end }} {{- end }} diff --git a/chart/templates/uds-package.yaml b/chart/templates/uds-package.yaml index 07e9ec3..279c0b7 100644 --- a/chart/templates/uds-package.yaml +++ b/chart/templates/uds-package.yaml @@ -18,6 +18,15 @@ spec: remoteNamespace: postgres remoteSelector: cluster-name: pg-cluster + + {{- if or (.Values.postgresql.enableConnectionPooler | default false) (.Values.postgresql.enableReplicaConnectionPooler | default false) }} + - direction: Egress + selector: + app.kubernetes.io/name: postgres-operator + remoteNamespace: postgres + remoteSelector: + application: db-connection-pooler + {{- end }} {{- end }} - direction: Egress From f00007fef8a200513e1e0eae42585147b3b7d65a Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Thu, 23 Apr 2026 16:39:36 -0400 Subject: [PATCH 07/23] feat: added minimal connectionPooler dict in uds-bundle.yaml --- bundle/uds-bundle.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bundle/uds-bundle.yaml b/bundle/uds-bundle.yaml index 5a711bc..d38c770 100644 --- a/bundle/uds-bundle.yaml +++ b/bundle/uds-bundle.yaml @@ -26,6 +26,15 @@ packages: numberOfInstances: 2 enableConnectionPooler: true enableReplicaConnectionPooler: true + connectionPooler: + numberOfInstances: 1 + resources: + requests: + cpu: 100m + memory: 100Mi + limits: + cpu: 500m + memory: 500Mi users: gitlab.gitlab: [] # database owner sonarqube.sonarqube: [] # database owner From 2c643cd8e7dd4554b49a4948c02ef141603cd4b2 Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Thu, 23 Apr 2026 17:22:03 -0400 Subject: [PATCH 08/23] feat: add pooler tests to postgres-test package --- tests/postgres/pooler-connection-test.yaml | 103 ++++++++++++++++++ .../postgres/uds-package-cross-namespace.yaml | 12 ++ tests/zarf.yaml | 39 +++++++ 3 files changed, 154 insertions(+) create mode 100644 tests/postgres/pooler-connection-test.yaml diff --git a/tests/postgres/pooler-connection-test.yaml b/tests/postgres/pooler-connection-test.yaml new file mode 100644 index 0000000..0fa54bc --- /dev/null +++ b/tests/postgres/pooler-connection-test.yaml @@ -0,0 +1,103 @@ +# Copyright 2024 Defense Unicorns +# SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial + +apiVersion: batch/v1 +kind: Job +metadata: + name: pooler-primary-test + namespace: gitlab + labels: + app: gitlab +spec: + template: + spec: + restartPolicy: Never + containers: + - name: pooler-primary-test + image: docker.io/postgres:18 + command: + - sh + - -c + - | + echo "Testing RW path via primary pooler at $DB_HOST" + SEL=$(psql -qtAc "SELECT 1" -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME") + if [ "$SEL" != "1" ]; then + echo "ERROR: SELECT 1 via primary pooler returned '$SEL', expected '1'" + exit 1 + fi + if ! psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" <<'SQL' + DO $$ + DECLARE + cnt int; + BEGIN + CREATE TEMP TABLE pooler_rw_probe(val int) ON COMMIT DROP; + INSERT INTO pooler_rw_probe VALUES (1),(2),(3); + SELECT count(*) INTO cnt FROM pooler_rw_probe; + IF cnt <> 3 THEN + RAISE EXCEPTION 'temp-table RW probe returned %, expected 3', cnt; + END IF; + END $$; + SQL + then + echo "ERROR: temp-table RW probe via primary pooler failed" + exit 1 + fi + echo "SUCCESS: primary pooler RW path verified" + exit 0 + env: + - name: DB_USER + valueFrom: + secretKeyRef: + name: gitlab.gitlab.pg-cluster.credentials.postgresql.acid.zalan.do + key: username + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: gitlab.gitlab.pg-cluster.credentials.postgresql.acid.zalan.do + key: password + - name: DB_HOST + value: pg-cluster-pooler.postgres.svc.cluster.local + - name: DB_NAME + value: gitlabdb +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: pooler-replica-test + namespace: gitlab + labels: + app: gitlab +spec: + template: + spec: + restartPolicy: Never + containers: + - name: pooler-replica-test + image: docker.io/postgres:18 + command: + - sh + - -c + - | + echo "Verifying replica pooler routes to a standby at $DB_HOST" + IN_RECOVERY=$(psql -tAc "SELECT pg_is_in_recovery()" -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME") + if [ "$IN_RECOVERY" != "t" ]; then + echo "ERROR: replica pooler routed to non-standby (pg_is_in_recovery='$IN_RECOVERY', expected 't')" + exit 1 + fi + echo "SUCCESS: replica pooler routed to a standby (pg_is_in_recovery=t)" + exit 0 + env: + - name: DB_USER + valueFrom: + secretKeyRef: + name: gitlab.gitlab.pg-cluster.credentials.postgresql.acid.zalan.do + key: username + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: gitlab.gitlab.pg-cluster.credentials.postgresql.acid.zalan.do + key: password + - name: DB_HOST + value: pg-cluster-pooler-repl.postgres.svc.cluster.local + - name: DB_NAME + value: gitlabdb diff --git a/tests/postgres/uds-package-cross-namespace.yaml b/tests/postgres/uds-package-cross-namespace.yaml index 62dfc26..d4cfa44 100644 --- a/tests/postgres/uds-package-cross-namespace.yaml +++ b/tests/postgres/uds-package-cross-namespace.yaml @@ -17,3 +17,15 @@ spec: remoteNamespace: postgres remoteSelector: cluster-name: pg-cluster + - direction: Egress + selector: + job-name: pooler-primary-test + remoteNamespace: postgres + remoteSelector: + cluster-name: pg-cluster + - direction: Egress + selector: + job-name: pooler-replica-test + remoteNamespace: postgres + remoteSelector: + cluster-name: pg-cluster diff --git a/tests/zarf.yaml b/tests/zarf.yaml index fb896f3..0335200 100644 --- a/tests/zarf.yaml +++ b/tests/zarf.yaml @@ -36,6 +36,45 @@ components: namespace: gitlab condition: "'{.status.succeeded}'=1" + - name: test-connection-pooler + required: true + manifests: + - name: pooler-connection-test + files: + - postgres/pooler-connection-test.yaml + images: + - docker.io/postgres:18 + actions: + onDeploy: + before: + - wait: + cluster: + kind: Deployment + name: pg-cluster-pooler + namespace: postgres + condition: available + maxTotalSeconds: 300 + - wait: + cluster: + kind: Deployment + name: pg-cluster-pooler-repl + namespace: postgres + condition: available + maxTotalSeconds: 300 + after: + - wait: + cluster: + kind: Job + name: pooler-primary-test + namespace: gitlab + condition: "'{.status.succeeded}'=1" + - wait: + cluster: + kind: Job + name: pooler-replica-test + namespace: gitlab + condition: "'{.status.succeeded}'=1" + - name: test-bad-password required: true manifests: From e8f4da85b304f6a4c9b0e4e1a937db0314c16e3f Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Thu, 23 Apr 2026 18:06:39 -0400 Subject: [PATCH 09/23] fix: k8s substitute double $ with single $ which is invalid SQL, use $probe$ instead; updated zarf tools wait-for with new zarf tools wait-for resource --- common/zarf.yaml | 4 ++-- tests/postgres/pooler-connection-test.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/zarf.yaml b/common/zarf.yaml index b454be0..df7fc68 100644 --- a/common/zarf.yaml +++ b/common/zarf.yaml @@ -41,7 +41,7 @@ components: maxTotalSeconds: 300 cmd: | if ./zarf tools kubectl get packages.uds.dev postgres -n postgres; then - ./zarf tools wait-for packages.uds.dev postgres -n postgres '{.status.phase}'=Ready + ./zarf tools wait-for resource packages.uds.dev postgres -n postgres '{.status.phase}'=Ready fi - description: Postgres Operator to be Healthy maxTotalSeconds: 90 @@ -55,5 +55,5 @@ components: maxTotalSeconds: 300 cmd: | if ./zarf tools kubectl get postgresql pg-cluster -n postgres; then - ./zarf tools wait-for postgresql pg-cluster -n postgres '{.status.PostgresClusterStatus}'=Running + ./zarf tools wait-for resource postgresql pg-cluster -n postgres '{.status.PostgresClusterStatus}'=Running fi diff --git a/tests/postgres/pooler-connection-test.yaml b/tests/postgres/pooler-connection-test.yaml index 0fa54bc..af80213 100644 --- a/tests/postgres/pooler-connection-test.yaml +++ b/tests/postgres/pooler-connection-test.yaml @@ -26,7 +26,7 @@ spec: exit 1 fi if ! psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" <<'SQL' - DO $$ + DO $probe$ DECLARE cnt int; BEGIN @@ -36,7 +36,7 @@ spec: IF cnt <> 3 THEN RAISE EXCEPTION 'temp-table RW probe returned %, expected 3', cnt; END IF; - END $$; + END $probe$; SQL then echo "ERROR: temp-table RW probe via primary pooler failed" From cbb32876867230ef84c7981d7979fcb2e2ef70b5 Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Thu, 23 Apr 2026 18:22:03 -0400 Subject: [PATCH 10/23] docs: add connection pooling section to configuration.md doc --- docs/configuration.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index b4a9ce6..4da8e9a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -34,6 +34,32 @@ Postgres Operator is configured through [`acid.zalan.do/v1` `Postgresql` custom value: ``` +## Connection Pooling + +Postgres Operator can deploy [pgbouncer](https://www.pgbouncer.org/) alongside the cluster to pool client connections, reducing backend churn for high-connection-count workloads. Poolers are deployed as separate `Deployment`s (`-pooler` and/or `-pooler-repl`) and exposed via matching `Service`s on port `5432`. Required network policies for the pooler pods are generated automatically when either flag below is enabled. + +- `postgresql.enableConnectionPooler`: deploy a pgbouncer pooler in front of the primary (RW traffic) +- `postgresql.enableReplicaConnectionPooler`: deploy a pgbouncer pooler in front of the replicas (RO traffic) +- `postgresql.connectionPooler`: optional map of pooler settings passed through to the `Postgresql` CR (e.g. `numberOfInstances`, `mode`, `resources`) + +Example: + +```yaml +postgresql: + enableConnectionPooler: true + enableReplicaConnectionPooler: true + connectionPooler: + numberOfInstances: 2 + mode: transaction +``` + +Clients connect through the pooler by pointing at `-pooler..svc.cluster.local` (primary) or `-pooler-repl..svc.cluster.local` (replicas) instead of the cluster Service. + +References: + +- [Zalando — Connection pooler](https://opensource.zalando.com/postgres-operator/docs/user.html#connection-pooler) +- [OneUptime — PostgreSQL with the Zalando operator](https://oneuptime.com/blog/post/2026-01-21-postgresql-zalando-operator/view) + ## Postgres HugePages Postgres Operator can also support HugePages by setting the following keys appropriately for your environment. You can learn more about HugePages in Kubernetes in their [Manage HugePages documentation](https://kubernetes.io/docs/tasks/manage-hugepages/scheduling-hugepages/#api) and learn more about these fields in the [`Postgresql` custom resource reference documentation](https://github.com/zalando/postgres-operator/blob/master/docs/reference/cluster_manifest.md#cluster-manifest-reference). From 25ab4f4b0a4a3c5880910bb43006e2a4bbf97ff8 Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Fri, 24 Apr 2026 13:56:46 -0400 Subject: [PATCH 11/23] feat: add shim to make IB pgbouncer image work without needing to rebuild image --- tasks/registry1-pooler-patch.yaml | 119 ++++++++++++++++++++++++++++++ zarf.yaml | 15 +++- 2 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 tasks/registry1-pooler-patch.yaml diff --git a/tasks/registry1-pooler-patch.yaml b/tasks/registry1-pooler-patch.yaml new file mode 100644 index 0000000..86ba098 --- /dev/null +++ b/tasks/registry1-pooler-patch.yaml @@ -0,0 +1,119 @@ +# Copyright 2024 Defense Unicorns +# SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial + +# yaml-language-server: $schema=https://raw.githubusercontent.com/defenseunicorns/uds-cli/refs/heads/main/tasks.schema.json + +# Registry1-flavor-only shim for the Zalando postgres-operator connection pooler. +# Justification: IB pgbouncer images run as UID 997, but the operator hardcodes pooler pods to +# run as UID 100, so the pooler cannot write its config, logs, or pidfile. This patch adds +# emptyDir volumes and a seed init container to work around the UID mismatch. +# furthermore, auth_mode is hardcoded to plain in the operator's templates, +# so the init container also patches it to scram-sha-256. + +tasks: + - name: patch-pooler-deployments + description: Shim Zalando pooler Deployments for Iron Bank pgbouncer UID mismatch + actions: + - description: Strategic-merge emptyDir + seed-templates initContainer into pooler Deployments + shell: + darwin: bash + linux: bash + cmd: | + set -e + + if ! uds zarf tools kubectl get postgresql pg-cluster -n postgres >/dev/null 2>&1; then + echo "pg-cluster Postgresql CR not present; skipping pooler shim" + exit 0 + fi + + master=$(uds zarf tools kubectl get postgresql pg-cluster -n postgres \ + -o jsonpath='{.spec.enableConnectionPooler}' 2>/dev/null) + repl=$(uds zarf tools kubectl get postgresql pg-cluster -n postgres \ + -o jsonpath='{.spec.enableReplicaConnectionPooler}' 2>/dev/null) + + if [ "$master" != "true" ] && [ "$repl" != "true" ]; then + echo "Connection pooler not enabled on Postgresql CR; skipping pooler shim" + exit 0 + fi + + patch_pooler() { + local dep="$1" + + echo "Waiting for Deployment ${dep} to be created by operator..." + for _ in $(seq 1 60); do + if uds zarf tools kubectl get deploy "${dep}" -n postgres >/dev/null 2>&1; then + break + fi + sleep 2 + done + uds zarf tools kubectl get deploy "${dep}" -n postgres >/dev/null + + local image + image=$(uds zarf tools kubectl get deploy "${dep}" -n postgres \ + -o jsonpath='{.spec.template.spec.containers[0].image}') + echo "Patching ${dep} (init container image: ${image})" + + uds zarf tools kubectl patch deploy "${dep}" -n postgres \ + --type=strategic --patch-file=/dev/stdin </dev/null 2>&1; then + uds zarf tools kubectl rollout status deploy "${dep}" -n postgres --timeout=180s + fi + done diff --git a/zarf.yaml b/zarf.yaml index 89e00be..084d4bd 100644 --- a/zarf.yaml +++ b/zarf.yaml @@ -29,8 +29,8 @@ components: required: true only: flavor: registry1 - cluster: - architecture: amd64 + # cluster: + # architecture: amd64 import: path: common charts: @@ -40,6 +40,17 @@ components: - name: uds-postgres-config valuesFiles: - ./values/registry1-config-values.yaml + actions: + onDeploy: + after: + # The Iron Bank pgbouncer image bakes /etc/pgbouncer, /var/log/pgbouncer, + # and /var/run/pgbouncer owned by pgbouncer:pgbouncer (UID 997:997), but + # the Zalando operator hardcodes the pooler pod's runAsUser=100 — so the + # pgbouncer entrypoint cannot write its TLS cert, log, or pidfile. Shim + # the operator-managed pooler Deployments with emptyDir mounts + a seed + # init container. See tasks/registry1-pooler-patch.yaml for rationale. + - description: Shim Iron Bank pgbouncer directory permissions on pooler Deployments + cmd: uds run -f tasks/registry1-pooler-patch.yaml patch-pooler-deployments --no-progress images: # Iron Bank - registry1.dso.mil/ironbank/opensource/zalando/postgres-operator:v1.15.0 From ee6aba65ff1d821269b44f4550f198b0f41b1a15 Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Fri, 24 Apr 2026 14:54:29 -0400 Subject: [PATCH 12/23] fix: resolved linting issues in registry1-pooler-patch.yaml task --- tasks.yaml | 1 + tasks/registry1-pooler-patch.yaml | 16 ++++++++-------- tasks/test.yaml | 1 + 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tasks.yaml b/tasks.yaml index 057bac4..4b3d5b4 100644 --- a/tasks.yaml +++ b/tasks.yaml @@ -1,6 +1,7 @@ # Copyright 2024 Defense Unicorns # SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial +# yaml-language-server: $schema=https://raw.githubusercontent.com/defenseunicorns/uds-cli/refs/heads/main/tasks.schema.json includes: - test: ./tasks/test.yaml - create: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.5/tasks/create.yaml diff --git a/tasks/registry1-pooler-patch.yaml b/tasks/registry1-pooler-patch.yaml index 86ba098..7d95ed3 100644 --- a/tasks/registry1-pooler-patch.yaml +++ b/tasks/registry1-pooler-patch.yaml @@ -7,7 +7,7 @@ # Justification: IB pgbouncer images run as UID 997, but the operator hardcodes pooler pods to # run as UID 100, so the pooler cannot write its config, logs, or pidfile. This patch adds # emptyDir volumes and a seed init container to work around the UID mismatch. -# furthermore, auth_mode is hardcoded to plain in the operator's templates, +# furthermore, auth_mode is hardcoded to plain in the operator's templates, # so the init container also patches it to scram-sha-256. tasks: @@ -53,9 +53,7 @@ tasks: -o jsonpath='{.spec.template.spec.containers[0].image}') echo "Patching ${dep} (init container image: ${image})" - uds zarf tools kubectl patch deploy "${dep}" -n postgres \ - --type=strategic --patch-file=/dev/stdin < Date: Fri, 24 Apr 2026 15:03:16 -0400 Subject: [PATCH 13/23] docs: add section to configuration.md document justifying reason for adding registry1 patch to pooler --- docs/configuration.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 4da8e9a..382e3bd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -55,6 +55,20 @@ postgresql: Clients connect through the pooler by pointing at `-pooler..svc.cluster.local` (primary) or `-pooler-repl..svc.cluster.local` (replicas) instead of the cluster Service. +### Registry1 pooler shim + +The `registry1` flavor consumes the Iron Bank pgbouncer image, which diverges from upstream Zalando in two ways the operator does not expose via configuration: + +1. The `pgbouncer` user is built as UID `997`, but the Zalando operator hardcodes the pooler pod to `runAsUser: 100`. As a result the entrypoint cannot write the self-signed TLS cert to `/etc/pgbouncer/`, the log to `/var/log/pgbouncer/`, or the pidfile to `/var/run/pgbouncer/`. +2. The baked-in `pgbouncer.ini.tmpl` hardcodes `auth_type = plain`, which fails against postgres's default `scram-sha-256` encryption (pgbouncer cannot replay SCRAM secrets returned by `auth_query` when operating in plain mode). + +To keep the pooler usable without rebuilding the image or adding a Pepr mutation, the `registry1` component in `zarf.yaml` runs [`tasks/registry1-pooler-patch.yaml`](../tasks/registry1-pooler-patch.yaml) as an `onDeploy.after` action. For each operator-managed pooler Deployment it strategic-merge patches in: + +- three in-memory `emptyDir` volumes mounted at `/etc/pgbouncer`, `/var/log/pgbouncer`, and `/var/run/pgbouncer` (writable under the pod's `fsGroup: 103`) +- a `seed-pgbouncer-etc` init container that copies Zalando's `.tmpl` files into the emptyDir and rewrites `auth_type = plain` to `auth_type = scram-sha-256` before the main container renders them via `envsubst` + +The operator's pooler sync does not compare `volumes`, `volumeMounts`, or `initContainers`, so the patch survives its reconcile loop. The `upstream` and `unicorn` flavors ship pgbouncer at UID `100` (and their tests have not surfaced the auth issue yet) and do not run the shim. + References: - [Zalando — Connection pooler](https://opensource.zalando.com/postgres-operator/docs/user.html#connection-pooler) From 2cc0316c2809464e57a33c703415a401a314be15 Mon Sep 17 00:00:00 2001 From: codyshoffner Date: Mon, 4 May 2026 16:00:55 -0500 Subject: [PATCH 14/23] chore: update images to cgr --- zarf.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zarf.yaml b/zarf.yaml index b874dc9..9836943 100644 --- a/zarf.yaml +++ b/zarf.yaml @@ -86,10 +86,10 @@ components: valuesFiles: - ./values/unicorn-config-values.yaml images: - - quay.io/rfcurated/zalando/postgres-operator:1.15-jammy-scratch-fips-rfcurated + - cgr.dev/defenseunicorns.com/postgres-operator:1.15.1 - quay.io/rfcurated/zalando/postgres-operator/logical-backup:1.15-jammy-scratch-fips-rfcurated - - quay.io/rfcurated/zalando/pgbouncer:32-jammy-rfcurated + - cgr.dev/defenseunicorns.com/pgbouncer:1.25.1 # Docker image that provides PostgreSQL and Patroni bundled together for PostgreSQL HA - - quay.io/rfcurated/zalando/spilo-17:4.0-p3-jammy-fips-rfcurated + - cgr.dev/defenseunicorns.com/spilo-17:4.1.2 # Container image that provides the postgres-exporter sidecar to create a metrics endpoint - - quay.io/rfcurated/prometheuscommunity/postgres-exporter:0.19.1-jammy-scratch-bnt-fips-rfcurated + - cgr.dev/defenseunicorns.com/prometheus-postgres-exporter:0.19.1 From b5e05f1a0f333175fd1413ade5a5846603269409 Mon Sep 17 00:00:00 2001 From: codyshoffner Date: Thu, 14 May 2026 10:13:21 -0500 Subject: [PATCH 15/23] chore: swap rf images for cgr --- releaser.yaml | 4 ++-- renovate.json | 13 ------------- values/unicorn-config-values.yaml | 4 ++-- values/unicorn-values.yaml | 10 +++++----- 4 files changed, 9 insertions(+), 22 deletions(-) diff --git a/releaser.yaml b/releaser.yaml index 2c4ef0e..efecb2a 100644 --- a/releaser.yaml +++ b/releaser.yaml @@ -9,5 +9,5 @@ flavors: # renovate-uds: datasource=docker depName=registry1.dso.mil/ironbank/opensource/zalando/postgres-operator extractVersion=^v?(?\d+\.\d+\.\d+)$ version: 1.15.0-uds.18 - name: unicorn - # renovate-uds: datasource=docker depName=quay.io/rfcurated/zalando/postgres-operator extractVersion=^v?(?\d+\.\d+\.\d+)$ - version: 1.15.0-uds.19 + # renovate-uds: datasource=docker depName=cgr.dev/defenseunicorns.com/postgres-operator extractVersion=^v?(?\d+\.\d+\.\d+)$ + version: 1.15.1-uds.0 diff --git a/renovate.json b/renovate.json index e30b2b9..3fee275 100644 --- a/renovate.json +++ b/renovate.json @@ -8,19 +8,6 @@ ":semanticCommitTypeAll(chore)" ], "packageRules": [ - { - "matchDatasources": ["docker"], - "matchPackagePatterns": ["^quay\\.io/rfcurated/"], - "versioning": "regex:^(?\\d+)\\.(?\\d+)(\\.(?\\d+))?-.*fips.*rfcurated$" - }, - { - "matchPackageNames": ["quay.io/rfcurated/prometheuscommunity/postgres-exporter"], - "versioning": "regex:^(?\\d+)\\.(?\\d+)(\\.(?\\d+))?-jammy-scratch-bnt-fips-rfcurated$" - }, - { - "matchPackageNames": ["quay.io/rfcurated/zalando/spilo-17"], - "versioning": "regex:^(?\\d+)\\.(?\\d+)-p(?\\d+)-jammy-fips-rfcurated$" - }, { "matchPackageNames": ["ghcr.io/zalando/spilo-17"], "versioning": "regex:^(?\\d+)\\.(?\\d+)-p(?\\d+)$" diff --git a/values/unicorn-config-values.yaml b/values/unicorn-config-values.yaml index 8b009fa..c1ca435 100644 --- a/values/unicorn-config-values.yaml +++ b/values/unicorn-config-values.yaml @@ -2,7 +2,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial metrics: - image: "quay.io/rfcurated/prometheuscommunity/postgres-exporter:0.19.1-jammy-scratch-bnt-fips-rfcurated" + image: "cgr.dev/defenseunicorns.com/prometheus-postgres-exporter:0.19.1" job: - image: "quay.io/rfcurated/zalando/spilo-17:4.0-p3-jammy-fips-rfcurated" + image: "cgr.dev/defenseunicorns.com/spilo-17:4.1.2" diff --git a/values/unicorn-values.yaml b/values/unicorn-values.yaml index 4179db5..3910c0b 100644 --- a/values/unicorn-values.yaml +++ b/values/unicorn-values.yaml @@ -2,12 +2,12 @@ # SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial image: - registry: quay.io - repository: rfcurated/zalando/postgres-operator - tag: 1.15-jammy-scratch-fips-rfcurated + registry: cgr.dev + repository: defenseunicorns.com/postgres-operator + tag: 1.15.1 configConnectionPooler: - connection_pooler_image: "quay.io/rfcurated/zalando/pgbouncer:32-jammy-rfcurated" + connection_pooler_image: "cgr.dev/defenseunicorns.com/pgbouncer:1.25.1" configLogicalBackup: logical_backup_docker_image: "quay.io/rfcurated/zalando/postgres-operator/logical-backup:1.15-jammy-scratch-fips-rfcurated" configGeneral: - docker_image: "quay.io/rfcurated/zalando/spilo-17:4.0-p3-jammy-fips-rfcurated" + docker_image: "cgr.dev/defenseunicorns.com/spilo-17:4.1.2" From 89fb9773309d9dda9c4e97c03a72469d1f6009bd Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Thu, 14 May 2026 11:33:26 -0400 Subject: [PATCH 16/23] chore: update workflows and tasks to use uds-common v1.24.10 --- .github/workflows/auto-update.yaml | 2 +- .github/workflows/commitlint.yaml | 2 +- .github/workflows/lint.yaml | 2 +- .github/workflows/release.yaml | 6 +++--- .github/workflows/scan.yaml | 2 +- .github/workflows/scorecard.yaml | 2 +- .github/workflows/test.yaml | 4 ++-- tasks.yaml | 18 +++++++++--------- zarf.yaml | 3 ++- 9 files changed, 21 insertions(+), 20 deletions(-) diff --git a/.github/workflows/auto-update.yaml b/.github/workflows/auto-update.yaml index 930d845..3987226 100644 --- a/.github/workflows/auto-update.yaml +++ b/.github/workflows/auto-update.yaml @@ -20,5 +20,5 @@ concurrency: jobs: auto-update: - uses: defenseunicorns/uds-common/.github/workflows/callable-auto-update.yaml@99fd276835257a9608656380d1d453356fe7539e # v1.24.8 + uses: defenseunicorns/uds-common/.github/workflows/callable-auto-update.yaml@86fcadc2845a318761276a8754e47e33c0d6ae31 # v1.24.10 secrets: inherit # Inherits all secrets from the parent workflow. diff --git a/.github/workflows/commitlint.yaml b/.github/workflows/commitlint.yaml index 1a0ebe8..5e7a2fc 100644 --- a/.github/workflows/commitlint.yaml +++ b/.github/workflows/commitlint.yaml @@ -15,4 +15,4 @@ permissions: jobs: validate: - uses: defenseunicorns/uds-common/.github/workflows/callable-commitlint.yaml@99fd276835257a9608656380d1d453356fe7539e # v1.24.8 + uses: defenseunicorns/uds-common/.github/workflows/callable-commitlint.yaml@86fcadc2845a318761276a8754e47e33c0d6ae31 # v1.24.10 diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 8e78439..b8b8098 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -15,5 +15,5 @@ permissions: jobs: validate: - uses: defenseunicorns/uds-common/.github/workflows/callable-lint.yaml@99fd276835257a9608656380d1d453356fe7539e # v1.24.8 + uses: defenseunicorns/uds-common/.github/workflows/callable-lint.yaml@86fcadc2845a318761276a8754e47e33c0d6ae31 # v1.24.10 secrets: inherit diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1c9ea95..185f13d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -17,8 +17,8 @@ permissions: jobs: publish: permissions: - contents: write # Allows reading the content of the repository. - packages: write # Allows reading the content of the repository's packages. + contents: write # Allows writing content to the repository. + packages: write # Allows writing content to the repository's packages. id-token: write strategy: matrix: @@ -27,7 +27,7 @@ jobs: exclude: - flavor: registry1 architecture: arm64 - uses: defenseunicorns/uds-common/.github/workflows/callable-publish.yaml@99fd276835257a9608656380d1d453356fe7539e # v1.24.8 + uses: defenseunicorns/uds-common/.github/workflows/callable-publish.yaml@86fcadc2845a318761276a8754e47e33c0d6ae31 # v1.24.10 with: flavor: ${{ matrix.flavor }} options: --set BASE_REPO="ghcr.io/uds-packages" diff --git a/.github/workflows/scan.yaml b/.github/workflows/scan.yaml index 0c5673f..739a13f 100644 --- a/.github/workflows/scan.yaml +++ b/.github/workflows/scan.yaml @@ -18,5 +18,5 @@ jobs: packages: read # Allows reading the content of the repository's packages. id-token: write # Allows authentication to Chainguard via OIDC. pull-requests: write # Allows writing the scan results comment to the pull request. - uses: defenseunicorns/uds-common/.github/workflows/callable-scan.yaml@99fd276835257a9608656380d1d453356fe7539e # v1.24.8 + uses: defenseunicorns/uds-common/.github/workflows/callable-scan.yaml@86fcadc2845a318761276a8754e47e33c0d6ae31 # v1.24.10 secrets: inherit # Inherits all secrets from the parent workflow. diff --git a/.github/workflows/scorecard.yaml b/.github/workflows/scorecard.yaml index d9f9d6e..f924212 100644 --- a/.github/workflows/scorecard.yaml +++ b/.github/workflows/scorecard.yaml @@ -32,5 +32,5 @@ jobs: security-events: write # Used to receive a badge. id-token: write - uses: defenseunicorns/uds-common/.github/workflows/callable-scorecard.yaml@99fd276835257a9608656380d1d453356fe7539e # v1.24.8 + uses: defenseunicorns/uds-common/.github/workflows/callable-scorecard.yaml@86fcadc2845a318761276a8754e47e33c0d6ae31 # v1.24.10 secrets: inherit diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 29d58c5..c7941cd 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -29,7 +29,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: test-flavor - uses: defenseunicorns/uds-common/.github/actions/test-flavor@99fd276835257a9608656380d1d453356fe7539e # v1.24.8 + uses: defenseunicorns/uds-common/.github/actions/test-flavor@86fcadc2845a318761276a8754e47e33c0d6ae31 # v1.24.10 id: test-flavor outputs: upgrade-flavors: ${{ steps.test-flavor.outputs.upgrade-flavors }} @@ -41,7 +41,7 @@ jobs: matrix: type: [install, upgrade] flavor: [upstream, registry1, unicorn] - uses: defenseunicorns/uds-common/.github/workflows/callable-test.yaml@99fd276835257a9608656380d1d453356fe7539e # v1.24.8 + uses: defenseunicorns/uds-common/.github/workflows/callable-test.yaml@86fcadc2845a318761276a8754e47e33c0d6ae31 # v1.24.10 with: options: --set BASE_REPO="ghcr.io/uds-packages" upgrade-flavors: ${{ needs.check-flavor.outputs.upgrade-flavors }} diff --git a/tasks.yaml b/tasks.yaml index e76dd6b..070bb90 100644 --- a/tasks.yaml +++ b/tasks.yaml @@ -4,15 +4,15 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/defenseunicorns/uds-cli/refs/heads/main/tasks.schema.json includes: - test: ./tasks/test.yaml - - create: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.8/tasks/create.yaml - - publish: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.8/tasks/publish.yaml - - lint: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.8/tasks/lint.yaml - - pull: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.8/tasks/pull.yaml - - deploy: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.8/tasks/deploy.yaml - - setup: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.8/tasks/setup.yaml - - actions: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.8/tasks/actions.yaml - - badge: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.8/tasks/badge.yaml - - upgrade: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.8/tasks/upgrade.yaml + - create: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.10/tasks/create.yaml + - publish: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.10/tasks/publish.yaml + - lint: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.10/tasks/lint.yaml + - pull: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.10/tasks/pull.yaml + - deploy: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.10/tasks/deploy.yaml + - setup: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.10/tasks/setup.yaml + - actions: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.10/tasks/actions.yaml + - badge: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.10/tasks/badge.yaml + - upgrade: https://raw.githubusercontent.com/defenseunicorns/uds-common/v1.24.10/tasks/upgrade.yaml tasks: - name: default diff --git a/zarf.yaml b/zarf.yaml index 017acd8..cc7297e 100644 --- a/zarf.yaml +++ b/zarf.yaml @@ -98,7 +98,8 @@ components: - ./values/unicorn-config-values.yaml images: - cgr.dev/defenseunicorns.com/postgres-operator:1.15.1 - - quay.io/rfcurated/zalando/postgres-operator/logical-backup:1.15-jammy-scratch-fips-rfcurated + # - quay.io/rfcurated/zalando/postgres-operator/logical-backup:1.15-jammy-scratch-fips-rfcurated + - registry1.dso.mil/ironbank/opensource/zalando/logical-backup:v1.15.1 - cgr.dev/defenseunicorns.com/pgbouncer:1.25.1 # Docker image that provides PostgreSQL and Patroni bundled together for PostgreSQL HA - cgr.dev/defenseunicorns.com/spilo-17:4.1.2 From 83014477ec8ea827b62ef11d89dd700fac858180 Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Thu, 14 May 2026 14:13:50 -0400 Subject: [PATCH 17/23] feat(cgr): add unicorn flavor; create pooler patch for chainguard similar to that needed for registry1 Signed-off-by: Mitch Murphy --- docs/configuration.md | 16 +- tasks.yaml | 2 +- tasks/unicorn-pooler-patch.yaml | 250 ++++++++++++++++++++++++++++++++ zarf.yaml | 9 +- 4 files changed, 273 insertions(+), 4 deletions(-) create mode 100644 tasks/unicorn-pooler-patch.yaml diff --git a/docs/configuration.md b/docs/configuration.md index 382e3bd..0b41186 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -67,7 +67,21 @@ To keep the pooler usable without rebuilding the image or adding a Pepr mutation - three in-memory `emptyDir` volumes mounted at `/etc/pgbouncer`, `/var/log/pgbouncer`, and `/var/run/pgbouncer` (writable under the pod's `fsGroup: 103`) - a `seed-pgbouncer-etc` init container that copies Zalando's `.tmpl` files into the emptyDir and rewrites `auth_type = plain` to `auth_type = scram-sha-256` before the main container renders them via `envsubst` -The operator's pooler sync does not compare `volumes`, `volumeMounts`, or `initContainers`, so the patch survives its reconcile loop. The `upstream` and `unicorn` flavors ship pgbouncer at UID `100` (and their tests have not surfaced the auth issue yet) and do not run the shim. +The operator's pooler sync does not compare `volumes`, `volumeMounts`, or `initContainers`, so the patch survives its reconcile loop. The `upstream` flavor ships the Zalando-curated pgbouncer image and does not need the shim. + +### Unicorn pooler shim + +The `unicorn` flavor consumes `cgr.dev/defenseunicorns.com/pgbouncer`, which is a generic upstream Chainguard pgbouncer image rather than a Zalando rebuild. Two consequences the operator does not expose via configuration: + +1. The image's `ENTRYPOINT` is `/usr/bin/pgbouncer` with `CMD` `["--help"]` and ships no `/etc/pgbouncer/*.tmpl` files or entrypoint script. The Zalando operator leaves the pooler container's `command`/`args` unset and depends on the image entrypoint to render config from `PGHOST`/`PGPORT`/`PGUSER`/`CONNECTION_POOLER_*` env vars and exec pgbouncer; with this image the pod just runs `pgbouncer --help` and exits. +2. The image is fully minimal (only the `pgbouncer` binary — no `sh`, `openssl`, or `envsubst`), so the rendering cannot be done inside the main container. + +To keep the pooler usable without rebuilding the image or adding a Pepr mutation, the `unicorn` component in `zarf.yaml` runs [`tasks/unicorn-pooler-patch.yaml`](../tasks/unicorn-pooler-patch.yaml) as an `onDeploy.after` action. For each operator-managed pooler Deployment it strategic-merge patches in: + +- three in-memory `emptyDir` volumes mounted at `/etc/pgbouncer`, `/var/log/pgbouncer`, and `/var/run/pgbouncer` (writable under the pod's `fsGroup: 103`) +- a `seed-pgbouncer-etc` init container that reuses the unicorn flavor's spilo image (already pulled for the postgres pods, has `sh` + `openssl`) to render `pgbouncer.ini`, `auth_file.txt`, and a self-signed TLS cert into the emptyDir — replicating upstream Zalando's `entrypoint.sh`, with `auth_type = scram-sha-256` (PG17 default) and without the Zalando downstream-fork-only `stats_users_prefix` directive that vanilla pgbouncer rejects +- the operator-set env block replicated onto the init container (extracted live via `kubectl jsonpath`) so the rendered config matches the configured `pool_mode`/sizes/ports; `PGUSER` and `PGPASSWORD` are preserved as `secretKeyRef`s so the password never lands in plaintext in the Deployment spec +- a `command` override on the main container to `["/usr/bin/pgbouncer", "/etc/pgbouncer/pgbouncer.ini"]` References: diff --git a/tasks.yaml b/tasks.yaml index 070bb90..300a447 100644 --- a/tasks.yaml +++ b/tasks.yaml @@ -27,7 +27,7 @@ tasks: actions: - task: create:package with: - options: "--skip-sbom" + options: "--skip-sbom --flavor=unicorn" - name: create-deploy-test-bundle description: Test and validate cluster is deployed with Postgres Operator diff --git a/tasks/unicorn-pooler-patch.yaml b/tasks/unicorn-pooler-patch.yaml new file mode 100644 index 0000000..350565f --- /dev/null +++ b/tasks/unicorn-pooler-patch.yaml @@ -0,0 +1,250 @@ +# Copyright 2024 Defense Unicorns +# SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial + +# yaml-language-server: $schema=https://raw.githubusercontent.com/defenseunicorns/uds-cli/refs/heads/main/tasks.schema.json + +# Unicorn-flavor-only shim for the Zalando postgres-operator connection pooler. +# This patch reproduces the upstream Zalando entrypoint behavior out-of-band: + +# auth_type is set to scram-sha-256 (Postgres 17 default); upstream's template +# hardcodes md5 which fails against modern Postgres. + +tasks: + - name: patch-pooler-deployments + description: Shim Zalando pooler Deployments for CGR pgbouncer entrypoint mismatch + actions: + - description: Strategic-merge emptyDir + render-config initContainer into pooler Deployments + shell: + darwin: bash + linux: bash + cmd: | + set -e + + if ! uds zarf tools kubectl get postgresql pg-cluster -n postgres >/dev/null 2>&1; then + echo "pg-cluster Postgresql CR not present; skipping pooler shim" + exit 0 + fi + + master=$(uds zarf tools kubectl get postgresql pg-cluster -n postgres \ + -o jsonpath='{.spec.enableConnectionPooler}' 2>/dev/null) + repl=$(uds zarf tools kubectl get postgresql pg-cluster -n postgres \ + -o jsonpath='{.spec.enableReplicaConnectionPooler}' 2>/dev/null) + + if [ "$master" != "true" ] && [ "$repl" != "true" ]; then + echo "Connection pooler not enabled on Postgresql CR; skipping pooler shim" + exit 0 + fi + + # Spilo image is already required by the unicorn flavor for postgres pods, + # so re-using it here adds no new image pull. It provides sh + openssl, + # which the minimal CGR pgbouncer image does not. + init_image=$(uds zarf tools kubectl get postgresql pg-cluster -n postgres \ + -o jsonpath='{.spec.dockerImage}') + if [ -z "$init_image" ]; then + init_image="cgr.dev/defenseunicorns.com/spilo-17:4.1.2" + fi + + # Pull a single env value (plain .value) from the operator-managed pooler + # container so the init container renders config using the same values + # the operator already computed (mode, sizes, ports). + get_env_value() { + local dep_name="$1" env_name="$2" + uds zarf tools kubectl get deploy "${dep_name}" -n postgres \ + -o jsonpath="{.spec.template.spec.containers[?(@.name=='connection-pooler')].env[?(@.name=='${env_name}')].value}" + } + + # Pull a secretKeyRef.name for a .valueFrom env entry (PGUSER, PGPASSWORD). + get_env_secret_name() { + local dep_name="$1" env_name="$2" + uds zarf tools kubectl get deploy "${dep_name}" -n postgres \ + -o jsonpath="{.spec.template.spec.containers[?(@.name=='connection-pooler')].env[?(@.name=='${env_name}')].valueFrom.secretKeyRef.name}" + } + + patch_pooler() { + local dep="$1" + + echo "Waiting for Deployment ${dep} to be created by operator..." + for _ in $(seq 1 60); do + if uds zarf tools kubectl get deploy "${dep}" -n postgres >/dev/null 2>&1; then + break + fi + sleep 2 + done + uds zarf tools kubectl get deploy "${dep}" -n postgres >/dev/null + + # Resolve operator-set env values up front so we can embed them in + # the init container's env block. Secret refs are kept as refs so + # the password never appears in plaintext in the Deployment spec. + local pghost pgport pgschema cp_port cp_mode cp_default cp_min cp_reserve cp_maxclient cp_maxdb + local pguser_secret pgpass_secret + pghost=$(get_env_value "${dep}" PGHOST) + pgport=$(get_env_value "${dep}" PGPORT) + pgschema=$(get_env_value "${dep}" PGSCHEMA) + cp_port=$(get_env_value "${dep}" CONNECTION_POOLER_PORT) + cp_mode=$(get_env_value "${dep}" CONNECTION_POOLER_MODE) + cp_default=$(get_env_value "${dep}" CONNECTION_POOLER_DEFAULT_SIZE) + cp_min=$(get_env_value "${dep}" CONNECTION_POOLER_MIN_SIZE) + cp_reserve=$(get_env_value "${dep}" CONNECTION_POOLER_RESERVE_SIZE) + cp_maxclient=$(get_env_value "${dep}" CONNECTION_POOLER_MAX_CLIENT_CONN) + cp_maxdb=$(get_env_value "${dep}" CONNECTION_POOLER_MAX_DB_CONN) + pguser_secret=$(get_env_secret_name "${dep}" PGUSER) + pgpass_secret=$(get_env_secret_name "${dep}" PGPASSWORD) + + if [ -z "${pghost}" ] || [ -z "${pguser_secret}" ] || [ -z "${pgpass_secret}" ]; then + echo "Failed to resolve required env from ${dep}; aborting shim" >&2 + return 1 + fi + + echo "Patching ${dep} (init image: ${init_image}, pooler creds secret: ${pguser_secret})" + + # NOTE on escaping: + # - The patch is a single-quoted bash string, so \$1 / \$PGHOST / etc. + # are preserved verbatim. + # - K8s only substitutes \$(VAR) and reduces \$\$ -> \$ on command/args. + # ${VAR} passes through unchanged for sh to expand at runtime. + # - Inside the sh heredocs (INI / AUTH) we use ${VAR} for substitution + # and \$1 to keep the literal $1 required by pgbouncer's auth_query. + local patch='spec: + template: + spec: + volumes: + - name: pgbouncer-etc + emptyDir: + medium: Memory + - name: pgbouncer-log + emptyDir: + medium: Memory + - name: pgbouncer-run + emptyDir: + medium: Memory + initContainers: + - name: seed-pgbouncer-etc + image: '"${init_image}"' + command: ["/bin/sh", "-c"] + args: + - | + set -e + umask 0077 + + cat > /seed/pgbouncer.ini < /seed/auth_file.txt </dev/null 2>&1 + + chmod 0644 /seed/pgbouncer.ini /seed/pgbouncer.crt + chmod 0600 /seed/auth_file.txt /seed/pgbouncer.key + env: + - name: PGHOST + value: "'"${pghost}"'" + - name: PGPORT + value: "'"${pgport}"'" + - name: PGSCHEMA + value: "'"${pgschema}"'" + - name: PGUSER + valueFrom: + secretKeyRef: + name: '"${pguser_secret}"' + key: username + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: '"${pgpass_secret}"' + key: password + - name: CONNECTION_POOLER_PORT + value: "'"${cp_port}"'" + - name: CONNECTION_POOLER_MODE + value: "'"${cp_mode}"'" + - name: CONNECTION_POOLER_DEFAULT_SIZE + value: "'"${cp_default}"'" + - name: CONNECTION_POOLER_MIN_SIZE + value: "'"${cp_min}"'" + - name: CONNECTION_POOLER_RESERVE_SIZE + value: "'"${cp_reserve}"'" + - name: CONNECTION_POOLER_MAX_CLIENT_CONN + value: "'"${cp_maxclient}"'" + - name: CONNECTION_POOLER_MAX_DB_CONN + value: "'"${cp_maxdb}"'" + volumeMounts: + - name: pgbouncer-etc + mountPath: /seed + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: [ALL] + runAsUser: 100 + runAsGroup: 101 + runAsNonRoot: true + containers: + - name: connection-pooler + command: ["/usr/bin/pgbouncer", "/etc/pgbouncer/pgbouncer.ini"] + volumeMounts: + - name: pgbouncer-etc + mountPath: /etc/pgbouncer + - name: pgbouncer-log + mountPath: /var/log/pgbouncer + - name: pgbouncer-run + mountPath: /var/run/pgbouncer' + + uds zarf tools kubectl patch deploy "${dep}" -n postgres \ + --type=strategic --patch="${patch}" + } + + if [ "$master" = "true" ]; then + patch_pooler pg-cluster-pooler + fi + if [ "$repl" = "true" ]; then + patch_pooler pg-cluster-pooler-repl + fi + + - description: Wait for pooler Deployments to become Available + maxTotalSeconds: 180 + cmd: | + set -e + for dep in pg-cluster-pooler pg-cluster-pooler-repl; do + if uds zarf tools kubectl get deploy "${dep}" -n postgres >/dev/null 2>&1; then + uds zarf tools kubectl rollout status deploy "${dep}" -n postgres --timeout=180s + fi + done diff --git a/zarf.yaml b/zarf.yaml index cc7297e..081dfc0 100644 --- a/zarf.yaml +++ b/zarf.yaml @@ -1,6 +1,7 @@ # Copyright 2024 Defense Unicorns # SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial +# yaml-language-server: $schema=https://raw.githubusercontent.com/defenseunicorns/zarf/main/zarf.schema.json kind: ZarfPackageConfig metadata: name: postgres-operator @@ -96,10 +97,14 @@ components: - name: uds-postgres-config valuesFiles: - ./values/unicorn-config-values.yaml + actions: + onDeploy: + after: + - description: Shim CGR pgbouncer entrypoint mismatch on pooler Deployments + cmd: uds run -f tasks/unicorn-pooler-patch.yaml patch-pooler-deployments --no-progress images: - cgr.dev/defenseunicorns.com/postgres-operator:1.15.1 - # - quay.io/rfcurated/zalando/postgres-operator/logical-backup:1.15-jammy-scratch-fips-rfcurated - - registry1.dso.mil/ironbank/opensource/zalando/logical-backup:v1.15.1 + - quay.io/rfcurated/zalando/postgres-operator/logical-backup:1.15-jammy-scratch-fips-rfcurated - cgr.dev/defenseunicorns.com/pgbouncer:1.25.1 # Docker image that provides PostgreSQL and Patroni bundled together for PostgreSQL HA - cgr.dev/defenseunicorns.com/spilo-17:4.1.2 From 53eabf57adfb6aa817374c3ce18ac0a4e459ad78 Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Thu, 14 May 2026 14:19:20 -0400 Subject: [PATCH 18/23] chore: removed --set=unicorn from tasks.yaml (used to test unicorn flavor) Signed-off-by: Mitch Murphy --- tasks.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.yaml b/tasks.yaml index 300a447..070bb90 100644 --- a/tasks.yaml +++ b/tasks.yaml @@ -27,7 +27,7 @@ tasks: actions: - task: create:package with: - options: "--skip-sbom --flavor=unicorn" + options: "--skip-sbom" - name: create-deploy-test-bundle description: Test and validate cluster is deployed with Postgres Operator From ff3c27f392fb8f6bdbce0cf67c8591de1c64b241 Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Thu, 14 May 2026 15:14:49 -0400 Subject: [PATCH 19/23] docs: create documentation for unicorn patch Signed-off-by: Mitch Murphy --- docs/pgbouncer-entrypoint.md | 104 ++++++++++++++++++++++++++++++++ tasks/unicorn-pooler-patch.yaml | 1 - 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 docs/pgbouncer-entrypoint.md diff --git a/docs/pgbouncer-entrypoint.md b/docs/pgbouncer-entrypoint.md new file mode 100644 index 0000000..b6d77fb --- /dev/null +++ b/docs/pgbouncer-entrypoint.md @@ -0,0 +1,104 @@ +# pgbouncer entrypoint script + +The `entrypoint.sh` script that upstream Zalando pgbouncer image uses is as follows: + +```bash +#!/bin/sh + +set -ex + +if [ "$PGUSER" = "postgres" ]; then + echo "WARNING: pgbouncer will connect with a superuser privileges!" + echo "You need to fix this as soon as possible." +fi + +if [ -z "${CONNECTION_POOLER_CLIENT_TLS_CRT}" ]; then + openssl req -nodes -new -x509 -subj /CN=spilo.dummy.org \ + -keyout /etc/ssl/certs/pgbouncer.key \ + -out /etc/ssl/certs/pgbouncer.crt +else + ln -s ${CONNECTION_POOLER_CLIENT_TLS_CRT} /etc/ssl/certs/pgbouncer.crt + ln -s ${CONNECTION_POOLER_CLIENT_TLS_KEY} /etc/ssl/certs/pgbouncer.key + if [ ! -z "${CONNECTION_POOLER_CLIENT_CA_FILE}" ]; then + ln -s ${CONNECTION_POOLER_CLIENT_CA_FILE} /etc/ssl/certs/ca.crt + fi +fi + +envsubst < /etc/pgbouncer/pgbouncer.ini.tmpl > /etc/pgbouncer/pgbouncer.ini +envsubst < /etc/pgbouncer/auth_file.txt.tmpl > /etc/pgbouncer/auth_file.txt + +exec /bin/pgbouncer /etc/pgbouncer/pgbouncer.ini +``` + +The Chainguard image for pgbouncer only contains the binary, so we need to explictly define the `pgbouncer.ini` and `auth_file.txt`. First, let's define what the template file (`pgbouncer.ini.tmpl`) looks like: + +```bash +# vim: set ft=dosini: + +[databases] +* = host=$PGHOST port=$PGPORT auth_user=$PGUSER +postgres = host=$PGHOST port=$PGPORT auth_user=$PGUSER + +[pgbouncer] +pool_mode = $CONNECTION_POOLER_MODE +listen_port = $CONNECTION_POOLER_PORT +listen_addr = * +auth_type = md5 +auth_file = /etc/pgbouncer/auth_file.txt +auth_dbname = postgres +admin_users = $PGUSER +stats_users_prefix = robot_ +auth_query = SELECT * FROM $PGSCHEMA.user_lookup($1) +logfile = /var/log/pgbouncer/pgbouncer.log +pidfile = /var/run/pgbouncer/pgbouncer.pid + +server_tls_sslmode = require +server_tls_ca_file = /etc/ssl/certs/pgbouncer.crt +server_tls_protocols = secure +client_tls_sslmode = require +client_tls_key_file = /etc/ssl/certs/pgbouncer.key +client_tls_cert_file = /etc/ssl/certs/pgbouncer.crt + +log_connections = 0 +log_disconnections = 0 + +# How many server connections to allow per user/database pair. +default_pool_size = $CONNECTION_POOLER_DEFAULT_SIZE + +# Add more server connections to pool if below this number. Improves behavior +# when usual load comes suddenly back after period of total inactivity. +# +# NOTE: This value is per pool, i.e. a pair of (db, user), not a global one. +# Which means on the higher level it has to be calculated from the max allowed +# database connections and number of databases and users. If not taken into +# account, then for too many users or databases PgBouncer will go crazy +# opening/evicting connections. For now disable it. +# +# min_pool_size = $CONNECTION_POOLER_MIN_SIZE + +# How many additional connections to allow to a pool +reserve_pool_size = $CONNECTION_POOLER_RESERVE_SIZE + +# Maximum number of client connections allowed. +max_client_conn = $CONNECTION_POOLER_MAX_CLIENT_CONN + +# Do not allow more than this many connections per database (regardless of +# pool, i.e. user) +max_db_connections = $CONNECTION_POOLER_MAX_DB_CONN + +# If a client has been in "idle in transaction" state longer, it will be +# disconnected. [seconds] +idle_transaction_timeout = 600 + +# If login failed, because of failure from connect() or authentication that +# pooler waits this much before retrying to connect. Default is 15. [seconds] +server_login_retry = 5 + +# To ignore extra parameter in startup packet. By default only 'database' and +# 'user' are allowed, all others raise error. This is needed to tolerate +# overenthusiastic JDBC wanting to unconditionally set 'extra_float_digits=2' +# in startup packet. +ignore_startup_parameters = extra_float_digits,options +``` + +According to this we have created a task to explictly create this file in [unicorn-pooler-patch.yaml](../tasks/unicorn-pooler-patch.yaml). diff --git a/tasks/unicorn-pooler-patch.yaml b/tasks/unicorn-pooler-patch.yaml index 350565f..4570ae4 100644 --- a/tasks/unicorn-pooler-patch.yaml +++ b/tasks/unicorn-pooler-patch.yaml @@ -5,7 +5,6 @@ # Unicorn-flavor-only shim for the Zalando postgres-operator connection pooler. # This patch reproduces the upstream Zalando entrypoint behavior out-of-band: - # auth_type is set to scram-sha-256 (Postgres 17 default); upstream's template # hardcodes md5 which fails against modern Postgres. From 4edb7025db5d5eeb8c7280446af127e2bc84736d Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Thu, 14 May 2026 15:19:41 -0400 Subject: [PATCH 20/23] feat(zarf): enable connection pooling by updating cluster architecture and refining deployment actions --- zarf.yaml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/zarf.yaml b/zarf.yaml index 081dfc0..985e43c 100644 --- a/zarf.yaml +++ b/zarf.yaml @@ -30,8 +30,8 @@ components: required: true only: flavor: registry1 - # cluster: - # architecture: amd64 + cluster: + architecture: amd64 import: path: common charts: @@ -44,12 +44,6 @@ components: actions: onDeploy: after: - # The Iron Bank pgbouncer image bakes /etc/pgbouncer, /var/log/pgbouncer, - # and /var/run/pgbouncer owned by pgbouncer:pgbouncer (UID 997:997), but - # the Zalando operator hardcodes the pooler pod's runAsUser=100 — so the - # pgbouncer entrypoint cannot write its TLS cert, log, or pidfile. Shim - # the operator-managed pooler Deployments with emptyDir mounts + a seed - # init container. See tasks/registry1-pooler-patch.yaml for rationale. - description: Shim Iron Bank pgbouncer directory permissions on pooler Deployments cmd: uds run -f tasks/registry1-pooler-patch.yaml patch-pooler-deployments --no-progress images: From 203bf5ac593e00ec9a12ac94d04ea1f985092914 Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Thu, 14 May 2026 15:39:12 -0400 Subject: [PATCH 21/23] fix(pooler): removed hasKey test for connectionPooler and use default from values --- chart/templates/postgres-minimal.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/chart/templates/postgres-minimal.yaml b/chart/templates/postgres-minimal.yaml index cf50e2e..3052981 100644 --- a/chart/templates/postgres-minimal.yaml +++ b/chart/templates/postgres-minimal.yaml @@ -18,12 +18,8 @@ spec: enableConnectionPooler: {{ .Values.postgresql.enableConnectionPooler | default false }} enableReplicaConnectionPooler: {{ .Values.postgresql.enableReplicaConnectionPooler | default false }} - {{- if and (hasKey .Values.postgresql "connectionPooler") (not (empty .Values.postgresql.connectionPooler)) }} - {{- with .Values.postgresql.connectionPooler }} connectionPooler: - {{- toYaml . | nindent 4 }} - {{- end }} - {{- end }} + {{- toYaml .Values.postgresql.connectionPooler | nindent 4 }} users: {{- toYaml .Values.postgresql.users | nindent 4 }} # database owner From 880dec35a1c075322864159c97e9b8a3efb1b10a Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Wed, 22 Apr 2026 17:26:33 -0400 Subject: [PATCH 22/23] feat: enable connection pooling (use correct image, pass through variable for CR) --- bundle/uds-config.yaml | 2 -- chart/templates/postgres-minimal.yaml | 10 ++++++++++ values/upstream-values.yaml | 2 +- zarf.yaml | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/bundle/uds-config.yaml b/bundle/uds-config.yaml index 2067cc5..e69de29 100644 --- a/bundle/uds-config.yaml +++ b/bundle/uds-config.yaml @@ -1,2 +0,0 @@ -# Copyright 2024 Defense Unicorns -# SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial diff --git a/chart/templates/postgres-minimal.yaml b/chart/templates/postgres-minimal.yaml index e3f3cad..efa474b 100644 --- a/chart/templates/postgres-minimal.yaml +++ b/chart/templates/postgres-minimal.yaml @@ -14,6 +14,16 @@ spec: volume: size: {{ .Values.postgresql.volume.size | quote }} numberOfInstances: {{ .Values.postgresql.numberOfInstances }} + {{- if hasKey .Values.postgresql "enableConnectionPooler" }} + enableConnectionPooler: {{ .Values.postgresql.enableConnectionPooler }} + {{- end }} + {{- if hasKey .Values.postgresql "enableReplicaConnectionPooler" }} + enableReplicaConnectionPooler: {{ .Values.postgresql.enableReplicaConnectionPooler }} + {{- end }} + {{- with .Values.postgresql.connectionPooler }} + connectionPooler: + {{- toYaml . | nindent 4 }} + {{- end }} users: {{- toYaml .Values.postgresql.users | nindent 4 }} # database owner databases: diff --git a/values/upstream-values.yaml b/values/upstream-values.yaml index df44cfe..53f206a 100644 --- a/values/upstream-values.yaml +++ b/values/upstream-values.yaml @@ -6,7 +6,7 @@ image: repository: zalando/postgres-operator tag: v1.15.1 configConnectionPooler: - connection_pooler_image: "ghcr.io/cloudnative-pg/pgbouncer:1.24.1-23" + connection_pooler_image: "registry.opensource.zalan.do/acid/pgbouncer:master-32" configLogicalBackup: logical_backup_docker_image: "ghcr.io/zalando/postgres-operator/logical-backup:v1.15.1" configGeneral: diff --git a/zarf.yaml b/zarf.yaml index b874dc9..89e00be 100644 --- a/zarf.yaml +++ b/zarf.yaml @@ -66,7 +66,7 @@ components: images: - ghcr.io/zalando/postgres-operator:v1.15.1 - ghcr.io/zalando/postgres-operator/logical-backup:v1.15.1 - - ghcr.io/cloudnative-pg/pgbouncer:1.24.1-23 + - registry.opensource.zalan.do/acid/pgbouncer:master-32 # Docker image that provides PostgreSQL and Patroni bundled together for PostgreSQL HA - ghcr.io/zalando/spilo-17:4.0-p3 # Container image that provides the postgres-exporter sidecar to create a metrics endpoint From 3f1b297f21ba02f9a3f479e4ec9ad1f473ec3764 Mon Sep 17 00:00:00 2001 From: Mitch Murphy Date: Thu, 14 May 2026 16:03:41 -0400 Subject: [PATCH 23/23] fix(lint): add empty line to end of uds-config.yaml Signed-off-by: Mitch Murphy --- bundle/uds-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundle/uds-config.yaml b/bundle/uds-config.yaml index e8e24de..2067cc5 100644 --- a/bundle/uds-config.yaml +++ b/bundle/uds-config.yaml @@ -1,2 +1,2 @@ # Copyright 2024 Defense Unicorns -# SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial \ No newline at end of file +# SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial