Skip to content

Commit 5e4badd

Browse files
authored
annotation to bypass globally configured instance limits (#1943)
1 parent ad32048 commit 5e4badd

File tree

12 files changed

+169
-6
lines changed

12 files changed

+169
-6
lines changed

charts/postgres-operator/crds/operatorconfigurations.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ spec:
9191
etcd_host:
9292
type: string
9393
default: ""
94+
ignore_instance_limits_annotation_key:
95+
type: string
9496
kubernetes_use_configmaps:
9597
type: boolean
9698
default: false

charts/postgres-operator/values.yaml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,15 @@ configGeneral:
3535
enable_spilo_wal_path_compat: false
3636
# etcd connection string for Patroni. Empty uses K8s-native DCS.
3737
etcd_host: ""
38-
# Select if setup uses endpoints (default), or configmaps to manage leader (DCS=k8s)
39-
# kubernetes_use_configmaps: false
4038
# Spilo docker image
4139
docker_image: registry.opensource.zalan.do/acid/spilo-14:2.1-p6
40+
41+
# key name for annotation to ignore globally configured instance limits
42+
# ignore_instance_limits_annotation_key: ""
43+
44+
# Select if setup uses endpoints (default), or configmaps to manage leader (DCS=k8s)
45+
# kubernetes_use_configmaps: false
46+
4247
# min number of instances in Postgres cluster. -1 = no limit
4348
min_instances: -1
4449
# max number of instances in Postgres cluster. -1 = no limit

docs/reference/operator_parameters.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,12 @@ Those are top-level keys, containing both leaf keys and groups.
147147
When `-1` is specified for `min_instances`, no limits are applied. The default
148148
is `-1`.
149149

150+
* **ignore_instance_limits_annotation_key**
151+
for some clusters it might be required to scale beyond the limits that can be
152+
configured with `min_instances` and `max_instances` options. You can define
153+
an annotation key that can be used as a toggle in cluster manifests to ignore
154+
globally configured instance limits. The default is empty.
155+
150156
* **resync_period**
151157
period between consecutive sync requests. The default is `30m`.
152158

manifests/configmap.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ data:
6666
# ignored_annotations: ""
6767
# infrastructure_roles_secret_name: "postgresql-infrastructure-roles"
6868
# infrastructure_roles_secrets: "secretname:monitoring-roles,userkey:user,passwordkey:password,rolekey:inrole"
69+
# ignore_instance_limits_annotation_key: ""
6970
# inherited_annotations: owned-by
7071
# inherited_labels: application,environment
7172
# kube_iam_role: ""

manifests/operatorconfiguration.crd.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ spec:
8989
etcd_host:
9090
type: string
9191
default: ""
92+
ignore_instance_limits_annotation_key:
93+
type: string
9294
kubernetes_use_configmaps:
9395
type: boolean
9496
default: false

manifests/postgresql-operator-default-configuration.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ configuration:
1212
# enable_shm_volume: true
1313
enable_spilo_wal_path_compat: false
1414
etcd_host: ""
15+
# ignore_instance_limits_annotation_key: ""
1516
# kubernetes_use_configmaps: false
1617
max_instances: -1
1718
min_instances: -1

pkg/apis/acid.zalan.do/v1/crds.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,9 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{
11151115
"etcd_host": {
11161116
Type: "string",
11171117
},
1118+
"ignore_instance_limits_annotation_key": {
1119+
Type: "string",
1120+
},
11181121
"kubernetes_use_configmaps": {
11191122
Type: "boolean",
11201123
},

pkg/apis/acid.zalan.do/v1/operator_configuration_type.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,6 @@ type OperatorConfigurationData struct {
236236
KubernetesUseConfigMaps bool `json:"kubernetes_use_configmaps,omitempty"`
237237
DockerImage string `json:"docker_image,omitempty"`
238238
Workers uint32 `json:"workers,omitempty"`
239-
MinInstances int32 `json:"min_instances,omitempty"`
240-
MaxInstances int32 `json:"max_instances,omitempty"`
241239
ResyncPeriod Duration `json:"resync_period,omitempty"`
242240
RepairPeriod Duration `json:"repair_period,omitempty"`
243241
SetMemoryRequestToLimit bool `json:"set_memory_request_to_limit,omitempty"`
@@ -257,6 +255,10 @@ type OperatorConfigurationData struct {
257255
Scalyr ScalyrConfiguration `json:"scalyr"`
258256
LogicalBackup OperatorLogicalBackupConfiguration `json:"logical_backup"`
259257
ConnectionPooler ConnectionPoolerConfiguration `json:"connection_pooler"`
258+
259+
MinInstances int32 `json:"min_instances,omitempty"`
260+
MaxInstances int32 `json:"max_instances,omitempty"`
261+
IgnoreInstanceLimitsAnnotationKey string `json:"ignore_instance_limits_annotation_key,omitempty"`
260262
}
261263

262264
//Duration shortens this frequently used name

pkg/cluster/k8sres.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,6 +1343,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef
13431343
return nil, fmt.Errorf("could not generate volume claim template: %v", err)
13441344
}
13451345

1346+
// global minInstances and maxInstances settings can overwrite manifest
13461347
numberOfInstances := c.getNumberOfInstances(spec)
13471348

13481349
// the operator has domain-specific logic on how to do rolling updates of PG clusters
@@ -1443,9 +1444,16 @@ func (c *Cluster) generateScalyrSidecarSpec(clusterName, APIKey, serverURL, dock
14431444
func (c *Cluster) getNumberOfInstances(spec *acidv1.PostgresSpec) int32 {
14441445
min := c.OpConfig.MinInstances
14451446
max := c.OpConfig.MaxInstances
1447+
instanceLimitAnnotationKey := c.OpConfig.IgnoreInstanceLimitsAnnotationKey
14461448
cur := spec.NumberOfInstances
14471449
newcur := cur
14481450

1451+
if instanceLimitAnnotationKey != "" {
1452+
if value, exists := c.ObjectMeta.Annotations[instanceLimitAnnotationKey]; exists && value == "true" {
1453+
return cur
1454+
}
1455+
}
1456+
14491457
if spec.StandbyCluster != nil {
14501458
if newcur == 1 {
14511459
min = newcur

pkg/cluster/k8sres_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,136 @@ func TestGenerateSpiloPodEnvVars(t *testing.T) {
864864
}
865865
}
866866

867+
func TestGetNumberOfInstances(t *testing.T) {
868+
testName := "TestGetNumberOfInstances"
869+
tests := []struct {
870+
subTest string
871+
config config.Config
872+
annotationKey string
873+
annotationValue string
874+
desired int32
875+
provided int32
876+
}{
877+
{
878+
subTest: "no constraints",
879+
config: config.Config{
880+
Resources: config.Resources{
881+
MinInstances: -1,
882+
MaxInstances: -1,
883+
IgnoreInstanceLimitsAnnotationKey: "",
884+
},
885+
},
886+
annotationKey: "",
887+
annotationValue: "",
888+
desired: 2,
889+
provided: 2,
890+
},
891+
{
892+
subTest: "minInstances defined",
893+
config: config.Config{
894+
Resources: config.Resources{
895+
MinInstances: 2,
896+
MaxInstances: -1,
897+
IgnoreInstanceLimitsAnnotationKey: "",
898+
},
899+
},
900+
annotationKey: "",
901+
annotationValue: "",
902+
desired: 1,
903+
provided: 2,
904+
},
905+
{
906+
subTest: "maxInstances defined",
907+
config: config.Config{
908+
Resources: config.Resources{
909+
MinInstances: -1,
910+
MaxInstances: 5,
911+
IgnoreInstanceLimitsAnnotationKey: "",
912+
},
913+
},
914+
annotationKey: "",
915+
annotationValue: "",
916+
desired: 10,
917+
provided: 5,
918+
},
919+
{
920+
subTest: "ignore minInstances",
921+
config: config.Config{
922+
Resources: config.Resources{
923+
MinInstances: 2,
924+
MaxInstances: -1,
925+
IgnoreInstanceLimitsAnnotationKey: "ignore-instance-limits",
926+
},
927+
},
928+
annotationKey: "ignore-instance-limits",
929+
annotationValue: "true",
930+
desired: 1,
931+
provided: 1,
932+
},
933+
{
934+
subTest: "want to ignore minInstances but wrong key",
935+
config: config.Config{
936+
Resources: config.Resources{
937+
MinInstances: 2,
938+
MaxInstances: -1,
939+
IgnoreInstanceLimitsAnnotationKey: "ignore-instance-limits",
940+
},
941+
},
942+
annotationKey: "ignoring-instance-limits",
943+
annotationValue: "true",
944+
desired: 1,
945+
provided: 2,
946+
},
947+
{
948+
subTest: "want to ignore minInstances but wrong value",
949+
config: config.Config{
950+
Resources: config.Resources{
951+
MinInstances: 2,
952+
MaxInstances: -1,
953+
IgnoreInstanceLimitsAnnotationKey: "ignore-instance-limits",
954+
},
955+
},
956+
annotationKey: "ignore-instance-limits",
957+
annotationValue: "active",
958+
desired: 1,
959+
provided: 2,
960+
},
961+
{
962+
subTest: "annotation set but no constraints to ignore",
963+
config: config.Config{
964+
Resources: config.Resources{
965+
MinInstances: -1,
966+
MaxInstances: -1,
967+
IgnoreInstanceLimitsAnnotationKey: "ignore-instance-limits",
968+
},
969+
},
970+
annotationKey: "ignore-instance-limits",
971+
annotationValue: "true",
972+
desired: 1,
973+
provided: 1,
974+
},
975+
}
976+
977+
for _, tt := range tests {
978+
var cluster = New(
979+
Config{
980+
OpConfig: tt.config,
981+
}, k8sutil.KubernetesClient{}, acidv1.Postgresql{}, logger, eventRecorder)
982+
983+
cluster.Spec.NumberOfInstances = tt.desired
984+
if tt.annotationKey != "" {
985+
cluster.ObjectMeta.Annotations = make(map[string]string)
986+
cluster.ObjectMeta.Annotations[tt.annotationKey] = tt.annotationValue
987+
}
988+
numInstances := cluster.getNumberOfInstances(&cluster.Spec)
989+
990+
if numInstances != tt.provided {
991+
t.Errorf("%s %s: Expected to get %d instances, have %d instead",
992+
testName, tt.subTest, tt.provided, numInstances)
993+
}
994+
}
995+
}
996+
867997
func TestCloneEnv(t *testing.T) {
868998
testName := "TestCloneEnv"
869999
tests := []struct {

0 commit comments

Comments
 (0)