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.
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
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
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: 30Verify
Kubernetes side
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 runnerpoolGitHub side
Check that runners appear as online in your repo/org:
GitHub → Settings → Actions → RunnersTrigger a job
Create a workflow that targets your runners:
# .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
kubectl delete impvmrunnerpool ci-pool -n default
kubectl delete secret github-runner-app -n defaultRunners deregister from GitHub automatically on VM deletion.
