Dérive de fuseau horaire Docker dans les conteneurs : corrigez sans reconstruire les images

Cet article vous a aidé ?

Votre pager sonne à 02:07. Le tableau de bord indique que le job batch « a raté son SLA », la base de données affiche des horodatages « du futur », et le rapport d’audit affirme
que des employés se sont connectés avant d’être embauchés. Personne n’a touché au code. Le seul vrai changement a été… un redémarrage de conteneur.

La dérive de fuseau horaire dans les conteneurs est le type de panne qui fait dire aux gens intelligents des trucs stupides comme « mais le temps c’est le temps ». En production, le temps est une dépendance.
Il a des configurations, des cas limites et des frontières politiques. Et quand il est mauvais, vous perdez de l’argent de la manière la plus bureaucratique qui soit.

Ce que signifie réellement la “dérive de fuseau horaire” dans les conteneurs

Séparons trois problèmes qu’on regroupe souvent dans un même message Slack en colère :

  • Dérive de l’horloge : l’horloge système est incorrecte (minutes/heures de décalage) parce que NTP/chrony est cassé ou que l’hôte est suspendu/virtualisé de manière problématique.
  • Discordance de fuseau horaire : l’horloge est correcte (secondes UTC), mais le conteneur interprète l’heure locale dans le mauvais fuseau (par ex. UTC vs America/New_York).
  • Discordance de la base de données des fuseaux : le nom du fuseau est correct, mais les règles (transitions DST) sont obsolètes parce que tzdata est ancien ou absent.

Les conteneurs n’ont pas leur propre horloge noyau. Ils lisent l’heure depuis le noyau de l’hôte. Si l’heure est fausse, c’est un problème d’hôte. Si l’heure est correcte mais que
l’heure locale affichée est erronée, c’est généralement une configuration du conteneur : /etc/localtime, /etc/timezone, comportement de libc,
cache des règles de zone de Java, ou une application qui surcharge la gestion du fuseau horaire.

La partie délicate est que « corriger le fuseau horaire » a plusieurs couches. Un conteneur basé sur Debian peut dépendre de glibc lisant
/etc/localtime comme un fichier binaire zoneinfo. Alpine (musl) a des attentes différentes. Java inclut parfois ses propres règles de zone et les met en cache
jusqu’au redémarrage. Les bases de données peuvent avoir leurs propres paramètres de fuseau. Et votre appli peut commettre la classique erreur « heure locale partout ».

Si vous ne retenez qu’une chose : ne reconstruisez pas l’image juste pour corriger le comportement du fuseau horaire. C’est une dette opérationnelle déguisée en propreté.
Il existe des correctifs sûrs à l’exécution, et vous devriez les privilégier.

Faits et historique qui expliquent pourquoi cela arrive sans cesse

Voici des faits concrets et brefs qui rendent l’étrangeté moins personnelle :

  1. UTC n’est pas « pas de fuseau ». C’est un fuseau horaire avec des règles et des secondes intercalaires ; beaucoup d’applications le traitent néanmoins comme une constante magique.
  2. La plupart des conteneurs sont livrés sans tzdata. Les images minimales l’omettent pour économiser de l’espace ; alors localtime devient ce que libc choisit par défaut.
  3. La base de données IANA des fuseaux change constamment. Les gouvernements redéfinissent les offsets et les règles DST sans préavis ; les mises à jour tzdata sont de l’opérationnel normal.
  4. Les abréviations de fuseaux sont ambiguës. « CST » peut signifier China Standard Time ou Central Standard Time ; utilisez des noms régionaux comme America/Chicago.
  5. Historiquement, Unix utilisait /etc/localtime comme commutateur canonique. Beaucoup de distributions le traitent encore comme la source de vérité pour « heure locale ».
  6. Docker ne virtualise pas le fuseau horaire par défaut. Les conteneurs peuvent avoir des fichiers de fuseau différents de l’hôte, mais ils partagent toujours l’horloge du noyau hôte.
  7. Le passage à l’heure d’été est le cadeau qui continue à prendre. Votre incident surviendra pendant une transition DST, parce que bien sûr.
  8. Java mettait historiquement en cache les règles de fuseau. Même si vous corrigez des fichiers sur le disque, certaines JVM gardent d’anciennes règles en mémoire jusqu’au redémarrage.

Une citation à garder sur un post-it près de votre portable d’astreinte :
Tout échoue, tout le temps. — Werner Vogels

Playbook de diagnostic rapide (vérifier premier/deuxième/troisième)

C’est la boucle « arrêtez de débattre, commencez à mesurer ». Exécutez-la quand vous suspectez un problème de fuseau horaire et que vous avez besoin de la cause racine rapidement.

