Skip to content

Runner Pool E2E Runbook

End-to-end validation of ImpVMRunnerPool against a real GitHub Actions organization.

Prerequisites

  • Imp installed on a cluster with KVM-capable nodes
  • A GitHub organization or repository for runner registration
  • A GitHub App installed for the target organization, with a runner group available for the pool
  • A Linux amd64 base image and runner layer that satisfy the runner image contract

Setup

1. Create the GitHub App secret

Use an external Secret provider in production. Do not commit the App private key or a runner registration configuration. Imp mints a one-use configuration for each runner VM and passes it to the guest only at launch.

bash
kubectl create secret generic github-runner-app -n default \
  --from-file=github-app-private-key=<app-private-key.pem> \
  --from-literal=github-app-id=<app-id> \
  --from-literal=github-app-installation-id=<installation-id>

2. Create a VMClass and template

yaml
apiVersion: imp.dev/v1alpha1
kind: ImpVMClass
metadata:
  name: runner-class
  namespace: default
spec:
  vcpu: 2
  memoryMiB: 2048
  diskGiB: 10
---
apiVersion: imp.dev/v1alpha1
kind: ImpVMTemplate
metadata:
  name: gh-runner
  namespace: default
spec:
  classRef:
    name: runner-class
  image: <immutable-runner-base-image>
  runnerLayer: <immutable-runner-layer-image>

The base image must provide a glibc userspace, CA certificates, the .NET runtime libraries used by the official runner, and a non-root runner user. The runner The runner layer must provide /home/runner/actions-runner and an executable /usr/local/bin/runner wrapper. Imp supplies the JIT value at launch as IMP_GITHUB_JITCONFIG. The wrapper exports it as ACTIONS_RUNNER_INPUT_JITCONFIG, sets RUNNER_ALLOW_RUNASROOT=1, and executes run.sh; it does not invoke config.sh --jitconfig itself. Imp composites the layer with the base image before booting the VM. Resolve both placeholders to independently verified immutable digests from the intended publication; this runbook does not establish a v0.10.1 version-to-image mapping.

3. Create the runner pool

yaml
apiVersion: imp.dev/v1alpha1
kind: ImpVMRunnerPool
metadata:
  name: ci-pool
  namespace: default
spec:
  templateName: gh-runner
  labels: [imp]
  platform:
    type: github-actions
    credentialsSecret: github-runner-app
    tokenSource: github_app
    scope:
      org: my-org
    runnerGroup: omni-runner
  scaling:
    mode: polling
    minIdle: 1
    maxConcurrent: 5
    scaleUpStep: 1
    cooldownSeconds: 30
    polling:
      enabled: true
      intervalSeconds: 30

Verify

Kubernetes side

bash
kubectl get impvmrunnerpool ci-pool -n default -o yaml
kubectl get impvm -n default -l imp.dev/pool=ci-pool
kubectl logs -n imp-system deploy/imp-controller-manager -c manager | grep runnerpool

GitHub side

Check that runners appear as online in your repo/org:

GitHub → Settings → Actions → Runners

Trigger a job

Create a workflow that targets your runners:

yaml
# .github/workflows/test-imp-runner.yml
on: workflow_dispatch
jobs:
  run:
    runs-on:
      group: omni-runner
      labels: [self-hosted, imp]
    steps:
      - run: echo "running on imp runner $(hostname)"

Dispatch it and confirm the job is picked up by one of the pool VMs.

Scaling validation

Set minIdle: 0 and queue multiple jobs. Confirm the pool scales up to maxConcurrent, respects scaleUpStep, and scales back down after cooldownSeconds.

For an organisation-scoped pool, do not assume polling supplies organisation-wide queue depth. Use webhook demand or a non-zero minIdle unless your deployment provides an external organisation queue signal. The controller owns capacity and node placement; GitHub still owns final runner/job matching.

Teardown

bash
kubectl delete impvmrunnerpool ci-pool -n default
kubectl delete secret github-runner-app -n default

Runners deregister from GitHub automatically on VM deletion.