feat(pooler): support priorityClassName for connection pooler pods

Add priorityClassName to the connectionPooler cluster spec and a
connection_pooler_priority_class_name operator config default.
Cluster spec overrides the operator default; unset means no priority
class. Applies to both master and replica pooler deployments and is
reconciled on config drift.
This commit is contained in:
Trent Schmidt 2026-07-22 11:49:43 -07:00
parent ca9513c831
commit e09ff7a570
No known key found for this signature in database
GPG Key ID: D2CB2F30B5BD8C60
17 changed files with 116 additions and 3 deletions

View File

@ -119,6 +119,8 @@ spec:
format: int32
minimum: 1
type: integer
connection_pooler_priority_class_name:
type: string
connection_pooler_schema:
default: pooler
type: string

View File

@ -168,6 +168,8 @@ spec:
format: int32
minimum: 1
type: integer
priorityClassName:
type: string
resources:
description: Resources describes requests and limits for the cluster
resouces.

View File

@ -474,6 +474,8 @@ configConnectionPooler:
connection_pooler_mode: "transaction"
# number of pooler instances
connection_pooler_number_of_instances: 2
# priorityClassName to assign to pooler pods
# connection_pooler_priority_class_name: ""
# default resources
connection_pooler_default_cpu_request: 500m
connection_pooler_default_memory_request: 100Mi

View File

@ -656,6 +656,14 @@ for both master and replica pooler services (if `enableReplicaConnectionPooler`
* **mode**
In which mode to run connection pooler, transaction or session.
* **priorityClassName**
a name of the [priority
class](https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass)
that should be assigned to the connection pooler pods. When not specified, the
value is taken from the `connection_pooler_priority_class_name` operator
parameter, if not set then the default priority class is taken. The priority
class itself must be defined in advance. Optional.
* **resources**
Resource configuration for connection pooler deployment.

View File

@ -1100,6 +1100,12 @@ operator being able to provide some reasonable defaults.
* **connection_pooler_mode**
Default pooler mode, `session` or `transaction`. Default is `transaction`.
* **connection_pooler_priority_class_name**
The name of the priority class to assign to connection pooler pods by default.
A cluster can override this with `connectionPooler.priorityClassName`. When
unset, the pooler pods use no priority class. The empty string signifies no
assignment.
* **connection_pooler_default_cpu_request**
**connection_pooler_default_memory_reques**
**connection_pooler_default_cpu_limit**

View File

@ -21,6 +21,7 @@ data:
connection_pooler_max_db_connections: "60"
connection_pooler_mode: "transaction"
connection_pooler_number_of_instances: "2"
# connection_pooler_priority_class_name: ""
connection_pooler_schema: "pooler"
connection_pooler_user: "pooler"
crd_categories: "all"

View File

@ -119,6 +119,8 @@ spec:
format: int32
minimum: 1
type: integer
connection_pooler_priority_class_name:
type: string
connection_pooler_schema:
default: pooler
type: string

View File

@ -233,6 +233,7 @@ configuration:
# connection_pooler_max_db_connections: 60
connection_pooler_mode: "transaction"
connection_pooler_number_of_instances: 2
# connection_pooler_priority_class_name: ""
# connection_pooler_schema: "pooler"
# connection_pooler_user: "pooler"
patroni:

View File

@ -168,6 +168,8 @@ spec:
format: int32
minimum: 1
type: integer
priorityClassName:
type: string
resources:
description: Resources describes requests and limits for the cluster
resouces.

View File

@ -347,6 +347,7 @@ type ConnectionPoolerConfiguration struct {
DefaultCPULimit string `json:"connection_pooler_default_cpu_limit,omitempty"`
// +kubebuilder:validation:Pattern=`^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$`
DefaultMemoryLimit string `json:"connection_pooler_default_memory_limit,omitempty"`
PriorityClassName string `json:"connection_pooler_priority_class_name,omitempty"`
}
// OperatorLogicalBackupConfiguration defines configuration for logical backup

View File

@ -119,6 +119,8 @@ spec:
format: int32
minimum: 1
type: integer
connection_pooler_priority_class_name:
type: string
connection_pooler_schema:
default: pooler
type: string

View File

@ -168,6 +168,8 @@ spec:
format: int32
minimum: 1
type: integer
priorityClassName:
type: string
resources:
description: Resources describes requests and limits for the cluster
resouces.

View File

@ -325,9 +325,10 @@ type ConnectionPooler struct {
Schema string `json:"schema,omitempty"`
User string `json:"user,omitempty"`
// +kubebuilder:validation:Enum=session;transaction
Mode string `json:"mode,omitempty"`
DockerImage string `json:"dockerImage,omitempty"`
MaxDBConnections *int32 `json:"maxDBConnections,omitempty"`
Mode string `json:"mode,omitempty"`
DockerImage string `json:"dockerImage,omitempty"`
MaxDBConnections *int32 `json:"maxDBConnections,omitempty"`
PriorityClassName string `json:"priorityClassName,omitempty"`
*Resources `json:"resources,omitempty"`
}

View File

@ -471,6 +471,9 @@ func (c *Cluster) generateConnectionPoolerPodTemplate(role PostgresRole) (
Volumes: poolerVolumes,
SecurityContext: &securityContext,
ServiceAccountName: c.OpConfig.PodServiceAccountName,
PriorityClassName: util.Coalesce(
connectionPoolerSpec.PriorityClassName,
c.OpConfig.ConnectionPooler.PriorityClassName),
},
}
@ -893,6 +896,16 @@ func (c *Cluster) needSyncConnectionPoolerDefaults(Config *Config, spec *acidv1.
reasons = append(reasons, msg)
}
if spec.PriorityClassName == "" {
expectedPriorityClassName := config.PriorityClassName
if podTemplate.Spec.PriorityClassName != expectedPriorityClassName {
sync = true
msg := fmt.Sprintf("priorityClassName is different (having %s, required %s)",
podTemplate.Spec.PriorityClassName, expectedPriorityClassName)
reasons = append(reasons, msg)
}
}
expectedResources, err := c.generateResourceRequirements(spec.Resources,
makeDefaultConnectionPoolerResources(&Config.OpConfig),
connectionPoolerContainer)