Premier : l’horloge de l’hôte est-elle correcte ?

  • Si l’horloge de l’hôte est incorrecte, les conteneurs seront incorrects. Corrigez NTP/chrony, la synchronisation de l’hyperviseur ou la source d’horloge de l’hôte.
  • Si l’hôte est correct, cessez de blâmer NTP. Passez à l’étape suivante.

Second : le conteneur affiche-t-il UTC vs local à cause d’une config manquante ?

  • Vérifiez date à l’intérieur du conteneur, et comparez avec la sortie UTC.
  • Vérifiez la présence et le type de /etc/localtime.
  • Vérifiez la variable d’environnement TZ dans le conteneur et si l’appli la respecte.

Troisième : est-ce spécifiquement les règles de tzdata (DST) ou la gestion du fuseau par l’appli ?

  • Recherchez un décalage « exactement d’une heure » autour des frontières DST.
  • Vérifiez la version du paquet tzdata (si présent) et comparez entre hôtes/conteneurs.
  • Pour les JVM, vérifiez user.timezone et si la JVM nécessite un redémarrage pour prendre en compte de nouvelles règles.

Blague #1 : Les fuseaux horaires sont la seule fonctionnalité où « ça marche sur ma machine » est une déclaration politique du parlement.

Tâches pratiques : commandes, sorties, décisions (12+)

Ce sont des tâches réelles que vous pouvez exécuter pendant un incident ou comme audit préventif. Chacune inclut : commande(s), sortie d’exemple et la décision que vous prenez.
Exécutez les commandes côté hôte sur le host Docker. Exécutez côté conteneur avec docker exec.

Task 1: Verify host time sync and time source

cr0x@server:~$ timedatectl status
               Local time: Sat 2026-01-03 11:40:12 UTC
           Universal time: Sat 2026-01-03 11:40:12 UTC
                 RTC time: Sat 2026-01-03 11:40:12
                Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

Ce que cela signifie : Si System clock synchronized est no ou si NTP est inactif, vous avez un problème d’hôte.

Décision : Réparez d’abord la synchronisation de l’hôte. Les correctifs côté conteneur ne serviront à rien si l’horloge noyau est erronée.

Task 2: Check host vs container “now” (UTC and local) in one go

cr0x@server:~$ date -u; date
Sat Jan  3 11:40:20 UTC 2026
Sat Jan  3 11:40:20 UTC 2026
cr0x@server:~$ docker exec web-1 date -u; docker exec web-1 date
Sat Jan  3 11:40:21 UTC 2026
Sat Jan  3 06:40:21 EST 2026

Ce que cela signifie : L’hôte est en UTC ; le conteneur affiche EST. Ce n’est pas une dérive ; c’est une différence de configuration.

Décision : Confirmez que c’est intentionnel. Sinon, standardisez : soit tout en UTC, soit tout en local, mais soyez cohérent.

Task 3: Inspect container environment for TZ overrides

cr0x@server:~$ docker exec web-1 env | grep -E '^TZ='
TZ=America/New_York

Ce que cela signifie : Quelque chose a défini explicitement TZ ; l’image de base peut rester en UTC.

Décision : Décidez si TZ est votre interface officielle. Si oui, appliquez-la dans les déploiements. Sinon, supprimez-la et montez /etc/localtime.

Task 4: Check whether /etc/localtime is present and what it is

cr0x@server:~$ docker exec web-1 ls -l /etc/localtime
lrwxrwxrwx 1 root root 36 Jan  3 10:00 /etc/localtime -> /usr/share/zoneinfo/Etc/UTC

Ce que cela signifie : C’est un lien symbolique vers la zone UTC.

Décision : Si vous avez besoin de l’heure locale, vous pouvez bind-monter /etc/localtime de l’hôte dans le conteneur (en lecture seule) sans reconstruire.

Task 5: Confirm zoneinfo exists in the container

cr0x@server:~$ docker exec web-1 ls -l /usr/share/zoneinfo/America/New_York
ls: cannot access '/usr/share/zoneinfo/America/New_York': No such file or directory

Ce que cela signifie : tzdata (fichiers zoneinfo) est manquant. Beaucoup d’images minimales font cela.

Décision : Préférez bind-monter le /usr/share/zoneinfo de l’hôte dans le conteneur, ou au moins montez le fichier unique /etc/localtime.

Task 6: Identify the base distro and libc expectations

cr0x@server:~$ docker exec web-1 cat /etc/os-release
PRETTY_NAME="Alpine Linux v3.19"
NAME="Alpine Linux"
VERSION_ID=3.19
ID=alpine

Ce que cela signifie : Alpine utilise musl ; le comportement du fuseau horaire peut différer de Debian/glibc, surtout lorsqu’il manque des fichiers.

Décision : Pour Alpine, monter /etc/localtime suffit généralement, mais les applis s’attendant à /etc/timezone peuvent quand même mal se comporter.

