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.