Certaines pannes se signalent par une erreur, un journal, une alerte. D'autres ne se signalent pas du tout : quelque chose ne se produit simplement plus, et personne ne s'en aperçoit parce qu'aucun système ne surveille les non-événements. Les tâches planifiées appartiennent à cette seconde catégorie, et elles y appartiennent particulièrement bien lorsqu'elles tournent dans un conteneur.
Le symptôme : rien
La découverte se fait toujours de biais. Un rapport hebdomadaire dont personne n'a signalé l'absence. Un cache qui aurait dû être régénéré. Une file d'attente qui grossit lentement. Une donnée périmée que quelqu'un finit par remarquer.
La chaîne de vérification commence alors — et donne des résultats rassurants à chaque étape :
- Le conteneur du planificateur est démarré, avec plusieurs semaines d'ancienneté.
- Ses journaux ne contiennent aucune erreur.
- La commande sous-jacente, exécutée manuellement, fonctionne parfaitement.
- La déclaration de la tâche dans le code est correcte.
Tout est vert, et pourtant rien ne s'exécute. C'est cette contradiction apparente qui rend la panne longue à identifier.
La cause : l'image héritée
Le mécanisme est presque toujours le même. Le service applicatif est construit à partir d'une image contenant le code et un serveur web. Cette image déclare une commande par défaut : démarrer le serveur.
Vient ensuite le besoin d'un planificateur. Le réflexe naturel — et correct dans son principe — consiste à réutiliser la même image : même code, même configuration, mêmes variables d'environnement, aucune duplication.
Un service supplémentaire est donc déclaré, pointant sur cette image. Mais si la commande à exécuter n'est pas explicitement remplacée, le nouveau conteneur hérite de la commande par défaut de l'image. Il démarre un second serveur web, qui écoute sur un port que personne n'interroge.
Du point de vue de l'orchestrateur, ce conteneur est en parfaite santé : son processus principal tourne, il ne redémarre pas, il ne consomme presque rien. Il fait exactement ce pour quoi il a été démarré. Ce n'est simplement pas ce qu'on attendait de lui.
Le conteneur ne ment pas. Il exécute fidèlement une commande — mais personne n'a vérifié laquelle.
Pourquoi l'absence de journaux n'alerte personne
Un deuxième facteur prolonge considérablement la durée de la panne : un planificateur correctement configuré est silencieux.
La boucle du planificateur se réveille chaque minute, examine les tâches déclarées, constate qu'aucune n'est due, et se rendort. Elle ne journalise rien, parce qu'il n'y a rien à signaler. Sur une application dont les tâches sont quotidiennes, l'écrasante majorité des passages ne produit aucune trace.
Conséquence : des journaux vides sont le comportement normal. Ils le sont aussi lorsque le planificateur ne tourne pas du tout. Les deux situations produisent le même signal, c'est-à-dire aucun.
Vérifier ce qui tourne réellement
Une seule commande tranche : afficher la commande effective des conteneurs, et non leur statut.
docker ps --format '{{.Names}}\t{{.Command}}\t{{.Status}}'
Le résultat est sans appel. Un conteneur nommé « scheduler » dont la commande démarre un serveur web ne planifie rien, quel que soit son statut affiché.
La correction consiste à imposer explicitement la commande dans la déclaration du service :
scheduler:
image: mon-application:latest
command: php artisan schedule:work
restart: unless-stopped
La commande utilisée ici maintient un processus vivant qui déclenche les tâches dues chaque minute. C'est la forme adaptée à un conteneur, contrairement à la variante destinée à être invoquée par une tâche cron externe : cette dernière s'exécute une fois puis rend la main, ce qui provoquerait l'arrêt immédiat du conteneur.
Les variantes du même piège
La commande héritée est le cas le plus courant, mais trois autres configurations produisent une panne identique.
Le fuseau horaire
Le conteneur fonctionne en temps universel tandis que les tâches sont pensées en heure locale. Elles s'exécutent, mais décalées d'une ou deux heures. Un rapport quotidien attendu à huit heures arrive à six, ou à dix — assez pour paraître absent à celui qui le guette.
La mise à l'échelle involontaire
Le planificateur partage sa déclaration avec le service applicatif, lui-même répliqué. Chaque réplique exécute alors le planificateur, et chaque tâche s'exécute autant de fois qu'il y a d'instances. Le symptôme est inverse du précédent — des doublons plutôt qu'un silence — mais la cause est la même : personne n'a vérifié quel processus tournait où.
Le déploiement partiel
Le script de déploiement ne reconstruit que le service applicatif. Le conteneur du planificateur continue de tourner avec une ancienne version du code, éventuellement une version où la tâche n'existait pas encore. Le symptôme est déroutant : la tâche est bien dans le dépôt, mais pas dans le conteneur qui devrait l'exécuter.
Superviser des non-événements
La leçon de fond dépasse le cadre des tâches planifiées : on ne supervise pas une tâche périodique en observant le processus qui la déclenche, mais en observant son effet.
La méthode qui fonctionne est le signal de vie : la tâche, à la fin de son exécution réussie, notifie un service externe. Ce service attend ce signal dans une fenêtre de temps donnée et alerte s'il n'arrive pas.
Ce renversement change tout. Le silence cesse d'être ambigu : il devient lui-même l'alerte. La supervision ne dépend plus de l'état d'un conteneur — qui peut être en bonne santé tout en étant inutile — mais de la réalisation effective du travail attendu.
Le coût est dérisoire : quelques lignes à la fin de la tâche, et un service de surveillance de tâches périodiques, gratuit dans la plupart des offres. Le bénéfice est de ne plus découvrir une panne trois semaines après son début, par hasard, parce qu'un rapport manquait.
Une liste de vérification
Après toute modification touchant au planificateur, quatre points valent d'être contrôlés :
- La commande effective du conteneur est bien celle du planificateur, pas celle héritée de l'image.
- Les tâches déclarées apparaissent dans la liste des tâches enregistrées, avec une prochaine échéance cohérente.
- Le fuseau horaire du conteneur correspond à celui dans lequel les horaires ont été pensés.
- Une seule instance exécute le planificateur.
Quatre vérifications, deux minutes. À comparer au coût d'une tâche critique arrêtée pendant un mois sans que rien ne le signale.