1. Objectif

Ce guide explique comment intégrer un nouveau projet (backend Java, frontend, base de données PostgreSQL/MySQL/MongoDB, cache Redis, storage) pour que son déploiement, backup et restore soient entièrement gérés par le système Ansible de sever-admin — exactement comme le sont déjà ClaraJob, Marketisia/PredictX, auth-server et Traefik.

Principe : on ne réinvente rien. On copie le pattern d’un projet existant équivalent :

Tu veux ajouter…​ Copie le pattern existant de…​

Un stack applicatif complet (API + DB + front)

clarajob-sa ou marketisia-sa

Un backend Java Spring Boot seul (image GitLab)

clarajob-front-api / marketisia-front-api

Un frontend Vue.js/React buildé sur le serveur

clarajob-front-gui (rôle deploy_frontend)

Un site statique non-npm buildé sur le serveur (AsciiDoc/Gradle…​)

documentation (rôle deploy_frontend + build_image/build_command)

Une base PostgreSQL (avec backup/restore)

clarajob-ddl / auth-server-db

Une base MongoDB (avec backup/restore)

clarajob-mongo

Une base MySQL (avec backup/restore)

le rôle backup/restore supporte déjà db_type: mysql

Un cache Redis

clarajob-redis (ou le redis partagé de sever-admin)

Un service d’infrastructure partagé (UI, broker…​)

un fichier docker-compose.<groupe>.yml + include: dans sever-admin

2. Prérequis

  • Le projet a un repo GitLab dans le groupe app81724

  • Le repo contient un docker-compose.yml fonctionnel (à la racine, ou dans docker/ comme marketisia)

  • Tous ses services sont sur le réseau proxy_server_network (external)

  • Si images privées : la CI du projet pushe vers le GitLab Container Registry et les images sont taguées (${IMAGE_TAG} dans le compose)

  • La clé GitLab du serveur (/home/admin/.ssh/id_ed25519) a accès au repo (deploy key)

  • Tu as le mot de passe vault (~/.vault_sever_admin)

3. Vue d’Ensemble des 6 Étapes

1. Préparer le docker-compose du projet (conventions du parc)
2. Déclarer les variables dans ansible/inventory/group_vars/all/vars.yml
3. Ajouter les secrets dans vault.yml (ansible-vault edit)
4. Ajouter la cible dans playbooks/deploy.yml (assert + task)
5. (si base de données) Ajouter aux playbooks backup.yml, restore.yml, setup-cron.yml
6. Tester : ENV=local → syntax-check → déploiement réel

4. Étape 1 — Préparer le docker-compose du Projet

Le compose vit dans le repo du projet (pas dans sever-admin). Conventions observées dans tout le parc, à respecter :

# monapp/docker-compose.yml
networks:
  proxy_server_network:
    external: true                    # ① réseau partagé, JAMAIS créé par le compose

volumes:
  monapp-db-data:                     # ② volume nommé pour toute donnée persistante

services:
  monapp-ddl:                         # ③ nom de service = nom de container = identifiant unique
    image: postgres:16-alpine
    container_name: monapp-ddl        #    dans tout le parc (préfixe par le nom du projet)
    restart: unless-stopped           # ④ redémarrage auto
    environment:
      POSTGRES_DB: ${MONAPP_DB_NAME}          # ⑤ secrets via variables d'env (.env local
      POSTGRES_USER: ${MONAPP_DB_USER}        #    sur le serveur, vault côté Ansible)
      POSTGRES_PASSWORD: ${MONAPP_DB_PASS}
    volumes:
      - monapp-db-data:/var/lib/postgresql/data
    networks:
      - proxy_server_network
    mem_limit: 256m                   # ⑥ limite RAM OBLIGATOIRE (Lightsail = RAM limitée)
    memswap_limit: 256m
    healthcheck:                      # ⑦ healthcheck sur les services critiques
      test: ["CMD-SHELL", "pg_isready -U ${MONAPP_DB_USER} -d ${MONAPP_DB_NAME}"]
      interval: 10s
      timeout: 5s
      retries: 5

  monapp-api:
    image: registry.gitlab.com/app81724/monapp/monapp-api:${IMAGE_TAG:-latest}   # ⑧ tag paramétré
    container_name: monapp-api
    restart: unless-stopped
    environment:
      SPRING_DATASOURCE_URL: jdbc:postgresql://monapp-ddl:5432/${MONAPP_DB_NAME}  # ⑨ hostname
      SPRING_DATASOURCE_USERNAME: ${MONAPP_DB_USER}                               #    = nom du
      SPRING_DATASOURCE_PASSWORD: ${MONAPP_DB_PASS}                               #    container
    depends_on:
      monapp-ddl:
        condition: service_healthy
    networks:
      - proxy_server_network
    mem_limit: 512m
    memswap_limit: 512m
    labels:                           # ⑩ exposition publique via Traefik (pas de port publié)
      - "traefik.enable=true"
      - "traefik.http.routers.monapp.rule=Host(`monapp.mondomaine.com`)"
      - "traefik.http.routers.monapp.entrypoints=websecure"

