Skip to content

K8SPG-1149: expose.externalDNS - #1812

Open
yoav-katz wants to merge 3 commits into
percona:mainfrom
yoav-katz:externalHostname
Open

K8SPG-1149: expose.externalDNS#1812
yoav-katz wants to merge 3 commits into
percona:mainfrom
yoav-katz:externalHostname

Conversation

@yoav-katz

Copy link
Copy Markdown
Contributor

Problem:
Exposing a cluster through external-dns required hand-writing the
external-dns.alpha.kubernetes.io/* annotations into expose.annotations, and even then
the published hostname was never added to the server certificate SANs - so clients
connecting through the external name with sslmode=verify-full failed on a hostname
mismatch.

Cause:
The operator had no notion of an external hostname. expose.annotations is an opaque
passthrough, so the operator could not tell an external-dns hostname it should trust from
any other annotation, and the certificate SANs were built purely from in-cluster service
DNS names.

Solution:
Add expose.externalDNS (hostname, optional ttl) to every ServiceExpose - primary,
replica, pgBouncer and logical replicas. The operator renders it into the external-dns
annotations and stamps percona.com/external-dns-managed: "true" to record ownership.
That marker is what makes the rest safe:

  • Only marked hostnames are appended to the TLS SANs (primary/replica certificate and the
    pgBouncer certificate, both the internal PKI and cert-manager paths), so
    sslmode=verify-full works against the external name.
  • Only marked annotations are pruned when externalDNS is removed from the CR, so
    hostnames a user wrote by hand into expose.annotations are never touched and existing
    clusters do not get their certificates silently reissued.
  • Everything but the logical-replica service is written with server-side apply, which
    prunes on its own; the logical-replica service uses CreateOrUpdate, so it clears stale
    annotations explicitly - including when the whole expose block is dropped.

The hostnames are appended after the in-cluster names so the FQDN used as the common name
is unchanged.

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?

@JNKPercona

Copy link
Copy Markdown
Collaborator
Test Name Result Time
backup-enable-disable passed 00:12:03
builtin-extensions passed 00:06:42
custom-extensions passed 00:23:52
cert-manager-tls passed 00:15:09
cert-management-policy passed 00:05:52
custom-envs passed 00:20:19
custom-tls passed 00:13:08
database-init-sql passed 00:04:59
demand-backup passed 00:29:25
demand-backup-offline-snapshot passed 00:14:42
dynamic-configuration passed 00:03:27
external-dns passed 00:04:03
extra-volumes passed 00:04:02
finalizers passed 00:04:41
init-deploy passed 00:03:07
huge-pages passed 00:03:17
logical-replica passed 00:13:13
major-upgrade-14-to-15 passed 00:11:54
major-upgrade-15-to-16 passed 00:12:11
major-upgrade-16-to-17 passed 00:10:51
major-upgrade-17-to-18 passed 00:17:16
major-upgrade-18-to-19 passed 00:09:30
major-upgrade-logical-replica passed 00:12:04
ldap passed 00:06:13
ldap-tls passed 00:06:36
logcollection passed 00:07:18
monitoring passed 00:10:39
operator-upgrade-cert-manager passed 00:12:46
operator-upgrade-extensions passed 00:13:31
one-pod passed 00:06:11
repo-host-autogrow passed 00:02:09
operator-self-healing passed 00:11:49
pgbouncer passed 00:08:43
pg-tde passed 00:11:52
pg-tde-wal-encrypt passed 00:23:06
pitr passed 00:12:43
scaling passed 00:05:40
scheduled-backup passed 00:27:14
self-healing passed 00:09:37
sidecars passed 00:03:06
standby-pgbackrest passed 00:21:51
standby-streaming passed 00:17:22
start-from-backup passed 00:12:49
start-from-volumes passed 00:03:30
tablespaces passed 00:08:12
telemetry-transfer passed 00:05:06
upgrade-consistency passed 00:06:53
upgrade-minor passed 00:15:58
users passed 00:06:17
migration-from-crunchy-standby passed 00:19:08
migration-from-crunchy-pv passed 00:08:50
migration-from-crunchy-backup-restore passed 00:17:12
Summary Value
Tests Run 52/52
Job Duration 01:51:09
Total Test Time 09:38:36

commit: 09e4007
image: perconalab/percona-postgresql-operator:PR-1812-09e4007fd

// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=253
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?(\.[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?)*$`
Hostname string `json:"hostname"`

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.

In PSMDB operator, we have two fields: domain and prefix. we build hostname like: prefix-component-podIndex.domain. should we do the same here?

@yoav-katz yoav-katz Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good comparison, and I looked at that before settling on a flat list. it doesn’t carry over cleanly because:

The addressing models differ. A MongoDB replica set is member-addressed: every pod is individually reachable, and the operator has to know each member’s hostname anyway because it writes those names into the replica set configuration. prefix-component-index.domain is a natural fit there because the names are structured and the operator is already generating them for a second purpose. PostgreSQL under Patroni isn’t member-addressed — clients connect through the primary/replica services, not to cluster1-instance1-abcd-0. The externally meaningful surface is one or two service names, not N pod names, so there’s much less for a template to generate.

@egegunes egegunes added this to the v3.2.0 milestone Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants