Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 1017 minIntermédiaire

Environnements : développement, recette, production

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • décrire le rôle de chaque environnement et les données qu'il contient ;
  • séparer l'artefact de la configuration de chaque environnement ;
  • protéger la production par une approbation dans GitHub ;
  • faire évoluer une base sans casser l'ancienne version ;
  • expliquer ce qu'est l'infrastructure as code.

4 exercices corrigés

Un environnement est une installation complète de l'application (serveurs, base de données, configuration) réservée à un usage : le développement, les tests automatiques, la recette par les utilisateurs, la production. Le pipeline fait passer la même version d'un environnement à l'autre ; seule la configuration change. Ce chapitre décrit les environnements classiques, la gestion de leur configuration, les environnements de GitHub et de GitLab avec leurs règles de protection, les migrations de base de données, puis l'infrastructure as code.

Les environnements classiques

EnvironnementRôleQui l'utiliseDonnées
DéveloppementÉcrire et essayer le codeLe développeur, sur son poste ou dans un conteneurJeu de données fictif
Intégration (CI)Lancer les tests automatiquesLe pipeline, dans des conteneurs jetablesCréées et détruites par les tests
Recette (staging, UAT)Valider les fonctionnalités avant la mise en production : tests d'acceptation, démonstrationsL'équipe, les utilisateurs référents, le clientFictives ou anonymisées
PréproductionRépéter la mise en production dans des conditions identiques : même configuration, même volumeL'exploitationCopie anonymisée de la production
ProductionServir les vrais utilisateursTout le mondeRéelles

Un projet de BTS a souvent trois environnements : le poste du développeur, la recette et la production. La préproduction existe surtout dans les organisations où une panne coûte cher.

Ce qui change d'un environnement à l'autre : la configuration

La règle « construire une fois, déployer partout » impose que l'artefact ne change pas d'un environnement à l'autre. Tout ce qui diffère est de la configuration, fournie au démarrage par des variables d'environnement :

RéglageRecetteProduction
URL_PUBLIQUEhttps://recette.sorbier.examplehttps://frais.sorbier.example
DB_HOST, DB_NAMEServeur et base de recetteServeur et base de production
DB_PASSWORDSecret de l'environnement recetteSecret de l'environnement production
LOG_LEVELdebuginfo
SMTP_HOSTUn faux serveur qui capture les courriels (Mailpit)Le vrai serveur d'envoi
PAIEMENT_MODEtest (aucun virement réel)production

L'application lit ces valeurs au démarrage (process.env.DB_HOST). Le code ne contient jamais de test du genre if (environnement === 'production') pour changer de serveur : ce serait du code jamais testé en recette.

Les environnements de GitHub

Dans Settings, Environments, on crée un environnement par cible : recette, production. Chacun peut avoir :

RéglageEffet
Required reviewersJusqu'à six personnes ou équipes ; le job attend qu'une d'elles approuve
Prevent self-reviewLa personne qui a déclenché le déploiement ne peut pas l'approuver elle-même
Wait timerUn délai d'attente avant le déploiement, de 1 minute à 30 jours
Deployment branches and tagsSeules certaines branches ou certains tags peuvent déployer (main, v*)
Environment secretsDes secrets réservés aux jobs de cet environnement, transmis seulement après l'approbation
Environment variablesDes variables propres à l'environnement (vars.URL_PUBLIQUE)

Un job se rattache à un environnement par la clé environment :

yaml
jobs:
  deploy-production:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://frais.sorbier.example
    concurrency:
      group: deploiement-production
      cancel-in-progress: false
    steps:
      - name: Déployer
        env:
          DEPLOY_HOOK: ${{ secrets.DEPLOY_HOOK }}    # secret de l'environnement production
        run: curl --fail --silent --show-error -X POST "$DEPLOY_HOOK"