Task 7: Verify what the container thinks its timezone is (glibc/musl friendly)

cr0x@server:~$ docker exec web-1 sh -lc 'date; readlink -f /etc/localtime || true; ls -l /etc/timezone 2>/dev/null || true'
Sat Jan  3 06:41:02 EST 2026
/usr/share/zoneinfo/Etc/UTC

Ce que cela signifie : La sortie est incohérente : date affiche EST mais /etc/localtime pointe vers UTC ; cela suggère la variable TZ ou des réglages au niveau de l’appli.

Décision : Standardisez sur un seul mécanisme. Mélanger TZ et un /etc/localtime périmé rend le dépannage inutilement épicé.

Task 8: Detect DST-rule issues using zdump (if present)

cr0x@server:~$ docker exec web-1 sh -lc 'command -v zdump || echo "zdump missing"'
zdump missing

Ce que cela signifie : Image minimale ; vous n’avez pas les outils tzdata.

Décision : Utilisez les outils de l’hôte contre la zoneinfo montée, ou exécutez un conteneur de debug temporaire dans le même réseau/espace de noms.

Task 9: Use a debug container to inspect time behavior without touching the app image

cr0x@server:~$ docker run --rm --network container:web-1 alpine:3.19 sh -lc 'date; ls -l /etc/localtime; echo ${TZ:-no-TZ}'
Sat Jan  3 11:41:45 UTC 2026
lrwxrwxrwx    1 root     root            36 Jan  3 11:41 /etc/localtime -> /usr/share/zoneinfo/UTC
no-TZ

Ce que cela signifie : Le conteneur de debug montre UTC ; votre conteneur d’appli affiche EST. Ce n’est pas « l’hôte ». C’est la configuration de votre conteneur.

Décision : Corrigez la configuration de fuseau horaire du conteneur d’appli via des montages/variables d’environnement, pas les paramètres de l’hôte.

Task 10: Confirm the running container’s mounts (are we already mounting /etc/localtime?)

cr0x@server:~$ docker inspect web-1 --format '{{json .Mounts}}'
[{"Type":"bind","Source":"/etc/localtime","Destination":"/etc/localtime","Mode":"ro","RW":false,"Propagation":"rprivate"}]

Ce que cela signifie : Vous montez déjà /etc/localtime. Si le fuseau horaire est toujours incorrect, cherchez la variable TZ ou les réglages applicatifs.

Décision : Si les montages semblent corrects, cessez de bidouiller les montages et commencez à vérifier la configuration d’exécution de l’application.

Task 11: Check Docker image history for timezone “optimizations”

cr0x@server:~$ docker history --no-trunc myorg/web:prod | head
IMAGE          CREATED        CREATED BY                                      SIZE      COMMENT
a1b2c3d4e5f6   2 weeks ago    /bin/sh -c rm -rf /usr/share/zoneinfo ...       0B
...

Ce que cela signifie : Quelqu’un a retiré zoneinfo pour économiser quelques mégaoctets et s’est acheté un incident en retour.

Décision : Ne reconstruisez pas maintenant (vous avez dit que vous ne le feriez pas), mais planifiez une correction d’image future : conservez tzdata ou montez-le depuis l’hôte.

Task 12: Verify application-level timezone (example: JVM)

cr0x@server:~$ docker exec api-1 sh -lc 'java -XshowSettings:properties -version 2>&1 | grep -E "user.timezone|java.version"'
    java.version = 17.0.10
    user.timezone = UTC

Ce que cela signifie : La JVM est fixée en UTC indépendamment du /etc/localtime du conteneur.

Décision : Si vous avez vraiment besoin de l’heure locale (évitez si possible), définissez le fuseau horaire JVM via une variable d’environnement ou des arguments JVM à l’exécution. Sinon, conservez UTC et corrigez vos attentes.

Task 13: Verify database timezone setting (example: PostgreSQL)

cr0x@server:~$ docker exec pg-1 psql -U postgres -Atc "show timezone; select now();"
UTC
2026-01-03 11:42:33.123456+00

Ce que cela signifie : La base est en UTC. Si votre appli affiche l’heure locale, vous avez une conversion qui se produit ailleurs.

Décision : Choisissez un fuseau canonique pour le stockage (UTC) et convertissez uniquement aux frontières (UI/rapports). Si vous déviez, documentez-le comme un matériau dangereux.

Task 14: Prove whether the problem is “timezone” or “time changed”

cr0x@server:~$ docker exec web-1 sh -lc 'date +%s; sleep 2; date +%s'
1704282160
1704282162

Ce que cela signifie : Les secondes avancent normalement. Si vous aviez vu des sauts ou des retours en arrière, vous soupçonneriez des ajustements de l’horloge de l’hôte, pas des fichiers de fuseau.

