From d268c589c29df174c7e5bc76976c52c9edc3a384 Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Mon, 27 Jul 2026 16:32:22 +0200 Subject: [PATCH] change volume specs to int32 and define max iops and throughput values (#3139) * change int64 volume specs to int32 like in used AWS lib * upper iops and throughput limits * minor refactoring in ebs.go --- .../templates/deployment.yaml | 4 +-- .../postgres-operator/crds/postgresqls.yaml | 6 ++-- docs/reference/cluster_manifest.md | 4 +-- manifests/postgresql.crd.yaml | 6 ++-- pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml | 6 ++-- pkg/apis/acid.zalan.do/v1/postgresql_type.go | 8 +++-- .../acid.zalan.do/v1/zz_generated.deepcopy.go | 4 +-- pkg/cluster/volumes.go | 20 +++++------ pkg/cluster/volumes_test.go | 25 +++++++------ pkg/util/volumes/ebs.go | 35 ++++++------------- pkg/util/volumes/volumes.go | 10 +++--- ui/manifests/deployment.yaml | 4 +-- ui/operator_ui/main.py | 4 +-- ui/run_local.sh | 4 +-- 14 files changed, 67 insertions(+), 73 deletions(-) diff --git a/charts/postgres-operator-ui/templates/deployment.yaml b/charts/postgres-operator-ui/templates/deployment.yaml index c8797e42e..d4a2a88b8 100644 --- a/charts/postgres-operator-ui/templates/deployment.yaml +++ b/charts/postgres-operator-ui/templates/deployment.yaml @@ -81,8 +81,8 @@ spec: "cost_memory": 0.014375, "free_iops": 3000, "free_throughput": 125, - "limit_iops": 16000, - "limit_throughput": 1000, + "limit_iops": 80000, + "limit_throughput": 2000, "postgresql_versions": [ "18", "17", diff --git a/charts/postgres-operator/crds/postgresqls.yaml b/charts/postgres-operator/crds/postgresqls.yaml index 0e3558638..f3dcbab59 100644 --- a/charts/postgres-operator/crds/postgresqls.yaml +++ b/charts/postgres-operator/crds/postgresqls.yaml @@ -3258,7 +3258,8 @@ spec: description: Volume describes a single volume in the manifest. properties: iops: - format: int64 + format: int32 + maximum: 80000 type: integer isSubPathExpr: type: boolean @@ -3319,7 +3320,8 @@ spec: subPath: type: string throughput: - format: int64 + format: int32 + maximum: 2000 type: integer type: type: string diff --git a/docs/reference/cluster_manifest.md b/docs/reference/cluster_manifest.md index 0717e411f..7dddd5951 100644 --- a/docs/reference/cluster_manifest.md +++ b/docs/reference/cluster_manifest.md @@ -554,11 +554,11 @@ properties of the persistent storage that stores Postgres data. * **iops** When running the operator on AWS the latest generation of EBS volumes (`gp3`) - allows for configuring the number of IOPS. Maximum is 16000. Optional. + allows for configuring the number of IOPS. Maximum is 80000. Optional. * **throughput** When running the operator on AWS the latest generation of EBS volumes (`gp3`) - allows for configuring the throughput in MB/s. Maximum is 1000. Optional. + allows for configuring the throughput in MB/s. Maximum is 2000. Optional. * **selector** A label query over PVs to consider for binding. See the [Kubernetes diff --git a/manifests/postgresql.crd.yaml b/manifests/postgresql.crd.yaml index 0e3558638..f3dcbab59 100644 --- a/manifests/postgresql.crd.yaml +++ b/manifests/postgresql.crd.yaml @@ -3258,7 +3258,8 @@ spec: description: Volume describes a single volume in the manifest. properties: iops: - format: int64 + format: int32 + maximum: 80000 type: integer isSubPathExpr: type: boolean @@ -3319,7 +3320,8 @@ spec: subPath: type: string throughput: - format: int64 + format: int32 + maximum: 2000 type: integer type: type: string diff --git a/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml b/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml index 0e3558638..f3dcbab59 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml +++ b/pkg/apis/acid.zalan.do/v1/postgresql.crd.yaml @@ -3258,7 +3258,8 @@ spec: description: Volume describes a single volume in the manifest. properties: iops: - format: int64 + format: int32 + maximum: 80000 type: integer isSubPathExpr: type: boolean @@ -3319,7 +3320,8 @@ spec: subPath: type: string throughput: - format: int64 + format: int32 + maximum: 2000 type: integer type: type: string diff --git a/pkg/apis/acid.zalan.do/v1/postgresql_type.go b/pkg/apis/acid.zalan.do/v1/postgresql_type.go index a03bb13cc..2ea8c6ba8 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql_type.go +++ b/pkg/apis/acid.zalan.do/v1/postgresql_type.go @@ -167,9 +167,11 @@ type Volume struct { StorageClass string `json:"storageClass,omitempty"` SubPath string `json:"subPath,omitempty"` IsSubPathExpr *bool `json:"isSubPathExpr,omitempty"` - Iops *int64 `json:"iops,omitempty"` - Throughput *int64 `json:"throughput,omitempty"` - VolumeType string `json:"type,omitempty"` + // +kubebuilder:validation:Maximum=80000 + Iops *int32 `json:"iops,omitempty"` + // +kubebuilder:validation:Maximum=2000 + Throughput *int32 `json:"throughput,omitempty"` + VolumeType string `json:"type,omitempty"` } // AdditionalVolume specs additional optional volumes for statefulset diff --git a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go index 60003adfa..2a3fefd00 100644 --- a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go @@ -1686,12 +1686,12 @@ func (in *Volume) DeepCopyInto(out *Volume) { } if in.Iops != nil { in, out := &in.Iops, &out.Iops - *out = new(int64) + *out = new(int32) **out = **in } if in.Throughput != nil { in, out := &in.Throughput, &out.Throughput - *out = new(int64) + *out = new(int32) **out = **in } return diff --git a/pkg/cluster/volumes.go b/pkg/cluster/volumes.go index c1762a073..e4451a12c 100644 --- a/pkg/cluster/volumes.go +++ b/pkg/cluster/volumes.go @@ -81,24 +81,24 @@ func (c *Cluster) syncUnderlyingEBSVolume() error { errors := make([]string, 0) for _, volume := range c.EBSVolumes { - var modifyIops *int64 - var modifyThroughput *int64 - var modifySize *int64 + var modifyIops *int32 + var modifyThroughput *int32 + var modifySize *int32 var modifyType *string - if targetValue.Iops != nil && *targetValue.Iops >= int64(3000) { - if volume.Iops != int64(*targetValue.Iops) { + if targetValue.Iops != nil && *targetValue.Iops >= int32(3000) { + if volume.Iops != int32(*targetValue.Iops) { modifyIops = targetValue.Iops } } - if targetValue.Throughput != nil && *targetValue.Throughput >= int64(125) { - if volume.Throughput != int64(*targetValue.Throughput) { + if targetValue.Throughput != nil && *targetValue.Throughput >= int32(125) { + if volume.Throughput != int32(*targetValue.Throughput) { modifyThroughput = targetValue.Throughput } } - if targetSize > int64(volume.Size) { + if targetSize > volume.Size { modifySize = &targetSize } @@ -437,6 +437,6 @@ func getPodNameFromPersistentVolume(pv *v1.PersistentVolume) *spec.NamespacedNam return &spec.NamespacedName{Namespace: namespace, Name: name} } -func quantityToGigabyte(q resource.Quantity) int64 { - return q.ScaledValue(0) / (1 * constants.Gigabyte) +func quantityToGigabyte(q resource.Quantity) int32 { + return int32(q.ScaledValue(0) / (1 * constants.Gigabyte)) } diff --git a/pkg/cluster/volumes_test.go b/pkg/cluster/volumes_test.go index 1f648712b..51ac92e06 100644 --- a/pkg/cluster/volumes_test.go +++ b/pkg/cluster/volumes_test.go @@ -13,7 +13,6 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/golang/mock/gomock" - "github.com/stretchr/testify/assert" "github.com/zalando/postgres-operator/mocks" acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1" @@ -25,9 +24,9 @@ import ( ) type testVolume struct { - size int64 - iops int64 - throughtput int64 + size int32 + iops int32 + throughtput int32 volType string } @@ -121,7 +120,7 @@ func TestQuantityToGigabyte(t *testing.T) { tests := []struct { name string quantityStr string - expected int64 + expected int32 }{ { "test with 1Gi", @@ -131,12 +130,12 @@ func TestQuantityToGigabyte(t *testing.T) { { "test with float", "1.5Gi", - int64(1), + int32(1), }, { "test with 1000Mi", "1000Mi", - int64(0), + int32(0), }, } @@ -247,8 +246,8 @@ func TestGp2Gp3Migration(t *testing.T) { }, client, acidv1.Postgresql{Spec: acidv1.PostgresSpec{Volume: acidv1.Volume{VolumeType: "gp3"}}}, logger, eventRecorder) cluster.Spec.Volume.Size = "150Gi" - cluster.Spec.Volume.Iops = aws.Int64(6000) - cluster.Spec.Volume.Throughput = aws.Int64(275) + cluster.Spec.Volume.Iops = aws.Int32(6000) + cluster.Spec.Volume.Throughput = aws.Int32(275) // set metadata, so that labels will get correct values cluster.Name = clusterName @@ -280,8 +279,8 @@ func TestGp2Gp3Migration(t *testing.T) { }, nil) // expect only gp2 volume to be modified - resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-1"), gomock.Eq(aws.String("gp3")), gomock.Nil(), gomock.Eq(aws.Int64(6000)), gomock.Eq(aws.Int64(275))).Return(nil) - resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-2"), gomock.Eq(aws.String("gp3")), gomock.Nil(), gomock.Eq(aws.Int64(6000)), gomock.Eq(aws.Int64(275))).Return(nil) + resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-1"), gomock.Eq(aws.String("gp3")), gomock.Nil(), gomock.Eq(aws.Int32(6000)), gomock.Eq(aws.Int32(275))).Return(nil) + resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-2"), gomock.Eq(aws.String("gp3")), gomock.Nil(), gomock.Eq(aws.Int32(6000)), gomock.Eq(aws.Int32(275))).Return(nil) cluster.VolumeResizer = resizer cluster.syncVolumes() @@ -343,8 +342,8 @@ func TestNoVolumeTypeChange(t *testing.T) { }, nil) // expect only gp2 volume to be modified - resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-1"), gomock.Nil(), gomock.Eq(aws.Int64(177)), gomock.Nil(), gomock.Nil()).Return(nil) - resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-2"), gomock.Nil(), gomock.Eq(aws.Int64(177)), gomock.Nil(), gomock.Nil()).Return(nil) + resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-1"), gomock.Nil(), gomock.Eq(aws.Int32(177)), gomock.Nil(), gomock.Nil()).Return(nil) + resizer.EXPECT().ModifyVolume(gomock.Eq("ebs-volume-2"), gomock.Nil(), gomock.Eq(aws.Int32(177)), gomock.Nil(), gomock.Nil()).Return(nil) cluster.VolumeResizer = resizer cluster.syncVolumes() diff --git a/pkg/util/volumes/ebs.go b/pkg/util/volumes/ebs.go index 49a4d5efa..7962a50b3 100644 --- a/pkg/util/volumes/ebs.go +++ b/pkg/util/volumes/ebs.go @@ -89,12 +89,12 @@ func (r *EBSVolumeResizer) DescribeVolumes(volumeIds []string) ([]VolumeProperti } for _, v := range volumeOutput.Volumes { - vp := VolumeProperties{VolumeID: *v.VolumeId, Size: int64(*v.Size), VolumeType: string(v.VolumeType)} + vp := VolumeProperties{VolumeID: *v.VolumeId, Size: int32(*v.Size), VolumeType: string(v.VolumeType)} if v.Iops != nil { - vp.Iops = int64(*v.Iops) + vp.Iops = int32(*v.Iops) } if v.Throughput != nil { - vp.Throughput = int64(*v.Throughput) + vp.Throughput = int32(*v.Throughput) } p = append(p, vp) } @@ -103,7 +103,7 @@ func (r *EBSVolumeResizer) DescribeVolumes(volumeIds []string) ([]VolumeProperti } // ResizeVolume actually calls AWS API to resize the EBS volume if necessary. -func (r *EBSVolumeResizer) ResizeVolume(volumeID string, newSize int64) error { +func (r *EBSVolumeResizer) ResizeVolume(volumeID string, newSize int32) error { /* first check if the volume is already of a requested size */ volumeOutput, err := r.connection.DescribeVolumes(context.TODO(), &ec2.DescribeVolumesInput{VolumeIds: []string{volumeID}}) if err != nil { @@ -113,12 +113,11 @@ func (r *EBSVolumeResizer) ResizeVolume(volumeID string, newSize int64) error { if *vol.VolumeId != volumeID { return fmt.Errorf("describe volume %q returned information about a non-matching volume %q", volumeID, *vol.VolumeId) } - sizeInt32 := int32(newSize) - if *vol.Size == sizeInt32 { + if *vol.Size == newSize { // nothing to do return nil } - input := ec2.ModifyVolumeInput{Size: &sizeInt32, VolumeId: &volumeID} + input := ec2.ModifyVolumeInput{Size: &newSize, VolumeId: &volumeID} output, err := r.connection.ModifyVolume(context.TODO(), &input) if err != nil { return fmt.Errorf("could not modify persistent volume: %v", err) @@ -154,28 +153,16 @@ func (r *EBSVolumeResizer) ResizeVolume(volumeID string, newSize int64) error { } // ModifyVolume Modify EBS volume -func (r *EBSVolumeResizer) ModifyVolume(volumeID string, newType *string, newSize *int64, iops *int64, throughput *int64) error { - var sizeInt32 *int32 - var iopsInt32 *int32 - var throughputInt32 *int32 - - input := ec2.ModifyVolumeInput{ - VolumeId: &volumeID, - } +func (r *EBSVolumeResizer) ModifyVolume(volumeID string, newType *string, newSize *int32, iops *int32, throughput *int32) error { + input := ec2.ModifyVolumeInput{VolumeId: &volumeID} if newSize != nil { - s := int32(*newSize) - sizeInt32 = &s - input.Size = sizeInt32 + input.Size = newSize } if iops != nil { - i := int32(*iops) - iopsInt32 = &i - input.Iops = iopsInt32 + input.Iops = iops } if throughput != nil { - t := int32(*throughput) - throughputInt32 = &t - input.Throughput = throughputInt32 + input.Throughput = throughput } if newType != nil { input.VolumeType = types.VolumeType(*newType) diff --git a/pkg/util/volumes/volumes.go b/pkg/util/volumes/volumes.go index 32f68c65e..501215b41 100644 --- a/pkg/util/volumes/volumes.go +++ b/pkg/util/volumes/volumes.go @@ -8,9 +8,9 @@ import v1 "k8s.io/api/core/v1" type VolumeProperties struct { VolumeID string VolumeType string - Size int64 - Iops int64 - Throughput int64 + Size int32 + Iops int32 + Throughput int32 } // VolumeResizer defines the set of methods used to implememnt provider-specific resizing of persistent volumes. @@ -20,8 +20,8 @@ type VolumeResizer interface { VolumeBelongsToProvider(pv *v1.PersistentVolume) bool GetProviderVolumeID(pv *v1.PersistentVolume) (string, error) ExtractVolumeID(volumeID string) (string, error) - ResizeVolume(providerVolumeID string, newSize int64) error - ModifyVolume(providerVolumeID string, newType *string, newSize *int64, iops *int64, throughput *int64) error + ResizeVolume(providerVolumeID string, newSize int32) error + ModifyVolume(providerVolumeID string, newType *string, newSize *int32, iops *int32, throughput *int32) error DisconnectFromProvider() error DescribeVolumes(providerVolumesID []string) ([]VolumeProperties, error) } diff --git a/ui/manifests/deployment.yaml b/ui/manifests/deployment.yaml index e19a850b8..eb48d9605 100644 --- a/ui/manifests/deployment.yaml +++ b/ui/manifests/deployment.yaml @@ -70,8 +70,8 @@ spec: "cost_memory": 0.014375, "free_iops": 3000, "free_throughput": 125, - "limit_iops": 16000, - "limit_throughput": 1000, + "limit_iops": 80000, + "limit_throughput": 2000, "postgresql_versions": [ "18", "17", diff --git a/ui/operator_ui/main.py b/ui/operator_ui/main.py index c91450d4c..469fe66ed 100644 --- a/ui/operator_ui/main.py +++ b/ui/operator_ui/main.py @@ -86,9 +86,9 @@ COST_ELB = float(getenv('COST_ELB', 0.03)) # per hour # maximum and limitation of IOPS and throughput FREE_IOPS = float(getenv('FREE_IOPS', 3000)) -LIMIT_IOPS = float(getenv('LIMIT_IOPS', 16000)) +LIMIT_IOPS = float(getenv('LIMIT_IOPS', 80000)) FREE_THROUGHPUT = float(getenv('FREE_THROUGHPUT', 125)) -LIMIT_THROUGHPUT = float(getenv('LIMIT_THROUGHPUT', 1000)) +LIMIT_THROUGHPUT = float(getenv('LIMIT_THROUGHPUT', 2000)) # get the default value of core and memory DEFAULT_MEMORY = getenv('DEFAULT_MEMORY', '300Mi') DEFAULT_MEMORY_LIMIT = getenv('DEFAULT_MEMORY_LIMIT', '300Mi') diff --git a/ui/run_local.sh b/ui/run_local.sh index 59729a92a..13d8f053a 100755 --- a/ui/run_local.sh +++ b/ui/run_local.sh @@ -28,8 +28,8 @@ default_operator_ui_config='{ "cost_memory": 0.014375, "free_iops": 3000, "free_throughput": 125, - "limit_iops": 16000, - "limit_throughput": 1000, + "limit_iops": 80000, + "limit_throughput": 2000, "postgresql_versions": [ "18", "17",