add pooler suffix to DNS annotation of pooler LoadBalancer service (#2188)

* add pooler suffix to DNS annotation of pooler LoadBalancer service
* need generatePoolerServiceAnnotations function
This commit is contained in:
Felix Kunde 2023-01-27 12:07:48 +01:00 committed by GitHub
parent 7887ebbbce
commit c9cada66c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 93 additions and 38 deletions

View File

@ -810,6 +810,9 @@ Load balancer services can also be enabled for the [connection pooler](user.md#c
pods with manifest flags `enableMasterPoolerLoadBalancer` and/or pods with manifest flags `enableMasterPoolerLoadBalancer` and/or
`enableReplicaPoolerLoadBalancer` or in the operator configuration with `enableReplicaPoolerLoadBalancer` or in the operator configuration with
`enable_master_pooler_load_balancer` and/or `enable_replica_pooler_load_balancer`. `enable_master_pooler_load_balancer` and/or `enable_replica_pooler_load_balancer`.
For the `external-dns.alpha.kubernetes.io/hostname` annotation the `-pooler`
suffix will be appended to the cluster name used in the template which is
defined in `master|replica_dns_name_format`.
## Running periodic 'autorepair' scans of K8s objects ## Running periodic 'autorepair' scans of K8s objects

View File

@ -670,6 +670,20 @@ class EndToEndTestCase(unittest.TestCase):
'LoadBalancer', 'LoadBalancer',
"Expected LoadBalancer service type for replica pooler pod, found {}") "Expected LoadBalancer service type for replica pooler pod, found {}")
master_annotations = {
"external-dns.alpha.kubernetes.io/hostname": "acid-minimal-cluster-pooler.default.db.example.com",
"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
}
self.eventuallyTrue(lambda: k8s.check_service_annotations(
master_pooler_label+","+pooler_label, master_annotations), "Wrong annotations")
replica_annotations = {
"external-dns.alpha.kubernetes.io/hostname": "acid-minimal-cluster-pooler-repl.default.db.example.com",
"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": "3600",
}
self.eventuallyTrue(lambda: k8s.check_service_annotations(
replica_pooler_label+","+pooler_label, replica_annotations), "Wrong annotations")
# Turn off only master connection pooler # Turn off only master connection pooler
k8s.api.custom_objects_api.patch_namespaced_custom_object( k8s.api.custom_objects_api.patch_namespaced_custom_object(
'acid.zalan.do', 'v1', 'default', 'acid.zalan.do', 'v1', 'default',

View File

@ -28,6 +28,8 @@ spec:
enableReplicaLoadBalancer: false enableReplicaLoadBalancer: false
enableConnectionPooler: false # enable/disable connection pooler deployment enableConnectionPooler: false # enable/disable connection pooler deployment
enableReplicaConnectionPooler: false # set to enable connectionPooler for replica service enableReplicaConnectionPooler: false # set to enable connectionPooler for replica service
enableMasterPoolerLoadBalancer: false
enableReplicaPoolerLoadBalancer: false
allowedSourceRanges: # load balancers' source ranges for both master and replica services allowedSourceRanges: # load balancers' source ranges for both master and replica services
- 127.0.0.1/32 - 127.0.0.1/32
databases: databases:

View File

@ -45,7 +45,7 @@ type ConnectionPoolerObjects struct {
} }
func (c *Cluster) connectionPoolerName(role PostgresRole) string { func (c *Cluster) connectionPoolerName(role PostgresRole) string {
name := c.Name + "-pooler" name := fmt.Sprintf("%s-%s", c.Name, constants.ConnectionPoolerResourceSuffix)
if role == Replica { if role == Replica {
name = fmt.Sprintf("%s-%s", name, "repl") name = fmt.Sprintf("%s-%s", name, "repl")
} }
@ -163,24 +163,27 @@ func (c *Cluster) createConnectionPooler(LookupFunction InstallFunction) (SyncRe
return reason, nil return reason, nil
} }
//
// Generate pool size related environment variables. // Generate pool size related environment variables.
// //
// MAX_DB_CONN would specify the global maximum for connections to a target // MAX_DB_CONN would specify the global maximum for connections to a target
// database. //
// database.
// //
// MAX_CLIENT_CONN is not configurable at the moment, just set it high enough. // MAX_CLIENT_CONN is not configurable at the moment, just set it high enough.
// //
// DEFAULT_SIZE is a pool size per db/user (having in mind the use case when // DEFAULT_SIZE is a pool size per db/user (having in mind the use case when
// most of the queries coming through a connection pooler are from the same //
// user to the same db). In case if we want to spin up more connection pooler // most of the queries coming through a connection pooler are from the same
// instances, take this into account and maintain the same number of // user to the same db). In case if we want to spin up more connection pooler
// connections. // instances, take this into account and maintain the same number of
// connections.
// //
// MIN_SIZE is a pool's minimal size, to prevent situation when sudden workload // MIN_SIZE is a pool's minimal size, to prevent situation when sudden workload
// have to wait for spinning up a new connections. //
// have to wait for spinning up a new connections.
// //
// RESERVE_SIZE is how many additional connections to allow for a pooler. // RESERVE_SIZE is how many additional connections to allow for a pooler.
func (c *Cluster) getConnectionPoolerEnvVars() []v1.EnvVar { func (c *Cluster) getConnectionPoolerEnvVars() []v1.EnvVar {
spec := &c.Spec spec := &c.Spec
connectionPoolerSpec := spec.ConnectionPooler connectionPoolerSpec := spec.ConnectionPooler
@ -475,23 +478,23 @@ func (c *Cluster) generateConnectionPoolerDeployment(connectionPooler *Connectio
} }
func (c *Cluster) generateConnectionPoolerService(connectionPooler *ConnectionPoolerObjects) *v1.Service { func (c *Cluster) generateConnectionPoolerService(connectionPooler *ConnectionPoolerObjects) *v1.Service {
spec := &c.Spec spec := &c.Spec
poolerRole := connectionPooler.Role
serviceSpec := v1.ServiceSpec{ serviceSpec := v1.ServiceSpec{
Ports: []v1.ServicePort{ Ports: []v1.ServicePort{
{ {
Name: connectionPooler.Name, Name: connectionPooler.Name,
Port: pgPort, Port: pgPort,
TargetPort: intstr.IntOrString{IntVal: c.servicePort(connectionPooler.Role)}, TargetPort: intstr.IntOrString{IntVal: c.servicePort(poolerRole)},
}, },
}, },
Type: v1.ServiceTypeClusterIP, Type: v1.ServiceTypeClusterIP,
Selector: map[string]string{ Selector: map[string]string{
"connection-pooler": c.connectionPoolerName(connectionPooler.Role), "connection-pooler": c.connectionPoolerName(poolerRole),
}, },
} }
if c.shouldCreateLoadBalancerForPoolerService(connectionPooler.Role, spec) { if c.shouldCreateLoadBalancerForPoolerService(poolerRole, spec) {
c.configureLoadBalanceService(&serviceSpec, spec.AllowedSourceRanges) c.configureLoadBalanceService(&serviceSpec, spec.AllowedSourceRanges)
} }
@ -500,7 +503,7 @@ func (c *Cluster) generateConnectionPoolerService(connectionPooler *ConnectionPo
Name: connectionPooler.Name, Name: connectionPooler.Name,
Namespace: connectionPooler.Namespace, Namespace: connectionPooler.Namespace,
Labels: c.connectionPoolerLabels(connectionPooler.Role, false).MatchLabels, Labels: c.connectionPoolerLabels(connectionPooler.Role, false).MatchLabels,
Annotations: c.annotationsSet(c.generateServiceAnnotations(connectionPooler.Role, spec)), Annotations: c.annotationsSet(c.generatePoolerServiceAnnotations(poolerRole, spec)),
// make StatefulSet object its owner to represent the dependency. // make StatefulSet object its owner to represent the dependency.
// By itself StatefulSet is being deleted with "Orphaned" // By itself StatefulSet is being deleted with "Orphaned"
// propagation policy, which means that it's deletion will not // propagation policy, which means that it's deletion will not
@ -515,6 +518,32 @@ func (c *Cluster) generateConnectionPoolerService(connectionPooler *ConnectionPo
return service return service
} }
func (c *Cluster) generatePoolerServiceAnnotations(role PostgresRole, spec *acidv1.PostgresSpec) map[string]string {
var dnsString string
annotations := c.getCustomServiceAnnotations(role, spec)
if c.shouldCreateLoadBalancerForPoolerService(role, spec) {
// set ELB Timeout annotation with default value
if _, ok := annotations[constants.ElbTimeoutAnnotationName]; !ok {
annotations[constants.ElbTimeoutAnnotationName] = constants.ElbTimeoutAnnotationValue
}
// -repl suffix will be added by replicaDNSName
clusterNameWithPoolerSuffix := c.connectionPoolerName(Master)
if role == Master {
dnsString = c.masterDNSName(clusterNameWithPoolerSuffix)
} else {
dnsString = c.replicaDNSName(clusterNameWithPoolerSuffix)
}
annotations[constants.ZalandoDNSNameAnnotation] = dnsString
}
if len(annotations) == 0 {
return nil
}
return annotations
}
func (c *Cluster) shouldCreateLoadBalancerForPoolerService(role PostgresRole, spec *acidv1.PostgresSpec) bool { func (c *Cluster) shouldCreateLoadBalancerForPoolerService(role PostgresRole, spec *acidv1.PostgresSpec) bool {
switch role { switch role {
@ -546,7 +575,7 @@ func (c *Cluster) listPoolerPods(listOptions metav1.ListOptions) ([]v1.Pod, erro
return pods.Items, nil return pods.Items, nil
} }
//delete connection pooler // delete connection pooler
func (c *Cluster) deleteConnectionPooler(role PostgresRole) (err error) { func (c *Cluster) deleteConnectionPooler(role PostgresRole) (err error) {
c.logger.Infof("deleting connection pooler spilo-role=%s", role) c.logger.Infof("deleting connection pooler spilo-role=%s", role)
@ -605,7 +634,7 @@ func (c *Cluster) deleteConnectionPooler(role PostgresRole) (err error) {
return nil return nil
} }
//delete connection pooler // delete connection pooler
func (c *Cluster) deleteConnectionPoolerSecret() (err error) { func (c *Cluster) deleteConnectionPoolerSecret() (err error) {
// Repeat the same for the secret object // Repeat the same for the secret object
secretName := c.credentialSecretName(c.OpConfig.ConnectionPooler.User) secretName := c.credentialSecretName(c.OpConfig.ConnectionPooler.User)
@ -654,7 +683,7 @@ func updateConnectionPoolerDeployment(KubeClient k8sutil.KubernetesClient, newDe
return deployment, nil return deployment, nil
} }
//updateConnectionPoolerAnnotations updates the annotations of connection pooler deployment // updateConnectionPoolerAnnotations updates the annotations of connection pooler deployment
func updateConnectionPoolerAnnotations(KubeClient k8sutil.KubernetesClient, deployment *appsv1.Deployment, annotations map[string]string) (*appsv1.Deployment, error) { func updateConnectionPoolerAnnotations(KubeClient k8sutil.KubernetesClient, deployment *appsv1.Deployment, annotations map[string]string) (*appsv1.Deployment, error) {
patchData, err := metaAnnotationsPatch(annotations) patchData, err := metaAnnotationsPatch(annotations)
if err != nil { if err != nil {

View File

@ -1901,6 +1901,28 @@ func (c *Cluster) configureLoadBalanceService(serviceSpec *v1.ServiceSpec, sourc
} }
func (c *Cluster) generateServiceAnnotations(role PostgresRole, spec *acidv1.PostgresSpec) map[string]string { func (c *Cluster) generateServiceAnnotations(role PostgresRole, spec *acidv1.PostgresSpec) map[string]string {
annotations := c.getCustomServiceAnnotations(role, spec)
if c.shouldCreateLoadBalancerForService(role, spec) {
dnsName := c.dnsName(role)
// Just set ELB Timeout annotation with default value, if it does not
// have a custom value
if _, ok := annotations[constants.ElbTimeoutAnnotationName]; !ok {
annotations[constants.ElbTimeoutAnnotationName] = constants.ElbTimeoutAnnotationValue
}
// External DNS name annotation is not customizable
annotations[constants.ZalandoDNSNameAnnotation] = dnsName
}
if len(annotations) == 0 {
return nil
}
return annotations
}
func (c *Cluster) getCustomServiceAnnotations(role PostgresRole, spec *acidv1.PostgresSpec) map[string]string {
annotations := make(map[string]string) annotations := make(map[string]string)
maps.Copy(annotations, c.OpConfig.CustomServiceAnnotations) maps.Copy(annotations, c.OpConfig.CustomServiceAnnotations)
@ -1915,22 +1937,6 @@ func (c *Cluster) generateServiceAnnotations(role PostgresRole, spec *acidv1.Pos
} }
} }
if c.shouldCreateLoadBalancerForService(role, spec) {
dnsName := c.dnsName(role)
// Just set ELB Timeout annotation with default value, if it does not
// have a cutom value
if _, ok := annotations[constants.ElbTimeoutAnnotationName]; !ok {
annotations[constants.ElbTimeoutAnnotationName] = constants.ElbTimeoutAnnotationValue
}
// External DNS name annotation is not customizable
annotations[constants.ZalandoDNSNameAnnotation] = dnsName
}
if len(annotations) == 0 {
return nil
}
return annotations return annotations
} }

View File

@ -509,9 +509,9 @@ func (c *Cluster) dnsName(role PostgresRole) string {
var dnsString, oldDnsString string var dnsString, oldDnsString string
if role == Master { if role == Master {
dnsString = c.masterDNSName() dnsString = c.masterDNSName(c.Name)
} else { } else {
dnsString = c.replicaDNSName() dnsString = c.replicaDNSName(c.Name)
} }
// if cluster name starts with teamID we might need to provide backwards compatibility // if cluster name starts with teamID we might need to provide backwards compatibility
@ -528,17 +528,17 @@ func (c *Cluster) dnsName(role PostgresRole) string {
return dnsString return dnsString
} }
func (c *Cluster) masterDNSName() string { func (c *Cluster) masterDNSName(clusterName string) string {
return strings.ToLower(c.OpConfig.MasterDNSNameFormat.Format( return strings.ToLower(c.OpConfig.MasterDNSNameFormat.Format(
"cluster", c.Name, "cluster", clusterName,
"namespace", c.Namespace, "namespace", c.Namespace,
"team", c.teamName(), "team", c.teamName(),
"hostedzone", c.OpConfig.DbHostedZone)) "hostedzone", c.OpConfig.DbHostedZone))
} }
func (c *Cluster) replicaDNSName() string { func (c *Cluster) replicaDNSName(clusterName string) string {
return strings.ToLower(c.OpConfig.ReplicaDNSNameFormat.Format( return strings.ToLower(c.OpConfig.ReplicaDNSNameFormat.Format(
"cluster", c.Name, "cluster", clusterName,
"namespace", c.Namespace, "namespace", c.Namespace,
"team", c.teamName(), "team", c.teamName(),
"hostedzone", c.OpConfig.DbHostedZone)) "hostedzone", c.OpConfig.DbHostedZone))

View File

@ -2,6 +2,7 @@ package constants
// Connection pooler specific constants // Connection pooler specific constants
const ( const (
ConnectionPoolerResourceSuffix = "pooler"
ConnectionPoolerUserName = "pooler" ConnectionPoolerUserName = "pooler"
ConnectionPoolerSchemaName = "pooler" ConnectionPoolerSchemaName = "pooler"
ConnectionPoolerDefaultType = "pgbouncer" ConnectionPoolerDefaultType = "pgbouncer"