Pipelines CI/CD avancés

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