Aller au contenu principal

Infrastructure · Budget · 09/10/2026 · 9 min de lecture

Endormir ses stagings la nuit et le week-end

Photo d'Emmanuel BALLERY, fondateur de x10 Emmanuel BALLERY · Directeur Technique (CTO) freelance & Architecte logiciel
Une rangée de serveurs endormis sous une lune, un seul voyant ambre allumé

Un environnement de recette sert pendant les heures où l'équipe travaille. Le reste du temps, il tourne pour personne : ses pods occupent leurs ressources, ses tâches planifiées s'exécutent contre des données que personne ne regarde, et sa base de données attend des requêtes qui ne viendront pas avant le lendemain matin. Une semaine compte 168 heures ; une équipe en utilise une cinquantaine.

Sur une plateforme de cinq applications Symfony hébergée sur Kubernetes, j'endors tous les stagings de 19 h à 8 h et pendant tout le week-end. Le mécanisme tient en un script et deux pipelines planifiés. Cet article détaille comment il fonctionne, ce qu'il faut mémoriser pour réveiller correctement un environnement, et la partie qu'on oublie toujours : prévenir les équipes qu'un staging muet n'est pas forcément cassé.

Ce que coûte un staging qui tourne 168 heures par semaine

Le calcul de départ est simple. Du lundi au vendredi, de 8 h à 19 h, cela fait 55 heures ouvrées par semaine. Les 113 heures restantes, soit les deux tiers du temps, le staging tourne sans utilisateur. Sur une plateforme de plusieurs applications, chacune avec son serveur web, ses consumers de messages, sa base et ses tâches planifiées, ces deux tiers représentent une part réelle de la capacité réservée.

Encore faut-il savoir ce qu'on économise vraiment, et cela dépend du mode de facturation :

  • facturation à la consommation — sur un cluster dont les nœuds suivent la charge, des pods à zéro libèrent des nœuds, et la facture baisse en proportion
  • quota ou capacité réservée — si l'hébergeur facture un quota fixe, éteindre les pods ne réduit pas la facture du mois. Le gain est ailleurs : la capacité libérée la nuit sert aux traitements lourds, et le quota peut être revu à la baisse au renouvellement
  • stockage — les volumes persistants restent en place pendant le sommeil, c'est ce qui permet de retrouver les données au réveil. Leur coût, lui, ne bouge pas

Il y a aussi un gain qui ne figure sur aucune facture. Un staging endormi n'exécute pas ses tâches planifiées : pas d'envoi de mails de test à trois heures du matin, pas de synchronisation avec un service tiers qui consomme un quota d'API, pas de logs qui s'accumulent pour rien. Et il n'est pas exposé quand personne ne le surveille.

L'impact écologique suit la même logique que la facture. Un serveur allumé consomme de l'électricité même sans charge, et cette consommation au repos n'est pas négligeable. Quand l'extinction des pods libère réellement des nœuds, ce sont autant de machines qui cessent de tourner pour personne. À plus long terme, une capacité dimensionnée sur l'usage réel évite d'acheter du matériel supplémentaire, dont la fabrication pèse lourd dans l'empreinte du numérique. Avec un quota fixe, le gain est plus indirect : les serveurs restent allumés, mais la capacité libérée sert à d'autres traitements au lieu d'en appeler de nouveaux.

Avant de comparer les hébergeurs sur leurs tarifs, il vaut la peine de regarder combien d'heures on leur paie pour rien. C'est l'un des leviers évoqués dans le choix d'un hébergement sans exploser ses coûts : la facture dépend autant de l'usage qu'on fait des ressources que du prix catalogue.

Le mécanisme : deux pipelines planifiés et un script

Pas besoin d'un opérateur Kubernetes ni d'un outil dédié. La plateforme utilise déjà GitLab CI pour déployer, et GitLab sait lancer un pipeline à heure fixe : les pipeline schedules. J'en ai posé deux sur un petit dépôt dédié, avec un fuseau horaire explicite (Europe/Paris) pour ne pas décaler d'une heure à chaque changement d'heure :

  • 0 8 * * 1-5 — avec la variable SCALE_ACTION=up, du lundi au vendredi à 8 h
  • 0 19 * * 1-5 — avec SCALE_ACTION=down, du lundi au vendredi à 19 h

Le vendredi soir, le down endort donc les stagings jusqu'au lundi matin. Le job ne tourne que sur ces déclenchements, et sur un lancement manuel depuis l'interface :

variables:
  SCALE_ACTION:
    description: "up or down"
    value: "up"
    options: ["up", "down"]
  SCALE_PROJECT:
    description: "One staging namespace, or all of them"
    value: "all"
    options: ["all", "app-a-staging", "app-b-staging"]

scale:
  stage: deploy
  image: $CI_REGISTRY_IMAGE:latest
  script:
    - ./scale.sh
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"
    - if: $CI_PIPELINE_SOURCE == "web"

Le script fait deux choses par namespace : il ramène les Deployments à zéro réplique, et il suspend les CronJobs. Les deux ne demandent pas le même soin.

