Skip to content

K8SPG-1115: allow pausing pgbouncer connections on demand - #1719

Open
mayankshah1607 wants to merge 30 commits into
mainfrom
K8SPG-1115
Open

K8SPG-1115: allow pausing pgbouncer connections on demand#1719
mayankshah1607 wants to merge 30 commits into
mainfrom
K8SPG-1115

Conversation

@mayankshah1607

@mayankshah1607 mayankshah1607 commented Aug 3, 2026

Copy link
Copy Markdown
Member

CHANGE DESCRIPTION

Adds a new spec.proxy.pgbouncer.paused field that allows pausing pgbouncer connections.

The following additions were made to support this feature:

  • added a new internal user _crunchypgbounceradmin. The operator uses this to connect to the pgbouncer admin. ConfigMap and Secret were updated accordingly
  • an AdminClient interface that connects to pgbouncer pods via sql.DB to issue PAUSE and RESUME (no execs)
  • added a new startup probe to pgbouncer that reconciles pause on start. Issuing PAUSE is not persisted across restarts. In order to have this new startup probe, an init container is also set on pgbouncer to install the needed binaries from the operator

CHECKLIST

Jira

  • Is the Jira ticket created and referenced properly?
  • Does the Jira ticket have the proper statuses for documentation (Needs Doc) and QA (Needs QA)?
  • Does the Jira ticket link to the proper milestone (Fix Version field)?

Tests

  • Is an E2E test/test case added for the new feature/change?
  • Are unit tests added where appropriate?

Config/Logging/Testability

  • Are all needed new/changed options added to default YAML files?
  • Are all needed new/changed options added to the Helm Chart?
  • Did we add proper logging messages for operator actions?
  • Did we ensure compatibility with the previous version or cluster upgrade process?
  • Does the change support oldest and newest supported PG version?
  • Does the change support oldest and newest supported Kubernetes version?

Signed-off-by: Mayank Shah <mayank.shah@percona.com>
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
Signed-off-by: Mayank Shah <mayank.shah@percona.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an on-demand PgBouncer connection pause feature to the PostgreSQL operator by introducing a new paused flag in the proxy/PgBouncer spec, persisting pause intent across restarts via a marker file, and wiring operator logic to execute PAUSE/RESUME against PgBouncer instances.

Changes:

  • Introduces spec.proxy.pgbouncer.paused (upstream + Percona API) and propagates it through conversions/deepcopies and CRD schemas.
  • Adds pause/resume reconciliation in the PostgresCluster controller, including a PGBouncerPaused status condition.
  • Adds a new pgbouncer-startup binary + init-container plumbing and config projections to re-apply pause state on PgBouncer restarts, with unit tests.

Reviewed changes

Copilot reviewed 23 out of 28 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
pkg/apis/upstream.pgv2.percona.com/v1beta1/zz_generated.deepcopy.go Deepcopy support for new Paused field in upstream PgBouncer spec.
pkg/apis/upstream.pgv2.percona.com/v1beta1/postgrescluster_types.go Adds PGBouncerPaused condition type and PGBouncerPaused() helper.
pkg/apis/upstream.pgv2.percona.com/v1beta1/pgbouncer_types.go Adds Paused *bool to upstream PGBouncerPodSpec.
pkg/apis/pgv2.percona.com/v2/zz_generated.deepcopy.go Deepcopy support for new Paused field in Percona API PgBouncer spec.
pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go Adds Paused to Percona PGBouncerSpec and maps it to Crunchy spec.
percona/controller/pgcluster/status.go Ensures Percona status conditions reflect upstream PGBouncerPaused presence/removal.
internal/pgbouncer/startup/startup.go Defines the shared “contract” constants for pause marker paths and admin env var.
internal/pgbouncer/reconcile.go Adds pause marker ConfigMap key, admin password handling, and init/startup-probe wiring for PgBouncer pods.
internal/pgbouncer/reconcile_test.go Adds unit tests for pause marker ConfigMap behavior, admin password generation, and pod probe/volumes/init container behavior.
internal/pgbouncer/config.go Adds pause marker projection, admin user handling, and supports writing admin credentials to the auth file.
internal/pgbouncer/config_test.go Adds unit tests for auth file admin user, ensureListed(), admin_users INI behavior, and pause marker projection.
internal/controller/runtime/pgbouncer/mock/admin_client.go Generated mock for PgBouncer admin client interface (tests).
internal/controller/runtime/pgbouncer/gen.go go:generate for mockery to generate the AdminClient mock.
internal/controller/runtime/pgbouncer/admin.go Implements PgBouncer admin client used for issuing PAUSE/RESUME.
internal/controller/postgrescluster/pgbouncer.go Adds pause/resume reconciliation logic and status condition management for PgBouncer.
internal/controller/postgrescluster/pgbouncer_test.go Adds controller-level tests for pause/resume reconciliation via mocked admin client + fake client pods.
internal/controller/postgrescluster/controller.go Injects newPGBouncerAdmin factory into reconciler for runtime/test wiring.
go.sum Updates module sums for new dependencies (pq driver, mockery tooling, transitive deps).
go.mod Adds github.com/lib/pq and mockery as a tool dependency plus new indirects.
deploy/cw-bundle.yaml CRD bundle update to include paused schema field.
deploy/crd.yaml CRD manifest update to include paused schema field.
deploy/bundle.yaml CRD bundle update to include paused schema field.
config/crd/bases/upstream.pgv2.percona.com_postgresclusters.yaml Upstream CRD base schema updated with paused.
config/crd/bases/pgv2.percona.com_perconapgclusters.yaml Percona CRD base schema updated with paused.
cmd/pgbouncer-startup/main.go New startup-probe binary that re-applies pause on container start when marker exists.
cmd/pgbouncer-startup/main_test.go Unit tests for pause marker detection and retry/pause logic.
build/postgres-operator/init-entrypoint.sh Installs the pgbouncer-startup binary into the shared crunchy bin volume.
build/postgres-operator/Dockerfile Builds and ships the new pgbouncer-startup binary (CGO disabled).
build/crd/percona/generated/pgv2.percona.com_perconapgclusters.yaml Generated Percona CRD updated with paused schema field.
Files not reviewed (3)
  • internal/controller/runtime/pgbouncer/mock/admin_client.go: Generated file
  • pkg/apis/pgv2.percona.com/v2/zz_generated.deepcopy.go: Generated file
  • pkg/apis/upstream.pgv2.percona.com/v1beta1/zz_generated.deepcopy.go: Generated file