Les 10 conventions numérotées ci-dessus sont celles appliquées par clarajob_sa, marketisia et les compose de sever-admin. Détail : Infrastructure Docker.

Le ${IMAGE_TAG:-latest} est indispensable : c’est ce qui permet à make deploy APP=monapp-api TAG=v1.2.0 de fonctionner (le rôle deploy exporte IMAGE_TAG avant chaque commande compose).

5. Étape 2 — Déclarer les Variables dans vars.yml

Fichier : ansible/inventory/group_vars/all/vars.yml. Suivre exactement le style existant (voir les blocs clarajob/marketisia déjà présents) :

# MonApp — description courte (repo principal, docker-compose.yml à la racine)
monapp_project_dir: "/home/admin/app/monapp"
monapp_git_url: "git@gitlab.com:app81724/monapp.git"

# Services applicatifs
monapp_api_service: "monapp-api"

# Base PostgreSQL
monapp_db_service: "monapp-ddl"
monapp_db_name: "{{ vault_monapp_db_name }}"
monapp_db_user: "{{ vault_monapp_db_user }}"
monapp_db_pass: "{{ vault_monapp_db_pass }}"

Règles de nommage observées dans le fichier réel :

  • Préfixe unique par projet : monapp_*

  • *_project_dir sous /home/admin/app/

  • *_service = le container_name exact du compose

  • Toute donnée sensible référence une variable vault_* — jamais de valeur en clair

Si le compose n’est pas à la racine (cas marketisia) : tu passeras deploy_compose_file: "docker/docker-compose.yml" à l’étape 4.

6. Étape 3 — Ajouter les Secrets dans vault.yml

cd sever-admin/ansible
ansible-vault edit inventory/group_vars/all/vault.yml
# (le mot de passe est lu automatiquement depuis ~/.vault_sever_admin via ansible.cfg)

Ajouter :

vault_monapp_db_name: "monappdb"
vault_monapp_db_user: "monapp_user"
vault_monapp_db_pass: "MotDePasseFort"

Vérifier : ansible-vault view inventory/group_vars/all/vault.yml | grep monapp

7. Étape 4 — Ajouter la Cible dans deploy.yml

Fichier : ansible/playbooks/deploy.yml. Deux modifications :

7.1. 4a. Étendre la liste du assert (pre_tasks)

- name: "Valider que target_app est fourni et connu"
  assert:
    that:
      - target_app is defined
      - target_app in ['clarajob-sa', ..., 'marketisia-front-gui',
                       'monapp-sa', 'monapp-api', 'monapp-ddl']    # ← AJOUT
    fail_msg: >-
      ... monapp-sa, monapp-api, monapp-ddl                        # ← AJOUT au message

7.2. 4b. Ajouter les tasks de déploiement

Cas service individuel (API, DB, cache — le cas le plus courant) — rôle deploy :

# ─── MonApp API (Spring Boot — image GitLab, service seul) ──────────
- name: "Deploy MonApp API"
  include_role:
    name: deploy
  vars:
    app_name: "monapp-api"
    app_service: "{{ monapp_api_service }}"
    git_url: "{{ monapp_git_url }}"
    project_dir: "{{ monapp_project_dir }}"
    compose_dir: "{{ monapp_project_dir }}"
    # deploy_compose_file: "docker/docker-compose.yml"   # si compose pas à la racine
  when: target_app == "monapp-api"

# ─── MonApp DDL (PostgreSQL) ────────────────────────────────────────
- name: "Deploy MonApp DDL"
  include_role:
    name: deploy
  vars:
    app_name: "monapp-ddl"
    app_service: "{{ monapp_db_service }}"
    git_url: "{{ monapp_git_url }}"
    project_dir: "{{ monapp_project_dir }}"
    compose_dir: "{{ monapp_project_dir }}"
  when: target_app == "monapp-ddl"

