K8SPG-1057: Allow using etcd as patroni DCS#1647
Conversation
|
I will wait for the jira ticket to open the PR in the helm charts repo |
|
and another note - this is my first OSS contribute so be gentel 😄 |
egegunes
left a comment
There was a problem hiding this comment.
@yoav-katz the implementation looks good to me in general. but we definitely need an e2e test that deploys etcd and configures PerconaPGCluster to use it.
|
would love to see this merged! |
…erator into etcd-dcs
QUESTIONS FOR REVIEWERS:
|
|
Operator-managed etcd (future consideration) The current design requires users to supply an external etcd cluster via spec:
patroni:
dcs:
type: etcd
etcd:
managed: # operator deploys etcd itself
replicas: 3 # 1 for dev, 3 for production HA
storage: 1Gi
storageClass: standard
# endpoints: omitted when managed: is setThe operator would create and reconcile an etcd StatefulSet (with PVCs) co-located with the PostgreSQL cluster. This raises a few design questions: |
Let's start with (a).
I don't think it's crucial but would be a good addition.
For the start, I think it's better to not allow live migration. We can revisit this after receiving feedback.
Why the longer-term goal is to replace routing by labels with HAProxy? Also, operator already creates a headless service covering all postgres pods.
I don't think we should ever have etcd managed by the operator. It should be the user who configure and manage etcd infrastructure. |
The label-based routing still tightly couples failover to the Kubernetes API - Patroni's callback needs to patch pod labels on every role change. That's the same dependency we're trying to reduce by moving to external etcd. HAProxy querying Patroni's REST health endpoints directly removes that dependency entirely - failover is self-contained within Patroni and etcd, no K8s API writes in the hot path. |
| ) error { | ||
| // With etcd DCS, Patroni stores distributed configuration in etcd, not k8s Endpoints. | ||
| if dcs := cluster.Spec.Patroni.GetDCS(); dcs != nil && dcs.Type == v1beta1.PatroniDCSTypeEtcd { | ||
| if dcs := cluster.GetDCS(); dcs != nil && dcs.Type == v1beta1.PatroniDCSTypeEtcd { |
There was a problem hiding this comment.
since we are repeating this code in many places, would it make sense to have something like cluster.IsDCSEtcd() and move this conditions into there?
There was a problem hiding this comment.
Instead of IsDCSEtcd() I went with a more general DCSType() method that normalizes a nil DCS to the default (kubernetes), so adding a new DCS type in the future doesn't require a function per type.
Do you think we should also add IsDCSEtcd() as a convenience on top of it?
There was a problem hiding this comment.
i think the current version is good, i mostly wanted to cleanup all those nil checks
There was a problem hiding this comment.
Pull request overview
This PR adds support for using external etcd as Patroni’s DCS backend (in addition to the existing Kubernetes Endpoints DCS), enabling deployments where workloads cannot reach the Kubernetes control plane API. It introduces a new spec.patroni.dcs API, updates Patroni config generation, RBAC/service behavior, and adds validations plus unit/E2E coverage for the etcd path.
Changes:
- Add
spec.patroni.dcs(defaultkubernetes, optionaletcd) with CEL immutability validation and generated deepcopy/CRD updates. - When
etcdDCS is selected: generateetcd3:Patroni config, add role-change callbacks, adjust primary Service selection logic, and validate referenced TLS/auth Secrets (with Secret watch/indexing). - Add unit tests and a new KUTTL E2E scenario (
etcd-dcs) to validate config, behavior, and immutability.
Reviewed changes
Copilot reviewed 41 out of 42 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 | Generated deepcopy updates for new Patroni DCS types. |
| pkg/apis/upstream.pgv2.percona.com/v1beta1/patroni_types.go | Adds dcs API types, helpers, and CEL validations. |
| pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go | Adds etcd DCS validation and Secret indexer for reconciliation triggers. |
| pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go | Unit tests for Percona CR validation of etcd DCS endpoints. |
| percona/controller/pgcluster/patroni_etcd.go | Reconcile-time Secret presence/key validation + Warning events for etcd DCS Secrets. |
| percona/controller/pgcluster/patroni_etcd_test.go | Unit tests for etcd DCS Secret validation reconciliation behavior. |
| percona/controller/pgcluster/controller.go | Watches Secrets via multiple field indexes (envFrom + patroni etcd secrets) with dedupe. |
| internal/patroni/reconcile.go | Mount etcd TLS Secret and inject Patroni etcd auth env vars into instance Pods. |
| internal/patroni/rbac.go | Conditionalize Endpoints/Service create RBAC rules based on DCS type. |
| internal/patroni/config.go | Emit etcd3: config + callbacks for etcd DCS; keep Kubernetes DCS behavior unchanged. |
| internal/patroni/config_test.go | Adds unit coverage for etcd DCS Patroni YAML generation and env behavior. |
| internal/controller/postgrescluster/patroni.go | Skip k8s-DCS-specific artifacts/status reads when using etcd DCS. |
| internal/controller/postgrescluster/cluster.go | Use selector-based primary Service for etcd DCS; avoid applying Endpoints when not used. |
| e2e-tests/tests/etcd-dcs/00-deploy-operator.yaml | E2E setup step: deploy operator and client. |
| e2e-tests/tests/etcd-dcs/00-assert.yaml | E2E assertions for operator/CRD readiness. |
| e2e-tests/tests/etcd-dcs/01-etcd-setup.yaml | E2E step: deploy a single-node etcd StatefulSet for testing. |
| e2e-tests/tests/etcd-dcs/01-assert.yaml | E2E assertion: etcd is ready. |
| e2e-tests/tests/etcd-dcs/02-create-cluster.yaml | E2E step: create cluster configured for etcd DCS. |
| e2e-tests/tests/etcd-dcs/02-assert.yaml | E2E assertions: cluster reaches ready state with etcd DCS. |
| e2e-tests/tests/etcd-dcs/03-write-data.yaml | E2E: write data to primary via client. |
| e2e-tests/tests/etcd-dcs/04-read-from-primary.yaml | E2E: read data back from primary. |
| e2e-tests/tests/etcd-dcs/04-assert.yaml | E2E assertion: expected read result. |
| e2e-tests/tests/etcd-dcs/05-assert.yaml | E2E assertions for created resources. |
| e2e-tests/tests/etcd-dcs/06-check-patroni-config.yaml | E2E: verify Patroni config contains etcd3 + callbacks and omits kubernetes:. |
| e2e-tests/tests/etcd-dcs/07-check-patronictl.yaml | E2E: verify patronictl list shows running/leader. |
| e2e-tests/tests/etcd-dcs/08-check-etcd-keys.yaml | E2E: verify Patroni keys appear in etcd. |
| e2e-tests/tests/etcd-dcs/09-check-no-warning-events.yaml | E2E: ensure no unexpected Warning events for etcd secrets. |
| e2e-tests/tests/etcd-dcs/10-check-dcs-immutability.yaml | E2E: ensure DCS type immutability is enforced by admission/CEL. |
| e2e-tests/tests/etcd-dcs/11-check-pod-labels.yaml | E2E: verify role labels are present (callback executed). |
| e2e-tests/tests/etcd-dcs/99-remove-cluster-gracefully.yaml | E2E teardown: remove resources and validate operator stability. |
| e2e-tests/run-release.csv | Adds etcd-dcs to release E2E run list. |
| e2e-tests/run-pr.csv | Adds etcd-dcs to PR E2E run list. |
| deploy/cw-bundle.yaml | Bundle CRD updates to include new DCS schema/validations. |
| deploy/crd.yaml | Generated CRD updates for new DCS schema/validations. |
| deploy/bundle.yaml | Bundle CRD updates for new DCS schema/validations. |
| config/crd/bases/upstream.pgv2.percona.com_postgresclusters.yaml | Base CRD schema updates for DCS fields/validations. |
| config/crd/bases/pgv2.percona.com_perconapgclusters.yaml | Base CRD schema updates for DCS fields/validations. |
| cmd/postgres-operator/main.go | Adds field index registration for etcd DCS referenced Secrets. |
| build/postgres-operator/patroni-role-change.sh | New Patroni callback script to patch pod role label + status annotation. |
| build/postgres-operator/init-entrypoint.sh | Installs the new Patroni role-change script into runtime bindir. |
| build/postgres-operator/Dockerfile | Ships the new Patroni role-change script in the image. |
| build/crd/percona/generated/pgv2.percona.com_perconapgclusters.yaml | Generated Percona CRD output updated for new DCS schema/validations. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
commit: 6cb833e |
CHANGE DESCRIPTION
Problem:
Patroni supports multiple DCS backends, but the operator hardcodes Kubernetes Endpoints as the only option. This blocks clusters on managed Kubernetes platforms where workloads cannot reach the control plane API.
Cause:
The kubernetes: stanza was hardcoded in the generated Patroni config with no mechanism to select a different backend.
Several other pieces of the operator also assumed k8s DCS: RBAC rules unconditionally granted Endpoints permissions, the primary service routed through Patroni-managed Endpoints objects, and pod role labels/annotations were expected to be set by Patroni itself (which only happens with k8s DCS).
Solution:
Add a spec.patroni.dcs field (type: kubernetes default, type: etcd alternative). The field is immutable after cluster creation, enforced by a CEL validation rule on the CRD.
When type: etcd, the operator:
The Kubernetes DCS path is unchanged.
CHECKLIST
Jira
Needs Doc) and QA (Needs QA)?Tests
Config/Logging/Testability