Les Deployments : mémoriser avant d'éteindre

Mettre un Deployment à zéro est trivial. Le remettre dans son état d'origine l'est moins : au réveil, il faut savoir qu'il tournait à deux répliques plutôt qu'à une. Cette information doit survivre au sommeil, et le meilleur endroit pour la garder est le Deployment lui-même, dans une annotation posée juste avant l'extinction :

scale_deployments() {
  local name replicas

  if [ "down" == "${SCALE_ACTION}" ]; then
    kubectl get deployments -o json \
      | jq -r '.items[] | "\(.metadata.name) \(.spec.replicas // 0)"' \
      | while read -r name replicas; do
          # Already asleep: overwriting the annotation with 0 would lose the real count.
          [ "${replicas}" -gt 0 ] || continue
          kubectl annotate deployment "${name}" "scaler/original-replicas=${replicas}" --overwrite
          kubectl scale deployment "${name}" --replicas=0
        done
  else
    kubectl get deployments -o json \
      | jq -r '.items[] | "\(.metadata.name) \((.metadata.annotations // {})["scaler/original-replicas"] // "0")"' \
      | while read -r name replicas; do
          # A missing or zero annotation must not leave the application asleep.
          if ! [[ "${replicas}" =~ ^[0-9]+$ ]] || [ "${replicas}" -le 0 ]; then
            replicas=1
          fi
          kubectl scale deployment "${name}" --replicas="${replicas}"
        done
  fi
}

Cette annotation est tout l'état du système. Aucune base, aucun fichier ne garde la trace du nombre de répliques : si un autre outil la supprime, le réveil suivant retombe silencieusement sur une réplique. Il faut donc la nommer clairement, la documenter, et s'assurer qu'aucun outil de nettoyage ne l'élague sur ces namespaces.

Deux garde-fous protègent cet état. À l'endormissement, un Deployment déjà à zéro est ignoré : un down manuel suivi du down planifié du soir écraserait sinon l'annotation par zéro. Au réveil, une annotation absente ou nulle donne une réplique : mieux vaut une application qui tourne en sous-capacité qu'une application qui reste endormie sans que personne ne comprenne pourquoi.

Les CronJobs : suspendre, sans rien mémoriser

Les tâches planifiées n'ont pas de répliques. Kubernetes leur offre un simple booléen, spec.suspend : un CronJob suspendu ne lance plus de job, et reprend à la levée de la suspension.

scale_crons() {
  local suspend=true
  [ "up" == "${SCALE_ACTION}" ] && suspend=false

  kubectl get cronjobs -o name | while read -r name; do
    kubectl patch "${name}" -p "{\"spec\":{\"suspend\":${suspend}}}"
  done
}

L'asymétrie entre les deux est voulue. Un booléen n'a pas d'état à préserver, un nombre de répliques en a un. La seule précaution côté CronJobs concerne les tâches qui doivent absolument tourner, comme une purge ou un export quotidien : il faut vérifier qu'elles sont planifiées en heures ouvrées, ou les sortir du périmètre.

Un namespace en échec ne bloque pas les autres

Le script parcourt la liste des stagings et traite chacun indépendamment. Si l'un est injoignable, il affiche l'erreur et passe au suivant : un namespace en panne ne doit pas laisser les autres allumés toute la nuit. La contrepartie est à connaître : un pipeline vert ne garantit pas que tous les stagings ont été traités. Il faut lire le log du job, ou mieux, retenir l'échec et faire sortir le script en erreur une fois la boucle terminée.

Côté droits, le job n'a besoin que de lister, annoter et mettre à l'échelle des Deployments, et de patcher des CronJobs. Un compte de service dédié, limité à ces verbes et aux seuls namespaces de staging, suffit. Ce n'est pas l'outil pour éteindre une production, et ses droits doivent le rendre impossible.

Le réveil : tout redémarre en même temps

Le réveil remet toutes les répliques en route et lève la suspension des CronJobs au même instant : le namespace redémarre d'un bloc, mais ses composants ne sont pas tous prêts au même moment. La base de données, qui tourne dans le même namespace, met une à deux minutes à remonter, le temps de rattacher son volume et de démarrer le moteur. Pendant cette fenêtre, les pods applicatifs, les consumers de messages et le premier cron de la journée peuvent échouer sur une base pas encore disponible ; Kubernetes les relance avec un délai croissant, et l'ensemble se stabilise seul en quelques minutes.

Pour lisser ce démarrage, il suffit d'ordonner le réveil :

  • d'abord les dépendances — base de données, cache, attendre qu'elles soient prêtes (kubectl rollout status)
  • ensuite les applications — serveurs web et consumers
  • en dernier les CronJobs — une tâche planifiée ne doit pas partir avant que tout ce dont elle dépend ne soit debout

Des sondes de démarrage et de disponibilité bien réglées limitent aussi le bruit : un pod qui n'est pas prêt ne reçoit pas de trafic, et le déploiement n'est pas déclaré en échec pour autant. C'est le même sujet que celui d'un déploiement sans interruption : un pod ne doit se déclarer prêt que lorsqu'il peut vraiment servir.