Décision : Si les secondes epoch se comportent, concentrez-vous sur la configuration du fuseau horaire et le parsing/formatage. Si l’epoch saute, enquêtez sur la synchronisation de l’hôte et la virtualisation.

Correctifs sans reconstruire les images (ce qui fonctionne réellement)

Vous voulez des correctifs à l’exécution qui sont réversibles, audités et ne nécessitent pas une nouvelle build d’image. Bien. Voici les modèles qui fonctionnent en production, et les
avertissements qui vous empêchent de « corriger » en provoquant un autre incident.

Fix 1: Bind-mount the host’s /etc/localtime (the workhorse)

C’est le plus courant et généralement le plus propre. Il permet au conteneur d’interpréter l’heure locale de la même façon que l’hôte. Cela ne change pas l’horloge de l’hôte.
Il fournit simplement le fichier zoneinfo.

cr0x@server:~$ docker run -d --name web-fixed \
  -v /etc/localtime:/etc/localtime:ro \
  myorg/web:prod

Ce que vous obtenez : La libc du conteneur utilisera la configuration de fuseau de l’hôte.

Ce qui peut vous mordre : Si le fuseau horaire de l’hôte est modifié plus tard (ou incohérent entre hôtes), le comportement des conteneurs change aussi. C’est soit une fonctionnalité, soit une histoire d’horreur.

Opinion : C’est acceptable pour « tout tourne dans le même DC avec une configuration d’hôte cohérente ». C’est risqué sur des flottes hétérogènes.

Fix 2: Bind-mount /usr/share/zoneinfo (when tzdata is missing)

Si votre conteneur manque de fichiers zoneinfo, certaines applis ont besoin de plus que /etc/localtime. Elles chargent peut-être des noms régionaux directement. Monter le
répertoire zoneinfo complet est plus lourd, mais c’est prévisible et n’exige pas d’ajouter des paquets dans l’image.

cr0x@server:~$ docker run -d --name api-fixed \
  -v /etc/localtime:/etc/localtime:ro \
  -v /usr/share/zoneinfo:/usr/share/zoneinfo:ro \
  -e TZ=America/New_York \
  myorg/api:prod

Ce que la sortie signifie (validation) : vous devriez maintenant pouvoir résoudre des noms de région à l’intérieur du conteneur.

cr0x@server:~$ docker exec api-fixed ls -l /usr/share/zoneinfo/America/New_York
-rw-r--r-- 1 root root 3552 Oct 15  2025 /usr/share/zoneinfo/America/New_York

Décision : Utilisez ceci quand des applis exigent des zones nommées et que vous ne pouvez pas reconstruire les images cette semaine. Planifiez d’incorporer tzdata plus tard.

Fix 3: Set TZ as an environment variable (only if you know your stack honors it)

TZ peut fonctionner. Il peut aussi être ignoré. Ou partiellement respecté. Ou respecté par libc mais écrasé par le runtime. Vous devez tester votre pile spécifique.

cr0x@server:~$ docker run -d --name worker-fixed \
  -e TZ=Etc/UTC \
  myorg/worker:prod

Validation :

cr0x@server:~$ docker exec worker-fixed sh -lc 'echo $TZ; date'
Etc/UTC
Sat Jan  3 11:44:01 UTC 2026

Décision : Si votre flotte est hétérogène (Debian, Alpine, images très légères), préférez monter /etc/localtime plutôt que de compter sur TZ.

Fix 4: Mount /etc/timezone for Debian-ish expectations (sometimes necessary)

Certaines piles lisent /etc/timezone (un fichier texte) même quand /etc/localtime existe. Ce n’est pas universel, mais c’est assez courant.

cr0x@server:~$ printf "America/New_York\n" | sudo tee /srv/timezone-files/etc.timezone
America/New_York
cr0x@server:~$ docker run -d --name web-fixed2 \
  -v /etc/localtime:/etc/localtime:ro \
  -v /srv/timezone-files/etc.timezone:/etc/timezone:ro \
  myorg/web:prod

Décision : Si vous voyez des applis se comporter différemment de date à l’intérieur du conteneur, ajouter /etc/timezone est un correctif ciblé raisonnable.

Fix 5: Don’t change time zones; change your logging format instead

Si la douleur réelle est « les logs ne correspondent pas entre systèmes », la correction est souvent de logger en UTC de façon cohérente et d’inclure le décalage quand c’est nécessaire.
Changer le fuseau horaire des conteneurs pour satisfaire un visualiseur de logs, c’est comme repeindre votre voiture parce que la radio est trop forte.

Mouvement pratique : configurez votre bibliothèque de logs pour émettre des timestamps ISO-8601 avec décalage. C’est de la config d’appli, pas une reconstruction d’image. Pour beaucoup de systèmes, c’est
une variable d’environnement ou un config map.

Fix 6: Sidecar/exec patching (the “yes, but don’t” method)

