Aller au contenu

Environnements & CI/CD — DYNORS

Guide pratique pour mettre en place une plateforme DevOps gratuite permettant de reproduire fidèlement la chaîne locale → dev → RMOA → prod.


Principe : 5 environnements, 1 pipeline

Local  ──►  DEV  ──►  RMOA DYNORS  ──►  RMOA CLIENT  ──►  PROD
  (dev)   (auto CI)   (manuel QA)      (manuel client)   (manuel CTO)

Chaque environnement est reproductible par un fichier docker-compose.<env>.yml + un fichier .env.<env>.


Stack CI/CD gratuite recommandée

DYNORS utilise déjà GitLab (gitlab.com). La solution la plus simple et gratuite est d'utiliser GitLab CI + runners auto-hébergés sur un petit VPS.

Composant Outil Coût Pourquoi
Code + pipeline GitLab CI Gratuit (gitlab.com) Déjà utilisé
Runners CI GitLab Runner self-hosted Gratuit (VPS 10–15 €/mois) Contrôle total
Registry Docker GitLab Container Registry Gratuit Intégré à GitLab
Secrets / variables GitLab CI/CD Variables Gratuit Protégées, masquées
Notifications Slack (plan gratuit) Gratuit Webhooks CI
Monitoring Grafana + Prometheus Gratuit (self-hosted) Stack standard
Logs Loki + Grafana Gratuit (self-hosted) Intégré à Grafana

Alternative si pas de GitLab : Woodpecker CI — open source, Docker-based, compatible GitHub/Gitea/Forgejo.


Installation d'un runner GitLab self-hosted

# Sur un VPS Ubuntu/Debian
# 1. Installer Docker
curl -fsSL https://get.docker.com | sh

# 2. Installer GitLab Runner
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
sudo apt-get install gitlab-runner

# 3. Enregistrer le runner (token depuis GitLab → Settings → CI/CD → Runners)
sudo gitlab-runner register \
  --url https://gitlab.com/ \
  --registration-token YOUR_TOKEN \
  --executor docker \
  --docker-image "docker:latest" \
  --description "dynors-runner-prod" \
  --tag-list "dynors-runner" \
  --docker-privileged

Un runner peut servir tous vos projets GitLab.


Structure des fichiers par projet

dynors-projects/clients/mon-projet/
├── .gitlab-ci.yml                   # Pipeline CI/CD
├── sirrat.config.yml                # Config SIRRAT du projet
├── docker-compose.local.yml         # Stack locale (dev machine)
├── docker-compose.dev.yml           # Stack DEV (serveur DYNORS)
├── docker-compose.rmoa-dynors.yml   # Stack RMOA DYNORS
├── docker-compose.rmoa-client.yml   # Stack RMOA CLIENT (infra client)
├── docker-compose.prod.yml          # Stack PRODUCTION
├── .env.example                     # Template (commité)
├── .env.local                       # LOCAL (gitignored)
├── .env.dev                         # DEV (gitignored)
├── .env.rmoa-dynors                 # RMOA DYNORS (gitignored)
├── .env.rmoa-client                 # RMOA CLIENT (gitignored)
├── .env.prod                        # PROD (gitignored)
├── backend/
│   └── src/main/resources/
│       ├── application.yml                  # Config commune
│       ├── application-local.yml
│       ├── application-dev.yml
│       ├── application-rmoa-dynors.yml
│       ├── application-rmoa-client.yml
│       └── application-prod.yml
└── scripts/
    ├── deploy-dev.sh
    ├── deploy-rmoa-dynors.sh
    ├── deploy-rmoa-client.sh
    ├── deploy-prod.sh
    ├── backup-prod.sh
    └── rollback.sh

Template docker-compose.dev.yml

version: "3.8"
services:
  backend:
    image: ${CI_REGISTRY_IMAGE}/backend:dev-${CI_COMMIT_SHORT_SHA}
    env_file: .env.dev
    ports:
      - "8080:8080"
    depends_on:
      - postgres
      - redis

  frontend:
    image: ${CI_REGISTRY_IMAGE}/frontend:dev-${CI_COMMIT_SHORT_SHA}
    env_file: .env.dev
    ports:
      - "80:80"

  postgres:
    image: postgres:15
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres_dev_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD}

