1. Panorama des outils #
Choisir sans dogmatisme.
Comparaison #
| Outil | Modèle | Points forts | Points d'attention |
|---|---|---|---|
| GitHub Actions | YAML dans .github/workflows, runners hébergés ou auto-hébergés | intégration GitHub, marketplace d'actions, OIDC | épingler les actions tierces, coût des minutes |
| GitLab CI | .gitlab-ci.yml, runners GitLab | plateforme tout-en-un (registre, sécurité, environnements), auto-hébergeable | fichiers complexes à grande échelle (utiliser include et components) |
| Jenkins | Jenkinsfile (Groovy), agents | très extensible, auto-hébergé, historique important | maintenance des plugins, sécurité du contrôleur |
| Argo CD / Flux | GitOps : le cluster tire l'état depuis Git | synchronisation continue, détection de dérive, retour arrière par Git | Kubernetes uniquement, ne remplace pas la CI |
| Autres | CircleCI, Azure Pipelines, Tekton, Dagger, Buildkite | selon l'écosystème | — |
Push ou pull #
En mode push, le pipeline se connecte à la cible et déploie (kubectl, Helm, SSH) : simple, mais la CI détient des accès à la production. En mode pull (GitOps), un agent dans le cluster lit le dépôt et applique les changements : la CI n'a besoin que d'écrire dans Git.
2. Un pipeline complet avec GitHub Actions #
Du commit à la production.
Le workflow #
name: delivery
on:
push: { branches: [main] }
pull_request: {}
permissions: { contents: read }
concurrency: { group: delivery-${{ github.ref }}, cancel-in-progress: true }
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12", cache: pip }
- run: pip install -r requirements.txt && ruff check . && pytest -q
build:
needs: test
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions: { contents: read, packages: write, id-token: write }
outputs: { image: "${{ steps.meta.outputs.image }}" }
steps:
- uses: actions/checkout@v4
- id: meta
run: echo "image=ghcr.io/${GITHUB_REPOSITORY,,}:${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
- uses: docker/login-action@v3
with: { registry: ghcr.io, username: "${{ github.actor }}", password: "${{ secrets.GITHUB_TOKEN }}" }
- uses: docker/build-push-action@v6
with: { push: true, tags: "${{ steps.meta.outputs.image }}" }
- uses: aquasecurity/trivy-action@0.28.0
with: { image-ref: "${{ steps.meta.outputs.image }}", severity: CRITICAL, exit-code: "1" }
deploy-staging:
needs: build
environment: staging
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh staging "${{ needs.build.outputs.image }}"
- run: ./smoke-test.sh https://staging.example.com
deploy-production:
needs: [build, deploy-staging]
environment: production # règle d'approbation configurée sur l'environnement
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh production "${{ needs.build.outputs.image }}"
- run: ./smoke-test.sh https://shop.example.comRéutiliser #
# workflow réutilisable appelé par plusieurs dépôts
jobs:
ci:
uses: mon-org/pipelines/.github/workflows/python-ci.yml@v3
with: { python-version: "3.12" }
secrets: inherit3. Le même pipeline avec GitLab CI et Jenkins #
Les concepts sont identiques.
GitLab CI #
stages: [test, build, deploy]
variables: { IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" }
test:
stage: test
image: python:3.12
cache: { paths: [.cache/pip] }
script: [pip install -r requirements.txt, pytest -q]
build:
stage: build
image: docker:27
services: [docker:27-dind]
rules: [{ if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' }]
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $IMAGE . && docker push $IMAGE
deploy-staging:
stage: deploy
environment: { name: staging, url: https://staging.example.com }
script: [./deploy.sh staging $IMAGE]
deploy-production:
stage: deploy
environment: { name: production }
when: manual
script: [./deploy.sh production $IMAGE]Jenkins #
// Jenkinsfile (pipeline déclaratif)
pipeline {
agent { docker { image 'python:3.12' } }
environment { IMAGE = "registry.example.com/shop/api:${env.GIT_COMMIT.take(7)}" }
stages {
stage('Test') { steps { sh 'pip install -r requirements.txt && pytest -q' } }
stage('Build') { agent any
steps { sh 'docker build -t $IMAGE . && docker push $IMAGE' } }
stage('Staging') { steps { sh './deploy.sh staging $IMAGE' } }
stage('Production') {
input { message 'Déployer en production ?' }
steps { sh './deploy.sh production $IMAGE' } }
}
post { failure { echo 'Pipeline en échec : prévenir l\'équipe' } }
}4. Sécurité et santé du pipeline #
La CI est une cible de choix.
Sécuriser #
- Permissions minimales par défaut (
permissions: contents: read), élargies job par job. - Actions tierces épinglées par SHA de commit, revues avant adoption.
- Pas de secrets exposés aux pull requests venant de forks.
- Runners auto-hébergés éphémères (un job, une machine), jamais partagés entre projets de confiance différente.
- OIDC vers le cloud plutôt que des clés longues.
Erreur Courante : Un runner auto-hébergé persistant attaché à un dépôt public permet à n'importe qui d'exécuter du code sur votre réseau via une pull request.
Mesurer le pipeline #
| Indicateur | Cible |
|---|---|
| Durée du pipeline de commit | < 10 min |
| Taux de succès sur main | > 90 % |
| Délai de réparation d'un build cassé | < 1 h |
| Taux de relance (tests instables) | en baisse |
| Délai commit → production | voir les métriques DORA |