Vous pouvez parfois docker exec et remplacer /etc/localtime à l’intérieur d’un conteneur en cours. C’est rapide. C’est aussi non-déclaratif et se perd au redémarrage.

cr0x@server:~$ docker exec -u 0 web-1 sh -lc 'cp /usr/share/zoneinfo/America/New_York /etc/localtime && date'
Sat Jan  3 06:45:10 EST 2026

Décision : Utilisez ceci uniquement pour la médecine légale à court terme. Puis implémentez un montage/variable d’environnement correct. Si vous « hotfixez » les fuseaux en place, vous oublierez et ce sera rebrisé lors du prochain déploiement.

Blague #2 : Si vous pensez « on standardisera le temps plus tard », félicitations — vous avez inventé l’heure d’été, mais pour les équipes d’ingénierie.

Docker Compose et Kubernetes : patrons

Docker Compose : déclarez les montages de fuseau horaire comme si vous le pensiez

Compose est l’endroit où la discipline sur les fuseaux horaires survit ou meurt. Déclarez les montages et l’environnement dans le YAML afin que chaque redémarrage reproduise le comportement.

cr0x@server:~$ cat docker-compose.yml
services:
  web:
    image: myorg/web:prod
    environment:
      - TZ=America/New_York
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /usr/share/zoneinfo:/usr/share/zoneinfo:ro

Décision : Si vous montez zoneinfo, vous pouvez définir en toute sécurité TZ sur une région nommée même si tzdata manque dans l’image.
Si vous ne montez pas zoneinfo, définir TZ peut silencieusement retomber sur un fallback ou casser selon libc/appli.

Kubernetes : vous pouvez monter des fichiers de fuseau horaire, mais choisissez la bonne abstraction

Dans Kubernetes, le pod partage l’horloge du noyau du nœud. La gestion du fuseau reste une histoire de système de fichiers et d’environnement.
Les deux options courantes :

  • Monter hostPath pour /etc/localtime (et éventuellement /usr/share/zoneinfo).
  • Utiliser UTC partout et ne plus se soucier de l’heure locale à l’intérieur des pods. C’est l’option qui vieillit bien.

Les montages hostPath sont tranchants opérationnellement. Ils couplent les pods à la disposition et à la politique du système de fichiers du nœud. Certains clusters les interdisent pour de bonnes raisons.
Si vous ne pouvez pas utiliser hostPath, vous pouvez empaqueter les fichiers timezone dans un ConfigMap pour un seul fichier de zone, mais c’est encombrant et demande de maintenir les règles.

Mon défaut opiniâtre pour Kubernetes : fonctionner en UTC. Convertissez dans l’UI/les rapports. Vos nœuds peuvent rester en UTC aussi. Tenez l’heure humaine hors du plan de données.

Erreurs courantes : symptômes → cause racine → correction

Voici la section qui vous évite de refaire la même erreur avec plus d’assurance.

1) Les logs montrent la bonne heure sur l’hôte, mauvaise heure dans le conteneur

Symptômes : date de l’hôte est correct ; date du conteneur affiche UTC ou une autre région.

Cause racine : Le conteneur a son propre /etc/localtime (souvent un lien vers UTC), ou l’absence de tzdata fait défaut et retourne UTC.

Correction : Bind-montez /etc/localtime en lecture seule. Si les applis exigent des zones nommées, montez aussi /usr/share/zoneinfo et définissez TZ.

2) Tout est décalé d’une heure exactement, uniquement autour du DST

Symptômes : L’heure correspond la plupart de l’année, puis se décale d’une heure autour d’une frontière DST.

Cause racine : Règles tzdata obsolètes dans le conteneur ou runtime. Ou ensembles de règles mixtes entre nœuds.

Correction : Utilisez zoneinfo monté depuis l’hôte pour centraliser les mises à jour de règles. Redémarrez les runtimes qui mettent en cache les règles (notamment les JVM).

3) Les horodatages d’appli sont corrects, ceux de la BD aussi, mais les rapports sont faux

Symptômes : Les données brutes semblent correctes ; les rapports destinés aux humains affichent « mauvais jour » ou « mauvaise heure ».

Cause racine : La couche de présentation convertit le fuseau deux fois ou utilise des abréviations ambiguës.

Correction : Standardisez le stockage en UTC, convertissez exactement une fois à la périphérie, loggez les offsets, et bannissez les abréviations « CST/IST ».

4) Vous définissez TZ, rien ne change

Symptômes : echo $TZ montre votre valeur ; date ou l’appli affiche toujours l’ancien fuseau.

Cause racine : L’application ignore TZ ; le runtime fixe le fuseau ; ou les fichiers de zone manquent et TZ ne peut pas résoudre le nom.

Correction : Montez /etc/localtime et zoneinfo ; configurez les réglages spécifiques au runtime (ex. JVM -Duser.timezone).