Le rôle deploy fait automatiquement : git clone/pull → stop → docker login → pull (si TAG) → up -d → pause 10s → vérification container → rollback si échec.

Cas frontend buildé sur le serveur (Vue.js/React sans image CI) — rôle deploy_frontend :

- name: "Deploy MonApp GUI (Vue.js + Nginx)"
  include_role:
    name: deploy_frontend
  vars:
    app_name: "monapp-gui"
    app_service: "{{ monapp_gui_service }}"
    git_url: "{{ monapp_git_url }}"
    project_dir: "{{ monapp_gui_project_dir }}"    # sous-dossier contenant package.json
    compose_dir: "{{ monapp_project_dir }}"        # dossier du docker-compose.yml
    node_image: "node:lts-alpine"
  when: target_app == "monapp-gui"

Sans TAG : npm ci && npm run build dans un container Node jetable puis restart Nginx. Avec TAG : pull de l’image pré-buildée.

Cas site statique buildé avec un autre outil que npm (AsciiDoc/Gradle, Hugo, mkdocs…​) — même rôle deploy_frontend, en surchargeant les deux variables optionnelles de build (build_image et build_command, défauts = pattern npm), par exemple build_image: "gradle:8.12.1-jdk17" + build_command: "gradle asciidoctor --no-daemon".

Cas site statique pré-généré et versionné (aucun build serveur) — quand le dossier généré est committé dans le repo, le rôle deploy simple suffit. Exemple réel du parc, la cible documentation (build/generatedSite versionné, généré en local par gradle asciidoctor) :

- name: "Deploy Documentation (AsciiDoc + Nginx)"
  include_role:
    name: deploy
  vars:
    app_name: "documentation"
    app_service: "{{ documentation_service }}"
    git_url: "{{ documentation_git_url }}"
    project_dir: "{{ documentation_project_dir }}"
    compose_dir: "{{ documentation_project_dir }}"
  when: target_app == "documentation"

Le container nginx du compose monte le dossier généré (./build/generatedSite) — aucune image Docker custom, donc jamais de TAG pour cette cible. Le deploy se réduit à git pull + restart : rapide et sans dépendance à Maven Central côté serveur.

Cas stack complet (git pull + pull de toutes les images + up -d global) : copier le bloc inline clarajob-sa de deploy.yml (block/rescue avec rollback) en adaptant repo, compose et liste d’images à puller.

7.3. 4c. (Recommandé pour les DBs) Backup pre-deploy automatique

Copier le pattern des pre_tasks existants — vérification que le container tourne, puis backup marqué pre-deploy :

# Dans pre_tasks, après les blocs existants :
- name: "Vérifier si le container monapp-ddl existe"
  shell: "docker inspect --format '{{ '{{' }}.State.Running{{ '}}' }}' {{ monapp_db_service }}"
  register: monapp_ddl_running
  ignore_errors: true
  changed_when: false
  when: target_app == "monapp-ddl" or target_app == "monapp-sa"

- name: "Backup pre-deploy MonApp DDL (PostgreSQL)"
  include_role:
    name: backup
  vars:
    db_service: "{{ monapp_db_service }}"
    db_name: "{{ monapp_db_name }}"
    db_user: "{{ monapp_db_user }}"
    db_pass: "{{ monapp_db_pass }}"
    db_type: "postgresql"
    dump_description: "pre-deploy"
  when: (target_app == "monapp-ddl" or target_app == "monapp-sa")
        and monapp_ddl_running.stdout | default('') == "true"

8. Étape 5 — Backup & Restore (si base de données)

8.1. 5a. backup.yml

- name: "Backup MonApp DDL (PostgreSQL)"
  include_role:
    name: backup
  vars:
    db_service: "{{ monapp_db_service }}"
    db_name: "{{ monapp_db_name }}"
    db_user: "{{ monapp_db_user }}"
    db_pass: "{{ monapp_db_pass }}"
    db_type: "postgresql"          # postgresql | mongodb | mysql
    dump_description: "auto-backup"
  when: target_app == "monapp-ddl" or target_app == "all"

Pour MongoDB, ajouter dump_ext: "archive" (cf. bloc clarajob-mongo). Pour MySQL, db_type: "mysql" — le rôle utilise alors mysqldump (déjà implémenté).

Les dumps iront automatiquement dans db-config/dump/monapp-ddl/monapp-ddl-YYYY-MM-DD_HHMMSS.sql.

8.2. 5b. restore.yml