View File

@ -871,6 +871,72 @@ func TestConnectionPoolerDeploymentSpec(t *testing.T) {
}
}
func TestConnectionPoolerPriorityClassName(t *testing.T) {
newCluster := func(specPooler, opPooler, specPod, opPod string) *Cluster {
c := New(
Config{
OpConfig: config.Config{
Resources: config.Resources{
PodPriorityClassName: opPod,
},
ConnectionPooler: config.ConnectionPooler{
PriorityClassName: opPooler,
},
},
}, k8sutil.KubernetesClient{}, acidv1.Postgresql{}, logger, eventRecorder)
c.Spec = acidv1.PostgresSpec{
PodPriorityClassName: specPod,
ConnectionPooler: &acidv1.ConnectionPooler{PriorityClassName: specPooler},
}
return c
}
tests := []struct {
subTest string
cluster *Cluster
expected string
}{
{
subTest: "pooler spec wins over everything",
cluster: newCluster("pooler-spec", "pooler-op", "pod-spec", "pod-op"),
expected: "pooler-spec",
},
{
subTest: "pooler operator default when spec empty",
cluster: newCluster("", "pooler-op", "pod-spec", "pod-op"),
expected: "pooler-op",
},
{
subTest: "does not inherit cluster pod priority class",
cluster: newCluster("", "", "pod-spec", "pod-op"),
expected: "",
},
{
subTest: "does not inherit operator pod priority class",
cluster: newCluster("", "", "", "pod-op"),
expected: "",
},
{
subTest: "empty when nothing set",
cluster: newCluster("", "", "", ""),
expected: "",
},
}
for _, role := range [2]PostgresRole{Master, Replica} {
for _, tt := range tests {
podSpec, err := tt.cluster.generateConnectionPoolerPodTemplate(role)
if err != nil {
t.Fatalf("%s [%s]: unexpected error %v", role, tt.subTest, err)
}
if podSpec.Spec.PriorityClassName != tt.expected {
t.Errorf("%s [%s]: got priorityClassName %q, expected %q",
role, tt.subTest, podSpec.Spec.PriorityClassName, tt.expected)
}
}
}
}
func testServiceAccount(cluster *Cluster, podSpec *v1.PodTemplateSpec, role PostgresRole) error {
poolerServiceAccount := podSpec.Spec.ServiceAccountName

View File

@ -282,6 +282,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
result.ConnectionPooler.ConnectionPoolerDefaultMemoryRequest = fromCRD.ConnectionPooler.DefaultMemoryRequest
result.ConnectionPooler.ConnectionPoolerDefaultCPULimit = fromCRD.ConnectionPooler.DefaultCPULimit
result.ConnectionPooler.ConnectionPoolerDefaultMemoryLimit = fromCRD.ConnectionPooler.DefaultMemoryLimit
result.ConnectionPooler.PriorityClassName = fromCRD.ConnectionPooler.PriorityClassName
result.ConnectionPooler.MaxDBConnections = util.CoalesceInt32(
fromCRD.ConnectionPooler.MaxDBConnections,

View File

@ -161,6 +161,7 @@ type ConnectionPooler struct {
Image string `name:"connection_pooler_image" default:"ghcr.io/zalando/postgres-operator/pgbouncer:latest"`
Mode string `name:"connection_pooler_mode" default:"transaction"`
MaxDBConnections *int32 `name:"connection_pooler_max_db_connections" default:"60"`
PriorityClassName string `name:"connection_pooler_priority_class_name"`
ConnectionPoolerDefaultCPURequest string `name:"connection_pooler_default_cpu_request"`
ConnectionPoolerDefaultMemoryRequest string `name:"connection_pooler_default_memory_request"`
ConnectionPoolerDefaultCPULimit string `name:"connection_pooler_default_cpu_limit"`