5) Les conteneurs diffèrent selon les nœuds dans le même cluster

Symptômes : Même image, mêmes vars d’env, heure locale différente selon le nœud.

Cause racine : Vous avez monté /etc/localtime de l’hôte, mais les nœuds ont des fuseaux différents ou des versions tzdata différentes.

Correction : Faites respecter l’UTC sur les nœuds (préféré) ou imposez un fuseau unique via la gestion de configuration. Ne laissez pas les « pets » configurer le temps.

6) « Corriger » le fuseau casse TLS, caches ou validation de tokens

Symptômes : Après un changement lié au temps, les tokens d’auth échouent, les caches expirent immédiatement, ou TLS se plaint de certificats non encore valides.

Cause racine : Vous avez changé l’horloge réelle (temps hôte), pas l’affichage du fuseau. Ou l’horloge a sauté à cause d’une correction NTP.

Correction : Restaurez la discipline de l’horloge hôte. Évitez les changements manuels d’horloge. Utilisez NTP/chrony correctement et surveillez les sauts de temps.

Listes de contrôle / plan pas-à-pas

Checklist A: Production incident triage (15 minutes)

  1. Confirmez la synchronisation du temps de l’hôte : timedatectl status. Si non synchronisé, corrigez l’hôte avant de toucher aux conteneurs.
  2. Comparez les secondes epoch de l’hôte et du conteneur : date +%s sur les deux. Si elles diffèrent, quelque chose ne va pas profondément (les conteneurs doivent correspondre à l’hôte).
  3. Vérifiez la sortie date et le fuseau du conteneur : cherchez UTC vs local et les offsets.
  4. Inspectez TZ et la cible de /etc/localtime. Évitez les signaux mélangés.
  5. Décidez de l’état désiré : UTC partout, ou une zone locale spécifique pour une raison métier.
  6. Appliquez un correctif à l’exécution de façon déclarative (Compose/K8s manifests), pas avec des hacks docker exec.
  7. Redémarrez uniquement ce qui doit l’être (les JVM peuvent nécessiter un redémarrage pour les changements de règles tzdata).

Checklist B: Standardizing a fleet (the boring, correct plan)

  1. Choisissez le fuseau canonique pour le stockage et les logs : UTC. Écrivez-le. Faites-en une règle plateforme.
  2. Assurez-vous que les hôtes tournent aussi en UTC. Cela réduit la « roulette hostPath fuseau horaire ».
  3. Pour les rares workloads qui ont vraiment besoin de l’heure locale, implémentez-le explicitement via des montages ou des flags d’exécution et isolez-les.
  4. Assurez-vous que les mises à jour tzdata font partie du patching OS sur les hôtes ; évitez un zoo de versions tzdata dans les images.
  5. Ajoutez un contrôle CI qui échoue les builds si quelqu’un supprime zoneinfo ou tzdata sans plan de remplacement.
  6. Ajoutez de la surveillance/alertes pour la dérive de synchronisation du temps sur les nœuds (santé chrony/NTP) et pour les grands sauts temporels.

Trois mini-récits d’entreprise depuis les tranchées des fuseaux horaires

Mini-récit 1 : L’incident causé par une mauvaise hypothèse

Un service lié à la finance exécutait une réconciliation nocturne et produisait un CSV pour ingestion en aval. Le service était conteneurisé, déployé sur un petit swarm d’hôtes Linux,
et « évidemment » utilisait l’heure locale du datacenter parce que les humains lisant le rapport étaient dans ce fuseau.

L’équipe a supposé que les conteneurs héritent du fuseau horaire de l’hôte. C’est une croyance courante car cela semble parfois vrai — surtout quand vous testez sur un laptop
où l’image de base correspond à votre zone locale ou que l’appli logge en UTC et que vous ne l’avez jamais remarqué.

Un hôte a été reconstruit pendant une maintenance routinière. La nouvelle installation OS a par défaut mis UTC. Les conteneurs ont été redémarrés, et le rapport a commencé à étiqueter « les transactions d’aujourd’hui »
en utilisant des dates UTC. Pendant quelques heures par jour, des transactions atterrissaient dans « demain », ce qui a déclenché une cascade silencieuse : tentatives de réingestion en double,
enregistrements manquants, et beaucoup d’humains faisant de l’archéologie de tableur.

Le correctif était embarrassant de simplicité : ils ont monté /etc/localtime dans le conteneur et rendu le fuseau horaire de l’hôte cohérent. La correction importante était culturelle :
ils ont rédigé une note plateforme indiquant que les conteneurs n’héritent pas du comportement de fuseau horaire de façon fiable, et ils ont standardisé tous les timestamps de stockage en UTC.

Mini-récit 2 : L’optimisation qui s’est retournée contre eux

