Aller au contenu
CI/CD & Automatisation medium

Runners éphémères GitHub Actions

9 min de lecture

Les runners éphémères sont créés à la demande et détruits après chaque job. Ce pattern élimine les risques de persistence de données entre jobs et garantit un environnement propre à chaque exécution.

  • Comprendre pourquoi un runner jetable est plus sûr qu'un runner persistant
  • Activer le mode --ephemeral du runner GitHub
  • Mettre en place l'auto-scaling avec ARC (Kubernetes) ou des VM cloud
  • Conteneuriser un runner éphémère avec Docker
  • Gérer le pool et compenser l'absence de cache local
  1. Données résiduelles : fichiers, caches, credentials d'un job précédent
  2. Drift de configuration : l'environnement change au fil du temps
  3. Surface d'attaque : un job malveillant peut compromettre les suivants
  1. Isolation parfaite : comme les runners GitHub-hosted
  2. Reproductibilité : même environnement à chaque exécution
  3. Sécurité : pas de persistence entre jobs

Le runner GitHub supporte nativement le mode éphémère :

Fenêtre de terminal
./config.sh --url https://github.com/OWNER/REPO \
--token TOKEN \
--ephemeral

Comportement :

  • Le runner accepte un seul job
  • Après exécution, il se désenregistre automatiquement
  • La VM/container peut être détruite

Créer un runner à la main pour chaque job n'est pas tenable. L'auto-scaling provisionne et détruit les runners automatiquement selon la charge.

ARC est la solution officielle pour Kubernetes :

runner-deployment.yaml
apiVersion: actions.summerwind.dev/v1alpha1
kind: RunnerDeployment
metadata:
name: ephemeral-runners
spec:
replicas: 1
template:
spec:
ephemeral: true
repository: owner/repo
labels:
- self-hosted
- linux
- ephemeral
---
apiVersion: actions.summerwind.dev/v1alpha1
kind: HorizontalRunnerAutoscaler
metadata:
name: ephemeral-runners-autoscaler
spec:
scaleTargetRef:
name: ephemeral-runners
minReplicas: 0
maxReplicas: 10
metrics:
- type: TotalNumberOfQueuedAndInProgressWorkflowRuns
repositoryNames:
- owner/repo

Script de scaling avec AWS :

launch-ephemeral-runner.sh
#!/bin/bash
# Créer une VM
INSTANCE_ID=$(aws ec2 run-instances \
--image-id ami-xxxxx \
--instance-type t3.medium \
--user-data file://runner-init.sh \
--query 'Instances[0].InstanceId' \
--output text)
echo "Launched: $INSTANCE_ID"

Script d'initialisation :

#!/bin/bash
# runner-init.sh (user-data)
# Télécharger le runner
cd /opt
curl -o runner.tar.gz -L https://github.com/actions/runner/releases/download/v2.xxx.x/actions-runner-linux-x64-2.xxx.x.tar.gz
tar xzf runner.tar.gz
# Obtenir un token d'enregistrement (via API GitHub)
TOKEN=$(curl -s -X POST \
-H "Authorization: token $GITHUB_PAT" \
https://api.github.com/repos/OWNER/REPO/actions/runners/registration-token \
| jq -r '.token')
# Configurer en mode éphémère
./config.sh --url https://github.com/OWNER/REPO \
--token $TOKEN \
--ephemeral \
--unattended \
--labels ephemeral,linux
# Exécuter (va s'arrêter après un job)
./run.sh
# Auto-destruction
aws ec2 terminate-instances --instance-ids $(curl -s http://169.254.169.254/latest/meta-data/instance-id)

Sans Kubernetes, Docker suffit à obtenir des runners jetables : un conteneur par job, détruit à la fin de l'exécution.

Dockerfile.runner
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
curl jq git \
&& rm -rf /var/lib/apt/lists/*
# Télécharger le runner
RUN curl -o /tmp/runner.tar.gz -L \
https://github.com/actions/runner/releases/download/v2.xxx.x/actions-runner-linux-x64-2.xxx.x.tar.gz \
&& mkdir /runner \
&& tar xzf /tmp/runner.tar.gz -C /runner \
&& rm /tmp/runner.tar.gz
WORKDIR /runner
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
entrypoint.sh
#!/bin/bash
./config.sh --url $REPO_URL \
--token $RUNNER_TOKEN \
--ephemeral \
--unattended \
--labels docker,ephemeral
./run.sh
# Le container s'arrête après un job
docker-compose.yml
version: '3.8'
services:
runner:
build:
context: .
dockerfile: Dockerfile.runner
environment:
- REPO_URL=https://github.com/owner/repo
- RUNNER_TOKEN=${RUNNER_TOKEN}
restart: "no" # Ne pas redémarrer (éphémère)
deploy:
replicas: 3

Un pool de runners éphémères se pilote : trouver l'équilibre entre réactivité (éviter les cold starts) et coût (ne pas garder trop de runners inactifs).

Pour éviter les cold starts, maintenez un pool minimum :

# Kubernetes HPA
spec:
minReplicas: 2 # Toujours 2 runners prêts
maxReplicas: 20 # Scale jusqu'à 20 si nécessaire
# Prometheus metrics avec ARC
- job_name: 'actions-runner-controller'
static_configs:
- targets: ['actions-runner-controller-metrics:8080']

Métriques utiles :

  • actions_runner_controller_running_runners : runners actifs
  • actions_runner_controller_pending_runners : en attente de job
  • actions_runner_controller_registered_runners : total enregistrés
name: Build with Ephemeral Runner
on:
push:
branches: [main]
# Aucun droit par défaut : chaque job demande le minimum
permissions: {}
jobs:
build:
runs-on: [self-hosted, ephemeral, linux]
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Build
run: |
npm ci
npm run build
# Pas besoin de cleanup : le runner sera détruit
deploy:
needs: build
runs-on: [self-hosted, ephemeral, linux]
permissions:
contents: read
# Nouveau runner frais pour le déploiement
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- run: ./deploy.sh

Le modèle éphémère a un prix : pas de cache local, des cold starts, un coût d'infrastructure variable. Voici les points à anticiper.

MéthodeCold start
Container Docker5-15s
VM cloud30-90s
Kubernetes pod10-30s

Avec des runners éphémères, le cache local n'existe pas. Options :

  1. actions/cache : cache sur GitHub Storage
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
  1. Registry de cache : images Docker avec dépendances pré-installées
container:
image: myregistry.com/node-with-deps:latest
  1. Volume partagé : NFS ou EFS monté sur les runners
  • Plus de VMs créées/détruites = plus de coût si facturation à l'heure
  • Optimiser avec des instances spot/preemptible
  • Équilibrer entre pool minimum et coût
  • Un runner éphémère accepte un seul job puis se désenregistre — l'isolation des runners hosted, sur votre infra.
  • Le mode natif s'active avec ./config.sh --ephemeral : pas d'outil tiers requis.
  • En Kubernetes, Actions Runner Controller gère création, scaling et destruction automatiquement.
  • Sans cache local, compensez avec actions/cache, des images pré-garnies ou un volume partagé.
  • Gardez un pool minimum chaud pour absorber les cold starts sans exploser le coût.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn