Pipelines CI/CD avancés

Principes d'un pipeline mature

Un pipeline CI/CD avancé ne se limite pas à builder et déployer. Il intègre la qualité, la sécurité, les tests et le déploiement progressif.

graph LR
    A[Commit] --> B[Lint & Format]
    B --> C[Tests unitaires]
    C --> D[Build & Scan sécurité]
    D --> E[Tests d'intégration]
    E --> F[Deploy Staging]
    F --> G[Tests E2E]
    G --> H[Deploy Canary 5%]
    H --> I{Métriques OK ?}
    I -->|Oui| J[Rollout 100%]
    I -->|Non| K[Rollback automatique]

GitHub Actions : pipelines complets

Pipeline de déploiement multi-environnement

name: Deploy Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - run: npm ci
      - run: npm run lint
      - run: npm run test -- --coverage

      - uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: coverage/

  security-scan:
    runs-on: ubuntu-latest
    needs: lint-and-test
    steps:
      - uses: actions/checkout@v4

      - name: Audit des dépendances
        run: npm audit --audit-level=high

      - name: Scan SAST avec Semgrep
        uses: semgrep/semgrep-action@v1
        with:
          config: p/typescript

      - name: Scan des secrets
        uses: trufflesecurity/trufflehog@main
        with:
          extra_args: --only-verified

  build-and-push:
    runs-on: ubuntu-latest
    needs: [lint-and-test, security-scan]
    permissions:
      contents: read
      packages: write
    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
    steps:
      - uses: actions/checkout@v4

      - name: Login au registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Metadata Docker
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha,prefix=
            type=ref,event=branch

      - name: Build et push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Scan de vulnérabilités de l'image
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          exit-code: 1
          severity: CRITICAL,HIGH

  deploy-staging:
    runs-on: ubuntu-latest
    needs: build-and-push
    if: github.ref == 'refs/heads/develop'
    environment: staging
    steps:
      - uses: actions/checkout@v4

      - name: Deploy sur staging
        run: |
          kubectl set image deployment/api \
            api=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
            -n staging
          kubectl rollout status deployment/api -n staging --timeout=300s

  deploy-production:
    runs-on: ubuntu-latest
    needs: build-and-push
    if: github.ref == 'refs/heads/main'
    environment: production
    steps:
      - uses: actions/checkout@v4

      - name: Canary deployment (5%)
        run: |
          kubectl apply -f k8s/canary.yaml
          kubectl set image deployment/api-canary \
            api=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
            -n production

      - name: Vérification des métriques (5 min)
        run: |
          sleep 300
          ERROR_RATE=$(curl -s "http://prometheus:9090/api/v1/query?query=rate(http_requests_total{status=~'5..'}[5m])" | jq '.data.result[0].value[1]')
          if (( $(echo "$ERROR_RATE > 0.01" | bc -l) )); then
            echo "Taux d'erreur trop élevé : $ERROR_RATE"
            kubectl rollout undo deployment/api-canary -n production
            exit 1
          fi

      - name: Rollout complet
        run: |
          kubectl set image deployment/api \
            api=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
            -n production
          kubectl rollout status deployment/api -n production --timeout=600s
          kubectl delete -f k8s/canary.yaml

GitLab CI/CD : pipeline avancé

stages:
  - validate
  - test
  - build
  - deploy
  - verify

variables:
  DOCKER_TLS_CERTDIR: "/certs"

.node-cache: &node-cache
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/

lint:
  stage: validate
  <<: *node-cache
  script:
    - npm ci
    - npm run lint
    - npm run type-check

test:
  stage: test
  <<: *node-cache
  services:
    - postgres:16-alpine
  variables:
    POSTGRES_DB: test
    POSTGRES_PASSWORD: test
    DATABASE_URL: postgres://postgres:test@postgres:5432/test
  script:
    - npm ci
    - npm run test:ci
    - npm run test:e2e
  coverage: '/All files[^|]*\|[^|]*\s+([\d.]+)/'
  artifacts:
    reports:
      junit: junit.xml
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

build:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

deploy_production:
  stage: deploy
  environment:
    name: production
    url: https://api.example.com
  when: manual
  only:
    - main
  script:
    - helm upgrade --install api ./charts/api
      --set image.tag=$CI_COMMIT_SHA
      --namespace production
      --wait --timeout 10m

smoke_test:
  stage: verify
  needs: [deploy_production]
  script:
    - curl -f https://api.example.com/health || exit 1
    - npm run test:smoke

Stratégies de déploiement

Blue/Green

Deux environnements identiques : blue (actuel) et green (nouveau). On bascule le trafic d'un coup.

# Service qui pointe vers l'environnement actif
apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api
    version: green  # Basculer entre blue et green
  ports:
    - port: 3000

Canary

Déploiement progressif : 5% → 25% → 50% → 100%.

Feature Flags

Découpler le déploiement de l'activation des fonctionnalités :

// Le code est déployé mais la feature est désactivée
if (await featureFlags.isEnabled('new-checkout', { userId })) {
  return newCheckoutFlow(order);
}
return legacyCheckoutFlow(order);

Bonnes pratiques CI/CD

  1. Fail fast : les étapes les plus rapides en premier (lint, types)
  2. Paralléliser les jobs indépendants
  3. Cache les dépendances entre les runs
  4. Artefacts immuables : builder une fois, déployer partout
  5. Environnements éphémères pour les PR (preview deployments)
  6. Rollback automatique si les métriques dégradent
  7. Secrets gérés par le CI, jamais dans le code

Nous utilisons Microsoft Clarity pour comprendre comment le site est utilisé et l'améliorer. En poursuivant votre navigation, vous l'acceptez. Vous pouvez le désactiver à tout moment.