Quand ce job démarre, GitHub le met en attente et prévient les relecteurs. L'un d'eux ouvre l'exécution, lit ce qui va être déployé, puis clique sur Approve and deploy (ou Reject). Le secret DEPLOY_HOOK de l'environnement n'est transmis qu'à ce moment-là. L'historique des déploiements s'affiche sur la page d'accueil du dépôt, avec le lien url vers l'application.

C'est exactement la livraison continue : tout est automatique jusqu'à la production, et une personne décide du moment.

Disponibilité. Avec l'offre gratuite, les relecteurs obligatoires, le délai et les secrets d'environnement ne sont disponibles que pour les dépôts publics ; pour un dépôt privé, GitHub Pro (offert aux étudiants) ajoute les restrictions de branches, les variables et les secrets d'environnement, mais pas les relecteurs obligatoires ni le délai, réservés aux dépôts publics et aux offres Enterprise. Pour un projet de BTS, un dépôt public est la solution la plus simple.

Utiliser un environnement sans déployer. Depuis mars 2026, la clé deployment: false, placée sous environment à côté de name, donne au job les secrets et les variables de l'environnement sans créer de déploiement dans l'historique : pratique pour un job de vérification qui lit une variable de recette mais ne déploie rien. Ce réglage n'est pas compatible avec les règles de protection personnalisées (celles fournies par une application tierce).

Les environnements de GitLab

Dans GitLab, la clé environment d'un job enregistre chaque déploiement dans Operate, Environments (environnements et déploiement manuel). La page d'un environnement liste ses déploiements successifs, avec un bouton pour redéployer une version précédente. Les variables peuvent être limitées à un environnement (Environment scope), et les environnements protégés, dans les offres payantes, restreignent qui peut lancer le déploiement ou l'approuver.

Les environnements éphémères

Un environnement éphémère (review app, preview) est créé automatiquement pour une pull request et détruit à sa fusion ou sa fermeture. Chaque fonctionnalité a ainsi sa propre adresse, comme pr-42.recette.sorbier.example, où l'utilisatrice référente peut l'essayer avant la fusion.

Les plateformes d'hébergement d'applications Web (Vercel, Netlify, Render…) le proposent en un réglage. GitLab le prévoit avec les review apps : environment:url contient le nom de la branche, et environment:on_stop désigne le job qui détruit l'environnement. Pour un projet de BTS, c'est une excellente démonstration : on montre la fonctionnalité au jury sans toucher à la production.

Les migrations de base de données

Le code d'une nouvelle version attend souvent une base différente : une colonne de plus, une table renommée. Ces changements de schéma s'écrivent dans des migrations : des scripts numérotés, versionnés avec le code, que l'outil applique dans l'ordre et note dans une table de suivi.

ÉcosystèmeOutil de migration
Node.jsKnex, Prisma Migrate, Sequelize
PHPDoctrine Migrations (Symfony), migrations Laravel
C#Entity Framework Core (dotnet ef database update)
Indépendant du langageFlyway, Liquibase

Dans le pipeline, la migration s'exécute juste avant le déploiement du nouveau code, dans chaque environnement, recette d'abord. En production, une sauvegarde la précède toujours (sauvegarder une base MySQL).

