Set maximum CPU and Memory requests on K8s (#1959)

* Set maximum CPU and Memory requests on K8s
This commit is contained in:
Jociele Padilha
2022-07-28 14:18:27 +02:00
committed by GitHub
parent 1c80ac0acd
commit b41daf4f76
14 changed files with 188 additions and 20 deletions
+8
View File
@@ -1471,6 +1471,14 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{
Type: "string",
Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
},
"max_cpu_request": {
Type: "string",
Pattern: "^(\\d+m|\\d+(\\.\\d{1,3})?)$",
},
"max_memory_request": {
Type: "string",
Pattern: "^(\\d+(e\\d+)?|\\d+(\\.\\d+)?(e\\d+)?[EPTGMK]i?)$",
},
"min_cpu_limit": {
Type: "string",
Pattern: "^(\\d+m|\\d+(\\.\\d{1,3})?)$",
@@ -109,6 +109,8 @@ type PostgresPodResourcesDefaults struct {
DefaultMemoryLimit string `json:"default_memory_limit,omitempty"`
MinCPULimit string `json:"min_cpu_limit,omitempty"`
MinMemoryLimit string `json:"min_memory_limit,omitempty"`
MaxCPURequest string `json:"max_cpu_request,omitempty"`
MaxMemoryRequest string `json:"max_memory_request,omitempty"`
}
// OperatorTimeouts defines the timeout of ResourceCheck, PodWait, ReadyWait
+33
View File
@@ -183,6 +183,32 @@ func (c *Cluster) enforceMinResourceLimits(resources *v1.ResourceRequirements) e
return nil
}
func (c *Cluster) enforceMaxResourceRequests(resources *v1.ResourceRequirements) error {
var (
err error
)
cpuRequest := resources.Requests[v1.ResourceCPU]
maxCPURequest := c.OpConfig.MaxCPURequest
maxCPU, err := util.MinResource(maxCPURequest, cpuRequest.String())
if err != nil {
return fmt.Errorf("could not compare defined CPU request %s for %q container with configured maximum value %s: %v",
cpuRequest.String(), constants.PostgresContainerName, maxCPURequest, err)
}
resources.Requests[v1.ResourceCPU] = maxCPU
memoryRequest := resources.Requests[v1.ResourceMemory]
maxMemoryRequest := c.OpConfig.MaxMemoryRequest
maxMemory, err := util.MinResource(maxMemoryRequest, memoryRequest.String())
if err != nil {
return fmt.Errorf("could not compare defined memory request %s for %q container with configured maximum value %s: %v",
memoryRequest.String(), constants.PostgresContainerName, maxMemoryRequest, err)
}
resources.Requests[v1.ResourceMemory] = maxMemory
return nil
}
func setMemoryRequestToLimit(resources *v1.ResourceRequirements, containerName string, logger *logrus.Entry) {
requests := resources.Requests[v1.ResourceMemory]
@@ -260,6 +286,13 @@ func (c *Cluster) generateResourceRequirements(
setMemoryRequestToLimit(&result, containerName, c.logger)
}
// enforce maximum cpu and memory requests for Postgres containers only
if containerName == constants.PostgresContainerName {
if err = c.enforceMaxResourceRequests(&result); err != nil {
return nil, fmt.Errorf("could not enforce maximum resource requests: %v", err)
}
}
return &result, nil
}
+73 -6
View File
@@ -1841,8 +1841,10 @@ func TestSidecars(t *testing.T) {
},
Resources: config.Resources{
DefaultCPURequest: "200m",
MaxCPURequest: "300m",
DefaultCPULimit: "500m",
DefaultMemoryRequest: "0.7Gi",
MaxMemoryRequest: "1.0Gi",
DefaultMemoryLimit: "1.3Gi",
},
SidecarImages: map[string]string{
@@ -2128,8 +2130,10 @@ func TestGenerateService(t *testing.T) {
},
Resources: config.Resources{
DefaultCPURequest: "200m",
MaxCPURequest: "300m",
DefaultCPULimit: "500m",
DefaultMemoryRequest: "0.7Gi",
MaxMemoryRequest: "1.0Gi",
DefaultMemoryLimit: "1.3Gi",
},
SidecarImages: map[string]string{
@@ -2415,18 +2419,21 @@ func TestGenerateResourceRequirements(t *testing.T) {
roleLabel := "spilo-role"
sidecarName := "postgres-exporter"
// two test cases will call enforceMinResourceLimits which emits 2 events per call
// hence bufferSize of 4 is required
newEventRecorder := record.NewFakeRecorder(4)
// enforceMinResourceLimits will be called 2 twice emitting 4 events (2x cpu, 2x memory raise)
// enforceMaxResourceRequests will be called 4 times emitting 6 events (2x cpu, 4x memory cap)
// hence event bufferSize of 10 is required
newEventRecorder := record.NewFakeRecorder(10)
configResources := config.Resources{
ClusterLabels: map[string]string{"application": "spilo"},
ClusterNameLabel: clusterNameLabel,
DefaultCPURequest: "100m",
DefaultCPULimit: "1",
MaxCPURequest: "500m",
MinCPULimit: "250m",
DefaultMemoryRequest: "100Mi",
DefaultMemoryLimit: "500Mi",
MinCPULimit: "250m",
MaxMemoryRequest: "1Gi",
MinMemoryLimit: "250Mi",
PodRoleLabel: roleLabel,
}
@@ -2558,6 +2565,10 @@ func TestGenerateResourceRequirements(t *testing.T) {
Namespace: namespace,
},
Spec: acidv1.PostgresSpec{
Resources: &acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{Memory: "200Mi"},
ResourceLimits: acidv1.ResourceDescription{Memory: "300Mi"},
},
TeamID: "acid",
Volume: acidv1.Volume{
Size: "1G",
@@ -2565,8 +2576,8 @@ func TestGenerateResourceRequirements(t *testing.T) {
},
},
expectedResources: acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "100m", Memory: "500Mi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "1", Memory: "500Mi"},
ResourceRequests: acidv1.ResourceDescription{CPU: "100m", Memory: "300Mi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "1", Memory: "300Mi"},
},
},
{
@@ -2691,6 +2702,62 @@ func TestGenerateResourceRequirements(t *testing.T) {
ResourceLimits: acidv1.ResourceDescription{CPU: "100m", Memory: "100Mi"},
},
},
{
subTest: "test enforcing max cpu and memory requests",
config: config.Config{
Resources: configResources,
PodManagementPolicy: "ordered_ready",
SetMemoryRequestToLimit: false,
},
pgSpec: acidv1.Postgresql{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName,
Namespace: namespace,
},
Spec: acidv1.PostgresSpec{
Resources: &acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "1", Memory: "2Gi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "2", Memory: "4Gi"},
},
TeamID: "acid",
Volume: acidv1.Volume{
Size: "1G",
},
},
},
expectedResources: acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "500m", Memory: "1Gi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "2", Memory: "4Gi"},
},
},
{
subTest: "test SetMemoryRequestToLimit flag but raise only until max memory request",
config: config.Config{
Resources: configResources,
PodManagementPolicy: "ordered_ready",
SetMemoryRequestToLimit: true,
},
pgSpec: acidv1.Postgresql{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName,
Namespace: namespace,
},
Spec: acidv1.PostgresSpec{
Resources: &acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{Memory: "500Mi"},
ResourceLimits: acidv1.ResourceDescription{Memory: "2Gi"},
},
TeamID: "acid",
Volume: acidv1.Volume{
Size: "1G",
},
},
},
expectedResources: acidv1.Resources{
ResourceRequests: acidv1.ResourceDescription{CPU: "100m", Memory: "1Gi"},
ResourceLimits: acidv1.ResourceDescription{CPU: "1", Memory: "2Gi"},
},
},
}
for _, tt := range tests {
+2
View File
@@ -129,6 +129,8 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
result.DefaultMemoryLimit = util.Coalesce(fromCRD.PostgresPodResources.DefaultMemoryLimit, "500Mi")
result.MinCPULimit = util.Coalesce(fromCRD.PostgresPodResources.MinCPULimit, "250m")
result.MinMemoryLimit = util.Coalesce(fromCRD.PostgresPodResources.MinMemoryLimit, "250Mi")
result.MaxCPURequest = fromCRD.PostgresPodResources.MaxCPURequest
result.MaxMemoryRequest = fromCRD.PostgresPodResources.MaxMemoryRequest
// timeout config
result.ResourceCheckInterval = util.CoalesceDuration(time.Duration(fromCRD.Timeouts.ResourceCheckInterval), "3s")
+2
View File
@@ -54,6 +54,8 @@ type Resources struct {
DefaultMemoryLimit string `name:"default_memory_limit" default:"500Mi"`
MinCPULimit string `name:"min_cpu_limit" default:"250m"`
MinMemoryLimit string `name:"min_memory_limit" default:"250Mi"`
MaxCPURequest string `name:"max_cpu_request"`
MaxMemoryRequest string `name:"max_memory_request"`
PodEnvironmentConfigMap spec.NamespacedName `name:"pod_environment_configmap"`
PodEnvironmentSecret string `name:"pod_environment_secret"`
NodeReadinessLabel map[string]string `name:"node_readiness_label" default:""`
+18
View File
@@ -367,3 +367,21 @@ func IsSmallerQuantity(requestStr, limitStr string) (bool, error) {
return request.Cmp(limit) == -1, nil
}
func MinResource(maxRequestStr, requestStr string) (resource.Quantity, error) {
isSmaller, err := IsSmallerQuantity(maxRequestStr, requestStr)
if isSmaller && err == nil {
maxRequest, err := resource.ParseQuantity(maxRequestStr)
if err != nil {
return maxRequest, err
}
return maxRequest, nil
}
request, err := resource.ParseQuantity(requestStr)
if err != nil {
return request, err
}
return request, nil
}