Enfin, il faut prévoir le réveil hors horaires. Une démonstration un samedi, un développeur qui avance le soir, une recette décalée : le même pipeline se lance à la main depuis l'interface de GitLab, en choisissant up et le namespace concerné. Compter une à deux minutes avant que tout réponde, le temps que la base remonte.

Le dire aux équipes : un staging muet n'est pas cassé

La partie technique est la plus courte. La partie humaine demande plus d'attention, parce qu'un environnement qui s'éteint tout seul modifie ce que les équipes considèrent comme normal.

Un staging qui ne répond pas un dimanche, ou à 22 h, n'est pas cassé : il dort.

Sans cette phrase, écrite noir sur blanc dans la documentation de chaque projet, le premier développeur qui teste un correctif un soir ouvre un incident, et la personne d'astreinte cherche une panne qui n'existe pas. La même règle vaut pour un cron de staging qui n'a pas tourné cette nuit, ou pour un déploiement du soir dont les pods restent à zéro : le rollout s'est bien passé, le Deployment attend simplement son réveil.

L'inverse surprend tout autant. Un staging allumé un samedi n'est pas un down en échec : quelqu'un l'a réveillé, et c'est permis. Comme les deux pipelines planifiés ne tournent qu'en semaine, rien ne le rendort avant le lundi 19 h. On l'accepte, ou on l'éteint à la main en partant. Avant de soupçonner le script, on regarde qui a lancé le dernier up dans la liste des pipelines.

Trois mesures rendent le dispositif lisible :

  • une notification à chaque passage — un message dans le canal de l'équipe au coucher et au réveil de chaque staging. Elle doit rester accessoire : un échec d'envoi ne doit jamais faire échouer la mise en sommeil
  • une procédure de réveil en trois lignes — où cliquer, quelles variables poser, combien de temps attendre. Chacun doit pouvoir réveiller un staging sans demander à personne
  • les horaires à un seul endroit — dans les pipelines planifiés, pas recopiés dans un wiki qui finira par diverger

Conclusion

Endormir ses stagings ne demande ni outil payant ni chantier d'infrastructure : deux pipelines planifiés, un script, et une annotation pour se souvenir de l'état d'origine. Les deux tiers des heures de la semaine cessent de consommer des ressources pour personne. Ce qui fait réussir le dispositif n'est pas le script, mais ce qui l'entoure : un réveil ordonné, une procédure que chacun peut suivre, et une phrase répétée à toute l'équipe. Pour un dirigeant qui compare des hébergements, c'est aussi un rappel utile : entre cloud et on-premise, le prix de l'heure compte moins que le nombre d'heures facturées sans usage.

Message clé

Un staging n'a pas besoin de tourner quand personne ne travaille. L'éteindre est simple ; le réveiller proprement, et le dire à l'équipe, est ce qui demande du soin.

Vous voulez savoir ce que votre infrastructure consomme sans servir ? Un audit technique mesure l'usage réel de vos environnements et priorise les économies. Parlons-en.

Questions fréquentes

Ce qu'on me demande souvent.

Combien peut-on économiser en éteignant les stagings hors des heures ouvrées ?

Du lundi au vendredi de 8 h à 19 h, un staging sert 55 heures sur 168 : il tourne sans utilisateur les deux tiers du temps. L'économie réelle dépend du mode de facturation. Sur un cluster facturé à la consommation, dont les nœuds suivent la charge, elle est proportionnelle. Avec un quota fixe, la facture du mois ne bouge pas, mais la capacité libérée sert à autre chose et le quota peut être revu. Le stockage des volumes persistants reste facturé.

Comment retrouver le bon nombre de répliques au réveil ?

En le mémorisant avant l'extinction dans une annotation du Deployment lui-même, puis en la relisant au réveil. Cette annotation est tout l'état du système : il faut l'ignorer quand le Deployment est déjà à zéro, pour ne pas l'écraser, et retomber sur une réplique si elle est absente, pour ne jamais laisser une application endormie.

Pourquoi les applications échouent-elles au réveil du staging ?

Parce que tout redémarre en même temps : la base de données met une à deux minutes à remonter, et les applications, les consumers et les tâches planifiées échouent sur une base absente avant de repartir seuls. La correction consiste à réveiller d'abord les dépendances, à attendre qu'elles soient prêtes, puis les applications, et à lever la suspension des CronJobs en dernier.

Que faire si l'on a besoin du staging le soir ou le week-end ?

Le réveiller soi-même, en lançant à la main le même pipeline avec l'action up et le namespace voulu ; tout répond en une à deux minutes, le temps que la base remonte. C'est permis et prévu. Comme la mise en sommeil planifiée ne tourne qu'en semaine, un staging réveillé le samedi reste allumé jusqu'au lundi soir, sauf à l'éteindre à la main en partant.

Contact

Un projet ou une question sur ce sujet ?

Pas de discours commercial, pas de jargon inutile : un échange clair sur vos besoins et sur la manière dont je peux y répondre.

Démarrer votre projet arrow_forward