OpenMCF logoOpenMCF

Loading...

Kubernetes GHA Runner Scale Set

Deploys a GitHub Actions Runner Scale Set on a Kubernetes cluster, providing self-hosted runners that automatically scale based on workflow demand. The module installs an AutoScalingRunnerSet custom resource via the official Helm chart. When GitHub Actions workflows request runners with matching labels, the controller creates ephemeral runner pods to execute the jobs, scaling down to a configurable minimum when idle.

What Gets Created

When you deploy a KubernetesGhaRunnerScaleSet resource, OpenMCF provisions:

  • Namespace — created only when createNamespace is true
  • Persistent Volume Claims — one PVC per entry in persistentVolumes, used to persist build caches or dependencies across job runs
  • GitHub Credentials Secret — a Kubernetes Secret containing the PAT token or GitHub App credentials (skipped when existingSecretName is provided)
  • Helm Release — the gha-runner-scale-set chart from oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set, which creates the AutoScalingRunnerSet custom resource that the controller watches

Prerequisites

  • KubernetesGhaRunnerScaleSetController must already be deployed in the cluster (use the KubernetesGhaRunnerScaleSetController component)
  • Kubernetes credentials configured via environment variables or OpenMCF provider config
  • A Kubernetes namespace that already exists, or set createNamespace to true
  • GitHub authentication — one of:
    • A Personal Access Token (PAT) with repo scope (repository-level) or admin:org scope (organization-level)
    • A GitHub App with an installation ID and private key
    • A pre-existing Kubernetes Secret containing the credentials

Quick Start

Create a file gha-runner-scale-set.yaml:

apiVersion: kubernetes.openmcf.org/v1
kind: KubernetesGhaRunnerScaleSet
metadata:
  name: my-runners
  labels:
    openmcf.org/provisioner: pulumi
    pulumi.openmcf.org/organization: my-org
    pulumi.openmcf.org/project: my-project
    pulumi.openmcf.org/stack.name: dev.KubernetesGhaRunnerScaleSet.my-runners
spec:
  namespace:
    value: gha-runners
  createNamespace: true
  github:
    configUrl: https://github.com/my-org
    patToken:
      token: ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  containerMode:
    type: DIND

Deploy:

openmcf apply -f gha-runner-scale-set.yaml

This registers a runner scale set named my-runners against the my-org GitHub organization using Docker-in-Docker mode. Workflows targeting runs-on: [self-hosted, my-runners] will be picked up by the ephemeral runner pods.

Configuration Reference

Required Fields

