File: README.md
Actions Runner Controller (ARC)
About
Actions Runner Controller (ARC) is a Kubernetes operator that orchestrates and scales self-hosted runners for GitHub Actions.
With ARC, you can create runner scale sets that automatically scale based on the number of workflows running in your repository, organization, or enterprise. Because controlled runners can be ephemeral and based on containers, new runner instances can scale up or down rapidly and cleanly. For more information about autoscaling, see "Autoscaling with self-hosted runners."
You can set up ARC on Kubernetes using Helm, then create and run a workflow that uses runner scale sets. For more information about runner scale sets, see "Deploying runner scale sets with Actions Runner Controller."
People
Actions Runner Controller (ARC) is an open-source project currently developed and maintained in collaboration with the GitHub Actions team, external maintainers @mumoshu and @toast-gear, various contributors, and the awesome community.
If you think the project is awesome and is adding value to your business, please consider directly sponsoring community maintainers and individual contributors via GitHub Sponsors.
If you are already the employer of one of the contributors, sponsoring via GitHub Sponsors might not be an option. Just support them by other means!
See the sponsorship dashboard for the former and the current sponsors.
Getting Started
To give ARC a try with just a handful of commands, please refer to the Quickstart guide.
For an overview of ARC, please refer to About ARC.
With the introduction of autoscaling runner scale sets, the existing autoscaling modes are now legacy. The legacy modes have certain use cases and will continue to be maintained by the community only.
For further information on what is supported by GitHub and what's managed by the community, please refer to this announcement discussion.
Documentation
ARC documentation is available on docs.github.com.
Legacy documentation
The following documentation is for the legacy autoscaling modes that continue to be maintained by the community:
- Quickstart guide
- About ARC
- Installing ARC
- Authenticating to the GitHub API
- Deploying ARC runners
- Adding ARC runners to a repository, organization, or enterprise
- Automatically scaling runners
- Using custom volumes
- Using ARC runners in a workflow
- Managing access with runner groups
- Configuring Windows runners
- Using ARC across organizations
- Using entrypoint features
- Deploying alternative runners
- Monitoring and troubleshooting
Contributing
We welcome contributions from the community. For more details on contributing to the project (including requirements), please refer to "Getting Started with Contributing."
Troubleshooting
We are very happy to help you with any issues you have. Please refer to the "Troubleshooting" section for common issues.
File: charts/actions-runner-controller/docs/UPGRADING.md
Upgrading
This project makes extensive use of CRDs to provide much of its functionality. Helm unfortunately does not support managing CRDs by design:
The full breakdown as to how they came to this decision and why they have taken the approach they have for dealing with CRDs can be found in Helm Improvement Proposal 11
There is no support at this time for upgrading or deleting CRDs using Helm. This was an explicit decision after much
community discussion due to the danger for unintentional data loss. Furthermore, there is currently no community
consensus around how to handle CRDs and their lifecycle. As this evolves, Helm will add support for those use cases.Helm will do an initial install of CRDs but it will not touch them afterwards (update or delete).
Additionally, because the project leverages CRDs so extensively you MUST run the matching controller app container with its matching CRDs i.e. always redeploy your CRDs if you are changing the app version.
Due to the above you can't just do a helm upgrade to release the latest version of the chart, the best practice steps are recorded below:
Steps
- Upgrade CRDs, this isn't optional, the CRDs you are using must be those that correspond with the version of the controller you are installing
# REMEMBER TO UPDATE THE CHART_VERSION TO RELEVANT CHART VERISON!!!!
CHART_VERSION=0.18.0
curl -L https://github.com/actions/actions-runner-controller/releases/download/actions-runner-controller-${CHART_VERSION}/actions-runner-controller-${CHART_VERSION}.tgz | tar zxv --strip 1 actions-runner-controller/crds
kubectl replace -f crds/Note that in case you're going to create prometheus-operator ServiceMonitor resources via the chart, you'd need to deploy prometheus-operator-related CRDs as well.
- Upgrade the Helm release
# helm repo [command]
helm repo update
# helm upgrade [RELEASE] [CHART] [flags]
helm upgrade actions-runner-controller \
actions-runner-controller/actions-runner-controller \
--install \
--namespace actions-runner-system \
--version ${CHART_VERSION}File: charts/actions-runner-controller/README.md
Docs
All additional docs are kept in the docs/ folder, this README is solely for documenting the values.yaml keys and values
Values
The values are documented as of HEAD, to review the configuration options for your chart version ensure you view this file at the relevant tag
Default values are the defaults set in the charts
values.yaml, some properties have default configurations in the code for when the property is omitted or invalid
| Key | Description | Default |
|---|---|---|
labels |
Set labels to apply to all resources in the chart | |
replicaCount |
Set the number of controller pods | 1 |
webhookPort |
Set the containerPort for the webhook Pod | 9443 |
syncPeriod |
Set the period in which the controller reconciles the desired runners count | 1m |
enableLeaderElection |
Enable election configuration | true |
leaderElectionId |
Set the election ID for the controller group | |
githubEnterpriseServerURL |
Set the URL for a self-hosted GitHub Enterprise Server | |
githubURL |
Override GitHub URL to be used for GitHub API calls | |
githubUploadURL |
Override GitHub Upload URL to be used for GitHub API calls | |
runnerGithubURL |
Override GitHub URL to be used by runners during registration | |
logLevel |
Set the log level of the controller container | |
logFormat |
Set the log format of the controller. Valid options are "text" and "json" | text |
additionalVolumes |
Set additional volumes to add to the manager container | |
additionalVolumeMounts |
Set additional volume mounts to add to the manager container | |
authSecret.create |
Deploy the controller auth secret | false |
authSecret.name |
Set the name of the auth secret | controller-manager |
authSecret.annotations |
Set annotations for the auth Secret | |
authSecret.github_app_id |
The ID of your GitHub App. This can't be set at the same time as authSecret.github_token |
|
authSecret.github_app_installation_id |
The ID of your GitHub App installation. This can't be set at the same time as authSecret.github_token |
|
authSecret.github_app_private_key |
The multiline string of your GitHub App's private key. This can't be set at the same time as authSecret.github_token |
|
authSecret.github_token |
Your chosen GitHub PAT token. This can't be set at the same time as the authSecret.github_app_* |
|
authSecret.github_basicauth_username |
Username for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API | |
authSecret.github_basicauth_password |
Password for GitHub basic auth to use instead of PAT or GitHub APP in case it's running behind a proxy API | |
dockerRegistryMirror |
The default Docker Registry Mirror used by runners. | |
hostNetwork |
The "hostNetwork" of the controller container | false |
dnsPolicy |
The "dnsPolicy" of the controller container | ClusterFirst |
image.repository |
The "repository/image" of the controller container | summerwind/actions-runner-controller |
image.tag |
The tag of the controller container | |
image.actionsRunnerRepositoryAndTag |
The "repository/image" of the actions runner container | summerwind/actions-runner:latest |
image.actionsRunnerImagePullSecrets |
Optional image pull secrets to be included in the runner pod's ImagePullSecrets | |
image.dindSidecarRepositoryAndTag |
The "repository/image" of the dind sidecar container | docker:dind |
image.pullPolicy |
The pull policy of the controller image | IfNotPresent |
metrics.serviceMonitor.enable |
Deploy serviceMonitor kind for for use with prometheus-operator CRDs | false |
metrics.serviceMonitor.interval |
Configure the interval that Prometheus should scrap the controller's metrics | 1m |
metrics.serviceMonitor.namespace |
Namespace which Prometheus is running in | Release.Namespace (the default namespace of the helm chart). |
metrics.serviceMonitor.timeout |
Configure the timeout the timeout of Prometheus scrapping. | 30s |
metrics.serviceAnnotations |
Set annotations for the provisioned metrics service resource | |
metrics.port |
Set port of metrics service | 8443 |
metrics.proxy.enabled |
Deploy kube-rbac-proxy container in controller pod | true |
metrics.proxy.image.repository |
The "repository/image" of the kube-proxy container | quay.io/brancz/kube-rbac-proxy |
metrics.proxy.image.tag |
The tag of the kube-proxy image to use when pulling the container | v0.13.1 |
metrics.serviceMonitorLabels |
Set labels to apply to ServiceMonitor resources | |
imagePullSecrets |
Specifies the secret to be used when pulling the controller pod containers | |
fullnameOverride |
Override the full resource names | |
nameOverride |
Override the resource name prefix | |
serviceAccount.annotations |
Set annotations to the service account | |
serviceAccount.create |
Deploy the controller pod under a service account | true |
podAnnotations |
Set annotations for the controller pod | |
podLabels |
Set labels for the controller pod | |
serviceAccount.name |
Set the name of the service account | |
securityContext |
Set the security context for each container in the controller pod | |
podSecurityContext |
Set the security context to controller pod | |
service.annotations |
Set annotations for the provisioned webhook service resource | |
service.port |
Set controller service ports | |
service.type |
Set controller service type | |
topologySpreadConstraints |
Set the controller pod topologySpreadConstraints | |
nodeSelector |
Set the controller pod nodeSelector | |
resources |
Set the controller pod resources | |
affinity |
Set the controller pod affinity rules | |
podDisruptionBudget.enabled |
Enables a PDB to ensure HA of controller pods | false |
podDisruptionBudget.minAvailable |
Minimum number of pods that must be available after eviction | |
podDisruptionBudget.maxUnavailable |
Maximum number of pods that can be unavailable after eviction. Kubernetes 1.7+ required. | |
tolerations |
Set the controller pod tolerations | |
env |
Set environment variables for the controller container | |
priorityClassName |
Set the controller pod priorityClassName | |
scope.watchNamespace |
Tells the controller and the github webhook server which namespace to watch if scope.singleNamespace is true |
Release.Namespace (the default namespace of the helm chart). |
scope.singleNamespace |
Limit the controller to watch a single namespace | false |
certManagerEnabled |
Enable cert-manager. If disabled you must set admissionWebHooks.caBundle and create TLS secrets manually | true |
runner.statusUpdateHook.enabled |
Use custom RBAC for runners (role, role binding and service account), this will enable reporting runner statuses | false |
admissionWebHooks.caBundle |
Base64-encoded PEM bundle containing the CA that signed the webhook's serving certificate | |
githubWebhookServer.logLevel |
Set the log level of the githubWebhookServer container | |
githubWebhookServer.logFormat |
Set the log format of the githubWebhookServer controller. Valid options are "text" and "json" | text |
githubWebhookServer.replicaCount |
Set the number of webhook server pods | 1 |
githubWebhookServer.useRunnerGroupsVisibility |
Enable supporting runner groups with custom visibility, you also need to set githubWebhookServer.secret.enabled to enable this feature. |
false |
githubWebhookServer.enabled |
Deploy the webhook server pod | false |
githubWebhookServer.queueLimit |
Set the queue size limit in the githubWebhookServer | |
githubWebhookServer.secret.enabled |
Passes the webhook hook secret to the github-webhook-server | false |
githubWebhookServer.secret.create |
Deploy the webhook hook secret | false |
githubWebhookServer.secret.name |
Set the name of the webhook hook secret | github-webhook-server |
githubWebhookServer.secret.github_webhook_secret_token |
Set the webhook secret token value | |
githubWebhookServer.imagePullSecrets |
Specifies the secret to be used when pulling the githubWebhookServer pod containers | |
githubWebhookServer.nameOverride |
Override the resource name prefix | |
githubWebhookServer.fullnameOverride |
Override the full resource names | |
githubWebhookServer.serviceAccount.create |
Deploy the githubWebhookServer under a service account | true |
githubWebhookServer.serviceAccount.annotations |
Set annotations for the service account | |
githubWebhookServer.serviceAccount.name |
Set the service account name | |
githubWebhookServer.podAnnotations |
Set annotations for the githubWebhookServer pod | |
githubWebhookServer.podLabels |
Set labels for the githubWebhookServer pod | |
githubWebhookServer.podSecurityContext |
Set the security context to githubWebhookServer pod | |
githubWebhookServer.securityContext |
Set the security context for each container in the githubWebhookServer pod | |
githubWebhookServer.resources |
Set the githubWebhookServer pod resources | |
githubWebhookServer.topologySpreadConstraints |
Set the githubWebhookServer pod topologySpreadConstraints | |
githubWebhookServer.nodeSelector |
Set the githubWebhookServer pod nodeSelector | |
githubWebhookServer.tolerations |
Set the githubWebhookServer pod tolerations | |
githubWebhookServer.affinity |
Set the githubWebhookServer pod affinity rules | |
githubWebhookServer.priorityClassName |
Set the githubWebhookServer pod priorityClassName | |
githubWebhookServer.terminationGracePeriodSeconds |
Set the githubWebhookServer pod terminationGracePeriodSeconds. Useful when using preStop hooks to drain/sleep. | 10 |
githubWebhookServer.lifecycle |
Set the githubWebhookServer pod lifecycle hooks | {} |
githubWebhookServer.service.type |
Set githubWebhookServer service type | |
githubWebhookServer.service.ports |
Set githubWebhookServer service ports | [{"port":80, "targetPort:"http", "protocol":"TCP", "name":"http"}] |
githubWebhookServer.service.loadBalancerSourceRanges |
Set githubWebhookServer loadBalancerSourceRanges for restricting loadBalancer type services | [] |
githubWebhookServer.ingress.enabled |
Deploy an ingress kind for the githubWebhookServer | false |
githubWebhookServer.ingress.annotations |
Set annotations for the ingress kind | |
githubWebhookServer.ingress.hosts |
Set hosts configuration for ingress | [{"host": "chart-example.local", "paths": []}] |
githubWebhookServer.ingress.tls |
Set tls configuration for ingress | |
githubWebhookServer.ingress.ingressClassName |
Set ingress class name | |
githubWebhookServer.podDisruptionBudget.enabled |
Enables a PDB to ensure HA of githubwebhook pods | false |
githubWebhookServer.podDisruptionBudget.minAvailable |
Minimum number of pods that must be available after eviction | |
githubWebhookServer.podDisruptionBudget.maxUnavailable |
Maximum number of pods that can be unavailable after eviction. Kubernetes 1.7+ required. | |
actionsMetricsServer.logLevel |
Set the log level of the actionsMetricsServer container | |
actionsMetricsServer.logFormat |
Set the log format of the actionsMetricsServer controller. Valid options are "text" and "json" | text |
actionsMetricsServer.enabled |
Deploy the actions metrics server pod | false |
actionsMetricsServer.secret.enabled |
Passes the webhook hook secret to the actions-metrics-server | false |
actionsMetricsServer.secret.create |
Deploy the webhook hook secret | false |
actionsMetricsServer.secret.name |
Set the name of the webhook hook secret | actions-metrics-server |
actionsMetricsServer.secret.github_webhook_secret_token |
Set the webhook secret token value | |
actionsMetricsServer.imagePullSecrets |
Specifies the secret to be used when pulling the actionsMetricsServer pod containers | |
actionsMetricsServer.nameOverride |
Override the resource name prefix | |
actionsMetricsServer.fullnameOverride |
Override the full resource names | |
actionsMetricsServer.serviceAccount.create |
Deploy the actionsMetricsServer under a service account | true |
actionsMetricsServer.serviceAccount.annotations |
Set annotations for the service account | |
actionsMetricsServer.serviceAccount.name |
Set the service account name | |
actionsMetricsServer.podAnnotations |
Set annotations for the actionsMetricsServer pod | |
actionsMetricsServer.podLabels |
Set labels for the actionsMetricsServer pod | |
actionsMetricsServer.podSecurityContext |
Set the security context to actionsMetricsServer pod | |
actionsMetricsServer.securityContext |
Set the security context for each container in the actionsMetricsServer pod | |
actionsMetricsServer.resources |
Set the actionsMetricsServer pod resources | |
actionsMetricsServer.topologySpreadConstraints |
Set the actionsMetricsServer pod topologySpreadConstraints | |
actionsMetricsServer.nodeSelector |
Set the actionsMetricsServer pod nodeSelector | |
actionsMetricsServer.tolerations |
Set the actionsMetricsServer pod tolerations | |
actionsMetricsServer.affinity |
Set the actionsMetricsServer pod affinity rules | |
actionsMetricsServer.priorityClassName |
Set the actionsMetricsServer pod priorityClassName | |
actionsMetricsServer.terminationGracePeriodSeconds |
Set the actionsMetricsServer pod terminationGracePeriodSeconds. Useful when using preStop hooks to drain/sleep. | 10 |
actionsMetricsServer.lifecycle |
Set the actionsMetricsServer pod lifecycle hooks | {} |
actionsMetricsServer.service.type |
Set actionsMetricsServer service type | |
actionsMetricsServer.service.ports |
Set actionsMetricsServer service ports | [{"port":80, "targetPort:"http", "protocol":"TCP", "name":"http"}] |
actionsMetricsServer.service.loadBalancerSourceRanges |
Set actionsMetricsServer loadBalancerSourceRanges for restricting loadBalancer type services | [] |
actionsMetricsServer.ingress.enabled |
Deploy an ingress kind for the actionsMetricsServer | false |
actionsMetricsServer.ingress.annotations |
Set annotations for the ingress kind | |
actionsMetricsServer.ingress.hosts |
Set hosts configuration for ingress | [{"host": "chart-example.local", "paths": []}] |
actionsMetricsServer.ingress.tls |
Set tls configuration for ingress | |
actionsMetricsServer.ingress.ingressClassName |
Set ingress class name | |
actionsMetrics.serviceMonitor.enable |
Deploy serviceMonitor kind for for use with prometheus-operator CRDs | false |
actionsMetrics.serviceMonitor.interval |
Configure the interval that Prometheus should scrap the controller's metrics | 1m |
actionsMetrics.serviceMonitor.namespace |
Namespace which Prometheus is running in. | Release.Namespace (the default namespace of the helm chart). |
actionsMetrics.serviceMonitor.timeout |
Configure the timeout the timeout of Prometheus scrapping. | 30s |
actionsMetrics.serviceAnnotations |
Set annotations for the provisioned actions metrics service resource | |
actionsMetrics.port |
Set port of actions metrics service | 8443 |
actionsMetrics.proxy.enabled |
Deploy kube-rbac-proxy container in controller pod | true |
actionsMetrics.proxy.image.repository |
The "repository/image" of the kube-proxy container | quay.io/brancz/kube-rbac-proxy |
actionsMetrics.proxy.image.tag |
The tag of the kube-proxy image to use when pulling the container | v0.13.1 |
actionsMetrics.serviceMonitorLabels |
Set labels to apply to ServiceMonitor resources |
File: contrib/examples/actions-runner/README.md
Docs
All additional docs are kept in the docs/ folder, this README is solely for documenting the values.yaml keys and values
Values
The values are documented as of HEAD, to review the configuration options for your chart version ensure you view this file at the relevent tag
Default values are the defaults set in the charts values.yaml, some properties have default configurations in the code for when the property is omitted or invalid
| Key | Description | Default |
|---|---|---|
labels |
Set labels to apply to all resources in the chart | |
replicaCount |
Set the number of runner pods | 1 |
image.repository |
The "repository/image" of the runner container | summerwind/actions-runner |
image.tag |
The tag of the runner container | |
image.pullPolicy |
The pull policy of the runner image | IfNotPresent |
imagePullSecrets |
Specifies the secret to be used when pulling the runner pod containers | |
fullnameOverride |
Override the full resource names | |
nameOverride |
Override the resource name prefix | |
podAnnotations |
Set annotations for the runner pod | |
podLabels |
Set labels for the runner pod | |
podSecurityContext |
Set the security context to runner pod | |
nodeSelector |
Set the pod nodeSelector | |
affinity |
Set the runner pod affinity rules | |
tolerations |
Set the runner pod tolerations | |
env |
Set environment variables for the runner container | |
organization |
Github organization where the runner will be registered | test |
repository |
Github repository where the runner will be registered | |
runnerLabels |
Labels you want to add in your runner | test |
autoscaler.enabled |
Enable the HorizontalRunnerAutoscaler, if its enabled then replica count will not be used | true |
autoscaler.minReplicas |
Minimum no of replicas | 1 |
autoscaler.maxReplicas |
Maximum no of replicas | 5 |
autoscaler.scaleDownDelaySecondsAfterScaleOut |
Anti-Flapping Configuration | 120 |
autoscaler.metrics |
Pull driven scaling | default |
autoscaler.scaleUpTriggers |
Webhook driven scaling |
File: contrib/README.md
The contrib directory is the place for sharing various example code for deploying and operating actions-runner-controller.
Anything contained in this directory is provided as-is. The maintainers of actions-runner-controller are not yet committed to provide
full support for using, fixing, and enhancing it. However, they will make their best effort to collect feedback from early adopters and advanced users like you, and may eventually consider graduating any of the examples as an official addition to the project.
See https://github.com/actions/actions-runner-controller/pull/1375#issuecomment-1258816470 and https://github.com/actions/actions-runner-controller/pull/1559#issuecomment-1258827496 for more context.
File: docs/adrs/2022-10-17-runner-image.md
ADR 2022-10-17: Produce the runner image for the scaleset client
Date: 2022-10-17
Status: Done
Breaking Changes
We aim to provide an similar experience (as close as possible) between self-hosted and GitHub-hosted runners. To achieve this, we are making the following changes to align our self-hosted runner container image with the Ubuntu runners managed by GitHub.
Here are the changes:
- We created a USER
runner(1001)and a GROUPdocker(123) sudohas been on the image and therunnerwill be a passwordless sudoer.- The runner binary was placed placed under
/home/runner/and launched using/home/runner/run.sh - The runner's work directory is
/home/runner/_work $HOMEwill point to/home/runner- The container image user will be the
runner(1001)
The latest Dockerfile can be found at: https://github.com/actions/runner/blob/main/images/Dockerfile
Context
users can bring their own runner images, the contract we require is:
- It must have a runner binary under
/actions-runneri.e./actions-runner/run.shexists - The
WORKDIRis set to/actions-runner - If the user inside the container is root, the environment variable
RUNNER_ALLOW_RUNASROOTshould be set to1
The existing ARC runner images will not work with the new ARC mode out-of-box for the following reason:
- The current runner image requires the caller to pass runner configuration info, ex: URL and Config Token
- The current runner image has the runner binary under
/runnerwhich violates the contract described above - The current runner image requires a special entrypoint script in order to work around some volume mount limitation for setting up DinD.
Since we expose the raw runner PodSpec to our end users, they can modify the helm values.yaml to adjust the runner container to their needs.
Guiding Principles
- Build image is separated in two stages.
The first stage (build)
- Reuses the same base image, so it is faster to build.
- Installs utilities needed to download assets (
runnerandrunner-container-hooks). - Downloads the runner and stores it into
/actions-runnerdirectory. - Downloads the runner-container-hooks and stores it into
/actions-runner/k8sdirectory. - You can use build arguments to control the runner version, the target platform and runner container hooks version.
Preview (the published runner image might vary):
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0 as build
ARG RUNNER_ARCH="x64"
ARG RUNNER_VERSION=2.298.2
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.1.3
RUN apt update -y && apt install curl unzip -y
WORKDIR /actions-runner
RUN curl -f -L -o runner.tar.gz https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz \
&& tar xzf ./runner.tar.gz \
&& rm runner.tar.gz
RUN curl -f -L -o runner-container-hooks.zip https://github.com/actions/runner-container-hooks/releases/download/v${RUNNER_CONTAINER_HOOKS_VERSION}/actions-runner-hooks-k8s-${RUNNER_CONTAINER_HOOKS_VERSION}.zip \
&& unzip ./runner-container-hooks.zip -d ./k8s \
&& rm runner-container-hooks.zipThe main image:
- Copies assets from the build stage to
/actions-runner - Does not provide an entrypoint. The entrypoint should be set within the container definition.
Preview:
FROM mcr.microsoft.com/dotnet/runtime-deps:6.0
WORKDIR /actions-runner
COPY --from=build /actions-runner .Example of pod spec with the init container copying assets
apiVersion: v1
kind: Pod
metadata:
name: <name>
spec:
containers:
- name: runner
image:
command: ["/runner/run.sh"]
volumeMounts:
- name: runner
mountPath: /runner
initContainers:
- name: setup
image:
command: ["sh", "-c", "cp -r /actions-runner/* /runner/"]
volumeMounts:
- name: runner
mountPath: /runner
volumes:
- name: runner
emptyDir: {}File: docs/adrs/2022-10-27-runnerscaleset-lifetime.md
ADR 2022-10-27: Lifetime of RunnerScaleSet on Service
Date: 2022-10-27
Status: Done
Context
We have created the RunnerScaleSet object and APIs around it on the GitHub Actions service for better support of any self-hosted runner auto-scale solution, like actions-runner-controller.
The RunnerScaleSet object will represent a set of homogeneous self-hosted runners to the Actions service job routing system.
A RunnerScaleSet client (ARC) needs to communicate with the Actions service via HTTP long-poll in a certain protocol to get a workflow job successfully landed on one of its homogeneous self-hosted runners.
In this ADR, we discuss the following within the context of actions-runner-controller's new scaling mode:
- Who and how to create a RunnerScaleSet on the service?
- Who and how to delete a RunnerScaleSet on the service?
- What will happen to all the runners and jobs when the deletion happens?
RunnerScaleSet creation
AutoScalingRunnerSetcustom resource controller will create theRunnerScaleSetobject in the Actions service on anyAutoScalingRunnerSetresource deployment.- The creation is via REST API on Actions service
POST _apis/runtime/runnerscalesets - The creation needs to use the runner registration token (admin).
RunnerScaleSet.Name==AutoScalingRunnerSet.metadata.Name- The created
RunnerScaleSetwill only have 1 label and it's theRunnerScaleSet's name AutoScalingRunnerSetcontroller will store theRunnerScaleSet.Idas an annotation on the k8s resource for future lookup.
RunnerScaleSet modification
- When the user patch existing
AutoScalingRunnerSet's RunnerScaleSet related properly, ex:runnerGroupName,runnerWorkDir, the controller needs to make an HTTP PATCH call to the_apis/runtime/runnerscalesets/2endpoint in order to update the object on the service. - We will put the deployed
AutoScalingRunnerSetresource in an error state when the user tries to patch the resource with a differentgithubConfigUrlBasically, you can't move a deployed
AutoScalingRunnerSetacross GitHub entity, repoA->repoB, repoA->OrgC, etc.
We evaluated blocking the change before instead of erroring at runtime and that we decided not to go down this route because it forces us to re-introduce admission webhooks (require cert-manager).
RunnerScaleSet deletion
AutoScalingRunnerSetcustom resource controller will delete theRunnerScaleSetobject in the Actions service on anyAutoScalingRunnerSetresource deletion.AutoScalingRunnerSetdeletion will contain several steps:- Stop the listener app so no more new jobs coming and no more scaling up/down.
- Request scale down to 0
- Force stop all runners
- Wait for the scale down to 0
- Delete the
RunnerScaleSetobject from service via REST API
- The deletion is via REST API on Actions service
DELETE _apis/runtime/runnerscalesets/1 - The deletion needs to use the runner registration token (admin).
The user's RunnerScaleSet will be deleted from the service by DormantRunnerScaleSetCleanupJob if the particular AutoScalingRunnerSet has not connected to the service for the past 7 days. We have a similar rule for self-hosted runners.
Jobs and Runners on deletion
RunnerScaleSetdeletion will be blocked if there is any job assigned to a runner within theRunnerScaleSet, which has to scale down to 0 before deletion.- Any job that has been assigned to the
RunnerScaleSetbut hasn't been assigned to a runner within theRunnerScaleSetwill get thrown back to the queue and wait for assignment again. - Any offline runners within the
RunnerScaleSetwill be deleted from the service side.
File: docs/adrs/2022-11-04-crd-api-group-name.md
ADR 2022-11-04: Technical detail about actions-runner-controller repository transfer
Date: 2022-11-04
Status: Done
Context
As part of ARC Private Beta: Repository Migration & Open Sourcing Process, we have decided to transfer the current actions-runner-controller repository into the Actions org.
Goals:
- A clear signal that GitHub will start taking over ARC and provide support.
- Since we are going to deprecate the existing auto-scale mode in ARC at some point, we want to have a clear separation between the legacy mode (not supported) and the new mode (supported).
- Avoid disrupting users as much as we can, existing ARC users will not notice any difference after the repository transfer, they can keep upgrading to the newer version of ARC and keep using the legacy mode.
Challenges
- The original creator's name (
summerwind) is all over the place, including some critical parts of ARC:- The k8s user resource API's full name is
actions.summerwind.dev/v1alpha1/RunnerDeployment, renaming it toactions.github.comis a breaking change and will force the user to rebuild their entire k8s cluster. - All docker images around ARC (controller + default runner) is published to dockerhub/summerwind
- The k8s user resource API's full name is
- The helm chart for ARC is currently hosted on GitHub pages for https://github.com/actions-runner-controller/actions-runner-controller, moving the repository means we will break users who install ARC via the helm chart
Decisions
APIs group names for k8s custom resources, `actions.summerwind` or `actions.github`
- We will not rename any existing ARC resources API name after moving the repository under Actions org. (keep
summerwindfor old stuff) - For any new resource API we are going to add, those will be named properly under GitHub, ex:
actions.github.com/v1alpha1/AutoScalingRunnerSet
Benefits:
- A clear separation from existing ARC:
- Easy for the support engineer to triage income tickets and figure out whether we need to support the use case from the user
- We won't break existing users when they upgrade to a newer version of ARC after the repository transfer
Based on the spike done by @nikola-jokic, we have confidence that we can host multiple resources with different API names under the same repository, and the published ARC controller can handle both resources properly.
ARC Docker images
We will not start using the GitHub container registry for hosting ARC images (controller + runner images) right after the repository transfer.
But over time, we will start using GHCR for hosting those images along with our deprecation story.
Helm chart
We will recreate the https://github.com/actions-runner-controller/actions-runner-controller repository after the repository transfer.
The recreated repository will only contain the helm chart assets which keep powering the https://actions-runner-controller.github.io/actions-runner-controller for users to install ARC via Helm.
Long term, we will switch to hosting the helm chart on GHCR (OCI) instead of using GitHub Pages.
This will require a one-time change to our users by runninghelm repo remove actions-runner-controller and helm repo add actions-runner-controller oci://ghcr.io/actions
File: docs/adrs/2022-12-05-adding-labels-k8s-resources.md
ADR 2022-12-05: Adding labels to our resources
Date: 2022-12-05
Status: Superceded [^1]
Context
users need to provide us with logs so that we can help support and troubleshoot their issues. We need a way for our users to filter and retrieve the logs we need.
Proposal
A good start would be a catch-all label to get all logs that are
ARC-related: one of the recommended labels
is app.kubernetes.io/part-of and we can set that for all ARC components
to be actions-runner-controller.
Assuming standard logging that would allow us to get all ARC logs by running
kubectl logs -l 'app.kubernetes.io/part-of=actions-runner-controller'which would be very useful for development to begin with.
The proposal is to add these sets of labels to the pods ARC creates:
controller-manager
Labels to be set by the Helm chart:
metadata:
labels:
app.kubernetes.io/part-of: actions-runner-controller
app.kubernetes.io/component: controller-manager
app.kubernetes.io/version: "x.x.x"Listener
Labels to be set by controller at creation:
metadata:
labels:
app.kubernetes.io/part-of: actions-runner-controller
app.kubernetes.io/component: runner-scale-set-listener
app.kubernetes.io/version: "x.x.x"
actions.github.com/scale-set-name: scale-set-name # this corresponds to metadata.name as set for AutoscalingRunnerSet
# the following labels are to be extracted by the config URL
actions.github.com/enterprise: enterprise
actions.github.com/organization: organization
actions.github.com/repository: repositoryRunner
Labels to be set by controller at creation:
metadata:
labels:
app.kubernetes.io/part-of: actions-runner-controller
app.kubernetes.io/component: runner
app.kubernetes.io/version: "x.x.x"
actions.github.com/scale-set-name: scale-set-name # this corresponds to metadata.name as set for AutoscalingRunnerSet
actions.github.com/runner-name: runner-name
actions.github.com/runner-group-name: runner-group-name
# the following labels are to be extracted by the config URL
actions.github.com/enterprise: enterprise
actions.github.com/organization: organization
actions.github.com/repository: repositoryThis would allow us to ask users:
Can you please send us the logs coming from pods labelled 'app.kubernetes.io/part-of=actions-runner-controller'?
Or for example if they're having problems specifically with runners:
Can you please send us the logs coming from pods labelled 'app.kubernetes.io/component=runner'?
This way users don't have to understand ARC moving parts but we still have a
way to target them specifically if we need to.
[^1]: Superseded by ADR 2023-03-14
File: docs/adrs/2022-12-27-pick-the-right-runner-to-scale-down.md
ADR 2022-12-27: Pick the right runner to scale down
Date: 2022-12-27
Status: Done
Context
- A custom resource
EphemeralRunnerSetmanage a set of custom resourceEphemeralRunners - The
EphemeralRunnerSethasReplicasin itsSpec, and the responsibility of theEphemeralRunnerSet_controlleris to reconcile a givenEphemeralRunnerSetto have
the same amount ofEphemeralRunnersas theSpec.Replicasdefined. - This means the
EphemeralRunnerSet_controllerwill scale up theEphemeralRunnerSetby creating moreEphemeralRunnerin the case of theSpec.Replicasis higher than
the current amount ofEphemeralRunners. - This also means the
EphemeralRunnerSet_controllerwill scale down theEphemeralRunnerSetby finding some existingEphemeralRunnerto delete in the case of
theSpec.Replicasis less than the current amount ofEphemeralRunners.
This ADR is about how can we find the right existing EphemeralRunner to delete when we need to scale down.
Current approach
EphemeralRunnerSet_controllerfigure out how manyEphemeralRunnerit needs to delete, ex: need to scale down from 10 to 2 means we need to delete 8EphemeralRunnerEphemeralRunnerSet_controllerfind allEphemeralRunnerthat is in theRunningorPendingphase.Pendingmeans theEphemeralRunneris still probably creating and a runner has not yet configured with the Actions service.Runningmeans theEphemeralRunneris created and a runner has probably configured with Actions service, the runner may sit there idle,
or maybe actively running a workflow job. We don't have a clear answer for it from the ARC side. (Actions service knows it for sure)EphemeralRunnerSet_controllermake an HTTP DELETE request to the Actions service for eachEphemeralRunnerfrom the previous step and ask the Actions service to delete the runner viaRunnerId.
(TheRunnerIdis generated after the runner registered with the Actions service, and stored on theEphemeralRunner.Status.RunnerId)- The HTTP DELETE request looks like the following:
DELETE https://pipelines.actions.githubusercontent.com/WoxlUxJHrKEzIp4Nz3YmrmLlZBonrmj9xCJ1lrzcJ9ZsD1Tnw7/_apis/distributedtask/pools/0/agents/1024
The Actions service will return 2 types of responses:
- 204 (No Content): The runner with Id 1024 has been successfully removed from the service or the runner with Id 1024 doesn't exist.
- 400 (Bad Request) with JSON body that contains an error message like
JobStillRunningException: The service can't remove this runner at this point since it has been
assigned to a job request, the client won't be able to remove the runner until the runner finishes its current assigned job request.
- The HTTP DELETE request looks like the following:
EphemeralRunnerSet_controllerwill ignore any deletion error from runners that are still running a job, and keep trying deletion until the amount of204equals the amount ofEphemeralRunnerneeds to delete.
The problem with the current approach
In a busy AutoScalingRunnerSet, the scale up and down may happen all the time as jobs are queued up and jobs finished.
We will make way too many HTTP requests to the Actions service and ask it to try to delete a certain runner, and rely on the exception from the service to figure out what to do next.
The runner deletion request is not cheap to the service, for synchronization, the JobStillRunningException is raised from the DB call for the request.
So we are wasting resources on both the Actions service (extra load to the database) and the actions-runner-controller (useless outgoing HTTP requests).
In the test ARC that I deployed to Azure, the ARC controller tried to delete RunnerId 12408 for bbq-beets/ting-test a total of 35 times within 10 minutes.
Root cause
The EphemeralRunnerSet_controller doesn't know whether a given EphemeralRunner is actually running a workflow job or not
(it only knows the runner is configured at the service), so it can't filter out the EphemeralRunner.
Additional context
The legacy ARC's custom resource allows the runner image to leverage the RunnerJobHook feature to update the status of the runner custom resource in K8S (Mark the runner as running workflow run Id XXX).
This brings a good value to users as it can provide some insight about which runner is running which job for all the runners in the cluster and it looks pretty close to what we want to fix the root cause
However, the legacy ARC approach means the service account for running the runner pod needs to have elevated permission to update the custom resource,
this would be a big NO from a security point of view since we may not trust the code running inside the runner pod.
Possible Solution
The nature of the k8s controller-runtime means we might reconcile the resource base on stale cache data.
I think our goal for the solution should be:
- Reduce wasteful HTTP requests on a scale-down as much as we can.
- We can accept that we might make 1 or 2 wasteful requests to Actions service, but we can't accept making 5/10+ of them.
- See if we can meet feature parity with what the RunnerJobHook support with compromise any security concerns.
Since the root cause of why the reconciliation can't skip an EphemeralRunner is that we don't know whether an EphemeralRunner is running a job,
a simple thought is how about we somehow attach some info to the EphemeralRunner to indicate it's currently running a job?
How about we send this info from the service to the auto-scaling-listener via the existing HTTP long-poll
and let the listener patch the EphemeralRunner.Status to indicate it's running a job?
The listener is normally in a separate namespace with elevated permission and it's something we can trust.
Changes:
- Introduce a new message type
JobStarted(in addition to the existingJobAvailable/JobAssigned/JobCompleted) on the service side, the message is sent when a runner of theRunnerScaleSetget assigned to a job,RequestId,RunnerId, andRunnerNamewill be included in the message. - Add
RequestId (int)toEphemeralRunner.Status, this will indicate which job the runner is running. - The
AutoScalingListenerwill base on the payload of this new message to patchEphemeralRunners/RunnerName/Statuswith theRequestId - When
EphemeralRunnerSet_controllertry to findEphemeralRunnerto delete on a scale down, it will skip anyEphemeralRunnerthat hasEphemeralRunner.Status.RequestIdset. - In the future, we can expose more info to this
JobStartedmessage and introduce more property underEphemeralRunner.Statusto reach feature parity with legacy ARC's RunnerJobHook