diff --git a/charts/retool/Chart.yaml b/charts/retool/Chart.yaml index 853effcf..79a41372 100644 --- a/charts/retool/Chart.yaml +++ b/charts/retool/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: retool description: A Helm chart for Kubernetes type: application -version: 6.11.18 +version: 6.11.19 maintainers: - name: Retool Engineering email: engineering+helm@retool.com diff --git a/charts/retool/ci/test-js-executor-hpa-option.yaml b/charts/retool/ci/test-js-executor-hpa-option.yaml new file mode 100644 index 00000000..f7c2f71c --- /dev/null +++ b/charts/retool/ci/test-js-executor-hpa-option.yaml @@ -0,0 +1,39 @@ +rr: + + # Exercises the JS executor HPA (opt-in via rr.jsExecutor.autoscaling). + # Overlaid on top of test-install-values.yaml. Covers CPU, memory, extra + # metrics, and scale behavior branches; replicaCount must be omitted from + # the Deployment when the HPA is enabled. + jsExecutor: + enabled: true + replicaCount: 3 + image: + repository: tryretool/js-executor-service + tag: 3.123.4 + autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 8 + targetCPUUtilizationPercentage: 70 + targetMemoryUtilizationPercentage: 80 + metrics: + - type: Pods + pods: + metric: + name: packets-per-second + target: + type: AverageValue + averageValue: 1k + behavior: + scaleDown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 50 + periodSeconds: 60 + scaleUp: + stabilizationWindowSeconds: 0 + policies: + - type: Percent + value: 100 + periodSeconds: 15 diff --git a/charts/retool/templates/_helpers.tpl b/charts/retool/templates/_helpers.tpl index e2a90e83..166edbee 100644 --- a/charts/retool/templates/_helpers.tpl +++ b/charts/retool/templates/_helpers.tpl @@ -255,6 +255,50 @@ app.kubernetes.io/instance: {{ .Release.Name }} telemetry.retool.com/service-name: js-executor {{- end }} +{{/* +Resource requests for a named container on the JS executor pod. +js-executor uses rr.jsExecutor.resources.requests; any other name is looked +up in extraContainers. Fails if the container is not part of the pod. +Usage: include "retool.jsExecutor.containerRequests" (dict "root" $ "container" "js-executor") +*/}} +{{- define "retool.jsExecutor.containerRequests" -}} +{{- $root := .root -}} +{{- $name := .container | default "" -}} +{{- if not $name -}} +{{- fail "rr.jsExecutor.autoscaling.metrics ContainerResource metric requires containerResource.container." -}} +{{- end -}} +{{- if eq $name "js-executor" -}} +{{- toYaml ((($root.Values.rr.jsExecutor).resources).requests | default dict) -}} +{{- else -}} +{{- $found := false -}} +{{- $requests := dict -}} +{{- $extra := $root.Values.extraContainers -}} +{{- $containers := list -}} +{{- if kindIs "string" $extra -}} +{{- $rendered := tpl $extra $root -}} +{{- if trim $rendered -}} +{{- $doc := fromYaml (printf "items:%s" ($rendered | nindent 2)) -}} +{{- if $doc.Error -}} +{{- fail (printf "rr.jsExecutor.autoscaling could not parse extraContainers while validating a ContainerResource metric: %s" $doc.Error) -}} +{{- end -}} +{{- $containers = $doc.items | default list -}} +{{- end -}} +{{- else if kindIs "slice" $extra -}} +{{- $containers = $extra -}} +{{- end -}} +{{- range $containers -}} +{{- if eq (toString .name) $name -}} +{{- $found = true -}} +{{- $requests = ((.resources).requests | default dict) -}} +{{- end -}} +{{- end -}} +{{- if not $found -}} +{{- fail (printf "rr.jsExecutor.autoscaling.metrics ContainerResource metric targets container %q, which is not the js-executor container and was not found in extraContainers." $name) -}} +{{- end -}} +{{- toYaml $requests -}} +{{- end -}} +{{- end -}} + {{/* Selector labels for agent worker. Note changes here will require manual deployment recreation and incur downtime, so should be avoided. diff --git a/charts/retool/templates/deployment_js_executor.yaml b/charts/retool/templates/deployment_js_executor.yaml index 2e005bf6..f3f87cd4 100644 --- a/charts/retool/templates/deployment_js_executor.yaml +++ b/charts/retool/templates/deployment_js_executor.yaml @@ -15,7 +15,9 @@ metadata: {{ toYaml .Values.deployment.annotations | indent 4 }} {{- end }} spec: + {{- if not ((.Values.rr.jsExecutor.autoscaling).enabled) }} replicas: {{ .Values.rr.jsExecutor.replicaCount }} + {{- end }} selector: matchLabels: {{- include "retool.jsExecutor.selectorLabels" . | nindent 6 }} @@ -258,4 +260,73 @@ spec: matchLabels: {{- include "retool.jsExecutor.selectorLabels" . | nindent 6 }} {{- end }} +{{- if (.Values.rr.jsExecutor.autoscaling).enabled }} +{{- $hpa := .Values.rr.jsExecutor.autoscaling }} +{{- $requests := ((.Values.rr.jsExecutor.resources).requests | default dict) }} +{{- if and $hpa.targetCPUUtilizationPercentage (not $requests.cpu) }} +{{- fail "rr.jsExecutor.autoscaling.targetCPUUtilizationPercentage requires rr.jsExecutor.resources.requests.cpu. CPU utilization is unavailable without a CPU request, so the HPA cannot scale on CPU. Set a CPU request or unset targetCPUUtilizationPercentage." }} +{{- end }} +{{- if and $hpa.targetMemoryUtilizationPercentage (not $requests.memory) }} +{{- fail "rr.jsExecutor.autoscaling.targetMemoryUtilizationPercentage requires rr.jsExecutor.resources.requests.memory. Memory utilization is unavailable without a memory request, so the HPA cannot scale on memory. Set a memory request or unset targetMemoryUtilizationPercentage." }} +{{- end }} +{{- range $hpa.metrics }} +{{- if eq (toString .type) "Resource" }} +{{- $name := (.resource).name | default "" }} +{{- $targetType := ((.resource).target).type | default "" }} +{{- if and (eq $targetType "Utilization") $name (not (index $requests $name)) }} +{{- fail (printf "rr.jsExecutor.autoscaling.metrics Resource Utilization target %q requires rr.jsExecutor.resources.requests.%s. Utilization is unavailable without that request, so the HPA cannot scale on it. Set the request or use an AverageValue target instead." $name $name) }} +{{- end }} +{{- else if eq (toString .type) "ContainerResource" }} +{{- $name := (.containerResource).name | default "" }} +{{- $containerName := (.containerResource).container | default "" }} +{{- $targetType := ((.containerResource).target).type | default "" }} +{{- if and (eq $targetType "Utilization") $name }} +{{- $containerRequests := fromYaml (include "retool.jsExecutor.containerRequests" (dict "root" $ "container" $containerName)) }} +{{- if not (index $containerRequests $name) }} +{{- fail (printf "rr.jsExecutor.autoscaling.metrics ContainerResource Utilization target %q on container %q requires a matching request on that container. Utilization is unavailable without it, so the HPA cannot scale on it. Set resources.requests.%s on %q (rr.jsExecutor.resources for js-executor, or the extraContainers entry) or use an AverageValue target instead." $name $containerName $name $containerName) }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ template "retool.jsExecutor.name" . }} + labels: + {{- include "retool.jsExecutor.selectorLabels" . | nindent 4 }} + {{- include "retool.jsExecutor.labels" . | nindent 4 }} + {{- include "retool.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ template "retool.jsExecutor.name" . }} + minReplicas: {{ .Values.rr.jsExecutor.autoscaling.minReplicas }} + maxReplicas: {{ .Values.rr.jsExecutor.autoscaling.maxReplicas }} + metrics: + {{- if .Values.rr.jsExecutor.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.rr.jsExecutor.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.rr.jsExecutor.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.rr.jsExecutor.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} + {{- with .Values.rr.jsExecutor.autoscaling.metrics }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.rr.jsExecutor.autoscaling.behavior }} + behavior: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} {{- end }} diff --git a/charts/retool/values.yaml b/charts/retool/values.yaml index 27d2c1b5..30307a1b 100644 --- a/charts/retool/values.yaml +++ b/charts/retool/values.yaml @@ -864,6 +864,30 @@ rr: replicaCount: 1 + # Horizontal Pod Autoscaler for the JS executor. Disabled by default; + # set enabled: true to opt in. When enabled, replicaCount is ignored + # and the HPA controls the replica count. + # + # CPU and memory Utilization targets (the shortcuts below, and any + # Resource / ContainerResource Utilization metric in autoscaling.metrics) + # require a matching container request. Resource metrics use + # rr.jsExecutor.resources.requests.; ContainerResource metrics use + # the named container (js-executor or an extraContainers entry). A + # limits-only or partial resources override leaves those metrics + # unavailable and the HPA will not scale on the affected resource. + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 70 + targetMemoryUtilizationPercentage: 70 + # Additional HPA metrics (Pods, Object, External, or extra Resource). + # Resource Utilization metrics here have the same request requirement + # as the CPU/memory shortcuts above. + # metrics: [] + # Scaling behavior (stabilization windows and policies). + # behavior: {} + seccompLocalhostProfile: profiles/nsjail-seccomp.json # JS-executor-specific environment; not inherited from the top-level @@ -887,6 +911,8 @@ rr: # Resources for the JS executor. Memory request and limit are kept equal: # JSE reads its memory limit and rejects requests at 80% of it, so the # request must reserve the full amount to avoid premature rejections. + # HPA Utilization metrics (see autoscaling above) also require these + # requests; do not drop them if autoscaling is enabled. resources: limits: cpu: '2' diff --git a/values.yaml b/values.yaml index 27d2c1b5..30307a1b 100644 --- a/values.yaml +++ b/values.yaml @@ -864,6 +864,30 @@ rr: replicaCount: 1 + # Horizontal Pod Autoscaler for the JS executor. Disabled by default; + # set enabled: true to opt in. When enabled, replicaCount is ignored + # and the HPA controls the replica count. + # + # CPU and memory Utilization targets (the shortcuts below, and any + # Resource / ContainerResource Utilization metric in autoscaling.metrics) + # require a matching container request. Resource metrics use + # rr.jsExecutor.resources.requests.; ContainerResource metrics use + # the named container (js-executor or an extraContainers entry). A + # limits-only or partial resources override leaves those metrics + # unavailable and the HPA will not scale on the affected resource. + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 70 + targetMemoryUtilizationPercentage: 70 + # Additional HPA metrics (Pods, Object, External, or extra Resource). + # Resource Utilization metrics here have the same request requirement + # as the CPU/memory shortcuts above. + # metrics: [] + # Scaling behavior (stabilization windows and policies). + # behavior: {} + seccompLocalhostProfile: profiles/nsjail-seccomp.json # JS-executor-specific environment; not inherited from the top-level @@ -887,6 +911,8 @@ rr: # Resources for the JS executor. Memory request and limit are kept equal: # JSE reads its memory limit and rejects requests at 80% of it, so the # request must reserve the full amount to avoid premature rejections. + # HPA Utilization metrics (see autoscaling above) also require these + # requests; do not drop them if autoscaling is enabled. resources: limits: cpu: '2'