Comment thread internal/controller/postgrescluster/pgbouncer.go
Comment thread internal/controller/postgrescluster/pgbouncer.go
Signed-off-by: Mayank Shah <mayank.shah@percona.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 28 changed files in this pull request and generated 1 comment.

Files not reviewed (3)
  • internal/controller/runtime/pgbouncer/mock/admin_client.go: Generated file
  • pkg/apis/pgv2.percona.com/v2/zz_generated.deepcopy.go: Generated file
  • pkg/apis/upstream.pgv2.percona.com/v1beta1/zz_generated.deepcopy.go: Generated file
Suppressed comments (3)

internal/controller/runtime/pgbouncer/admin.go:37

    1. Problem: The DSN format string does not include a password parameter, so the password argument is ignored and PgBouncer admin connections will fail authentication.
  1. Why it matters: Pause/resume (and the startup probe) depend on successfully connecting as the admin user.
  2. Fix: Include the password in the DSN parameters passed to sql.Open.
	dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=pgbouncer sslmode=require",
		host, user, password)

internal/controller/runtime/pgbouncer/admin.go:38

    1. Problem: NewAdminClient has no way to specify the PgBouncer port, so it will always connect using lib/pq’s default (5432).
  1. Why it matters: PgBouncer’s listen port is configurable in the cluster spec (the Pod uses *inCluster.Spec.Proxy.PGBouncer.Port), so pause/resume will fail when the port is not 5432.
  2. Fix: Thread the configured port into the admin client (e.g., add a port int32 parameter and include port=%d in the DSN) and update callers (controller + pgbouncer-startup) to pass it.
func NewAdminClient(user, password, host string) (AdminClient, error) {
	if user == "" {
		return nil, errors.New("user is required")
	}
	if password == "" {
		return nil, errors.New("password is required")
	}
	if host == "" {
		return nil, errors.New("host is required")
	}

	dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=pgbouncer sslmode=require",
		host, user, password)
	db, err := sql.Open("postgres", dsn)

internal/controller/postgrescluster/pgbouncer.go:789

    1. Problem: handlePGBouncerPause dereferences cluster.Spec.Proxy.PGBouncer.Replicas without a nil check, which can panic when replicas is unset.
  1. Why it matters: A panic in reconciliation can crash the operator and block further progress.
  2. Fix: Guard against nil replicas (consistent with other PgBouncer reconcile code in this file) before dereferencing.
	if len(podList.Items) < int(*cluster.Spec.Proxy.PGBouncer.Replicas) {
		return errors.Errorf("pgbouncer pods are not ready, expected %d, got %d", *cluster.Spec.Proxy.PGBouncer.Replicas, len(podList.Items))
	}

Comment thread internal/pgbouncer/config.go
mayankshah1607 and others added 3 commits August 4, 2026 10:09
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
hors and others added 2 commits August 4, 2026 23:15
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
Comment thread internal/pgbouncer/reconcile.go Outdated
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
egegunes
egegunes previously approved these changes Aug 5, 2026
@mayankshah1607 mayankshah1607 added this to the v3.1.0 milestone Aug 6, 2026
}

