Merge pull request #1847 from sosan/feature-1631-add-hpa

Feature 1635 - added labels to generate HPA
This commit is contained in:
Kubernetes Prow Robot 2024-04-19 05:24:52 -07:00 committed by GitHub
commit cdcb0e7f61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 1565 additions and 3 deletions

View File

@ -214,6 +214,10 @@ The currently supported options are:
| kompose.init.containers.name | kubernetes init container name |
| kompose.init.containers.image | kubernetes init container image |
| kompose.init.containers.command | kubernetes init container commands |
| kompose.hpa.replicas.min | defines Horizontal Pod Autoscaler minimum number of pod replicas |
| kompose.hpa.replicas.max | defines Horizontal Pod Autoscaler maximum number of pod replicas |
| kompose.hpa.cpu | defines Horizontal Pod Autoscaler cpu utilization trigger |
| kompose.hpa.memory | defines Horizontal Pod Autoscaler memory utilization trigger |
**Note**: `kompose.service.type` label should be defined with `ports` only (except for headless service), otherwise `kompose` will fail.
@ -512,6 +516,55 @@ services:
kompose.init.containers.image: perl
```
- `kompose.hpa.replicas.min` defines the floor for the number of replicas that the HPA can scale down to during a scaling event. Default value is set to 1. This means that, regardless of the load on the system, the HPA will always maintain at least one replica. More info: [HPA Min Replicas](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics).
For example:
```yaml
services:
pgadmin:
image: postgres
labels:
kompose.hpa.replicas.min: 1
```
- `kompose.hpa.replicas.max` defines the upper limit for the number of replicas that the HPA can create during a scaling event. Default value is set to 3. This default value serves as a safeguard, providing a conservative starting point for your HPA configuration. More info: [HPA Max Replicas](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics).
For example:
```yaml
services:
pgadmin:
image: postgres
labels:
kompose.hpa.replicas.max: 10
```
- `kompose.hpa.cpu` defines % cpu utilization that triggers to scale the number of pods. It is represented as a percentage of a resource. Default value is set to 50. This default value serves as a safeguard, providing a conservative starting point for your HPA configuration. More info: [HPA CPU Utilization](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics).
For example:
```yaml
services:
pgadmin:
image: postgres
labels:
kompose.hpa.cpu: 50
```
- `kompose.hpa.memory` defines memory utilization that triggers to scale the number of pods. It is represented as a percentage of a resource. Default value is set to 70. This default value serves as a safeguard, providing a conservative starting point for your HPA configuration. More info: [HPA Memory Utilization](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#autoscaling-on-multiple-metrics-and-custom-metrics).
For example:
```yaml
services:
pgadmin:
image: postgres
labels:
kompose.hpa.memory: 50
```
## Restart
If you want to create normal pods without controller you can use `restart` construct of compose to define that. Follow table below to see what happens on the `restart` value.

View File

@ -93,6 +93,14 @@ const (
LabelInitContainerImage = "kompose.init.containers.image"
// LabelInitContainerCommand defines commands
LabelInitContainerCommand = "kompose.init.containers.command"
// LabelHpaMinReplicas defines min pod replicas
LabelHpaMinReplicas = "kompose.hpa.replicas.min"
// LabelHpaMaxReplicas defines max pod replicas
LabelHpaMaxReplicas = "kompose.hpa.replicas.max"
// LabelHpaCpu defines scaling decisions based on CPU utilization
LabelHpaCPU = "kompose.hpa.cpu"
// LabelHpaMemory defines scaling decisions based on memory utilization
LabelHpaMemory = "kompose.hpa.memory"
)
// load environment variables from compose file

View File

@ -41,6 +41,7 @@ import (
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
appsv1 "k8s.io/api/apps/v1"
hpa "k8s.io/api/autoscaling/v2beta2"
api "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -48,6 +49,29 @@ import (
"k8s.io/apimachinery/pkg/runtime"
)
// Default values for Horizontal Pod Autoscaler (HPA)
const (
DefaultMinReplicas = 1
DefaultMaxReplicas = 3
DefaultCPUUtilization = 50
DefaultMemoryUtilization = 70
)
// LabelKeys are the keys for HPA related labels in the service
var LabelKeys = []string{
compose.LabelHpaCPU,
compose.LabelHpaMemory,
compose.LabelHpaMinReplicas,
compose.LabelHpaMaxReplicas,
}
type HpaValues struct {
MinReplicas int32
MaxReplicas int32
CPUtilization int32
MemoryUtilization int32
}
/**
* Generate Helm Chart configuration
*/
@ -1030,3 +1054,122 @@ func parseContainerCommandsFromStr(line string) []string {
}
return commands
}
// searchHPAValues is useful to check if labels
// contains any labels related to Horizontal Pod Autoscaler
func searchHPAValues(labels map[string]string) bool {
for _, value := range LabelKeys {
if _, ok := labels[value]; ok {
return true
}
}
return false
}
// createHPAResources creates a HorizontalPodAutoscaler (HPA) resource
// It sets the number of replicas in the service to 0 because
// the number of replicas will be managed by the HPA
func createHPAResources(name string, service *kobject.ServiceConfig) hpa.HorizontalPodAutoscaler {
valuesHpa := getResourceHpaValues(service)
service.Replicas = 0
metrics := getHpaMetricSpec(valuesHpa)
scalerSpecs := hpa.HorizontalPodAutoscaler{
TypeMeta: metav1.TypeMeta{
Kind: "HorizontalPodAutoscaler",
APIVersion: "autoscaling/v2",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: hpa.HorizontalPodAutoscalerSpec{
ScaleTargetRef: hpa.CrossVersionObjectReference{
Kind: "Deployment",
Name: name,
APIVersion: "apps/v1",
},
MinReplicas: &valuesHpa.MinReplicas,
MaxReplicas: valuesHpa.MaxReplicas,
Metrics: metrics,
},
}
return scalerSpecs
}
// getResourceHpaValues retrieves the min/max replicas and CPU/memory utilization values
// control if maxReplicas is less than minReplicas
func getResourceHpaValues(service *kobject.ServiceConfig) HpaValues {
minReplicas := getHpaValue(service, compose.LabelHpaMinReplicas, DefaultMinReplicas)
maxReplicas := getHpaValue(service, compose.LabelHpaMaxReplicas, DefaultMaxReplicas)
if maxReplicas < minReplicas {
log.Warnf("maxReplicas %d is less than minReplicas %d. Using minReplicas value %d", maxReplicas, minReplicas, minReplicas)
maxReplicas = minReplicas
}
cpuUtilization := validatePercentageMetric(service, compose.LabelHpaCPU, DefaultCPUUtilization)
memoryUtilization := validatePercentageMetric(service, compose.LabelHpaMemory, DefaultMemoryUtilization)
return HpaValues{
MinReplicas: minReplicas,
MaxReplicas: maxReplicas,
CPUtilization: cpuUtilization,
MemoryUtilization: memoryUtilization,
}
}
// validatePercentageMetric validates the CPU or memory metrics value
// ensuring that it falls within the acceptable range [1, 100].
func validatePercentageMetric(service *kobject.ServiceConfig, metricLabel string, defaultValue int32) int32 {
metricValue := getHpaValue(service, metricLabel, defaultValue)
if metricValue > 100 || metricValue < 1 {
log.Warnf("Metric value %d is not within the acceptable range [1, 100]. Using default value %d", metricValue, defaultValue)
return defaultValue
}
return metricValue
}
// getHpaValue convert the label value to integer
// If the label is not present or the conversion fails
// it returns the provided default value
func getHpaValue(service *kobject.ServiceConfig, label string, defaultValue int32) int32 {
valueFromLabel, err := strconv.Atoi(service.Labels[label])
if err != nil || valueFromLabel < 0 {
log.Warnf("Error converting label %s. Using default value %d", label, defaultValue)
return defaultValue
}
return int32(valueFromLabel)
}
// getHpaMetricSpec returns a list of metric specs for the HPA resource
// Target type is hardcoded to hpa.UtilizationMetricType
// Each MetricSpec specifies the type metric CPU/memory and average utilization value
// to trigger scaling
func getHpaMetricSpec(hpaValues HpaValues) []hpa.MetricSpec {
var metrics []hpa.MetricSpec
if hpaValues.CPUtilization > 0 {
metrics = append(metrics, hpa.MetricSpec{
Type: hpa.ResourceMetricSourceType,
Resource: &hpa.ResourceMetricSource{
Name: api.ResourceCPU,
Target: hpa.MetricTarget{
Type: hpa.UtilizationMetricType,
AverageUtilization: &hpaValues.CPUtilization,
},
},
})
}
if hpaValues.MemoryUtilization > 0 {
metrics = append(metrics, hpa.MetricSpec{
Type: hpa.ResourceMetricSourceType,
Resource: &hpa.ResourceMetricSource{
Name: api.ResourceMemory,
Target: hpa.MetricTarget{
Type: hpa.UtilizationMetricType,
AverageUtilization: &hpaValues.MemoryUtilization,
},
},
})
}
return metrics
}

File diff suppressed because it is too large Load Diff

View File

@ -1654,6 +1654,10 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
return nil, err
}
}
err = k.configHorizontalPodScaler(name, service, opt, &objects)
if err != nil {
return nil, errors.Wrap(err, "Error creating Kubernetes HPA")
}
allobjects = append(allobjects, objects...)
}
@ -1718,3 +1722,16 @@ func (k *Kubernetes) UpdateController(obj runtime.Object, updateTemplate func(*a
}
return nil
}
// configHorizontalPodScaler create Hpa resource also append to the objects
// first checks if the service labels contain any HPA labels using the searchHPAValues
func (k *Kubernetes) configHorizontalPodScaler(name string, service kobject.ServiceConfig, opt kobject.ConvertOptions, objects *[]runtime.Object) (err error) {
found := searchHPAValues(service.Labels)
if !found {
return nil
}
hpa := createHPAResources(name, &service)
*objects = append(*objects, &hpa)
return nil
}

View File

@ -344,3 +344,8 @@ convert::expect_success "$os_cmd" "$os_output" || exit 1
k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/initcontainer/compose.yaml convert --stdout --with-kompose-annotation=false"
k8s_output="$KOMPOSE_ROOT/script/test/fixtures/initcontainer/output-k8s.yaml"
convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1
# Test HPA
k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/hpa/compose.yaml convert --stdout --with-kompose-annotation=false"
k8s_output="$KOMPOSE_ROOT/script/test/fixtures/hpa/output-k8s.yaml"
convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1

10
script/test/fixtures/hpa/compose.yaml vendored Normal file
View File

@ -0,0 +1,10 @@
version: "3.8"
services:
web:
image: nginx
labels:
kompose.hpa.cpu: 50
kompose.hpa.memory: 70
kompose.hpa.replicas.min: 1
kompose.hpa.replicas.max: 10

View File

@ -0,0 +1,49 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
io.kompose.service: web
name: web
spec:
replicas: 1
selector:
matchLabels:
io.kompose.service: web
template:
metadata:
labels:
io.kompose.network/hpa-default: "true"
io.kompose.service: web
spec:
containers:
- image: nginx
name: web
restartPolicy: Always
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web
spec:
maxReplicas: 10
metrics:
- resource:
name: cpu
target:
averageUtilization: 50
type: Utilization
type: Resource
- resource:
name: memory
target:
averageUtilization: 70
type: Utilization
type: Resource
minReplicas: 1
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web