- name: "Restore MonApp DDL (PostgreSQL)"
  include_role:
    name: restore
  vars:
    db_service: "{{ monapp_db_service }}"
    db_name: "{{ monapp_db_name }}"
    db_user: "{{ monapp_db_user }}"
    db_pass: "{{ monapp_db_pass }}"
    db_type: "postgresql"
    dump_file: "{{ dump_file }}"
  when: target_app == "monapp-ddl"

8.3. 5c. setup-cron.yml (backup quotidien automatique)

Choisir une heure libre (3h = marketisia, 4h = clarajob-ddl, 5h = clarajob-mongo) :

- name: "Cron backup MonApp DDL — tous les jours à 6h"
  cron:
    name: "backup-monapp-ddl-daily"
    minute: "0"
    hour: "6"
    job: >
      cd {{ server_admin_dir }}/ansible &&
      ansible-playbook {{ server_admin_dir }}/ansible/playbooks/backup.yml
      -e target_app=monapp-ddl
      --vault-password-file /opt/.vault_password
      >> /var/log/ansible-backup.log 2>&1
    state: present
    user: root

Puis réappliquer : make setup-cron.

8.4. 5d. Makefile (documentation des usages)

Le Makefile passe APP tel quel aux playbooks — aucune modification fonctionnelle nécessaire. Mettre à jour uniquement les commentaires d’usage et la ligne help pour documenter les nouvelles valeurs.

9. Étape 6 — Tester

cd sever-admin/ansible

# 1. Syntaxe
ansible-playbook --syntax-check playbooks/deploy.yml
ansible-playbook --syntax-check playbooks/backup.yml
ansible-playbook --syntax-check playbooks/restore.yml

# 2. Test en local (ta machine, si Docker dispo)
cd ..
make deploy APP=monapp-ddl ENV=local
make backup APP=monapp-ddl ENV=local
make restore APP=monapp-ddl ENV=local DUMP=<fichier-produit-au-2>

# 3. Production
make deploy APP=monapp-ddl          # la DB d'abord
make deploy APP=monapp-api          # puis l'API
make backup APP=monapp-ddl          # backup manuel de validation

# 4. Vérifier
ssh -i ~/.ssh/serverAdminSSHKeypair.pem admin@18.158.207.98
docker ps | grep monapp
docker logs monapp-api --tail 50
ls -lh /home/admin/app/sever-admin/db-config/dump/monapp-ddl/

10. Recettes par Type de Projet

10.1. Backend Java Spring Boot

  • Image buildée par la CI du projet, pushée sur registry.gitlab.com/app81724/…​, référencée avec ${IMAGE_TAG:-latest} dans le compose

  • Ansible : rôle deploy (étape 4b, cas service individuel)

  • Connexion DB par hostname de container : jdbc:postgresql://monapp-ddl:5432/…​

  • Kafka si besoin : consommer le broker partagé marketisia-kafka:29092 — ne pas redéployer de Kafka dans le projet

  • Déploiement : make deploy APP=monapp-api TAG=v1.0.0

10.2. Frontend Vue.js / React

Deux options (les deux existent dans le parc) :

  1. Build serveur (pattern clarajob-front-gui) : rôle deploy_frontend, le serveur exécute npm ci && npm run build dans un container Node, Nginx monte le dist/. Déploiement : make deploy APP=monapp-gui (sans TAG)

  2. Image CI (pattern marketisia-front-gui) : la CI construit une image Nginx+dist, rôle deploy classique. Déploiement : make deploy APP=monapp-gui TAG=v1.0.0

10.3. Site Statique non-npm (AsciiDoc, Hugo, mkdocs…​)

  • Pattern documentation : site pré-généré en local (gradle asciidoctor) avec le dossier build/ versionné dans le repo → rôle deploy simple, aucun build serveur. (Alternative si on ne versionne pas le build : rôle deploy_frontend avec build_image/build_command surchargés)

  • Compose : nginx:latest stock qui monte le dossier généré en volume — aucune image Docker custom, aucun registre

  • Pas de vault, pas de backup/restore/cron : le site est entièrement régénérable depuis Git

  • Déploiement : make deploy APP=documentation (toujours sans TAG)

10.4. Base PostgreSQL

  • Compose : postgres:16-alpine, volume nommé, healthcheck pg_isready, mem_limit

  • Ansible : étapes 4 (deploy + backup pre-deploy) et 5 (backup/restore/cron) avec db_type: "postgresql"

  • Le dump est fait avec --clean --if-exists → le restore recrée les objets

