Skip to content

Commit 36e7998

Browse files
authored
Merge pull request #51 from AechGG/feat/issue-48/hpa
feat: issue 48 adding Horizontal Pod Autoscaler resource
2 parents 71c20db + 077e20b commit 36e7998

File tree

4 files changed

+241
-0
lines changed

4 files changed

+241
-0
lines changed

charts/k8s-service/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ The following resources will be deployed with this Helm Chart, depending on whic
3737
- `Ingress`: The `Ingress` resource providing host and path routing rules to the `Service` for the deployed `Ingress`
3838
controller in the cluster. Created only if you configure the `ingress` input (and set
3939
`ingress.enabled = true`).
40+
- `Horizontal Pod Autoscaler`: The `Horizontal Pod Autoscaler` automatically scales the number of pods in a replication
41+
controller, deployment, replica set or stateful set based on observed CPU or memory utilization.
42+
Created only if the user sets `horizontalPodAutoscaler.enabled = true`.
4043
- `PodDisruptionBudget`: The `PodDisruptionBudget` resource that specifies a disruption budget for the `Pods` managed by
4144
the `Deployment`. This manages how many pods can be disrupted by a voluntary disruption (e.g
4245
node maintenance). Created if you specify a non-zero value for the `minPodsAvailable` input
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{{- if .Values.horizontalPodAutoscaler.enabled }}
2+
apiVersion: autoscaling/v2beta2
3+
kind: HorizontalPodAutoscaler
4+
metadata:
5+
name: {{ include "k8s-service.fullname" . }}
6+
namespace: {{ $.Release.Namespace }}
7+
spec:
8+
scaleTargetRef:
9+
apiVersion: apps/v1
10+
kind: Deployment
11+
name: {{ include "k8s-service.fullname" . }}
12+
minReplicas: {{ .Values.horizontalPodAutoscaler.minReplicas }}
13+
maxReplicas: {{ .Values.horizontalPodAutoscaler.maxReplicas }}
14+
metrics:
15+
{{ if .Values.horizontalPodAutoscaler.avgCpuUtilization }}
16+
- type: Resource
17+
resource:
18+
name: cpu
19+
target:
20+
type: Utilization
21+
averageUtilization: {{ .Values.horizontalPodAutoscaler.avgCpuUtilization }}
22+
{{- end }}
23+
{{ if .Values.horizontalPodAutoscaler.avgMemoryUtilization }}
24+
- type: Resource
25+
resource:
26+
name: memory
27+
target:
28+
type: Utilization
29+
averageUtilization: {{ .Values.horizontalPodAutoscaler.avgMemoryUtilization }}
30+
{{- end }}
31+
{{- end }}

charts/k8s-service/values.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,23 @@ imagePullSecrets: []
404404
serviceAccount:
405405
name: ""
406406