Une équipe plateforme réduisait la taille des images. Ils avaient des graphiques, des objectifs, un tableur. Quelqu’un a remarqué que tzdata et zoneinfo étaient « volumineux et inutilisés »
dans la plupart des services. Un commit est arrivé qui supprimait /usr/share/zoneinfo lors de la construction d’images de base.

Rien n’a cassé immédiatement. C’était le piège. Beaucoup de services loggaient en UTC. Certains utilisaient des timestamps epoch en interne. Le nettoyage ressemblait à une victoire. L’équipe a même célébré
un pull plus rapide dans quelques environnements.

Des semaines plus tard, un workflow support client a commencé à planifier des rappels en utilisant des fuseaux locaux des profils utilisateurs. Le service utilisait des régions nommées (comme
America/Los_Angeles) pour calculer le prochain jour ouvrable. Avec zoneinfo manquant, il est tombé silencieusement en UTC dans un runtime et a levé des exceptions dans un autre.
Le résultat fut un mélange salissant de scheduling incorrect et de pannes partielles. Le compte rendu d’incident contenait la phrase « régression d’exigence non-fonctionnelle », qui est la façon corporative de dire « on s’est tiré une balle dans le pied en optimisant nos lacets ».

Le correctif pratique fut un montage runtime de zoneinfo de l’hôte à court terme (pas de rebuild), suivi d’un changement réfléchi de l’image de base : conserver tzdata dans l’image générale,
et ne le retirer que dans les images explicitement « UTC-only » avec des tests de contrat qui le prouvent.

Mini-récit 3 : La pratique ennuyeuse qui a sauvé la mise

Un service d’identité interne émettait des tokens à courte durée. Il tournait sur plusieurs régions. L’équipe avait une politique : tous les nœuds du parc tournent en UTC, et tous
les conteneurs montent /etc/localtime en lecture seule seulement quand c’est nécessaire. Pour la plupart des services, ils ne s’embêtaient pas ; l’appli était écrite et testée en UTC.

Pendant un incident d’hôte de virtualisation, plusieurs nœuds de calcul ont connu une instabilité NTP temporaire. L’horloge n’a pas dérivé beaucoup, mais elle a jitter suffisamment pour que
quelques services voient des échecs de validation de token en comparant « issued at » et « not before » trop strictement.

L’équipe identité avait déjà mis en place deux garde-fous ennuyeux : la surveillance de la santé chrony sur les nœuds, et une logique applicative qui tolérait de petits skews d’horloge.
Pendant l’incident, leurs logs étaient cohérents en UTC avec offsets. Ils ont pu corréler rapidement les événements entre services sans faire de calcul mental,
et prouver que le problème était la discipline de l’horloge, pas le formatage du fuseau.

Ils se sont rétablis rapidement en drainant les nœuds affectés et en restaurant la synchro temporelle. Pas de patch de fuseau horaire, pas de devinettes. Juste un comportement mesuré et une politique applicable.

Correctifs sans reconstruire les images (conseils approfondis que vous utiliserez vraiment)

Vous avez vu les correctifs de base plus tôt. Parlons maintenant de comment choisir entre eux sans créer un chaos à long terme.

Cadre de décision : UTC d’abord, heure locale uniquement par exception

Si vous exploitez des systèmes de production assez longtemps, vous cessez de traiter « heure locale » comme un défaut. Les humains vivent en heure locale. Les systèmes distribués vivent en UTC.
L’approche la plus sûre à long terme :

  • Stockez les timestamps en UTC dans les bases, queues et logs.
  • Incluez les offsets lors de la génération d’horodatages destinés aux humains.
  • Convertissez aux frontières (UI, génération de rapports, réponses API quand demandées).

Quand avez-vous besoin de l’heure locale dans le conteneur ? Typiquement :

  • Applications legacy qui lisent la « minuit locale » du système sans bibliothèques timezone-aware.
  • Planificateurs de type cron à l’intérieur des conteneurs (que vous devriez éviter, mais la réalité existe).
  • Binaries tiers que vous ne pouvez pas changer et qui supposent l’heure locale OS.

Modifications à l’exécution : déclaratif bat l’héroïque

Un correctif de fuseau horaire doit survivre aux redémarrages. Cela signifie qu’il appartient à :

  • YAML Compose
  • overrides systemd des unités lançant docker run
  • manifests Kubernetes / valeurs Helm

Pas dans l’historique shell de quelqu’un.

Stratégie de montage : fichier unique vs zoneinfo complète

Monter /etc/localtime est minimal et fonctionne pour les conversions « heure locale » basées sur libc. Monter /usr/share/zoneinfo permet les noms de région.
Si vous définissez TZ=America/New_York, assurez-vous que le conteneur peut résoudre ce fichier.