volumes:
  postgres_dev_data:

Template .env.example

# ─── Environnement ─────────────────────────────────────
ENVIRONMENT=               # local | dev | rmoa-dynors | rmoa-client | prod
LOG_LEVEL=                 # DEBUG | INFO | WARN | ERROR

# ─── URLs ──────────────────────────────────────────────
BASE_URL=
API_URL=

# ─── Base de données ────────────────────────────────────
DB_HOST=
DB_PORT=5432
DB_NAME=
DB_USER=
DB_PASSWORD=
DB_SSL_MODE=               # disable | require

# ─── Inter-app DYNORS (SLY) ─────────────────────────────
SLY_BASE_URL=              # https://sly.dynors.com (prod) | http://sly-dev.dynors.com (dev)
SLY_FORWARD_SECRET=        # partagé avec SLY — OBLIGATOIRE en prod, vide OK en local

# ─── Sécurité ──────────────────────────────────────────
JWT_SECRET=
JWT_EXPIRATION=3600

# ─── Paiements (Wave, Orange Money) ─────────────────────
PAYMENT_PROVIDER=          # mock | wave-sandbox | wave-production | orange-sandbox | orange-production
PAYMENT_API_KEY=

# ─── Emails ─────────────────────────────────────────────
EMAIL_PROVIDER=            # mock | smtp-dev | sendgrid
EMAIL_API_KEY=

# ─── Storage ────────────────────────────────────────────
STORAGE_PROVIDER=          # filesystem | minio | s3
MINIO_ENDPOINT=
MINIO_BUCKET=
MINIO_ACCESS_KEY=
MINIO_SECRET_KEY=

# ─── SIRRAT ─────────────────────────────────────────────
SIRRAT_ENABLED=            # true | false
SIRRAT_API_URL=            # https://sirrat.dynors.com/api
SIRRAT_PROJECT_ID=
SIRRAT_API_TOKEN=

# ─── Monitoring ─────────────────────────────────────────
SENTRY_ENABLED=false
SENTRY_DSN=
GRAFANA_ENABLED=false

Pipeline GitLab CI — structure complète

# .gitlab-ci.yml — DYNORS projet satellite
stages:
  - build
  - test
  - deploy-dev
  - deploy-rmoa-dynors
  - deploy-rmoa-client
  - deploy-prod

variables:
  GRADLE_OPTS: "-Dorg.gradle.daemon=false"
  DOCKER_DRIVER: overlay2
  SIRRAT_API_URL: "https://sirrat.dynors.com/api"