Le piège : pendant quelques secondes ou quelques minutes, l'ancien et le nouveau code tournent en même temps, et un retour arrière remet l'ancien code face au nouveau schéma. La méthode expansion, puis contraction (expand and contract) évite les cassures :

  1. Expansion : on ajoute sans rien casser. Nouvelle colonne facultative, nouvelle table. L'ancien code fonctionne toujours.
  2. On déploie le nouveau code, qui utilise la nouvelle structure (et remplit éventuellement la nouvelle colonne à partir de l'ancienne).
  3. Contraction, dans une version ultérieure, quand plus aucun code n'utilise l'ancienne structure : on supprime l'ancienne colonne.

L'infrastructure as code en bref

L'infrastructure as code (IaC) consiste à décrire les serveurs, les réseaux et les services dans des fichiers texte versionnés, appliqués par un outil, plutôt que de les configurer à la main. Les fichiers passent en revue dans des pull requests et sont appliqués par le pipeline, comme le code de l'application.

Deux idées la caractérisent :

  • Déclaratif : on décrit l'état voulu (« ce serveur a Docker et ce fichier Compose »), pas la suite de commandes pour y arriver ; l'outil calcule les actions nécessaires.
  • Idempotent : appliquer deux fois la même description donne le même résultat ; la seconde fois, l'outil constate que tout est déjà conforme et ne change rien.
OutilCe qu'il décritExemple
Terraform, OpenTofuLes ressources : machines virtuelles, réseaux, DNS, bases managées chez un fournisseur« deux VM, un réseau, un enregistrement DNS »
AnsibleLa configuration des serveurs existants : paquets, fichiers, services, comptes« Docker installé, compte deploy créé, fichier Compose copié »
Docker ComposeLes conteneurs d'une application et leurs liens« l'API et sa base MySQL, avec un volume »
Kubernetes (manifestes, Helm)Les applications sur un cluster« trois répliques de l'API derrière un service »
cloud-initLa configuration d'une VM à son premier démarrage« créer l'utilisateur, installer les clés SSH »

Terraform est passé sous une licence non libre en 2023 ; OpenTofu, son dérivé libre maintenu par la Linux Foundation, reprend la même syntaxe.

Le fichier Compose du serveur de recette de sorbier-frais, versionné dans le dépôt :

yaml
services:
  api:
    image: ghcr.io/sorbier/sorbier-frais:${VERSION:?VERSION manquante}
    restart: unless-stopped
    env_file: .env
    ports:
      - "127.0.0.1:3000:3000"
    depends_on:
      - mysql
  mysql:
    image: mysql:8.4
    restart: unless-stopped
    env_file: .env.mysql
    volumes:
      - donnees-mysql:/var/lib/mysql

volumes:
  donnees-mysql:

La version à déployer arrive par la variable VERSION : VERSION=sha-3f9c2e1 docker compose up -d démarre exactement cette image, et refuse de démarrer si la variable manque. Les fichiers .env, qui contiennent les secrets, restent sur le serveur et ne sont pas versionnés. Le port est publié sur 127.0.0.1 seulement : un serveur web frontal (Nginx, Caddy) reçoit le trafic HTTPS et le transmet à l'API.

Une tâche Ansible équivalente, pour préparer le serveur :

yaml
- name: Préparer le serveur de recette de sorbier-frais
  hosts: recette
  become: true
  tasks:
    - name: Créer le dossier de l'application
      ansible.builtin.file:
        path: /opt/sorbier-frais
        state: directory
        owner: deploy
        mode: "0750"
    - name: Copier le fichier Compose
      ansible.builtin.copy:
        src: compose.yaml
        dest: /opt/sorbier-frais/compose.yaml
        owner: deploy
        mode: "0640"

GitOps pousse l'idée jusqu'au bout : un dépôt Git décrit l'état voulu de l'infrastructure, et un agent installé dans l'environnement (Argo CD, Flux pour Kubernetes) le compare en permanence à l'état réel et corrige les écarts. On ne déploie plus en se connectant au serveur : on fusionne une pull request.

En SISR, ces outils prolongent les scripts que tu écris en Bash et en PowerShell (scripting) : la différence est qu'ils décrivent un état à atteindre plutôt qu'une suite d'ordres, et qu'on peut les rejouer sans risque.

Exercices

Exercice 1 sur 4 Quel environnement ? Réussi À revoir

Dans quel environnement chaque activité doit-elle avoir lieu ?

  1. La comptable de Sorbier Logistique vérifie que le nouvel écran de validation des notes de frais correspond à sa demande.
  2. Le pipeline lance les tests d'intégration contre une base MySQL.
  3. Lina essaie une requête SQL pour un nouvel export.
  4. L'équipe d'exploitation répète la migration de la base sur un volume de données identique à celui de la production, avant le grand jour.
  5. Les 140 salariés saisissent leurs notes de frais.
Voir la réponseMasquer la réponse de l'exercice 1
  1. Recette : validation fonctionnelle par une utilisatrice référente, sur des données fictives ou anonymisées.
  2. Intégration (CI) : une base jetable dans un conteneur du job.
  3. Développement : son poste, avec un jeu de données fictif.
  4. Préproduction : mêmes conditions que la production, données anonymisées.
  5. Production.

Tu avais trouvé ?

Exercice 2 sur 4 Un déploiement approuvé Réussi À revoir

Écris un job deploy-production qui :

  • attend la réussite du job deploy-recette ;
  • ne tourne que pour un push sur main ;
  • utilise l'environnement GitHub production (adresse https://frais.sorbier.example), configuré avec un relecteur obligatoire ;
  • appelle le script ./scripts/deployer.sh avec le secret d'environnement PROD_SSH_KEY passé dans la variable SSH_KEY ;
  • ne peut jamais tourner en double.
Voir la réponseMasquer la réponse de l'exercice 2
yaml
  deploy-production:
    needs: deploy-recette
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://frais.sorbier.example
    concurrency:
      group: deploiement-production
      cancel-in-progress: false
    steps:
      - uses: actions/checkout@v7
      - name: Déployer
        env:
          SSH_KEY: ${{ secrets.PROD_SSH_KEY }}
        run: ./scripts/deployer.sh

Le relecteur obligatoire ne s'écrit pas dans le YAML : il se règle dans Settings, Environments, production. Le checkout est nécessaire, car le script est un fichier du dépôt. Le secret n'est transmis qu'après l'approbation.

Tu avais trouvé ?

Exercice 3 sur 4 Renommer une colonne sans casser Réussi À revoir

La table note a une colonne montant. La version 3.0 doit l'appeler montant_ttc. Le déploiement se fait sans interruption : pendant une minute, l'ancienne et la nouvelle version de l'API tournent ensemble. Décris les étapes, réparties sur plusieurs versions, avec la méthode expansion puis contraction.

Voir la réponseMasquer la réponse de l'exercice 3
  1. Version 2.9 (expansion) : une migration ajoute la colonne montant_ttc, facultative, et copie les valeurs de montant. Le code écrit dans les deux colonnes et lit encore montant. L'ancienne version 2.8 fonctionne toujours, puisque montant existe.
  2. Version 3.0 : le code lit et écrit montant_ttc (et continue d'écrire montant, pour qu'un retour arrière vers la 2.9 reste possible).
  3. Version 3.1 (contraction), une fois la 3.0 stable et le retour arrière vers la 2.x écarté : le code n'écrit plus montant, puis une migration supprime la colonne.

À aucun moment une version en service ne lit une colonne absente.

Tu avais trouvé ?

Exercice 4 sur 4 Idempotent ou pas ? Réussi À revoir

Ces deux façons d'ajouter une ligne au fichier de configuration d'un serveur sont-elles idempotentes ? Que se passe-t-il si on les exécute trois fois ?

  1. La commande echo "LOG_LEVEL=info" >> /opt/sorbier-frais/.env
  2. La tâche Ansible ansible.builtin.lineinfile avec path: /opt/sorbier-frais/.env et line: LOG_LEVEL=info
Voir la réponseMasquer la réponse de l'exercice 4
  1. Non : >> ajoute la ligne à chaque exécution. Après trois exécutions, le fichier contient trois fois LOG_LEVEL=info.
  2. Oui : lineinfile vérifie si la ligne existe et ne l'ajoute que si elle manque. Après trois exécutions, la ligne est présente une seule fois ; Ansible indique changed la première fois, puis ok.

C'est tout l'intérêt de l'infrastructure as code : on peut rejouer la description autant de fois qu'on veut, elle amène le serveur dans l'état décrit sans effet de bord.

Tu avais trouvé ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Déployer automatiquement une application (CD)Revoir le sommaire du cours