Il y a aussi un angle de cohérence subtil : monter les deux garantit que /etc/localtime et le fichier de zone référencé proviennent de la même version tzdata.
Mélanger les versions peut créer des comportements bizarres où « les règles DST se contredisent » dans certaines piles.

Et pour « changer le fuseau horaire » d’un conteneur en cours ?

Si vous devez le faire sans redéployer (fenêtre d’incident serrée), vous pouvez :

  • Attacher un volume au prochain redémarrage (meilleur).
  • Ou patcher en place avec docker exec (rapide, non-déclaratif).

Si vous patchez en place, considérez-le comme un pansement temporaire. Vous avez toujours besoin du vrai correctif dans votre configuration de déploiement.

FAQ

1) Les conteneurs peuvent-ils avoir des fuseaux horaires différents de l’hôte ?

Oui. Ils partagent l’horloge du noyau hôte, mais le fuseau horaire concerne principalement la configuration du système de fichiers et l’environnement. Deux conteneurs sur le même hôte peuvent afficher des heures locales différentes.

2) Si je monte /etc/localtime, est-ce que cela corrigera la « dérive » ?

Cela corrige la discordance de fuseau horaire, pas la dérive de l’horloge. Si l’heure de l’hôte est fausse, les montages n’aideront pas. Vérifiez d’abord la synchronisation de l’hôte.

3) Définir TZ suffit-il ?

Parfois. Cela dépend de libc, du runtime et de la présence des fichiers zoneinfo. Si vous définissez une région nommée, assurez-vous que /usr/share/zoneinfo est présent ou monté.

4) Pourquoi je vois UTC même après avoir défini TZ ?

Soit l’appli ignore TZ, soit elle ne peut pas résoudre le nom de zone à cause d’un tzdata manquant. Vérifiez /usr/share/zoneinfo et les réglages spécifiques à l’appli.

5) Quel est le comportement par défaut le plus sûr pour la production ?

UTC partout pour le stockage et les logs. Convertissez aux frontières. Cela réduit la confusion multi-région et élimine le mode panne « quel serveur est dans quel fuseau ».

6) Dois-je redémarrer le conteneur après avoir changé les montages de fuseau horaire ?

Oui, car les montages sont définis au démarrage du conteneur. Certains runtimes mettent aussi en cache les règles de fuseau ; les JVM nécessitent souvent un redémarrage pour prendre en compte les changements tzdata de façon fiable.

7) Comment gérer DST en toute sécurité ?

Maintenez tzdata à jour (de préférence via l’hôte), utilisez des noms de région (America/New_York), et n’exécutez pas de jobs critiques à des heures ambiguës comme 02:30 lors des passages DST.

8) Et si mon cluster Kubernetes interdit les montages hostPath ?

Alors orientez-vous vers UTC. Si vous avez vraiment besoin d’une zone locale, vous pouvez fournir le fichier zoneinfo nécessaire via un ConfigMap, mais vous devenez responsable de la mise à jour des règles.

9) Monter /usr/share/zoneinfo crée-t-il des problèmes de sécurité ?

Les montages en lecture seule présentent un faible risque, mais hostPath reste un point de couplage. Dans des clusters durcis, il peut être interdit. Sur des hôtes Docker que vous contrôlez, c’est couramment acceptable.

10) Comment prouver que le problème est applicatif et non système ?

Comparez la sortie date avec les horodatages applicatifs, et vérifiez les réglages d’exécution (JVM user.timezone, BD show timezone).
Si le temps OS est correct et que l’appli ne l’est pas, c’est un problème de configuration appli/runtime.

Conclusion : prochaines étapes qui ne vous hanteront pas

La dérive de fuseau horaire dans les conteneurs n’est rarement mystique. C’est généralement l’une des trois choses : tzdata manquant, signaux de fuseau contradictoires (TZ vs
/etc/localtime), ou une appli/runtime qui fait sa propre chose.

Étapes pratiques suivantes :

  1. Décidez votre standard : UTC pour le stockage/logging. Heure locale uniquement si une exigence métier l’impose.
  2. Auditez vos conteneurs : vérifiez /etc/localtime, TZ, et la présence de zoneinfo en utilisant les tâches ci-dessus.
  3. Implémentez des correctifs déclaratifs : Compose/K8s config avec montages /etc/localtime (et optionnellement /usr/share/zoneinfo).
  4. Prévenez les régressions : arrêtez de supprimer tzdata « pour optimiser » sauf si vous avez des tests prouvant que vous n’en avez pas besoin.
  5. Surveillez l’horloge hôte : la santé de la synchronisation temporelle est une dépendance de production. Traitez-la comme l’espace disque : ennuyeuse, essentielle, toujours en voie d’échec.
← Précédent
Blocages de politique 550 5.7.1 — la voie réaliste de correction
Suivant →
ATI avant AMD : l’autre école de l’ingénierie graphique

Laisser un commentaire