func TestReconcilePause(t *testing.T) {
ctx := context.Background()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ctx := context.Background()
ctx := t.Context()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread cmd/pgbouncer-startup/main.go Outdated
Comment on lines +61 to +62
ctx, cancel := context.WithTimeout(context.Background(), pauseTimeout)
defer cancel()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use context from signal.NotifyContext as we do in cmd/pgbackrest/main.go instead of context.Background()? This would allow exit early on SIGTERM, preventing pod from being stuck for 30 seconds trying to terminate

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return nil, errors.New("host is required")
}

dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=pgbouncer sslmode=require",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add a port=%s and use .Spec.Proxy.PGBouncer.Port as value

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +810 to +813
password, ok := secret.Data[pgbouncer.AdminPasswordSecretKey]
if !ok {
return errors.New("pgbouncer admin password not found in secret")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If proxy.PGBouncerEnabled() is false, then secret will be nil. We will get a panic here

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mayankshah1607 and others added 2 commits August 6, 2026 16:26
Signed-off-by: Mayank Shah <mayank.shah@percona.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 48 out of 53 changed files in this pull request and generated 2 comments.

Suppressed comments (1)

cmd/pgbouncer-startup/main.go:26

  • Problem: The startup log file is created with mode 0666 (world-writable).
    Why it matters: Overly-permissive file modes can allow unintended modification of logs inside the container.
    Fix: Use a more restrictive mode like 0644 (or 0600 if only the current user should read it).
	f, err := os.OpenFile(startup.LogAbsolutePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666)

Comment thread internal/controller/runtime/pgbouncer/admin.go
Comment thread internal/controller/postgrescluster/pgbouncer.go
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
egegunes
egegunes previously approved these changes Aug 7, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 48 out of 53 changed files in this pull request and generated no new comments.

Suppressed comments (2)

internal/controller/postgrescluster/pgbouncer.go:842

    1. Problem: handlePGBouncerPause executes PAUSE/RESUME using the controller reconcile ctx without any additional timeout, so a hung TCP connect/query can block reconciliation indefinitely.
  1. Why it matters: A stuck reconcile can stall the controller and prevent other cluster updates from being processed.
  2. Fix: Wrap each PAUSE/RESUME call in a bounded context.WithTimeout (e.g., 30s) and pass that derived context to the admin client call.
    internal/controller/runtime/pgbouncer/admin.go:46
    1. Problem: The PgBouncer admin DSN is hard-coded to sslmode=require and does not provide any way to supply a client certificate/key.
  1. Why it matters: If PgBouncer is configured to require verified client certificates (e.g., client_tls_sslmode=verify-ca), the operator/startup-probe admin connection will fail and pause/resume cannot work.
  2. Fix: Extend AdminClientOptions to accept optional TLS parameters (sslrootcert/sslcert/sslkey) and include them in the DSN (or explicitly detect/guard against mTLS-required configs and surface a clear error).

pooknull
pooknull previously approved these changes Aug 7, 2026
mayankshah1607 and others added 2 commits August 7, 2026 15:22
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
@JNKPercona

Copy link
Copy Markdown
Collaborator
Test Name Result Time
backup-enable-disable passed 00:00:00
builtin-extensions passed 00:00:00
custom-extensions passed 00:00:00
cert-manager-tls passed 00:00:00
cert-management-policy passed 00:03:31
custom-envs passed 00:00:00
custom-tls passed 00:07:43
database-init-sql passed 00:00:00
demand-backup passed 00:25:17
demand-backup-offline-snapshot passed 00:00:00
dynamic-configuration passed 00:00:00
extra-volumes passed 00:00:00
finalizers passed 00:00:00
init-deploy passed 00:00:00
huge-pages passed 00:00:00
major-upgrade-14-to-15 passed 00:00:00
major-upgrade-15-to-16 passed 00:00:00
major-upgrade-16-to-17 passed 00:11:26
major-upgrade-17-to-18 passed 00:00:00
ldap passed 00:00:00
ldap-tls passed 00:00:00
logcollection passed 00:00:00
monitoring passed 00:00:00
one-pod passed 00:00:00
repo-host-autogrow passed 00:00:00
operator-self-healing passed 00:00:00
pgbouncer passed 00:00:00
pg-tde passed 00:00:00
pg-tde-wal-encrypt passed 00:00:00
pitr passed 00:00:00
scaling passed 00:00:00
scheduled-backup passed 00:00:00
self-healing passed 00:00:00
sidecars passed 00:00:00
standby-pgbackrest passed 00:00:00
standby-streaming passed 00:00:00
start-from-backup passed 00:00:00
tablespaces passed 00:00:00
telemetry-transfer passed 00:00:00
upgrade-consistency passed 00:00:00
upgrade-minor passed 00:00:00
users passed 00:00:00
migration-from-crunchy-standby passed 00:00:00
migration-from-crunchy-pv passed 00:00:00
migration-from-crunchy-backup-restore passed 00:00:00
Summary Value
Tests Run 45/45
Job Duration 01:20:49
Total Test Time 00:47:59

commit: 9b93373
image: perconalab/percona-postgresql-operator:PR-1719-9b93373df

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants