mirror of
https://github.com/actions-runner-controller/actions-runner-controller.git
synced 2026-10-18 05:49:11 +02:00
Set listener qps and burst (#4558)
This commit is contained in:
+16
@@ -123,6 +123,22 @@ spec:
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: array
|
||||
listenerConfig:
|
||||
description: ListenerConfig holds configuration for the ghalistener
|
||||
pod.
|
||||
properties:
|
||||
scaler:
|
||||
description: ScalerConfig configures the Kubernetes client used
|
||||
by the ghalistener scaler.
|
||||
properties:
|
||||
burst:
|
||||
minimum: 1
|
||||
type: integer
|
||||
qps:
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
maxRunners:
|
||||
minimum: 0
|
||||
type: integer
|
||||
|
||||
+14
@@ -145,6 +145,20 @@ spec:
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
type: object
|
||||
listenerConfig:
|
||||
description: ListenerConfig holds configuration for the ghalistener pod.
|
||||
properties:
|
||||
scaler:
|
||||
description: ScalerConfig configures the Kubernetes client used by the ghalistener scaler.
|
||||
properties:
|
||||
burst:
|
||||
minimum: 1
|
||||
type: integer
|
||||
qps:
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
listenerConfigSecretMetadata:
|
||||
description: ResourceMeta carries metadata common to all internal resources
|
||||
properties:
|
||||
|
||||
+16
@@ -123,6 +123,22 @@ spec:
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: array
|
||||
listenerConfig:
|
||||
description: ListenerConfig holds configuration for the ghalistener
|
||||
pod.
|
||||
properties:
|
||||
scaler:
|
||||
description: ScalerConfig configures the Kubernetes client used
|
||||
by the ghalistener scaler.
|
||||
properties:
|
||||
burst:
|
||||
minimum: 1
|
||||
type: integer
|
||||
qps:
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
maxRunners:
|
||||
minimum: 0
|
||||
type: integer
|
||||
|
||||
+14
@@ -145,6 +145,20 @@ spec:
|
||||
x-kubernetes-map-type: atomic
|
||||
type: object
|
||||
type: object
|
||||
listenerConfig:
|
||||
description: ListenerConfig holds configuration for the ghalistener pod.
|
||||
properties:
|
||||
scaler:
|
||||
description: ScalerConfig configures the Kubernetes client used by the ghalistener scaler.
|
||||
properties:
|
||||
burst:
|
||||
minimum: 1
|
||||
type: integer
|
||||
qps:
|
||||
minimum: 1
|
||||
type: integer
|
||||
type: object
|
||||
type: object
|
||||
listenerConfigSecretMetadata:
|
||||
description: ResourceMeta carries metadata common to all internal resources
|
||||
properties:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{{- define "listener-template.pod" -}}
|
||||
{{- $metadata := .Values.listenerPodTemplate.metadata | default dict -}}
|
||||
{{- $spec := .Values.listenerPodTemplate.spec | default dict -}}
|
||||
{{- $metadata := .metadata | default dict -}}
|
||||
{{- $spec := .spec | default dict -}}
|
||||
{{- if and (empty $metadata) (empty $spec) -}}
|
||||
{{- fail "listenerPodTemplate must have at least metadata or spec defined" -}}
|
||||
{{- fail ".Values.listener.podTemplate must have at least metadata or spec defined" -}}
|
||||
{{- end -}}
|
||||
{{- with $metadata -}}
|
||||
metadata:
|
||||
|
||||
@@ -5,6 +5,55 @@
|
||||
{{- $kubeDefaults := (index $kubeMode "default" | default true) }}
|
||||
{{- $kubeServiceAccountName := (index $kubeMode "serviceAccountName" | default "") }}
|
||||
{{- $usesKubernetesSecrets := or (not .Values.secretResolution) (eq .Values.secretResolution.type "kubernetes") }}
|
||||
{{- /* All listener configuration lives under .Values.listener */ -}}
|
||||
{{- if hasKey .Values "listenerMetrics" }}
|
||||
{{- fail ".Values.listenerMetrics has moved to .Values.listener.metrics" }}
|
||||
{{- end }}
|
||||
{{- if hasKey .Values "listenerPodTemplate" }}
|
||||
{{- fail ".Values.listenerPodTemplate has moved to .Values.listener.podTemplate" }}
|
||||
{{- end }}
|
||||
{{- $listener := .Values.listener | default dict }}
|
||||
{{- if not (kindIs "map" $listener) }}
|
||||
{{- fail ".Values.listener must be an object" }}
|
||||
{{- end }}
|
||||
{{- $listenerKeys := list "metrics" "podTemplate" "scaler" }}
|
||||
{{- range $name, $_ := $listener }}
|
||||
{{- if not (has $name $listenerKeys) }}
|
||||
{{- fail (printf ".Values.listener.%s is not a supported key; supported keys are: %s" $name (join ", " $listenerKeys)) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range $name := list "metrics" "podTemplate" }}
|
||||
{{- $value := index $listener $name | default dict }}
|
||||
{{- if not (kindIs "map" $value) }}
|
||||
{{- fail (printf ".Values.listener.%s must be an object" $name) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- $scaler := $listener.scaler | default dict }}
|
||||
{{- if not (kindIs "map" $scaler) }}
|
||||
{{- fail ".Values.listener.scaler must be an object" }}
|
||||
{{- end }}
|
||||
{{- $scalerKeys := list "burst" "qps" }}
|
||||
{{- range $name, $_ := $scaler }}
|
||||
{{- if not (has $name $scalerKeys) }}
|
||||
{{- fail (printf ".Values.listener.scaler.%s is not a supported key; supported keys are: %s" $name (join ", " $scalerKeys)) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range $name := $scalerKeys }}
|
||||
{{- if hasKey $scaler $name }}
|
||||
{{- $value := index $scaler $name }}
|
||||
{{- if not (or (kindIs "int" $value) (kindIs "int64" $value) (kindIs "float64" $value)) }}
|
||||
{{- fail (printf ".Values.listener.scaler.%s must be an integer greater than 0" $name) }}
|
||||
{{- else }}
|
||||
{{- $number := float64 $value }}
|
||||
{{- if ne $number (floor $number) }}
|
||||
{{- fail (printf ".Values.listener.scaler.%s must be an integer greater than 0" $name) }}
|
||||
{{- else if lt $number (float64 1) }}
|
||||
{{- fail (printf ".Values.listener.scaler.%s must be greater than 0" $name) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- $listenerPodTemplate := $listener.podTemplate | default dict }}
|
||||
|
||||
{{- $runnerPod := (index $runner "pod" | default dict) -}}
|
||||
{{- if not (kindIs "map" $runnerPod) -}}
|
||||
@@ -165,12 +214,17 @@ spec:
|
||||
minRunners: {{ .Values.scaleset.minRunners | int }}
|
||||
{{- end }}
|
||||
|
||||
{{- if and .Values.listenerPodTemplate (or .Values.listenerPodTemplate.metadata .Values.listenerPodTemplate.spec) }}
|
||||
{{- if or $listenerPodTemplate.metadata $listenerPodTemplate.spec }}
|
||||
listenerTemplate:
|
||||
{{- include "listener-template.pod" . | nindent 4}}
|
||||
{{- include "listener-template.pod" $listenerPodTemplate | nindent 4}}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Values.listenerMetrics }}
|
||||
{{- with $listener.scaler }}
|
||||
listenerConfig:
|
||||
scaler:
|
||||
{{- toYaml . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- with $listener.metrics }}
|
||||
listenerMetrics:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
|
||||
+118
-13
@@ -1,7 +1,111 @@
|
||||
suite: "Test AutoscalingRunnerSet Listener Metrics"
|
||||
suite: "Test AutoscalingRunnerSet Listener"
|
||||
templates:
|
||||
- autoscalingrunnserset.yaml
|
||||
tests:
|
||||
- it: should render default scaler values when listener.scaler is not overridden
|
||||
set:
|
||||
scaleset.name: "test"
|
||||
auth.url: "https://github.com/org"
|
||||
auth.githubToken: "gh_token12345"
|
||||
controllerServiceAccount.name: "arc"
|
||||
controllerServiceAccount.namespace: "arc-system"
|
||||
release:
|
||||
name: "test-name"
|
||||
namespace: "test-namespace"
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.listenerConfig.scaler.qps
|
||||
value: 50
|
||||
- equal:
|
||||
path: spec.listenerConfig.scaler.burst
|
||||
value: 100
|
||||
|
||||
- it: should render custom scaler values when overridden
|
||||
set:
|
||||
scaleset.name: "test"
|
||||
auth.url: "https://github.com/org"
|
||||
auth.githubToken: "gh_token12345"
|
||||
controllerServiceAccount.name: "arc"
|
||||
controllerServiceAccount.namespace: "arc-system"
|
||||
listener:
|
||||
scaler:
|
||||
qps: 100
|
||||
burst: 200
|
||||
release:
|
||||
name: "test-name"
|
||||
namespace: "test-namespace"
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.listenerConfig.scaler.qps
|
||||
value: 100
|
||||
- equal:
|
||||
path: spec.listenerConfig.scaler.burst
|
||||
value: 200
|
||||
|
||||
- it: should render qps override while keeping default burst
|
||||
set:
|
||||
scaleset.name: "test"
|
||||
auth.url: "https://github.com/org"
|
||||
auth.githubToken: "gh_token12345"
|
||||
controllerServiceAccount.name: "arc"
|
||||
controllerServiceAccount.namespace: "arc-system"
|
||||
listener:
|
||||
scaler:
|
||||
qps: 75
|
||||
release:
|
||||
name: "test-name"
|
||||
namespace: "test-namespace"
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.listenerConfig.scaler.qps
|
||||
value: 75
|
||||
- equal:
|
||||
path: spec.listenerConfig.scaler.burst
|
||||
value: 100
|
||||
|
||||
- it: should render scaler and metrics together
|
||||
set:
|
||||
scaleset.name: "test"
|
||||
auth.url: "https://github.com/org"
|
||||
auth.githubToken: "gh_token12345"
|
||||
controllerServiceAccount.name: "arc"
|
||||
controllerServiceAccount.namespace: "arc-system"
|
||||
listener:
|
||||
scaler:
|
||||
qps: 75
|
||||
metrics:
|
||||
counters:
|
||||
gha_started_jobs_total:
|
||||
labels:
|
||||
- repository
|
||||
release:
|
||||
name: "test-name"
|
||||
namespace: "test-namespace"
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.listenerConfig.scaler.qps
|
||||
value: 75
|
||||
- exists:
|
||||
path: spec.listenerMetrics
|
||||
- equal:
|
||||
path: spec.listenerMetrics.counters.gha_started_jobs_total.labels[0]
|
||||
value: repository
|
||||
|
||||
- it: should not render listenerConfig when listener is null
|
||||
set:
|
||||
scaleset.name: "test"
|
||||
auth.url: "https://github.com/org"
|
||||
auth.githubToken: "gh_token12345"
|
||||
controllerServiceAccount.name: "arc"
|
||||
controllerServiceAccount.namespace: "arc-system"
|
||||
listener: null
|
||||
release:
|
||||
name: "test-name"
|
||||
namespace: "test-namespace"
|
||||
asserts:
|
||||
- notExists:
|
||||
path: spec.listenerConfig
|
||||
|
||||
- it: should not render listenerMetrics when not configured
|
||||
set:
|
||||
scaleset.name: "test"
|
||||
@@ -23,18 +127,19 @@ tests:
|
||||
auth.githubToken: "gh_token12345"
|
||||
controllerServiceAccount.name: "arc"
|
||||
controllerServiceAccount.namespace: "arc-system"
|
||||
listenerMetrics:
|
||||
counters:
|
||||
gha_started_jobs_total:
|
||||
labels:
|
||||
- repository
|
||||
- organization
|
||||
histograms:
|
||||
gha_job_startup_duration_seconds:
|
||||
buckets:
|
||||
- 0.1
|
||||
- 1
|
||||
- 2.5
|
||||
listener:
|
||||
metrics:
|
||||
counters:
|
||||
gha_started_jobs_total:
|
||||
labels:
|
||||
- repository
|
||||
- organization
|
||||
histograms:
|
||||
gha_job_startup_duration_seconds:
|
||||
buckets:
|
||||
- 0.1
|
||||
- 1
|
||||
- 2.5
|
||||
release:
|
||||
name: "test-name"
|
||||
namespace: "test-namespace"
|
||||
|
||||
+60
-12
@@ -1,21 +1,22 @@
|
||||
suite: "AutoscalingRunnerSet listenerPodTemplate"
|
||||
suite: "AutoscalingRunnerSet listener.podTemplate"
|
||||
templates:
|
||||
- autoscalingrunnserset.yaml
|
||||
tests:
|
||||
- it: should render listenerTemplate from listenerPodTemplate
|
||||
- it: should render listenerTemplate from listener.podTemplate
|
||||
set:
|
||||
scaleset.name: "test"
|
||||
auth.url: "https://github.com/org"
|
||||
auth.githubToken: "gh_token12345"
|
||||
controllerServiceAccount.name: "arc"
|
||||
controllerServiceAccount.namespace: "arc-system"
|
||||
listenerPodTemplate:
|
||||
spec:
|
||||
containers:
|
||||
- name: listener
|
||||
image: "ghcr.io/actions/actions-runner-controller/actionsmetricsserver:latest"
|
||||
securityContext:
|
||||
runAsUser: 1000
|
||||
listener:
|
||||
podTemplate:
|
||||
spec:
|
||||
containers:
|
||||
- name: listener
|
||||
image: "ghcr.io/actions/actions-runner-controller/actionsmetricsserver:latest"
|
||||
securityContext:
|
||||
runAsUser: 1000
|
||||
release:
|
||||
name: "test-name"
|
||||
namespace: "test-namespace"
|
||||
@@ -34,9 +35,10 @@ tests:
|
||||
auth.githubToken: "gh_token12345"
|
||||
controllerServiceAccount.name: "arc"
|
||||
controllerServiceAccount.namespace: "arc-system"
|
||||
listenerPodTemplate:
|
||||
spec:
|
||||
restartPolicy: Always
|
||||
listener:
|
||||
podTemplate:
|
||||
spec:
|
||||
restartPolicy: Always
|
||||
release:
|
||||
name: "test-name"
|
||||
namespace: "test-namespace"
|
||||
@@ -44,3 +46,49 @@ tests:
|
||||
- equal:
|
||||
path: spec.listenerTemplate.spec.containers[0].name
|
||||
value: listener
|
||||
|
||||
- it: should render podTemplate alongside scaler and metrics
|
||||
set:
|
||||
scaleset.name: "test"
|
||||
auth.url: "https://github.com/org"
|
||||
auth.githubToken: "gh_token12345"
|
||||
controllerServiceAccount.name: "arc"
|
||||
controllerServiceAccount.namespace: "arc-system"
|
||||
listener:
|
||||
scaler:
|
||||
qps: 75
|
||||
podTemplate:
|
||||
spec:
|
||||
restartPolicy: Always
|
||||
metrics:
|
||||
counters:
|
||||
gha_started_jobs_total:
|
||||
labels:
|
||||
- repository
|
||||
release:
|
||||
name: "test-name"
|
||||
namespace: "test-namespace"
|
||||
asserts:
|
||||
- equal:
|
||||
path: spec.listenerConfig.scaler.qps
|
||||
value: 75
|
||||
- equal:
|
||||
path: spec.listenerTemplate.spec.containers[0].name
|
||||
value: listener
|
||||
- equal:
|
||||
path: spec.listenerMetrics.counters.gha_started_jobs_total.labels[0]
|
||||
value: repository
|
||||
|
||||
- it: should not render listenerTemplate when podTemplate is empty
|
||||
set:
|
||||
scaleset.name: "test"
|
||||
auth.url: "https://github.com/org"
|
||||
auth.githubToken: "gh_token12345"
|
||||
controllerServiceAccount.name: "arc"
|
||||
controllerServiceAccount.namespace: "arc-system"
|
||||
release:
|
||||
name: "test-name"
|
||||
namespace: "test-namespace"
|
||||
asserts:
|
||||
- notExists:
|
||||
path: spec.listenerTemplate
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
suite: "Test AutoscalingRunnerSet Listener Validation"
|
||||
templates:
|
||||
- autoscalingrunnserset.yaml
|
||||
set: &base
|
||||
scaleset.name: "test"
|
||||
auth.url: "https://github.com/org"
|
||||
auth.githubToken: "gh_token12345"
|
||||
controllerServiceAccount.name: "arc"
|
||||
controllerServiceAccount.namespace: "arc-system"
|
||||
tests:
|
||||
- it: rejects zero qps
|
||||
set:
|
||||
<<: *base
|
||||
listener.scaler.qps: 0
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: ".Values.listener.scaler.qps must be greater than 0"
|
||||
|
||||
- it: rejects negative burst
|
||||
set:
|
||||
<<: *base
|
||||
listener.scaler.burst: -1
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: ".Values.listener.scaler.burst must be greater than 0"
|
||||
|
||||
- it: rejects fractional qps
|
||||
set:
|
||||
<<: *base
|
||||
listener.scaler.qps: 1.5
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: ".Values.listener.scaler.qps must be an integer greater than 0"
|
||||
|
||||
- it: rejects a string burst
|
||||
set:
|
||||
<<: *base
|
||||
listener.scaler.burst: "100"
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: ".Values.listener.scaler.burst must be an integer greater than 0"
|
||||
|
||||
- it: rejects a boolean qps
|
||||
set:
|
||||
<<: *base
|
||||
listener.scaler.qps: true
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: ".Values.listener.scaler.qps must be an integer greater than 0"
|
||||
|
||||
- it: rejects a scaler that is not an object
|
||||
set:
|
||||
<<: *base
|
||||
listener.scaler: "nope"
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: ".Values.listener.scaler must be an object"
|
||||
|
||||
- it: rejects a listener that is not an object
|
||||
set:
|
||||
<<: *base
|
||||
listener: "nope"
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: ".Values.listener must be an object"
|
||||
|
||||
- it: rejects a metrics value that is not an object
|
||||
set:
|
||||
<<: *base
|
||||
listener.metrics: "nope"
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: ".Values.listener.metrics must be an object"
|
||||
|
||||
- it: rejects a podTemplate that is not an object
|
||||
set:
|
||||
<<: *base
|
||||
listener.podTemplate: "nope"
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: ".Values.listener.podTemplate must be an object"
|
||||
|
||||
- it: rejects a misspelled key under listener
|
||||
set:
|
||||
<<: *base
|
||||
listener.metrcs.counters: {}
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: ".Values.listener.metrcs is not a supported key; supported keys are: metrics, podTemplate, scaler"
|
||||
|
||||
- it: rejects a misspelled key under listener.scaler
|
||||
set:
|
||||
<<: *base
|
||||
listener.scaler.qbs: 10
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: ".Values.listener.scaler.qbs is not a supported key; supported keys are: burst, qps"
|
||||
|
||||
- it: rejects the legacy top-level listenerMetrics key
|
||||
set:
|
||||
<<: *base
|
||||
listenerMetrics.counters: {}
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: ".Values.listenerMetrics has moved to .Values.listener.metrics"
|
||||
|
||||
- it: rejects the legacy top-level listenerPodTemplate key
|
||||
set:
|
||||
<<: *base
|
||||
listenerPodTemplate.spec.restartPolicy: Always
|
||||
asserts:
|
||||
- failedTemplate:
|
||||
errorMessage: ".Values.listenerPodTemplate has moved to .Values.listener.podTemplate"
|
||||
|
||||
- it: treats a podTemplate with neither metadata nor spec as a no-op
|
||||
set:
|
||||
<<: *base
|
||||
listener.podTemplate.spec: {}
|
||||
listener.podTemplate.metadata: {}
|
||||
asserts:
|
||||
- notExists:
|
||||
path: spec.listenerTemplate
|
||||
@@ -75,23 +75,6 @@ secretResolution:
|
||||
# - example.com
|
||||
# - example.org
|
||||
|
||||
## listenerTemplate is the PodSpec for each listener Pod
|
||||
## For reference: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec
|
||||
# listenerPodTemplate:
|
||||
# spec:
|
||||
# containers:
|
||||
# # Use this section to append additional configuration to the listener container.
|
||||
# # If you change the name of the container, the configuration will not be applied to the listener,
|
||||
# # and it will be treated as a side-car container.
|
||||
# - name: listener
|
||||
# securityContext:
|
||||
# runAsUser: 1000
|
||||
# # Use this section to add the configuration of a side-car container.
|
||||
# # Comment it out or remove it if you don't need it.
|
||||
# # Spec for this container will be applied as is without any modifications.
|
||||
# - name: side-car
|
||||
# image: example-sidecar
|
||||
|
||||
## Resource object allows modifying resources created by the chart itself
|
||||
resource:
|
||||
# Specifies metadata that will be applied to all resources managed by ARC
|
||||
@@ -312,158 +295,183 @@ controllerServiceAccount:
|
||||
namespace: ""
|
||||
name: ""
|
||||
|
||||
## listenerMetrics are configurable metrics applied to the listener.
|
||||
## In order to avoid helm merging these fields, we left the metrics commented out.
|
||||
## When configuring metrics, please uncomment the listenerMetrics object below.
|
||||
## You can modify the configuration to remove the label or specify custom buckets for histogram.
|
||||
##
|
||||
## If the buckets field is not specified, the default buckets will be applied. Default buckets are
|
||||
## provided here for documentation purposes
|
||||
# listenerMetrics:
|
||||
# counters:
|
||||
# gha_started_jobs_total:
|
||||
# labels:
|
||||
# ["repository", "organization", "enterprise", "job_name", "event_name", "job_workflow_ref", "job_workflow_name", "job_workflow_target"]
|
||||
# gha_completed_jobs_total:
|
||||
# labels:
|
||||
# [
|
||||
# "repository",
|
||||
# "organization",
|
||||
# "enterprise",
|
||||
# "job_name",
|
||||
# "event_name",
|
||||
# "job_result",
|
||||
# "job_workflow_ref",
|
||||
# "job_workflow_name",
|
||||
# "job_workflow_target",
|
||||
# ]
|
||||
# gauges:
|
||||
# gha_assigned_jobs:
|
||||
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
|
||||
# gha_running_jobs:
|
||||
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
|
||||
# gha_registered_runners:
|
||||
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
|
||||
# gha_busy_runners:
|
||||
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
|
||||
# gha_min_runners:
|
||||
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
|
||||
# gha_max_runners:
|
||||
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
|
||||
# gha_desired_runners:
|
||||
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
|
||||
# gha_idle_runners:
|
||||
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
|
||||
# histograms:
|
||||
# gha_job_startup_duration_seconds:
|
||||
# labels:
|
||||
# ["repository", "organization", "enterprise", "job_name", "event_name","job_workflow_ref", "job_workflow_name", "job_workflow_target"]
|
||||
# buckets:
|
||||
# [
|
||||
# 0.01,
|
||||
# 0.05,
|
||||
# 0.1,
|
||||
# 0.5,
|
||||
# 1.0,
|
||||
# 2.0,
|
||||
# 3.0,
|
||||
# 4.0,
|
||||
# 5.0,
|
||||
# 6.0,
|
||||
# 7.0,
|
||||
# 8.0,
|
||||
# 9.0,
|
||||
# 10.0,
|
||||
# 12.0,
|
||||
# 15.0,
|
||||
# 18.0,
|
||||
# 20.0,
|
||||
# 25.0,
|
||||
# 30.0,
|
||||
# 40.0,
|
||||
# 50.0,
|
||||
# 60.0,
|
||||
# 70.0,
|
||||
# 80.0,
|
||||
# 90.0,
|
||||
# 100.0,
|
||||
# 110.0,
|
||||
# 120.0,
|
||||
# 150.0,
|
||||
# 180.0,
|
||||
# 210.0,
|
||||
# 240.0,
|
||||
# 300.0,
|
||||
# 360.0,
|
||||
# 420.0,
|
||||
# 480.0,
|
||||
# 540.0,
|
||||
# 600.0,
|
||||
# 900.0,
|
||||
# 1200.0,
|
||||
# 1800.0,
|
||||
# 2400.0,
|
||||
# 3000.0,
|
||||
# 3600.0,
|
||||
# ]
|
||||
# gha_job_execution_duration_seconds:
|
||||
# labels:
|
||||
# [
|
||||
# "repository",
|
||||
# "organization",
|
||||
# "enterprise",
|
||||
# "job_name",
|
||||
# "event_name",
|
||||
# "job_result",
|
||||
# "job_workflow_ref",
|
||||
# "job_workflow_name",
|
||||
# "job_workflow_target"
|
||||
# ]
|
||||
# buckets:
|
||||
# [
|
||||
# 0.01,
|
||||
# 0.05,
|
||||
# 0.1,
|
||||
# 0.5,
|
||||
# 1.0,
|
||||
# 2.0,
|
||||
# 3.0,
|
||||
# 4.0,
|
||||
# 5.0,
|
||||
# 6.0,
|
||||
# 7.0,
|
||||
# 8.0,
|
||||
# 9.0,
|
||||
# 10.0,
|
||||
# 12.0,
|
||||
# 15.0,
|
||||
# 18.0,
|
||||
# 20.0,
|
||||
# 25.0,
|
||||
# 30.0,
|
||||
# 40.0,
|
||||
# 50.0,
|
||||
# 60.0,
|
||||
# 70.0,
|
||||
# 80.0,
|
||||
# 90.0,
|
||||
# 100.0,
|
||||
# 110.0,
|
||||
# 120.0,
|
||||
# 150.0,
|
||||
# 180.0,
|
||||
# 210.0,
|
||||
# 240.0,
|
||||
# 300.0,
|
||||
# 360.0,
|
||||
# 420.0,
|
||||
# 480.0,
|
||||
# 540.0,
|
||||
# 600.0,
|
||||
# 900.0,
|
||||
# 1200.0,
|
||||
# 1800.0,
|
||||
# 2400.0,
|
||||
# 3000.0,
|
||||
# 3600.0,
|
||||
# ]
|
||||
# listener specific configuration. This configuration is applied to the listener component of the chart.
|
||||
listener:
|
||||
# scaler is config applied to the kubernetes client component of the listener.
|
||||
# Both values must be integers greater than 0.
|
||||
scaler:
|
||||
# qps is the sustained rate of requests the listener may issue to the Kubernetes API server.
|
||||
qps: 50
|
||||
# burst is the number of requests the listener may issue to the Kubernetes API server in a burst.
|
||||
burst: 100
|
||||
## podTemplate is the PodSpec for each listener Pod
|
||||
## For reference: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec
|
||||
# podTemplate:
|
||||
# spec:
|
||||
# containers:
|
||||
# # Use this section to append additional configuration to the listener container.
|
||||
# # If you change the name of the container, the configuration will not be applied to the listener,
|
||||
# # and it will be treated as a side-car container.
|
||||
# - name: listener
|
||||
# securityContext:
|
||||
# runAsUser: 1000
|
||||
# # Use this section to add the configuration of a side-car container.
|
||||
# # Comment it out or remove it if you don't need it.
|
||||
# # Spec for this container will be applied as is without any modifications.
|
||||
# - name: side-car
|
||||
# image: example-sidecar
|
||||
## metrics configuration for the listener.
|
||||
## In order to avoid helm merging these fields, we left the metrics commented out.
|
||||
## When configuring metrics, please uncomment the metrics object below.
|
||||
## You can modify the configuration to remove the label or specify custom buckets for histogram.
|
||||
##
|
||||
## If the buckets field is not specified, the default buckets will be applied. Default buckets are
|
||||
## provided here for documentation purposes
|
||||
# metrics:
|
||||
# counters:
|
||||
# gha_started_jobs_total:
|
||||
# labels:
|
||||
# ["repository", "organization", "enterprise", "job_name", "event_name", "job_workflow_ref", "job_workflow_name", "job_workflow_target"]
|
||||
# gha_completed_jobs_total:
|
||||
# labels:
|
||||
# [
|
||||
# "repository",
|
||||
# "organization",
|
||||
# "enterprise",
|
||||
# "job_name",
|
||||
# "event_name",
|
||||
# "job_result",
|
||||
# "job_workflow_ref",
|
||||
# "job_workflow_name",
|
||||
# "job_workflow_target",
|
||||
# ]
|
||||
# gauges:
|
||||
# gha_assigned_jobs:
|
||||
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
|
||||
# gha_running_jobs:
|
||||
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
|
||||
# gha_registered_runners:
|
||||
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
|
||||
# gha_busy_runners:
|
||||
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
|
||||
# gha_min_runners:
|
||||
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
|
||||
# gha_max_runners:
|
||||
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
|
||||
# gha_desired_runners:
|
||||
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
|
||||
# gha_idle_runners:
|
||||
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
|
||||
# histograms:
|
||||
# gha_job_startup_duration_seconds:
|
||||
# labels:
|
||||
# ["repository", "organization", "enterprise", "job_name", "event_name","job_workflow_ref", "job_workflow_name", "job_workflow_target"]
|
||||
# buckets:
|
||||
# [
|
||||
# 0.01,
|
||||
# 0.05,
|
||||
# 0.1,
|
||||
# 0.5,
|
||||
# 1.0,
|
||||
# 2.0,
|
||||
# 3.0,
|
||||
# 4.0,
|
||||
# 5.0,
|
||||
# 6.0,
|
||||
# 7.0,
|
||||
# 8.0,
|
||||
# 9.0,
|
||||
# 10.0,
|
||||
# 12.0,
|
||||
# 15.0,
|
||||
# 18.0,
|
||||
# 20.0,
|
||||
# 25.0,
|
||||
# 30.0,
|
||||
# 40.0,
|
||||
# 50.0,
|
||||
# 60.0,
|
||||
# 70.0,
|
||||
# 80.0,
|
||||
# 90.0,
|
||||
# 100.0,
|
||||
# 110.0,
|
||||
# 120.0,
|
||||
# 150.0,
|
||||
# 180.0,
|
||||
# 210.0,
|
||||
# 240.0,
|
||||
# 300.0,
|
||||
# 360.0,
|
||||
# 420.0,
|
||||
# 480.0,
|
||||
# 540.0,
|
||||
# 600.0,
|
||||
# 900.0,
|
||||
# 1200.0,
|
||||
# 1800.0,
|
||||
# 2400.0,
|
||||
# 3000.0,
|
||||
# 3600.0,
|
||||
# ]
|
||||
# gha_job_execution_duration_seconds:
|
||||
# labels:
|
||||
# [
|
||||
# "repository",
|
||||
# "organization",
|
||||
# "enterprise",
|
||||
# "job_name",
|
||||
# "event_name",
|
||||
# "job_result",
|
||||
# "job_workflow_ref",
|
||||
# "job_workflow_name",
|
||||
# "job_workflow_target"
|
||||
# ]
|
||||
# buckets:
|
||||
# [
|
||||
# 0.01,
|
||||
# 0.05,
|
||||
# 0.1,
|
||||
# 0.5,
|
||||
# 1.0,
|
||||
# 2.0,
|
||||
# 3.0,
|
||||
# 4.0,
|
||||
# 5.0,
|
||||
# 6.0,
|
||||
# 7.0,
|
||||
# 8.0,
|
||||
# 9.0,
|
||||
# 10.0,
|
||||
# 12.0,
|
||||
# 15.0,
|
||||
# 18.0,
|
||||
# 20.0,
|
||||
# 25.0,
|
||||
# 30.0,
|
||||
# 40.0,
|
||||
# 50.0,
|
||||
# 60.0,
|
||||
# 70.0,
|
||||
# 80.0,
|
||||
# 90.0,
|
||||
# 100.0,
|
||||
# 110.0,
|
||||
# 120.0,
|
||||
# 150.0,
|
||||
# 180.0,
|
||||
# 210.0,
|
||||
# 240.0,
|
||||
# 300.0,
|
||||
# 360.0,
|
||||
# 420.0,
|
||||
# 480.0,
|
||||
# 540.0,
|
||||
# 600.0,
|
||||
# 900.0,
|
||||
# 1200.0,
|
||||
# 1800.0,
|
||||
# 2400.0,
|
||||
# 3000.0,
|
||||
# 3600.0,
|
||||
# ]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{{- $resourceMeta := default (dict) .Values.resourceMeta }}
|
||||
{{- $hasCustomResourceMeta := (and .Values.resourceMeta .Values.resourceMeta.autoscalingRunnerSet) }}
|
||||
{{- /* .Values.listenerConfig is validated by values.schema.json */ -}}
|
||||
apiVersion: actions.github.com/v1alpha1
|
||||
kind: AutoscalingRunnerSet
|
||||
metadata:
|
||||
@@ -167,6 +168,11 @@ spec:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Values.listenerConfig }}
|
||||
listenerConfig:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
|
||||
{{- with (index $resourceMeta "autoscalingListener") }}
|
||||
autoscalingListener:
|
||||
{{- include "gha-runner-scale-set.resourceMetaSpec" . | nindent 4 }}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func TestTemplateRenderedGitHubSecretWithGitHubToken(t *testing.T) {
|
||||
@@ -141,6 +142,170 @@ func TestTemplateRenderedGitHubSecretErrorWithMissingAppInput(t *testing.T) {
|
||||
assert.ErrorContains(t, err, "provide .Values.githubConfigSecret.github_app_installation_id and .Values.githubConfigSecret.github_app_private_key")
|
||||
}
|
||||
|
||||
func TestTemplateListenerScalerValidation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set")
|
||||
require.NoError(t, err)
|
||||
|
||||
baseValues := map[string]string{
|
||||
"githubConfigUrl": "https://github.com/actions",
|
||||
"githubConfigSecret.github_token": "gh_token12345",
|
||||
"controllerServiceAccount.name": "arc",
|
||||
"controllerServiceAccount.namespace": "arc-system",
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
setValues map[string]string
|
||||
setStrValues map[string]string
|
||||
wantErrorText string
|
||||
}{
|
||||
{
|
||||
name: "zero qps",
|
||||
setValues: map[string]string{"listenerConfig.scaler.qps": "0"},
|
||||
wantErrorText: "at '/listenerConfig/scaler/qps': minimum: got 0, want 1",
|
||||
},
|
||||
{
|
||||
name: "negative burst",
|
||||
setValues: map[string]string{"listenerConfig.scaler.burst": "-1"},
|
||||
wantErrorText: "at '/listenerConfig/scaler/burst': minimum: got -1, want 1",
|
||||
},
|
||||
{
|
||||
name: "fractional qps",
|
||||
setValues: map[string]string{"listenerConfig.scaler.qps": "1.5"},
|
||||
wantErrorText: "at '/listenerConfig/scaler/qps'",
|
||||
},
|
||||
{
|
||||
name: "string burst",
|
||||
setStrValues: map[string]string{"listenerConfig.scaler.burst": "100"},
|
||||
wantErrorText: "at '/listenerConfig/scaler/burst': got string, want integer",
|
||||
},
|
||||
{
|
||||
name: "boolean qps",
|
||||
setValues: map[string]string{"listenerConfig.scaler.qps": "true"},
|
||||
wantErrorText: "at '/listenerConfig/scaler/qps': got boolean, want integer",
|
||||
},
|
||||
{
|
||||
name: "scaler is not an object",
|
||||
setValues: map[string]string{"listenerConfig.scaler[0]": "a"},
|
||||
wantErrorText: "at '/listenerConfig/scaler': got array, want null or object",
|
||||
},
|
||||
{
|
||||
name: "listenerConfig is not an object",
|
||||
setStrValues: map[string]string{"listenerConfig": "foo"},
|
||||
wantErrorText: "at '/listenerConfig': got string, want null or object",
|
||||
},
|
||||
{
|
||||
name: "misspelled scaler key",
|
||||
setValues: map[string]string{"listenerConfig.scalar.qps": "5"},
|
||||
wantErrorText: "at '/listenerConfig': additional properties 'scalar' not allowed",
|
||||
},
|
||||
{
|
||||
name: "unknown key under scaler",
|
||||
setStrValues: map[string]string{"listenerConfig.scaler.foo": "bar"},
|
||||
wantErrorText: "at '/listenerConfig/scaler': additional properties 'foo' not allowed",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
setValues := make(map[string]string, len(baseValues)+len(tt.setValues))
|
||||
for key, value := range baseValues {
|
||||
setValues[key] = value
|
||||
}
|
||||
for key, value := range tt.setValues {
|
||||
setValues[key] = value
|
||||
}
|
||||
|
||||
options := &helm.Options{
|
||||
Logger: logger.Discard,
|
||||
SetValues: setValues,
|
||||
SetStrValues: tt.setStrValues,
|
||||
KubectlOptions: k8s.NewKubectlOptions("", "", "test"),
|
||||
}
|
||||
_, err := helm.RenderTemplateContextE(t, t.Context(), options, helmChartPath, "test-runners", []string{"templates/autoscalingrunnerset.yaml"})
|
||||
require.Error(t, err)
|
||||
assert.ErrorContains(t, err, tt.wantErrorText)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateListenerScalerConfig(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set")
|
||||
require.NoError(t, err)
|
||||
|
||||
baseValues := map[string]string{
|
||||
"githubConfigUrl": "https://github.com/actions",
|
||||
"githubConfigSecret.github_token": "gh_token12345",
|
||||
"controllerServiceAccount.name": "arc",
|
||||
"controllerServiceAccount.namespace": "arc-system",
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
setValues map[string]string
|
||||
wantQPS *int
|
||||
wantBurst *int
|
||||
}{
|
||||
{
|
||||
name: "defaults from values.yaml",
|
||||
wantQPS: ptr.To(50),
|
||||
wantBurst: ptr.To(100),
|
||||
},
|
||||
{
|
||||
name: "both overridden",
|
||||
setValues: map[string]string{"listenerConfig.scaler.qps": "100", "listenerConfig.scaler.burst": "200"},
|
||||
wantQPS: ptr.To(100),
|
||||
wantBurst: ptr.To(200),
|
||||
},
|
||||
{
|
||||
name: "qps overridden keeps default burst",
|
||||
setValues: map[string]string{"listenerConfig.scaler.qps": "75"},
|
||||
wantQPS: ptr.To(75),
|
||||
wantBurst: ptr.To(100),
|
||||
},
|
||||
{
|
||||
name: "listenerConfig disabled",
|
||||
setValues: map[string]string{"listenerConfig": "null"},
|
||||
wantQPS: nil,
|
||||
wantBurst: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
setValues := make(map[string]string, len(baseValues)+len(tt.setValues))
|
||||
for key, value := range baseValues {
|
||||
setValues[key] = value
|
||||
}
|
||||
for key, value := range tt.setValues {
|
||||
setValues[key] = value
|
||||
}
|
||||
|
||||
options := &helm.Options{
|
||||
Logger: logger.Discard,
|
||||
SetValues: setValues,
|
||||
KubectlOptions: k8s.NewKubectlOptions("", "", "test"),
|
||||
}
|
||||
output := helm.RenderTemplateContext(t, t.Context(), options, helmChartPath, "test-runners", []string{"templates/autoscalingrunnerset.yaml"})
|
||||
|
||||
var ars v1alpha1.AutoscalingRunnerSet
|
||||
helm.UnmarshalK8SYaml(t, output, &ars)
|
||||
|
||||
if tt.wantQPS == nil && tt.wantBurst == nil {
|
||||
assert.Nil(t, ars.Spec.ListenerConfig.GetScaler())
|
||||
return
|
||||
}
|
||||
|
||||
scaler := ars.Spec.ListenerConfig.GetScaler()
|
||||
require.NotNil(t, scaler)
|
||||
assert.Equal(t, tt.wantQPS, scaler.QPS)
|
||||
assert.Equal(t, tt.wantBurst, scaler.Burst)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateNotRenderedGitHubSecretWithPredefinedSecret(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "gha-runner-scale-set values",
|
||||
"description": "Only values that require strict validation are described here. Any value not listed is intentionally unconstrained.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"listenerConfig": {
|
||||
"description": "Configuration for the ghalistener pod.",
|
||||
"type": ["object", "null"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"scaler": {
|
||||
"description": "Configuration for the Kubernetes client used by the ghalistener scaler.",
|
||||
"type": ["object", "null"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"qps": {
|
||||
"description": "Queries per second the listener may issue to the Kubernetes API server.",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"burst": {
|
||||
"description": "Burst of queries the listener may issue to the Kubernetes API server.",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,6 +146,12 @@ githubConfigSecret:
|
||||
# - name: side-car
|
||||
# image: example-sidecar
|
||||
|
||||
## listenerConfig holds configuration for the ghalistener pod.
|
||||
listenerConfig:
|
||||
scaler:
|
||||
qps: 50
|
||||
burst: 100
|
||||
|
||||
## listenerMetrics are configurable metrics applied to the listener.
|
||||
## In order to avoid helm merging these fields, we left the metrics commented out.
|
||||
## When configuring metrics, please uncomment the listenerMetrics object below.
|
||||
|
||||
Reference in New Issue
Block a user