# ─── BUILD ──────────────────────────────────────────────────────────
build:backend:
  stage: build
  image: gradle:8.5-jdk17
  script:
    - cd backend && gradle clean build -x test
  artifacts:
    paths: [backend/build/libs/*.jar]
    expire_in: 1 day
  only: [develop, release/*, main]
  tags: [dynors-runner]

build:frontend:
  stage: build
  image: node:20
  script:
    - cd frontend && npm ci && npm run build
  artifacts:
    paths: [frontend/dist/]
    expire_in: 1 day
  only: [develop, release/*, main]
  tags: [dynors-runner]

# ─── TESTS ──────────────────────────────────────────────────────────
test:unit:
  stage: test
  image: gradle:8.5-jdk17
  services: [postgres:15, redis:7]
  variables:
    POSTGRES_DB: test_db
    POSTGRES_USER: test
    POSTGRES_PASSWORD: test
  script:
    - cd backend && gradle test jacocoTestReport
  artifacts:
    reports:
      junit: backend/build/test-results/test/TEST-*.xml
  only: [develop, release/*, main]
  tags: [dynors-runner]

test:integration:
  stage: test
  image: gradle:8.5-jdk17
  services: [postgres:15, redis:7]
  script:
    - cd backend && gradle integrationTest
  only: [develop, release/*, main]
  tags: [dynors-runner]

security:scan:
  stage: test
  image: gradle:8.5-jdk17
  script:
    - cd backend && gradle dependencyCheckAnalyze
    - |
      CRITICAL=$(cat build/reports/dependency-check-report.json \
        | jq '.vulnerabilities | map(select(.severity=="CRITICAL")) | length')
      if [ "$CRITICAL" -gt 0 ]; then
        echo "CRITICAL vulnerabilities found: $CRITICAL"
        exit 1
      fi
  only: [develop, release/*, main]
  tags: [dynors-runner]

# ─── DEPLOY DEV (automatique sur develop) ───────────────────────────
deploy:dev:
  stage: deploy-dev
  image: docker:latest
  services: [docker:dind]
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - cd backend
    - docker build -t $CI_REGISTRY_IMAGE/backend:dev-$CI_COMMIT_SHORT_SHA .
    - docker push $CI_REGISTRY_IMAGE/backend:dev-$CI_COMMIT_SHORT_SHA
    - ssh dynors-dev@dev.dynors.internal
        "cd /opt/$CI_PROJECT_NAME &&
         docker-compose -f docker-compose.dev.yml pull &&
         docker-compose -f docker-compose.dev.yml up -d"
    # Notifier SIRRAT
    - |
      curl -sX POST $SIRRAT_API_URL/projects/$SIRRAT_PROJECT_ID/deployments \
        -H "Authorization: Bearer $SIRRAT_API_TOKEN" \
        -H "Content-Type: application/json" \
        -d "{\"environment\":\"dev\",\"version\":\"$CI_COMMIT_SHORT_SHA\",\"status\":\"deployed\"}"
  environment:
    name: development
    url: https://dev-$CI_PROJECT_NAME.dynors.com
  only: [develop]
  when: on_success
  tags: [dynors-runner]

# ─── DEPLOY RMOA DYNORS (manuel — après Porte #1) ───────────────────
deploy:rmoa-dynors:
  stage: deploy-rmoa-dynors
  script:
    # Vérifier Quality Gate #1 dans SIRRAT
    - |
      STATUS=$(curl -s $SIRRAT_API_URL/projects/$SIRRAT_PROJECT_ID/quality-gates/1/status \
        -H "Authorization: Bearer $SIRRAT_API_TOKEN" | jq -r '.status')
      [ "$STATUS" = "passed" ] || { echo "QG #1 non passé"; exit 1; }
    # ... build + deploy ...
  only: [release/*-internal]
  when: manual
  tags: [dynors-runner]

# ─── DEPLOY RMOA CLIENT (manuel — après Porte #2) ───────────────────
deploy:rmoa-client:
  stage: deploy-rmoa-client
  script:
    # Vérifier QG #2 + générer PV de recette
    - |
      curl -sX POST $SIRRAT_API_URL/projects/$SIRRAT_PROJECT_ID/pv-recette/generate \
        -H "Authorization: Bearer $SIRRAT_API_TOKEN" \
        -H "Content-Type: application/json" \
        -d "{\"version\":\"$CI_COMMIT_TAG\"}"
  only: [release/*]
  when: manual
  tags: [dynors-runner]

# ─── DEPLOY PROD (manuel — PV signé + CTO) ──────────────────────────
deploy:prod:
  stage: deploy-prod
  script:
    # Vérifier PV signé
    - |
      SIGNED=$(curl -s $SIRRAT_API_URL/projects/$SIRRAT_PROJECT_ID/pv-recette/status \
        -H "Authorization: Bearer $SIRRAT_API_TOKEN" | jq -r '.signed')
      [ "$SIGNED" = "true" ] || { echo "PV non signé"; exit 1; }
    # ... build prod image + deploy ...
    - sleep 30 && curl -f $PROD_URL/actuator/health || exit 1
  only: [tags]
  when: manual
  tags: [dynors-runner]

# ─── NOTIFICATIONS ──────────────────────────────────────────────────
notify:failure:
  stage: .post
  script:
    - |
      curl -sX POST $SLACK_WEBHOOK \
        -H 'Content-Type: application/json' \
        -d "{\"text\":\"Pipeline failed — $CI_PROJECT_NAME ($CI_COMMIT_REF_NAME) — $CI_PIPELINE_URL\"}"
  when: on_failure
  tags: [dynors-runner]

Variables CI/CD GitLab à configurer

Dans GitLab → Settings → CI/CD → Variables (protégées + masquées pour les valeurs sensibles) :

# SIRRAT
SIRRAT_API_TOKEN          # token d'authentification SIRRAT
SIRRAT_PROJECT_ID         # ex. supergest-001

# SLY
SLY_BASE_URL              # https://sly.dynors.com
SLY_FORWARD_SECRET        # secret HMAC partagé avec SLY — PROTÉGÉ + MASQUÉ

# Docker Registry
CI_REGISTRY               # registry.dynors.com ou registry.gitlab.com
CI_REGISTRY_USER
CI_REGISTRY_PASSWORD

# Base de données par env
DB_DEV_PASSWORD
DB_RMOA_DYNORS_PASSWORD
DB_RMOA_CLIENT_PASSWORD
DB_PROD_PASSWORD

# JWT
JWT_DEV_SECRET
JWT_PROD_SECRET           # ≥ 256 bits — PROTÉGÉ + MASQUÉ

# Paiement (sandbox → prod)
WAVE_SANDBOX_KEY
WAVE_PROD_KEY             # PROTÉGÉ + MASQUÉ
ORANGE_MONEY_PROD_KEY     # PROTÉGÉ + MASQUÉ

# Notifications
SLACK_WEBHOOK
SLACK_WEBHOOK_QA
SLACK_WEBHOOK_PROD

# Monitoring
SENTRY_DSN
SENTRY_PROD_DSN

Environnement pré-prod (RMOA DYNORS) — aussi proche que possible de la PROD

Points critiques pour que RMOA DYNORS soit crédible :

Aspect RMOA DYNORS PROD Comment aligner
Base de données PostgreSQL 15 PostgreSQL 15 ✅ même version
SSL Requis (DB_SSL_MODE=require) Requis ✅ même config
Storage MinIO (compatible S3) S3 ou MinIO ✅ même API
Paiements Sandbox Wave/OM Production ⚠ Les providers sandbox ont des limites (ne pas tester les cas edge en RMOA CLIENT)
SLY sly-dev.dynors.com sly.dynors.com ✅ même stack, URL différente
SLY_FORWARD_SECRET Même secret Production ✅ variable GitLab protégée
Redis Oui Oui ✅ même config
Sentry Activé (env rmoa-dynors) Activé (env production) ✅ séparer les environnements dans Sentry

Branches Git (GitFlow adapté DYNORS)

main                          # 😇 PRODUCTION
develop                       # 🔥 DEV (auto-deploy)
release/v1.3.0-internal       # 🌫 RMOA DYNORS
release/v1.3.0                # 🌟 RMOA CLIENT
feature/nom-feature           # 😈 En dev
hotfix/correctif-critique     # 🚨 Urgence prod

Protections GitLab (Settings → Repository → Protected branches) :

main:
  push: No one
  merge: Maintainers
  force_push: false

develop:
  push: Developers + Maintainers
  merge: Developers + Maintainers

release/*:
  push: Maintainers only
  merge: Maintainers only

Checklist nouveau projet

☐ 1. Créer le repo GitLab dans dynors-projects/
☐ 2. Copier template .gitlab-ci.yml + adapter les stages
☐ 3. Créer sirrat.config.yml à la racine
☐ 4. Créer les docker-compose.*.yml
☐ 5. Créer .env.example (commité) et les .env.* (gitignorés)
☐ 6. Configurer les branches protégées GitLab
☐ 7. Ajouter les variables CI/CD GitLab (cf. liste § 4)
☐ 8. Déclarer le projet dans SIRRAT
☐ 9. Configurer les canaux Slack (#dev-X, #qa-X, #prod-alerts)
☐ 10. Premier push → vérifier pipeline build + deploy DEV