FieldTypeDescriptionValidation
namespaceStringValueOrRefKubernetes namespace where the runner scale set is installed. Can reference a KubernetesNamespace resource via valueFrom.Required
githubobjectGitHub connection configuration.Required
github.configUrlstringGitHub URL to register runners against. Accepts repository (https://github.com/org/repo), organization (https://github.com/org), or enterprise (https://github.com/enterprises/ent) URLs.Must start with https://github.com/ or https://github.
github.patToken.tokenstringPersonal Access Token. Required when using PAT authentication.Required (if PAT auth)
github.githubApp.appIdstringGitHub App ID or Client ID.Required (if App auth)
github.githubApp.installationIdstringGitHub App Installation ID.Required (if App auth)
github.githubApp.privateKeyBase64stringBase64-encoded PEM private key for the GitHub App.Required (if App auth)
containerModeobjectContainer mode configuration for running workflows.Required
containerMode.typeenumContainer mode type. Valid values: DIND, KUBERNETES, KUBERNETES_NO_VOLUME, DEFAULT.Required

Optional Fields

FieldTypeDefaultDescription
targetCluster.clusterKindenum—Kubernetes cluster kind. Valid values: AwsEksCluster, GcpGkeCluster, AzureAksCluster, DigitalOceanKubernetesCluster, CivoKubernetesCluster.
targetCluster.clusterNamestring—Name of the target Kubernetes cluster in the same environment.
createNamespaceboolfalseWhen true, creates the namespace before deploying the scale set.
helmChartVersionstring"0.13.1"Version of the gha-runner-scale-set Helm chart. Chart versions align with the runner image versions.
scaling.minRunnersint320Minimum number of idle runners. Set to 0 for scale-to-zero. Must be >= 0.
scaling.maxRunnersint325Maximum concurrent runners. Limits resource consumption. Must be >= 1.
runnerGroupstring"default"Runner group name in GitHub (organization or enterprise level).
runnerScaleSetNamestringmetadata.nameName of the scale set as it appears in GitHub. Used as the runs-on label in workflow YAML.
containerMode.workVolumeClaim.sizestring—Size of the ephemeral work volume for Kubernetes mode (e.g., 10Gi). Required when containerMode.type is KUBERNETES.
containerMode.workVolumeClaim.storageClassstringcluster defaultStorage class for the work volume.
containerMode.workVolumeClaim.accessModesstring[]["ReadWriteOnce"]Access modes for the work volume.
runner.image.repositorystring"ghcr.io/actions/actions-runner"Runner container image repository.
runner.image.tagstring"2.331.0"Runner container image tag.
runner.image.pullPolicystring"IfNotPresent"Image pull policy. Valid values: Always, IfNotPresent, Never.
runner.resources.requests.cpustring"500m"CPU request for the runner container.
runner.resources.requests.memorystring"1Gi"Memory request for the runner container.
runner.resources.limits.cpustring"2"CPU limit for the runner container.
runner.resources.limits.memorystring"4Gi"Memory limit for the runner container.
runner.envobject[]—Environment variables injected into the runner container. Each entry has name and value.
runner.volumeMountsobject[]—Additional volume mounts for the runner container. Each entry has name, mountPath, and optional readOnly / subPath.
persistentVolumesobject[]—Persistent volumes to create and mount. Each entry has name, size, mountPath, and optional storageClass, accessModes, readOnly.
controllerServiceAccount.namespacestring—Namespace where the controller is installed. Required when automatic controller discovery does not work.
controllerServiceAccount.namestring—Name of the controller's service account.
imagePullSecretsstring[]—Names of Kubernetes Secrets for pulling images from private registries.
labelsmap<string, string>—Labels applied to all resources created by the scale set.
annotationsmap<string, string>—Annotations applied to all resources created by the scale set.
github.existingSecretNamestring—Name of a pre-existing Secret containing GitHub credentials. Must be in the same namespace. Mutually exclusive with patToken and githubApp.

Note on StringValueOrRef: The namespace field accepts either an inline value or a valueFrom reference that resolves the value from another OpenMCF resource's output at deploy time.

Examples

Minimal — Organization Runners with PAT and DinD

Registers a Docker-in-Docker runner scale set against a GitHub organization using a Personal Access Token:

apiVersion: kubernetes.openmcf.org/v1
kind: KubernetesGhaRunnerScaleSet
metadata:
  name: org-runners
  labels:
    openmcf.org/provisioner: pulumi
    pulumi.openmcf.org/organization: my-org
    pulumi.openmcf.org/project: my-project
    pulumi.openmcf.org/stack.name: dev.KubernetesGhaRunnerScaleSet.org-runners
spec:
  namespace:
    value: gha-runners
  createNamespace: true
  github:
    configUrl: https://github.com/my-org
    patToken:
      token: ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  containerMode:
    type: DIND

Use in a workflow:

jobs:
  build:
    runs-on: [self-hosted, org-runners]

Kubernetes Mode with GitHub App and Custom Scaling

Uses GitHub App authentication, Kubernetes container mode with an ephemeral work volume, and custom scaling limits suitable for a busy CI pipeline:

apiVersion: kubernetes.openmcf.org/v1
kind: KubernetesGhaRunnerScaleSet
metadata:
  name: ci-runners
  labels:
    openmcf.org/provisioner: pulumi
    pulumi.openmcf.org/organization: my-org
    pulumi.openmcf.org/project: my-project
    pulumi.openmcf.org/stack.name: staging.KubernetesGhaRunnerScaleSet.ci-runners
spec:
  namespace:
    value: gha-runners
  createNamespace: true
  helmChartVersion: "0.13.1"
  github:
    configUrl: https://github.com/my-org
    githubApp:
      appId: "123456"
      installationId: "78901234"
      privateKeyBase64: LS0tLS1CRUdJTi4uLg==
  scaling:
    minRunners: 2
    maxRunners: 20
  runnerGroup: ci-group
  runnerScaleSetName: ci-runners
  containerMode:
    type: KUBERNETES
    workVolumeClaim:
      size: 50Gi
      storageClass: gp3
  runner:
    resources:
      requests:
        cpu: "1"
        memory: 2Gi
      limits:
        cpu: "4"
        memory: 8Gi

Full — Persistent Cache, Custom Image, Existing Secret, and Target Cluster

Deploys a runner scale set on a specific GKE cluster with a persistent build cache volume, a custom runner image, environment variables, an existing credentials secret, and resource annotations:

apiVersion: kubernetes.openmcf.org/v1
kind: KubernetesGhaRunnerScaleSet
metadata:
  name: build-runners
  labels:
    openmcf.org/provisioner: pulumi
    pulumi.openmcf.org/organization: my-org
    pulumi.openmcf.org/project: my-project
    pulumi.openmcf.org/stack.name: prod.KubernetesGhaRunnerScaleSet.build-runners
spec:
  targetCluster:
    clusterKind: GcpGkeCluster
    clusterName: prod-cluster
  namespace:
    valueFrom:
      kind: KubernetesNamespace
      name: gha-runners-ns
      field: spec.name
  github:
    configUrl: https://github.com/my-org/my-repo
    existingSecretName: github-runner-creds
  scaling:
    minRunners: 1
    maxRunners: 10
  runnerScaleSetName: build-runners
  containerMode:
    type: DIND
  runner:
    image:
      repository: my-registry.example.com/custom-runner
      tag: "2.331.0-custom"
      pullPolicy: Always
    resources:
      requests:
        cpu: "2"
        memory: 4Gi
      limits:
        cpu: "4"
        memory: 8Gi
    env:
      - name: DOCKER_BUILDKIT
        value: "1"
      - name: RUNNER_TOOL_CACHE
        value: /home/runner/.cache/tools
    volumeMounts:
      - name: build-cache
        mountPath: /home/runner/.cache
  persistentVolumes:
    - name: build-cache
      size: 100Gi
      storageClass: gp3
      mountPath: /home/runner/.cache
  imagePullSecrets:
    - my-registry-secret
  controllerServiceAccount:
    namespace: arc-system
    name: arc-gha-rs-controller
  labels:
    team: platform
  annotations:
    cost-center: engineering

Stack Outputs

After deployment, the following outputs are available in status.outputs:

OutputTypeDescription
namespacestringNamespace where the runner scale set is deployed
releaseNamestringName of the Helm release
chartVersionstringVersion of the deployed Helm chart
runnerScaleSetNamestringName of the scale set as registered with GitHub (use in runs-on labels)
githubConfigUrlstringGitHub URL the runners are connected to
githubSecretNamestringName of the Kubernetes Secret containing GitHub credentials
pvcNamesstring[]Names of PVCs created for persistent volumes
minRunnersstringConfigured minimum runners
maxRunnersstringConfigured maximum runners
containerModestringContainer mode type in use (dind, kubernetes, kubernetes-novolume, or empty for default)

Related Components

  • KubernetesGhaRunnerScaleSetController — required prerequisite; deploys the controller that watches AutoScalingRunnerSet resources and manages runner pod lifecycle
  • KubernetesNamespace — provides the target namespace via valueFrom reference
  • KubernetesHelmRelease — generic Helm release component; use KubernetesGhaRunnerScaleSet instead for GitHub Actions runners since it provides typed configuration and validation
  • KubernetesSecret — can be used to manage the GitHub credentials secret independently when using existingSecretName

Next article

Kubernetes GHA Runner Scale Set Controller

Kubernetes GHA Runner Scale Set Controller Deploys the GitHub Actions Runner Scale Set Controller on Kubernetes using the official OCI Helm chart from ghcr.io/actions/actions-runner-controller-charts. The controller manages AutoScalingRunnerSet and EphemeralRunner custom resources, enabling self-hosted GitHub Actions runners that scale dynamically based on workflow demand. This component installs only the controller; runner scale sets (the actual runner pods) are deployed separately. What Gets...
Read next article
Presets
1 ready-to-deploy configurationView presets →