10.5. Base MySQL

  • Identique à PostgreSQL avec db_type: "mysql" — le rôle backup utilise mysqldump --add-drop-table --routines --triggers, le restore mysql < dump

  • C’était le type historique du parc (anciens crons WordPress) : tout est déjà supporté

10.6. Base MongoDB

  • Compose : mongo, healthcheck db.adminCommand('ping'), volume nommé

  • Ansible : db_type: "mongodb" et dump_ext: "archive" au backup

  • ⚠️ Le restore utilise --drop : les collections existantes sont écrasées

10.7. Cache Redis

  • Décision d’abord : le parc a un redis partagé (sever-admin, mot de passe REDIS_PASSWORD, 32mb LRU) et un clarajob-redis dédié. Un nouveau projet peut :

  • utiliser le redis partagé → aucune intégration Ansible, juste l’URL redis://default:${REDIS_PASSWORD}@redis:6379

  • OU avoir son redis dédié → copier le service du compose cache de sever-admin dans le compose du projet + cible deploy (étape 4b). Pas de backup Ansible pour Redis (cache reconstructible ; l’AOF vit dans le volume)

10.8. Service d’Infrastructure Partagé (nouveau groupe sever-admin)

Pour un service transverse (nouvelle UI d’admin, nouveau broker…​) qui appartient à sever-admin et non à un projet applicatif :

# 1. Créer le fichier compose dédié dans sever-admin/
#    docker-compose.montool.yml  (networks: proxy_server_network external,
#                                 mem_limit, restart, healthcheck...)

# 2. L'ajouter à l'include du docker-compose.yml racine :
include:
  - docker-compose.cache.yml
  ...
  - docker-compose.montool.yml      # ← AJOUT

# 3. (Optionnel) cible make deploy : copier le pattern adminer/server-admin-doc
#    dans deploy.yml (rôle deploy, git_url: server_admin_git_url,
#    project_dir/compose_dir: server_admin_dir)

# 4. Démarrer :
ssh admin@18.158.207.98 "cd /home/admin/app/sever-admin && git pull && docker compose up -d montool"

11. Checklist Finale d’Intégration

Compose (repo du projet) :
□ networks: proxy_server_network (external: true)
□ container_name explicites et uniques dans le parc
□ ${IMAGE_TAG:-latest} sur les images GitLab
□ Secrets via ${VARS} (jamais en dur) + .env sur le serveur
□ mem_limit / memswap_limit sur chaque service
□ restart: unless-stopped + healthchecks
□ Volumes nommés pour les données
□ Labels Traefik si exposition publique

Ansible (sever-admin) :
□ vars.yml : bloc de variables préfixées (project_dir, git_url, *_service, db_*)
□ vault.yml : secrets vault_* ajoutés (ansible-vault edit)
□ deploy.yml : assert étendu + task(s) include_role
□ deploy.yml : backup pre-deploy si DB
□ backup.yml / restore.yml : blocs DB ajoutés
□ setup-cron.yml : cron quotidien à une heure libre + make setup-cron relancé
□ Makefile : commentaires d'usage mis à jour

Serveur :
□ Deploy key GitLab du serveur autorisée sur le nouveau repo
□ .env du projet créé sur le serveur (variables du compose)
□ CI du projet pushe bien les images taguées au registry

Validation :
□ ansible-playbook --syntax-check sur les 3 playbooks
□ Cycle complet ENV=local si possible
□ make deploy → docker ps → docker logs OK
□ make backup → dump non-vide dans db-config/dump/<service>/
□ make restore testé avec le dump produit
□ Documentation mise à jour (ce dossier + README du repo)

12. Erreurs Fréquentes d’Intégration

Erreur Correction

Application 'monapp-api' inconnue

Oublié d’ajouter la cible dans le assert de deploy.yml (étape 4a)

Le deploy tourne mais l’image ne change pas

Le compose n’utilise pas ${IMAGE_TAG} — le rôle exporte IMAGE_TAG mais le compose l’ignore

Les variables db_service…​ sont obligatoires

Variable manquante dans vars.yml, ou faute de frappe dans le nom vault_*

Git échoue sur le serveur

Deploy key GitLab pas ajoutée au nouveau repo pour /home/admin/.ssh/id_ed25519

Le container ne joint pas la DB/Kafka

Service pas sur proxy_server_network, ou hostname ≠ container_name

Backup vide

Credentials vault ≠ credentials réels du container (le .env du serveur fait foi)

Cron ne tourne pas pour la nouvelle DB

make setup-cron pas relancé après modification de setup-cron.yml

13. Prochaines Étapes