Comment contrôler le temps dans un container Docker

Thiery Michel
Thiery MichelApril 15, 2021
#testing#tutorial

Les tests automatisés dépendant de la date sont instables. Pour remédier à ce problème, on peut les exécuter dans un container dont on fixe la date. Mais est-ce que ce pouvoir n'est pas trop dangereux ?

Problème : Comment stabiliser des tests sensibles à la date ?

Sur un projet client, j'ai ajouté une fonctionnalité mettant en évidence les items dont c'était le dernier jour de disponibilité.

Problème: Il existait des tests e2e qui testaient la réponse de l'api.

expect(json).toEqual(program);

Avec cette nouvelle fonctionnalité, les tests ne dépendaient plus uniquement des fixtures en entrée. Désormais, le résultat du test changeait en fonction de la date courante.

Une solution était de générer des fixtures à date variable. Mais cela demandait beaucoup de modifications, notamment pour garder des dates réalistes dans des tables liées. J'ai donc cherché une solution moins coûteuse.

Et si, au lieu de changer les fixtures, on changeait la date du serveur ?

On ne peut pas changer la date d'un container Docker

Sur ce projet, nos tests tournent dans Docker. Si je change la date du systéme dans le container docker, je peux fixer la date du test, et son résultat ne dépendra plus de la date courante.

Super simple : je change la date dans le container via une commande bash.

docker exec my_node_container bash 'date -s \'1985-10-25 12:00:00\''

Sauf que non, cette opération n'est pas permise :

date: cannot set date: Operation not permitted

Il s'avère que Docker ne permet pas de changer la date, et ce pour une trés bonne raison : il utilise la même horloge que le noyau externe du système hôte.

Comment faire alors ?

Mocker la date d'un container avec libfaketime

Heureusement, il existe un projet appelé libfaketime. Cette librairie intercepte les appels systèmes censés récupérer la date, et leur injecte la valeur désirée. En installant libfaketime dans l'image Docker, il devient donc possible de contrôler le temps.

libfaketime s'installe comme ceci:

git clone https://github.com/wolfcw/libfaketime.git
cd /libfaketime/src
make install

Dans un dockerfile, cela devient:

# feel free to replace with the image you want
FROM node:15.14

WORKDIR /

# install git
RUN apt-get update && \
    apt-get upgrade -y && \
    apt-get install -y git

# install libfaketime
RUN git clone https://github.com/wolfcw/libfaketime.git
WORKDIR /libfaketime/src
RUN make install

# do other things as needed

Remarque: Il est également possible d'utiliser un build multistage pour que seul libfaketime soit installé dans l'image final.

Je peux ensuite contruire l'image en lui donnant un nom - ici, faketime_node :

docker build . -t faketime_node

Définir une fausse date système via des variables d'environnement

Maintenant que je dispose d'une image contenant libfaketime, comment l'utiliser pour changer la date ?

Il suffit d'ajouter deux variables d'environnement:

  • LD_PRELOAD=/usr/local/lib/faketime/libfaketime.so.1

LD_PRELOAD permet de précharger une librairie avant toutes les autres y compris les librairies système. Ce qui permet de charger libfaketime en tout premier, et ainsi lui permettre de modifier le temps.

  • FAKETIME="1985-10-25 12:00:00"

FAKETIME permet de configurer le temps. Cette variable d'environnement accepte une date au format "YYYY-MM-DD hh:mm:ss". Elle accepte également une date relative sous la forme d'un chiffre suivi d'une unité. -15d pour il y a 15 jours par exemple.

Il suffit donc de lancer le serveur testé avec ces deux variables d'environnement, pour l'envoyer dans une autre époque (façon de parler) :

docker run -d \
-e LD_PRELOAD='/usr/local/lib/faketime/libfaketime.so.1' \
-e FAKETIME='1985-10-25 12:00:00' \
faketime_node

On peut aussi utiliser docker-compose :

version: '3.5'

services:
    node:
        build: ./dockerfile/directory
        environment:
            - LD_PRELOAD=/usr/local/lib/faketime/libfaketime.so.1
            - FAKETIME='2021-03-01 12:00:00'
        command: 'your command'

Relancer le temps

Sauf qu'à ce stade, je suis tombé sur un problème pour le moins gênant. Le serveur Node ne démarrait plus. La commande se lançait bien, mais ensuite plus rien.

J'ai débuggé: le temps était bien modifié. Je pouvais afficher la date, qui était bien celle attendue. Mais lorsque je démarrais le serveur Node, plus rien.

A ce stade, j'ai bien failli abandonner. Et c'est alors que j'ai réalisé. Je n'étais pas simplement retourné dans le passé.

J'avais stoppé le temps.

oups

En effet pour lancer le serveur, Node a besoin de réaliser des opérations asynchrones. Des opérations qui utilisent la boucle d'événement. Il ajoute des événements à la boucle, pour y retourner plus tard, c'est à dire au process.nextTick(). Sauf que si le temps du système est gelé, le nextTick ne se déclenche jamais.

Avec une date fixée par libfaketime, pas une seule milliseconde ne s'écoule. Donc Node attend que le temps s'écoule ... indéfiniment ...

Fort heureusement, libfaketime permet de changer la date, tout en laissant le temps s'écouler. Il suffit de préfixer FAKETIME avec un @.

FAKETIME='@2021-03-01 12:00:00'

Et maintenant tout fonctionne. On peut régler la date du container et avoir des tests stables... du moins en ce qui concerne la date.

En résumé

Il est possible de changer la date dans un container Docker en utilisant libfaketime.

Pour cela, créer un dockerfile semblabe à :

# feel free to replace with the image you want
FROM node:15.14

WORKDIR /

# install git
RUN apt-get update && \
    apt-get upgrade -y && \
    apt-get install -y git

# install libfaketime
RUN git clone https://github.com/wolfcw/libfaketime.git
WORKDIR /libfaketime/src
RUN make install

# do other things as needed

Puis passer ces deux variables d'environment au moment de lancer l'image:

  • LD_PRELOAD='/usr/local/lib/faketime/libfaketime.so.1'
  • FAKETIME='@YYYY-MM-DD hh:mm:ss' (complétez avec la date voulue). Le @ est important pour laisser le temps s'écouler.
Did you like this article? Share it!