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.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Comprendre pourquoi un runner jetable est plus sûr qu'un runner persistant
- Activer le mode
--ephemeraldu 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
Pourquoi des runners éphémères ?
Section intitulée « Pourquoi des runners éphémères ? »Problèmes des runners persistants
Section intitulée « Problèmes des runners persistants »Ces trois problèmes n'ont pas la même gravité, et le troisième contient les deux autres.
Un runner persistant conserve son disque et sa mémoire d'un job à l'autre : un job
malveillant peut donc y déposer un binaire, modifier le PATH de l'utilisateur de
service ou installer un hook Git qui s'exécutera dans le job du projet voisin. La
latéralisation entre équipes partageant le même pool est le scénario réel derrière
ce pattern.
- Données résiduelles : fichiers, caches, credentials d'un job précédent
- Drift de configuration : l'environnement change au fil du temps
- Surface d'attaque : un job malveillant peut compromettre les suivants
Avantages des runners éphémères
Section intitulée « Avantages des runners éphémères »Le gain se résume à une propriété : l'état ne survit pas au job. Cela ferme d'un coup toute la classe d'attaques décrite ci-dessus, mais cela vous prive aussi du cache local et des outils préinstallés que vous aviez peut-être accumulés à la main sur vos runners. Ce n'est donc pas une amélioration gratuite : c'est un déplacement du travail vers l'image ou vers un cache distant.
- Isolation parfaite : comme les runners GitHub-hosted
- Reproductibilité : même environnement à chaque exécution
- Sécurité : pas de persistence entre jobs
L'option --ephemeral
Section intitulée « L'option --ephemeral »Le runner GitHub supporte nativement le mode éphémère :
./config.sh --url https://github.com/OWNER/REPO \ --token TOKEN \ --ephemeralComportement :
- Le runner accepte un seul job
- Après exécution, il se désenregistre automatiquement
- La VM/container peut être détruite
Architecture avec auto-scaling
Section intitulée « Architecture avec auto-scaling »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.
Avec Actions Runner Controller (ARC)
Section intitulée « Avec Actions Runner Controller (ARC) »Attention à un point de vocabulaire qui prête à confusion : ARC comporte deux modes,
et un seul est supporté par GitHub. Le mode actuel repose sur les runner scale sets,
installés par les charts Helm gha-runner-scale-set-controller et gha-runner-scale-set
du dépôt actions/actions-runner-controller ; leurs runners sont éphémères par
conception, il n'y a rien à activer. Le mode historique, celui des ressources
RunnerDeployment et HorizontalRunnerAutoscaler en actions.summerwind.dev, est
désormais qualifié de legacy par le projet et n'est plus maintenu que par la
communauté.
Installation du mode supporté, avec un jeu de runners nommé arc-runner-set :
helm install arc \ --namespace arc-systems --create-namespace \ --version 0.14.2 \ oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller
helm install arc-runner-set \ --namespace arc-runners --create-namespace \ --version 0.14.2 \ --set githubConfigUrl="https://github.com/OWNER/REPO" \ --set githubConfigSecret.github_token="$GITHUB_TOKEN" \ --set minRunners=0 --set maxRunners=10 \ oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-setLe nom du jeu de runners devient directement la valeur de runs-on dans vos workflows.
Vérifiez le déploiement avec kubectl get pods -n arc-systems, puis kubectl get autoscalingrunnerset -n arc-runners.
Le manifeste ci-dessous illustre le mode legacy, que vous rencontrerez encore sur des
clusters existants. Ne le déployez pas sur une nouvelle installation : il est conservé
ici pour que vous sachiez le reconnaître, et le champ ephemeral: true y est
indispensable puisque ce mode ne l'active pas seul.
# runner-deployment.yaml (mode legacy, community-maintained)apiVersion: actions.summerwind.dev/v1alpha1kind: RunnerDeploymentmetadata: name: ephemeral-runnersspec: replicas: 1 template: spec: ephemeral: true repository: owner/repo labels: - self-hosted - linux - ephemeral---apiVersion: actions.summerwind.dev/v1alpha1kind: HorizontalRunnerAutoscalermetadata: name: ephemeral-runners-autoscalerspec: scaleTargetRef: name: ephemeral-runners minReplicas: 0 maxReplicas: 10 metrics: - type: TotalNumberOfQueuedAndInProgressWorkflowRuns repositoryNames: - owner/repoAvec des VMs cloud
Section intitulée « Avec des VMs cloud »Hors Kubernetes, le principe reste le même mais vous portez vous-même le cycle de vie : une VM démarre, s'enregistre, exécute un job, puis se supprime. Les deux scripts qui suivent illustrent ces deux moitiés. Le point sensible n'est pas le scaling mais le jeton d'enregistrement : il est éphémère (une heure) et s'obtient par l'API, ce qui évite de figer un secret dans l'image.
Script de scaling avec AWS :
#!/bin/bash# Créer une VMINSTANCE_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)
set -euo pipefail
# Télécharger le runner et vérifier son empreinte avant extraction.# La somme SHA-256 est publiée par le projet dans les notes de la release.RUNNER_VERSION=2.336.0RUNNER_SHA256=04cf0be1aff4c3ec3554466c39124ca250e3effd8873bb7e8d68535aa9505d5d
cd /optcurl -fsSL -o actions-runner.tar.gz \ "https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz"echo "${RUNNER_SHA256} actions-runner.tar.gz" | sha256sum --check -tar xzf actions-runner.tar.gz
# Obtenir un token d'enregistrement (via API GitHub)TOKEN=$(curl -fsS -X POST \ -H "Authorization: Bearer $GITHUB_PAT" \ -H "Accept: application/vnd.github+json" \ 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-destructionaws ec2 terminate-instances --instance-ids $(curl -s http://169.254.169.254/latest/meta-data/instance-id)Avec Docker
Section intitulée « Avec Docker »Sans Kubernetes, Docker suffit à obtenir des runners jetables : un conteneur par job, détruit à la fin de l'exécution.
Runner dans un container
Section intitulée « Runner dans un container »L'image ci-dessous embarque le binaire du runner mais aucun secret : le jeton arrive
à l'exécution par variable d'environnement, sinon il resterait lisible dans une couche de
l'image par quiconque peut la tirer. Notez l'épinglage de l'image de base par digest :
un tag comme 24.04 est mutable et le registre peut republier autre chose derrière, ce
qui ruinerait la reproductibilité recherchée avec des runners jetables.
# Image de base épinglée par digest : un tag comme 24.04 est mutable.# Relevez le digest courant avec `docker manifest inspect ubuntu:24.04`.FROM ubuntu:24.04@sha256:4fbb8e6a8395de5a7550b33509421a2bafbc0aab6c06ba2cef9ebffbc7092d90
RUN apt-get update && apt-get install -y \ curl jq git \ && rm -rf /var/lib/apt/lists/*
# Télécharger le runner, vérifier son empreinte, puis extraire.# La somme SHA-256 est publiée dans les notes de la release actions/runner.ARG RUNNER_VERSION=2.336.0ARG RUNNER_SHA256=04cf0be1aff4c3ec3554466c39124ca250e3effd8873bb7e8d68535aa9505d5d
RUN set -eux; \ curl -fsSL -o /tmp/runner.tar.gz \ "https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz"; \ echo "${RUNNER_SHA256} /tmp/runner.tar.gz" | sha256sum --check -; \ mkdir /runner; \ tar xzf /tmp/runner.tar.gz -C /runner; \ rm /tmp/runner.tar.gz
WORKDIR /runner
COPY entrypoint.sh /entrypoint.shENTRYPOINT ["/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 jobOrchestration avec docker-compose
Section intitulée « Orchestration avec docker-compose »Ce fichier sert à lancer plusieurs conteneurs d'un coup sur une même machine. La ligne
qui compte est restart: "no" : sans elle, Docker relancerait le conteneur dès qu'il
s'arrête, et vous obtiendriez un runner qui se réenregistre en boucle, exactement
l'inverse du comportement recherché. Chaque conteneur doit recevoir son propre jeton
d'enregistrement, un jeton ne valant que pour un enregistrement.
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: 3Gérer le pool de runners
Section intitulée « Gérer le pool de runners »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).
Stratégie de pré-chauffage
Section intitulée « Stratégie de pré-chauffage »Pour éviter les cold starts, maintenez un pool minimum :
# Kubernetes HPAspec: minReplicas: 2 # Toujours 2 runners prêts maxReplicas: 20 # Scale jusqu'à 20 si nécessaireMonitoring du pool
Section intitulée « Monitoring du pool »Sans supervision, un pool éphémère échoue silencieusement : les jobs restent en file et personne ne le voit avant qu'un développeur ne s'en plaigne. La métrique à surveiller en premier n'est pas le nombre de runners actifs mais l'attente en file, car c'est elle qui indique que le plafond de scaling est atteint ou que l'enregistrement échoue.
# 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 actifsactions_runner_controller_pending_runners: en attente de jobactions_runner_controller_registered_runners: total enregistrés
Workflow utilisant les runners éphémères
Section intitulée « Workflow utilisant les runners éphémères »Côté workflow, rien ne change ou presque : vous ciblez vos runners par leurs labels
dans runs-on. Le détail qui compte est la séparation en deux jobs, build puis
deploy : chacun obtient un runner neuf, donc le job de déploiement ne peut hériter
d'aucun fichier ni d'aucune variable laissés par le build. C'est le bénéfice de sécurité
recherché, et c'est aussi pourquoi tout artefact à transmettre entre les deux doit passer
explicitement par actions/upload-artifact.
name: Build with Ephemeral Runner
on: push: branches: [main]
# Aucun droit par défaut : chaque job demande le minimumpermissions: {}
# Annule l'exécution précédente encore en cours sur la même branche :# inutile de mobiliser des runners pour un commit déjà remplacé.concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true
jobs: build: name: Compiler l'application runs-on: [self-hosted, ephemeral, linux] timeout-minutes: 15 permissions: contents: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false
- name: Build run: | npm ci npm run build
# Pas besoin de cleanup : le runner sera détruit
deploy: name: Déployer en production needs: build runs-on: [self-hosted, ephemeral, linux] timeout-minutes: 10 permissions: contents: read # Nouveau runner frais pour le déploiement steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - run: ./deploy.shConsidérations
Section intitulée « Considérations »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.
Temps de démarrage
Section intitulée « Temps de démarrage »Ces ordres de grandeur ne sont pas des mesures de votre plateforme : traitez-les comme un classement, pas comme des valeurs cibles. Le rapport entre les lignes est ce qui compte, et il explique le choix par défaut : le conteneur l'emporte tant que le job n'exige pas une isolation au niveau du noyau, cas dans lequel la VM redevient nécessaire malgré son démarrage cinq à dix fois plus lent.
| Méthode | Cold start |
|---|---|
| Container Docker | 5-15s |
| VM cloud | 30-90s |
| Kubernetes pod | 10-30s |
Cache et dépendances
Section intitulée « Cache et dépendances »Avec des runners éphémères, le cache local n'existe pas. Options :
- actions/cache : cache sur GitHub Storage
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ~/.npm key: npm-${{ hashFiles('package-lock.json') }}- Registry de cache : images Docker avec dépendances pré-installées
container: image: myregistry.com/node-with-deps:latest- Volume partagé : NFS ou EFS monté sur les runners
Le coût d'un pool éphémère dépend surtout du grain de facturation de votre fournisseur. Facturé à la seconde, détruire une VM après chaque job coûte à peu près la même chose qu'un pool permanent bien dimensionné. Facturé à l'heure entamée, chaque job de trois minutes vous coûte une heure, et le calcul s'inverse complètement.
- 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
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
À retenir
Section intitulée « À retenir »- 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.