CI/CD GitLab
Vue d'ensemble du pipeline
push / merge request
│
├── stage: test
│ ├── test:unit (tests Jest)
│ ├── test:quality:sonarqube (analyse qualité SonarQube)
│ └── test:build:docker (build partiel sur MR)
│
├── stage: containerize
│ └── package:image (build + push image Docker)
│
└── stage: deploy
├── deploy:int:ansible (staging, automatique)
├── deploy:dev:ansible (dev, automatique)
└── deploy:prod:manual (production, ⚠️ MANUEL)
Déclenchement par branche
| Branche | Tests | Build image | Deploy |
|---|---|---|---|
main |
✅ | ✅ (tag main) |
⚠️ manuel → prod |
tag v1.x.x |
✅ | ✅ (tag v1.x.x) |
⚠️ manuel → prod |
develop |
✅ | ✅ (tag develop) |
Auto → staging |
test-ci |
✅ | ✅ (tag test-ci) |
Auto → staging |
qa |
✅ | ✅ (tag qa) |
— |
dev |
— | ✅ (tag dev) |
Auto → dev |
| Merge Request | ✅ | ✅ (build partiel) | — |
| Autre branche | — | — | — |
Stages
Stage : test
test:unit
test:unit:
image: node:20-alpine
stage: test
cache:
key:
files: [package-lock.json]
paths: [.npm/]
before_script:
- npm ci --cache .npm --prefer-offline
script:
- npm test
rules:
- if: main/develop/qa/test-ci ou tag
- if: merge_request_event
Exécute npm test (Jest). Le cache npm accélère les runs suivants.
test:quality:sonarqube
test:quality:sonarqube:
image: sonarsource/sonar-scanner-cli:11
stage: test
script:
- sonar-scanner -Dsonar.host.url="${SONAR_HOST_URL}"
allow_failure: true # Ne bloque pas le pipeline en cas d'échec
rules:
- if: main/develop/qa/test-ci ou tag
Analyse statique du code. allow_failure: true : un échec SonarQube ne bloque pas le déploiement.
test:build:docker
test:build:docker:
stage: test
script:
- docker build --target builder .
rules:
- if: merge_request_event # Uniquement sur les MR
Build partiel (stage builder) pour valider que le Dockerfile est correct. Ne produit pas d'image finale.
Stage : containerize
package:image
package:image:
stage: containerize
needs: [] # Parallèle avec les tests
environment:
name: $DEPLOY_ENV
script:
- docker build --pull --tag ${TARGET_IMAGE} .
- docker push ${TARGET_IMAGE}
rules:
- if: develop/qa/test-ci → DEPLOY_ENV=staging
- if: dev → DEPLOY_ENV=development
- if: main ou tag → DEPLOY_ENV=production
Construction du tag image :
IMAGE_TAG = CI_COMMIT_TAG (si tag git) OU CI_COMMIT_REF_SLUG (nom de branche)
TARGET_IMAGE = ${DOCKER_REG_URL}/${CI_PROJECT_PATH}:${IMAGE_TAG}
Exemples :
registry.id2real.net/racines/racines-app:develop
registry.id2real.net/racines/racines-app:main
registry.id2real.net/racines/racines-app:v1.2.3
Pas de --build-arg AUDIO_CDN_URL : l'URL CDN est injectée au runtime via variable d'environnement Docker. Une seule image sert tous les environnements.
Stage : deploy
deploy:int:ansible (staging)
deploy:int:ansible:
stage: deploy
environment:
name: staging
needs:
- package:image
script:
- chmod 400 $SSHKEY_CI
- ansible-playbook -i ansible/inventory.yaml -l integration \
--private-key $SSHKEY_CI ansible/deployment_playbook.yaml
rules:
- if: develop ou test-ci → automatique
deploy:dev:ansible (dev)
deploy:dev:ansible:
stage: deploy
environment:
name: development
needs:
- package:image
script:
- chmod 400 $SSHKEY_DEV
- ansible-playbook -i ansible/inventory.yaml -l dev \
--private-key $SSHKEY_DEV ansible/deployment_playbook.yaml
rules:
- if: dev → automatique
deploy:prod:manual (production)
deploy:prod:manual:
stage: deploy
environment:
name: production
when: manual # ⚠️ Ne se déclenche pas automatiquement
needs:
- package:image
script:
- chmod 400 $DEPLOY_SSHKEY_FILE
- ansible-playbook -i ansible/inventory.yaml -l prod \
--private-key $DEPLOY_SSHKEY_FILE ansible/deployment_playbook.yaml
rules:
- if: main ou tag → disponible manuellement
Déclenchement :
GitLab → CI/CD → Pipelines → [pipeline sur main] → deploy:prod:manual → ▶
Playbook Ansible
Le playbook ansible/deployment_playbook.yaml exécute sur le serveur cible :
# 1. Créer le répertoire de déploiement
- file: path=/docker-apps/racines state=directory
# 2. Copier le docker-compose de l'environnement
- copy: src=ansible/docker-compose.{env}.yaml dest=/docker-apps/racines/docker-compose.yml
# 3. Arrêter l'application courante
- shell: docker compose -f /docker-apps/racines/docker-compose.yml down
# 4. Puller la nouvelle image et relancer
- shell: |
docker compose -f /docker-apps/racines/docker-compose.yml pull
docker compose -f /docker-apps/racines/docker-compose.yml up -d --force-recreate
L'inventaire (ansible/inventory.yaml) définit les hôtes :
- integration → serveur staging (port SSH 9257, user ci_gitlab_deploy)
- dev → serveur de développement
- prod → serveur de production
Variables CI/CD
À définir dans GitLab → Settings → CI/CD → Variables :
| Variable | Type | Description | Environnements |
|---|---|---|---|
DOCKER_REG_URL |
Variable | URL du registry Docker | Tous |
DOCKER_REG_LOGIN |
Variable | Login registry | Tous |
DOCKER_REG_PASS |
Masked | Mot de passe registry | Tous |
SSHKEY_CI |
File | Clé SSH → staging | CI |
SSHKEY_DEV |
File | Clé SSH → dev | Dev |
DEPLOY_SSHKEY_FILE |
File | Clé SSH → production | Prod |
SONAR_HOST_URL |
Variable | URL SonarQube | Tous |
Aucune variable
AUDIO_CDN_URLdans les variables CI/CD — cette variable est dans ledocker-compose.ymlde chaque environnement sur le serveur, pas dans le pipeline.
Fichiers de configuration par environnement
| Fichier | Environnement | Contenu |
|---|---|---|
ansible/docker-compose.integration.yaml |
Staging | Compose avec URLs staging |
ansible/docker-compose.dev.yaml |
Dev | Compose avec URLs dev |
docker-compose.yml |
Local dev | Compose pour développement local |
Chaque fichier contient les variables d'environnement spécifiques à l'environnement (notamment AUDIO_CDN_URL).
Rollback
Via pipeline GitLab
GitLab → CI/CD → Pipelines → [pipeline précédent sur main] → deploy:prod:manual → ▶
Le pipeline précédent est encore disponible et son image est dans le registry.
Manuel sur le serveur
cd /docker-apps/racines
# Identifier la version précédente
docker images | grep racines-app
# Forcer une image précédente
docker compose pull app
# Modifier docker-compose.yml pour pointer sur l'ancien tag
docker compose up -d --force-recreate app
Durées typiques
| Stage | Durée estimée |
|---|---|
test:unit |
1–2 min |
test:quality:sonarqube |
2–4 min |
package:image |
4–8 min (cache npm) |
deploy:int:ansible |
1–3 min |
| Total push → staging | ~8–15 min |
Diagnostics
Le job test:unit échoue
# En local pour reproduire
npm test
# ou
npm test -- --verbose
Le job package:image échoue à la connexion au registry
# Vérifier les variables DOCKER_REG_* dans GitLab CI/CD Settings
# Tester manuellement :
docker login registry.id2real.net -u USER -p PASS
Le déploiement Ansible échoue sur SSH
# Vérifier que la clé SSH est bien configurée dans les variables File
# Vérifier l'accès SSH depuis le runner GitLab :
ssh -i $SSHKEY_CI ci_gitlab_deploy@serveur-staging -p 9257 "echo ok"
L'image déployée n'est pas la bonne version
# Sur le serveur :
docker inspect racines-app | grep Image
# Comparer avec le tag attendu dans le registry