diff --git a/.github/spell-ignore-words.txt b/.github/spell-ignore-words.txt index 5e79552..b0db462 100644 --- a/.github/spell-ignore-words.txt +++ b/.github/spell-ignore-words.txt @@ -22,3 +22,7 @@ secretKey 1h 1m 1s +NotIn +HPA +cpu +Resource diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index deb2a4b..89b4838 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/sbrunner/jsonschema2md - rev: 1.2.3 + rev: 1.3.0 hooks: - id: jsonschema2md files: values.schema.json @@ -25,7 +25,7 @@ repos: - id: trailing-whitespace - id: mixed-line-ending - repo: https://github.com/sbrunner/hooks - rev: 1.0.0 + rev: 1.1.0 hooks: - id: copyright - id: workflows-require-timeout @@ -47,11 +47,13 @@ repos: - . - tests/expected.yaml - repo: https://github.com/codespell-project/codespell - rev: v2.2.6 + rev: v2.3.0 hooks: - id: codespell + args: + - --ignore-words=.github/spell-ignore-words.txt - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.1.0 + rev: v4.0.0-alpha.8 hooks: - id: prettier additional_dependencies: @@ -68,7 +70,7 @@ repos: hooks: - id: git-check - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.6 + rev: 0.29.2 hooks: - id: check-github-actions - id: check-github-workflows @@ -105,6 +107,6 @@ repos: - --fields=description,title - --spelling=.github/spell-ignore-words.txt - repo: https://github.com/renovatebot/pre-commit-hooks - rev: 37.428.1 + rev: 38.103.1 hooks: - id: renovate-config-validator diff --git a/LICENSE b/LICENSE index 76c35ee..c67b2fc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2022-2023, Camptocamp +Copyright (c) 2022-2024, Camptocamp All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/templates/hpa.yaml b/templates/hpa.yaml new file mode 100644 index 0000000..6b78f01 --- /dev/null +++ b/templates/hpa.yaml @@ -0,0 +1,27 @@ +{{- range $serviceName, $serviceDefinition := .Values.services }} +{{- if and (eq $serviceDefinition.enabled true) (hasKey $serviceDefinition "hpa") (eq $serviceDefinition.hpa.enabled true) }} +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + {{- if hasKey $serviceDefinition.hpa "name" }} + name: {{ $serviceDefinition.hpa.name }} + {{- else }} + name: {{ include "common.fullname" (dict "root" $ "service" $serviceDefinition "serviceName" $serviceName ) }}-hpa + {{- end }} + {{- $hpaDict := deepCopy $serviceDefinition }} + {{- $hpaDict := merge $hpaDict (dict "annotations" $serviceDefinition.hpa.annotations "labels" $serviceDefinition.hpa.labels) }} + {{- include "common.metadata" (dict "root" $ "service" $hpaDict "serviceName" $serviceName) | nindent 2 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "common.fullname" (dict "root" $ "service" $serviceDefinition "serviceName" $serviceName ) }} + minReplicas: {{ $serviceDefinition.hpa.minReplicas | default 1 }} + maxReplicas: {{ $serviceDefinition.hpa.maxReplicas | default 10 }} + metrics: + {{- with $serviceDefinition.hpa.metrics }} + {{- . | toYaml | nindent 4 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/tests/expected.yaml b/tests/expected.yaml index d6ec8c5..8828a9a 100644 --- a/tests/expected.yaml +++ b/tests/expected.yaml @@ -102,6 +102,26 @@ spec: # Source: application/templates/pdb.yaml apiVersion: policy/v1 kind: PodDisruptionBudget +metadata: + name: example-application-example5 + labels: + helm.sh/chart: application + app.kubernetes.io/version: "1.0" + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: application + app.kubernetes.io/instance: example + app.kubernetes.io/component: example5 +spec: + maxUnavailable: 1 + selector: + matchLabels: + app.kubernetes.io/name: application + app.kubernetes.io/instance: example + app.kubernetes.io/component: example5 +--- +# Source: application/templates/pdb.yaml +apiVersion: policy/v1 +kind: PodDisruptionBudget metadata: name: example-application-internal labels: @@ -229,6 +249,8 @@ data: SERVICE_EXAMPLE3_CONTAINER_DD_IMAGE_TAG: "latest" SERVICE_EXAMPLE4_NAME: example-application-example4 SERVICE_EXAMPLE4_CONTAINER_MAIN_IMAGE_TAG: "latest" + SERVICE_EXAMPLE5_NAME: service-autoscaled + SERVICE_EXAMPLE5_CONTAINER_MAIN_IMAGE_TAG: "latest" SERVICE_INTERNAL_NAME: example-application-internal SERVICE_INTERNAL_CONTAINER_MAIN_IMAGE_TAG: "latest" SERVICE_JOB-ALL_NAME: example-application-job-all @@ -344,6 +366,28 @@ spec: app.kubernetes.io/instance: example app.kubernetes.io/component: example3 --- +# Source: application/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: service-autoscaled + labels: + helm.sh/chart: application + app.kubernetes.io/version: "1.0" + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: application + app.kubernetes.io/instance: example + app.kubernetes.io/component: example5 +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 80 + selector: + app.kubernetes.io/name: application + app.kubernetes.io/instance: example + app.kubernetes.io/component: example5 +--- # Source: application/templates/deployment.yaml apiVersion: apps/v1 kind: Deployment @@ -979,6 +1023,58 @@ spec: # Source: application/templates/deployment.yaml apiVersion: apps/v1 kind: Deployment +metadata: + name: example-application-example5 + labels: + helm.sh/chart: application + app.kubernetes.io/version: "1.0" + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: application + app.kubernetes.io/instance: example + app.kubernetes.io/component: example5 +spec: + revisionHistoryLimit: 3 + strategy: + type: RollingUpdate + selector: + matchLabels: + app.kubernetes.io/name: application + app.kubernetes.io/instance: example + app.kubernetes.io/component: example5 + template: + metadata: + labels: + app.kubernetes.io/name: application + app.kubernetes.io/instance: example + app.kubernetes.io/component: example5 + spec: + imagePullSecrets: + - name: example-application-docker-registry + serviceAccountName: example-application-myserviceaccount + securityContext: + {} + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: k1 + operator: In + values: + - "v1" + topologyKey: "kubernetes.io/hostname" + containers: + - name: main + securityContext: + runAsNonRoot: true + runAsUser: 33 + image: "camptocamp/image:latest" + imagePullPolicy: IfNotPresent + terminationMessagePolicy: FallbackToLogsOnError +--- +# Source: application/templates/deployment.yaml +apiVersion: apps/v1 +kind: Deployment metadata: name: example-application-internal labels: @@ -1045,6 +1141,33 @@ spec: key: "hostname" terminationMessagePolicy: FallbackToLogsOnError --- +# Source: application/templates/hpa.yaml +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: example-application-example5-hpa + labels: + helm.sh/chart: application + app.kubernetes.io/version: "1.0" + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: application + app.kubernetes.io/instance: example + app.kubernetes.io/component: example5 +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: example-application-example5 + minReplicas: 2 + maxReplicas: 5 + metrics: + - resource: + name: cpu + target: + averageUtilization: 50 + type: Utilization + type: Resource +--- # Source: application/templates/statefulset.yaml apiVersion: apps/v1 kind: StatefulSet @@ -1581,3 +1704,26 @@ spec: - port: prometheus honorLabels: true interval: 10s +--- +# Source: application/templates/podmonitor.yaml +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + name: example-application-example5 + labels: + helm.sh/chart: application + app.kubernetes.io/version: "1.0" + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: application + app.kubernetes.io/instance: example + app.kubernetes.io/component: example5 +spec: + selector: + matchLabels: + app.kubernetes.io/name: application + app.kubernetes.io/instance: example + app.kubernetes.io/component: example5 + podMetricsEndpoints: + - port: prometheus + honorLabels: true + interval: 10s diff --git a/tests/values.yaml b/tests/values.yaml index 1d73bfb..652ebed 100644 --- a/tests/values.yaml +++ b/tests/values.yaml @@ -395,6 +395,43 @@ services: interval: 10s honorLabels: true + # Example with HPA and labelSelector in affinity + example5: + enabled: true + affinity: + podAntiAffinity: + labelSelector: + k1: v1 + topologyKey: kubernetes.io/hostname + containers: + main: + image: + repository: camptocamp/image + tag: latest + podMonitor: + enabled: true + podMetricsEndpoints: + prometheus: + interval: 10s + honorLabels: true + service: + name: service-autoscaled + type: ClusterIP + ports: + - port: 80 + targetPort: 80 + hpa: + enabled: true + minReplicas: 2 + maxReplicas: 5 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 50 + # Minimal configuration of a deployment deployment-min: enabled: true diff --git a/values.md b/values.md index b9fd0c7..93576c4 100644 --- a/values.md +++ b/values.md @@ -210,6 +210,18 @@ - **`labels`**: Refer to _[#/definitions/labels](#definitions/labels)_. - **`annotations`**: Refer to _[#/definitions/annotations](#definitions/annotations)_. - **`ports`** _(array)_ + - **`hpa`** _(object)_ + - **`enabled`** _(boolean, required)_: Enable or disable HPA for the service. + - **`minReplicas`** _(integer, required)_: Minimal number of replicas. Minimum: `1`. + - **`maxReplicas`** _(integer, required)_: Max number of replicas. Minimum: `1`. + - **`metrics`** _(array)_: Metrics definition. + - **Items** _(object)_ + - **`type`** _(string, required)_: Metric types (Resource, Pods, External). Must be one of: `["Resource", "Pods", "External"]`. + - **`resource`** _(object)_ + - **`name`** _(string, required)_: CPU or memory value. Must be one of: `["cpu", "memory"]`. + - **`target`** _(object, required)_ + - **`type`** _(string, required)_: Metric target. Must be one of: `["Utilization", "Value", "AverageValue"]`. + - **`averageUtilization`** _(integer)_: % average use. - **`podMonitor`** _(object)_: The Prometheus Pod monitor configuration. Cannot contain additional properties. - **`enabled`** _(boolean)_: Enable the Pod monitor for this service (Pod). - **`name`** _(string)_: The name of the Pod monitor. diff --git a/values.schema.json b/values.schema.json index 08a5fe0..8fbdcc6 100644 --- a/values.schema.json +++ b/values.schema.json @@ -1085,6 +1085,67 @@ } } }, + "hpa": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable HPA for the service." + }, + "minReplicas": { + "type": "integer", + "minimum": 1, + "description": "Minimal number of replicas." + }, + "maxReplicas": { + "type": "integer", + "minimum": 1, + "description": "Max number of replicas." + }, + "metrics": { + "type": "array", + "description": "Metrics definition.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["Resource", "Pods", "External"], + "description": "Metric types (Resource, Pods, External)." + }, + "resource": { + "type": "object", + "properties": { + "name": { + "type": "string", + "enum": ["cpu", "memory"], + "description": "CPU or memory value." + }, + "target": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["Utilization", "Value", "AverageValue"], + "description": "Metric target." + }, + "averageUtilization": { + "type": "integer", + "description": "% average use." + } + }, + "required": ["type"] + } + }, + "required": ["name", "target"] + } + }, + "required": ["type"] + } + } + }, + "required": ["enabled", "minReplicas", "maxReplicas"] + }, "podMonitor": { "type": "object", "description": "The Prometheus Pod monitor configuration",