Aller au contenu
CI/CD & Automatisation medium

Isolation des runners GitHub Actions

11 min de lecture

L'isolation des runners permet de contrôler quels workflows peuvent s'exécuter sur quelles machines. C'est essentiel pour la sécurité et la gestion des ressources dans les grandes organisations.

  • Comprendre pourquoi isoler les runners (sécurité, performance, conformité)
  • Router les jobs avec les labels et les groupes de runners
  • Appliquer les patterns d'isolation : par environnement, confiance, équipe
  • Contrôler l'accès au niveau dépôt ou organisation
  • Segmenter le réseau pour cloisonner les runners
  • Empêcher les PRs non fiables d'accéder au réseau de production
  • Limiter l'impact d'un workflow compromis
  • Respecter le principe de moindre privilège
  • Réserver des runners puissants aux builds critiques
  • Éviter la contention entre équipes
  • Garantir des SLAs différents par projet
  • Séparer les données sensibles par environnement
  • Traçabilité des exécutions par équipe
  • Respect des zones géographiques

Les labels identifient les capacités d'un runner :

Fenêtre de terminal
# À l'enregistrement
./config.sh --url https://github.com/ORG/REPO \
--token TOKEN \
--labels linux,x64,docker,gpu,production

Utilisation dans un workflow :

jobs:
build:
runs-on: [self-hosted, linux, docker]
deploy-prod:
runs-on: [self-hosted, production]
ml-training:
runs-on: [self-hosted, gpu]

Au niveau organisation, les groupes permettent de limiter l'accès :

  1. Allez dans Settings > Actions > Runner groups
  2. Créez un groupe (ex: production-runners)
  3. Assignez des runners au groupe
  4. Définissez quels repos peuvent l'utiliser
# Seuls les repos autorisés peuvent utiliser ce runner
jobs:
deploy:
runs-on:
group: production-runners
labels: [linux, x64]

Trois découpages couvrent la plupart des besoins. Ils se combinent : un runner peut être à la fois production, team-platform et gpu.

┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Dev Runners │ │ Staging Runners │ │ Prod Runners │
│ │ │ │ │ │
│ Labels: │ │ Labels: │ │ Labels: │
│ - dev │ │ - staging │ │ - production │
│ - self-hosted │ │ - self-hosted │ │ - self-hosted │
│ │ │ │ │ │
│ Accès: tous │ │ Accès: équipe QA │ │ Accès: ops │
└─────────────────┘ └──────────────────┘ └─────────────────┘
jobs:
test:
runs-on: [self-hosted, dev]
staging:
runs-on: [self-hosted, staging]
environment: staging # Protection supplémentaire
production:
runs-on: [self-hosted, production]
environment: production
┌─────────────────────┐ ┌─────────────────────┐
│ Trusted Runners │ │ Untrusted Runners │
│ │ │ │
│ Pour: │ │ Pour: │
│ - Pushes sur main │ │ - PRs externes │
│ - Tags de release │ │ - Forks │
│ │ │ │
│ Accès: │ │ Accès: │
│ - Réseau interne │ │ - Internet seul │
│ - Secrets sensibles │ │ - Secrets limités │
└─────────────────────┘ └─────────────────────┘
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
# Code de confiance (push) → runner trusted ; PR → runner untrusted
runs-on:
- self-hosted
- ${{ github.event_name == 'push' && 'trusted' || 'untrusted' }}
jobs:
frontend-build:
runs-on: [self-hosted, team-frontend]
backend-build:
runs-on: [self-hosted, team-backend]
ml-training:
runs-on: [self-hosted, team-data, gpu]

Le niveau d'enregistrement d'un runner détermine qui peut l'utiliser. Plus le périmètre est large, plus le contrôle d'accès doit être strict.

Un runner enregistré au niveau repository :

  • N'est accessible que par ce repository
  • Isolation maximale
Fenêtre de terminal
./config.sh --url https://github.com/OWNER/REPO ...