407+
# horizontalPodAutoscaler is a map that configures the Horizontal Pod Autoscaler information for this pod
408+
# The expected keys of hpa are:
409+
# - enabled (bool) : Whether or not Horizontal Pod Autoscaler should be created, if false the
410+
# Horizontal Pod Autoscaler will not be created
411+
# - minReplicas (int) : The minimum amount of replicas allowed
412+
# - maxReplicas (int) : The maximum amount of replicas allowed
413+
# - avgCpuUtilization (int) : The target average CPU utilization to be used with the metrics
414+
# - avgMemoryUtilization (int) : The target average Memory utilization to be used with the metrics
415+
#
416+
# The default config will not create the Horizontal Pod Autoscaler by setting enabled = false, the default values are
417+
# set so if enabled is true the horizontalPodAutoscaler has valid values.
418+
horizontalPodAutoscaler:
419+
enabled: false
420+
minReplicas: 1
421+
maxReplicas: 10
422+
423+
407424
#----------------------------------------------------------------------------------------------------------------------
408425
# AWS SPECIFIC VALUES
409426
# These input values relate to AWS specific features, such as those relating to EKS and the AWS ALB Ingress Controller.
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// +build all tpl
2+
3+
// NOTE: We use build flags to differentiate between template tests and integration tests so that you can conveniently
4+
// run just the template tests. See the test README for more information.
5+
6+
package test
7+
8+
import (
9+
"path/filepath"
10+
"strconv"
11+
"testing"
12+
13+
"github.com/ghodss/yaml"
14+
"github.com/gruntwork-io/terratest/modules/helm"
15+
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
17+
)
18+
19+
// Test that setting horizontalPodAutoscaler.enabled = true will cause the helm template to render the Horizontal Pod
20+
// Autoscaler resource with both metrics
21+
func TestK8SServiceHorizontalPodAutoscalerCreateTrueCreatesHorizontalPodAutoscalerWithAllMetrics(t *testing.T) {
22+
t.Parallel()
23+
minReplicas := "20"
24+
maxReplicas := "30"
25+
avgCpuUtil := "55"
26+
avgMemoryUtil := "65"
27+
28+
helmChartPath, err := filepath.Abs(filepath.Join("..", "charts", "k8s-service"))
29+
require.NoError(t, err)
30+
31+
// We make sure to pass in the linter_values.yaml values file, which we assume has all the required values defined.
32+
// We then use SetValues to override all the defaults.
33+
options := &helm.Options{
34+
ValuesFiles: []string{filepath.Join("..", "charts", "k8s-service", "linter_values.yaml")},
35+
SetValues: map[string]string{
36+
"horizontalPodAutoscaler.enabled": "true",
37+
"horizontalPodAutoscaler.minReplicas": minReplicas,
38+
"horizontalPodAutoscaler.maxReplicas": maxReplicas,
39+
"horizontalPodAutoscaler.avgCpuUtilization": avgCpuUtil,
40+
"horizontalPodAutoscaler.avgMemoryUtilization": avgMemoryUtil,
41+
},
42+
}
43+
out := helm.RenderTemplate(t, options, helmChartPath, []string{"templates/horizontalpodautoscaler.yaml"})
44+
45+
// We take the output and render it to a map to validate it has created a Horizontal Pod Autoscaler output or not
46+
rendered := map[string]interface{}{}
47+
err = yaml.Unmarshal([]byte(out), &rendered)
48+
assert.NoError(t, err)
49+
assert.NotEqual(t, 0, len(rendered))
50+
min, err := strconv.ParseFloat(minReplicas, 64)
51+
max, err := strconv.ParseFloat(maxReplicas, 64)
52+
avgCpu, err := strconv.ParseFloat(avgCpuUtil, 64)
53+
avgMem, err := strconv.ParseFloat(avgMemoryUtil, 64)
54+
assert.Equal(t, min, rendered["spec"].(map[string]interface{})["minReplicas"])
55+
assert.Equal(t, max, rendered["spec"].(map[string]interface{})["maxReplicas"])
56+
assert.Equal(t, avgCpu, rendered["spec"].(map[string]interface{})["metrics"].([]interface{})[0].(map[string]interface{})["resource"].(map[string]interface{})["target"].(map[string]interface{})["averageUtilization"])
57+
assert.Equal(t, avgMem, rendered["spec"].(map[string]interface{})["metrics"].([]interface{})[1].(map[string]interface{})["resource"].(map[string]interface{})["target"].(map[string]interface{})["averageUtilization"])
58+
}
59+
60+
// Test that setting horizontalPodAutoscaler.enabled = false will cause the helm template to not render the Horizontal
61+
// Pod Autoscaler resource
62+
func TestK8SServiceHorizontalPodAutoscalerCreateFalse(t *testing.T) {
63+
t.Parallel()
64+
65+
helmChartPath, err := filepath.Abs(filepath.Join("..", "charts", "k8s-service"))
66+
require.NoError(t, err)
67+
68+
// We make sure to pass in the linter_values.yaml values file, which we assume has all the required values defined.
69+
// We then use SetValues to override all the defaults.
70+
options := &helm.Options{
71+
ValuesFiles: []string{filepath.Join("..", "charts", "k8s-service", "linter_values.yaml")},
72+
SetValues: map[string]string{
73+
"horizontalPodAutoscaler.enabled": "false",
74+
},
75+
}
76+
out := helm.RenderTemplate(t, options, helmChartPath, []string{"templates/horizontalpodautoscaler.yaml"})
77+
78+
// We take the output and render it to a map to validate it has created a Horizontal Pod Autoscaler output or not
79+
rendered := map[string]interface{}{}
80+
err = yaml.Unmarshal([]byte(out), &rendered)
81+
assert.NoError(t, err)
82+
assert.Equal(t, 0, len(rendered))
83+
}
84+
85+
// Test that setting horizontalPodAutoscaler.enabled = true will cause the helm template to render the Horizontal Pod
86+
// Autoscaler resource with the cpu metric
87+
func TestK8SServiceHorizontalPodAutoscalerCreateTrueCreatesHorizontalPodAutoscalerWithCpuMetric(t *testing.T) {
88+
t.Parallel()
89+
minReplicas := "20"
90+
maxReplicas := "30"
91+
avgCpuUtil := "55"
92+
93+
helmChartPath, err := filepath.Abs(filepath.Join("..", "charts", "k8s-service"))
94+
require.NoError(t, err)
95+
96+
// We make sure to pass in the linter_values.yaml values file, which we assume has all the required values defined.
97+
// We then use SetValues to override all the defaults.
98+
options := &helm.Options{
99+
ValuesFiles: []string{filepath.Join("..", "charts", "k8s-service", "linter_values.yaml")},
100+
SetValues: map[string]string{
101+
"horizontalPodAutoscaler.enabled": "true",
102+
"horizontalPodAutoscaler.minReplicas": minReplicas,
103+
"horizontalPodAutoscaler.maxReplicas": maxReplicas,
104+
"horizontalPodAutoscaler.avgCpuUtilization": avgCpuUtil,
105+
},
106+
}
107+
out := helm.RenderTemplate(t, options, helmChartPath, []string{"templates/horizontalpodautoscaler.yaml"})
108+
109+
// We take the output and render it to a map to validate it has created a Horizontal Pod Autoscaler output or not
110+
rendered := map[string]interface{}{}
111+
err = yaml.Unmarshal([]byte(out), &rendered)
112+
assert.NoError(t, err)
113+
assert.NotEqual(t, 0, len(rendered))
114+
min, err := strconv.ParseFloat(minReplicas, 64)
115+
max, err := strconv.ParseFloat(maxReplicas, 64)
116+
avgCpu, err := strconv.ParseFloat(avgCpuUtil, 64)
117+
assert.Equal(t, min, rendered["spec"].(map[string]interface{})["minReplicas"])
118+
assert.Equal(t, max, rendered["spec"].(map[string]interface{})["maxReplicas"])
119+
assert.Equal(t, avgCpu, rendered["spec"].(map[string]interface{})["metrics"].([]interface{})[0].(map[string]interface{})["resource"].(map[string]interface{})["target"].(map[string]interface{})["averageUtilization"])
120+
}
121+
122+
// Test that setting horizontalPodAutoscaler.enabled = true will cause the helm template to render the Horizontal Pod
123+
// Autoscaler resource with the memory metric
124+
func TestK8SServiceHorizontalPodAutoscalerCreateTrueCreatesHorizontalPodAutoscalerWithMemoryMetric(t *testing.T) {
125+
t.Parallel()
126+
minReplicas := "20"
127+
maxReplicas := "30"
128+
avgMemoryUtil := "65"
129+
130+
helmChartPath, err := filepath.Abs(filepath.Join("..", "charts", "k8s-service"))
131+
require.NoError(t, err)
132+
133+
// We make sure to pass in the linter_values.yaml values file, which we assume has all the required values defined.
134+
// We then use SetValues to override all the defaults.
135+
options := &helm.Options{
136+
ValuesFiles: []string{filepath.Join("..", "charts", "k8s-service", "linter_values.yaml")},
137+
SetValues: map[string]string{
138+
"horizontalPodAutoscaler.enabled": "true",
139+
"horizontalPodAutoscaler.minReplicas": minReplicas,
140+
"horizontalPodAutoscaler.maxReplicas": maxReplicas,
141+
"horizontalPodAutoscaler.avgMemoryUtilization": avgMemoryUtil,
142+
},
143+
}
144+
out := helm.RenderTemplate(t, options, helmChartPath, []string{"templates/horizontalpodautoscaler.yaml"})
145+
146+
// We take the output and render it to a map to validate it has created a Horizontal Pod Autoscaler output or not
147+
rendered := map[string]interface{}{}
148+
err = yaml.Unmarshal([]byte(out), &rendered)
149+
assert.NoError(t, err)
150+
assert.NotEqual(t, 0, len(rendered))
151+
min, err := strconv.ParseFloat(minReplicas, 64)
152+
max, err := strconv.ParseFloat(maxReplicas, 64)
153+
avgMem, err := strconv.ParseFloat(avgMemoryUtil, 64)
154+
assert.Equal(t, min, rendered["spec"].(map[string]interface{})["minReplicas"])
155+
assert.Equal(t, max, rendered["spec"].(map[string]interface{})["maxReplicas"])
156+
assert.Equal(t, avgMem, rendered["spec"].(map[string]interface{})["metrics"].([]interface{})[0].(map[string]interface{})["resource"].(map[string]interface{})["target"].(map[string]interface{})["averageUtilization"])
157+
}
158+
159+
// Test that setting horizontalPodAutoscaler.enabled = true will cause the helm template to render the Horizontal Pod
160+
// Autoscaler resource with the no metrics
161+
func TestK8SServiceHorizontalPodAutoscalerCreateTrueCreatesHorizontalPodAutoscalerWithNoMetrics(t *testing.T) {
162+
t.Parallel()
163+
minReplicas := "20"
164+
maxReplicas := "30"
165+
166+
helmChartPath, err := filepath.Abs(filepath.Join("..", "charts", "k8s-service"))
167+
require.NoError(t, err)
168+
169+
// We make sure to pass in the linter_values.yaml values file, which we assume has all the required values defined.
170+
// We then use SetValues to override all the defaults.
171+
options := &helm.Options{
172+
ValuesFiles: []string{filepath.Join("..", "charts", "k8s-service", "linter_values.yaml")},
173+
SetValues: map[string]string{
174+
"horizontalPodAutoscaler.enabled": "true",
175+
"horizontalPodAutoscaler.minReplicas": minReplicas,
176+
"horizontalPodAutoscaler.maxReplicas": maxReplicas,
177+
},
178+
}
179+
out := helm.RenderTemplate(t, options, helmChartPath, []string{"templates/horizontalpodautoscaler.yaml"})
180+
181+
// We take the output and render it to a map to validate it has created a Horizontal Pod Autoscaler output or not
182+
rendered := map[string]interface{}{}
183+
err = yaml.Unmarshal([]byte(out), &rendered)
184+
assert.NoError(t, err)
185+
assert.NotEqual(t, 0, len(rendered))
186+
min, err := strconv.ParseFloat(minReplicas, 64)
187+
max, err := strconv.ParseFloat(maxReplicas, 64)
188+
assert.Equal(t, min, rendered["spec"].(map[string]interface{})["minReplicas"])
189+
assert.Equal(t, max, rendered["spec"].(map[string]interface{})["maxReplicas"])
190+
}

0 commit comments

Comments
 (0)