Un runner enregistré au niveau organisation :

  • Peut être partagé entre repos
  • Contrôlé par les groupes
Fenêtre de terminal
./config.sh --url https://github.com/ORG ...

Utilisez les environments pour ajouter une couche de contrôle :

jobs:
deploy:
runs-on: [self-hosted, production]
environment:
name: production
# Nécessite approbation manuelle
┌─────────────────────────────────────────────────────────────┐
│ VPC / Réseau │
├─────────────────┬─────────────────┬─────────────────────────┤
│ Subnet Dev │ Subnet Staging │ Subnet Production │
│ │ │ │
│ ┌─────────────┐ │ ┌─────────────┐ │ ┌─────────────────────┐ │
│ │ Dev Runner │ │ │Staging Runner│ │ │ Prod Runner │ │
│ │ │ │ │ │ │ │ │ │
│ │ Accès: │ │ │ Accès: │ │ │ Accès: │ │
│ │ - DB dev │ │ │ - DB staging│ │ │ - DB prod │ │
│ │ - API dev │ │ │ - API staging│ │ │ - K8s prod │ │
│ └─────────────┘ │ └─────────────┘ │ └─────────────────────┘ │
└─────────────────┴─────────────────┴─────────────────────────┘
Fenêtre de terminal
# Runner dev : accès limité
iptables -A OUTPUT -d 10.0.1.0/24 -j ACCEPT # Subnet dev
iptables -A OUTPUT -d 0.0.0.0/0 -p tcp --dport 443 -j ACCEPT # GitHub
iptables -A OUTPUT -j DROP
# Runner production : accès plus large mais contrôlé
iptables -A OUTPUT -d 10.0.0.0/16 -j ACCEPT # Tout le VPC
iptables -A OUTPUT -d 0.0.0.0/0 -p tcp --dport 443 -j ACCEPT
iptables -A OUTPUT -j DROP

Ce workflow met bout à bout les patterns du guide : tests sur runners mutualisés, build sur runners d'équipe, déploiements sur runners par environnement avec approbation pour la production.

.github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
# Aucun droit par défaut : chaque job demande le minimum
permissions: {}
jobs:
# Tests sur runners mutualisés
test:
runs-on: [self-hosted, linux, shared]
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- run: npm test
# Build sur runners de l'équipe
build:
needs: test
runs-on: [self-hosted, linux, team-platform]
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- run: docker build -t app .
# Déploiement staging (réseau staging)
deploy-staging:
if: github.ref == 'refs/heads/develop'
needs: build
runs-on: [self-hosted, staging-network]
environment: staging
steps:
- run: kubectl --context staging apply -f k8s/
# Déploiement production (réseau prod, approbation requise)
deploy-production:
if: github.ref == 'refs/heads/main'
needs: build
runs-on:
group: production-runners
labels: [linux, x64]
environment:
name: production
url: https://app.company.com
steps:
- run: kubectl --context production apply -f k8s/
Fenêtre de terminal
# ✅ Labels explicites
--labels linux,x64,docker,production,team-platform
# ❌ Labels vagues
--labels runner1,fast

Maintenez un document décrivant :

  • Quels runners existent
  • Leurs labels et capacités
  • Quels repos/équipes peuvent les utiliser
  • Les règles réseau
Fenêtre de terminal
# Lister les runners et leurs labels
gh api /orgs/ORG/actions/runners --jq '.runners[] | {name, labels: [.labels[].name]}'

Utilisez Infrastructure as Code pour les runners :

  • L'isolation contrôle quel workflow tourne sur quelle machine — un levier de sécurité et de performance.
  • Les labels routent les jobs ; les groupes de runners restreignent quels dépôts peuvent les utiliser.
  • Séparez au minimum les runners trusted (push, releases) des runners untrusted (PR, forks).
  • Un runner enregistré au niveau dépôt offre l'isolation maximale ; au niveau organisation, il se mutualise via les groupes.
  • Complétez l'isolation logique par une segmentation réseau : chaque environnement dans son sous-réseau.

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