Aller au contenu

Blog ·

Mis à jour à trois heures du matin, exprès

La mise à jour automatique de l’agent est active à 03:00. Restait à décider quelles situations consomment un créneau, et une nuit à deux fois trois heures.

Par Provibr

L’agent qui tourne dans le réseau propre d’un client reçoit une nouvelle compilation tous les quelques jours. Jusqu’à cette manche, le mettre à jour était un bouton que quelqu’un devait presser, ce qui veut dire qu’en pratique cela se produisait quand quelqu’un pensait à regarder. Le souhait tenait en une phrase : chacun peut fixer sur son agent un horaire de mise à jour, plusieurs moments par jour, actif par défaut à trois heures du matin.

La phrase est facile. Ce qui en a fait une manche, c’est qu’un redémarrage planifié d’infrastructure a de nombreuses façons d’être discrètement faux, et que presque toutes ont l’air correctes de l’extérieur.

La valeur par défaut est sur la colonne, et c’est là tout le souhait

« Actif par défaut pour tout le monde » se lit de deux façons. Écrit dans le code applicatif, cela vaut pour les agents créés à partir de maintenant, ce qui exclut précisément les clients qui en ont déjà un. Écrit comme valeur par défaut de colonne dans la migration, cela vaut pour chaque ligne existante en une seule instruction.

C’est donc une valeur par défaut de colonne : un booléen qui démarre à vrai et un tableau JSON qui démarre avec une seule entrée 03:00. Mesuré après l’application de la migration sur la base de développement : chaque agent existant actif, chacun à trois heures, et un agent fraîchement créé auquel aucune des deux valeurs n’avait été transmise a reçu le même état. Cette dernière vérification est celle qui prouve que c’est la valeur par défaut qui travaille et non un script d’amorçage.

Les moments sont des heures de pendule dans le fuseau de la plateforme, pas en UTC. Il n’existe pas de fuseau par organisation et le panneau affiche tout dans ce seul fuseau, un horaire stocké en UTC dériverait donc d’une heure en été, et le client qui a saisi trois heures serait interrompu à cinq.

Deux couches sur une même expression régulière

Un moment est HH:MM sur une grille de cinq minutes, et ce motif existe deux fois : une fois dans le validateur de schéma, qui refuse un champ de formulaire avec un message lisible, et une fois comme contrainte de base, qui refuse tout ce qui n’est jamais passé par ce formulaire.

Une contrainte de vérification ne peut pas contenir de sous-requête, « chaque élément de ce tableau est un moment valide » ne peut donc pas être un test d’existence sur le tableau déplié. Ce qui est possible, c’est un prédicat de chemin JSON qui sélectionne précisément les éléments faux, enveloppé dans un test disant que cette sélection est vide.

La première version de ce prédicat était subtilement inutile, et elle avait l’air complète :

text
$[*] ? ( !(@ like_regex "^([01][0-9]|2[0-3]):(00|05|10|15|20|25|30|35|40|45|50|55)$") )

La correspondance d’une expression régulière avec quelque chose qui n’est pas une chaîne n’est pas fausse en chemin JSON. Elle est Unknown. Et la négation d’Unknown est également Unknown, et un filtre ne garde que les éléments pour lesquels le prédicat est vraiment vrai. La sélection des éléments faux revenait donc vide pour un tableau de nombres, et la contrainte l’acceptait. Mesuré contre PostgreSQL 17.10, [3], [null] et [{"a":1}] sont passés tous les trois.

La correction est une clause en amont : les éléments dont le type n’est pas une chaîne sont faux par définition. Les trois sont désormais refusés. C’est exactement la classe d’erreur qui continue de nous coûter des manches : une garde qui n’attrape rien et qui a l’air complète, et aucun test qui échoue, parce qu’il n’y avait rien contre quoi échouer.

Cinq étapes, et l’ordre est la manche

Chaque minute, un travailleur demande qui a un horaire, remet les lignes à un planificateur pur qui décide quel créneau est dû, puis parcourt cinq étapes par agent. La réservation est l’étape quatre et non la première, et chaque étape antérieure est là pour une raison propre.

ÉtapeSi la réponse est non
Y a-t-il une compilation plus récente ?Le créneau est quand même réservé, marqué comme rien à faire
L’agent est-il connecté ?Rien n’est réservé, et la minute suivante réessaie
Une mise à jour est-elle déjà en cours ?Rien n’est réservé ; la première honorera le créneau
Réserver le créneauUn index unique tranche ; le perdant n’envoie rien
Envoyer la commandeLa même fonction que le bouton du panneau

Les deux premières lignes sont des contraires assumés, et cette asymétrie est la décision la plus intéressante de la manche.

Rien à faire consomme le créneau, parce que la réponse ne peut plus changer à l’intérieur d’un créneau : le manifeste de publication sur le disque est le même pour toute la fenêtre, rejuger chaque minute coûte donc des requêtes et n’apporte rien. Et cela donne au client quelque chose à voir. Une page d’agent qui dit « 03:00, déjà à jour » se distingue d’un horaire qui ne tourne pas. Une liste vide, non.

Être hors ligne ne consomme rien, parce qu’un créneau réservé pendant que l’hôte redémarre est consommé pour toujours, et ce client rate alors une nuit en silence. La minute suivante réessaie, jusqu’à ce que la fenêtre de rattrapage se ferme ; ensuite l’agent prend simplement son créneau suivant.

Quinze minutes, et non six heures

Les sauvegardes ont dans cette plateforme une fenêtre de rattrapage de six heures. Celle-ci est de quinze minutes, et la différence tient à ce qui se passe mal quand on est généreux.

Une sauvegarde faite à neuf heures du matin plutôt qu’à trois reste une sauvegarde. Un agent qui redémarre à neuf heures du matin plutôt qu’à trois est une interruption en pleine journée de travail, et trois heures du matin, c’est précisément ce que le client a choisi pour l’éviter. Quinze minutes couvrent ce à quoi cette fenêtre sert vraiment, une passerelle qui vient de redémarrer ou deux ou trois manches sautées, et rien d’autre.

Le planificateur regarde deux jours calendaires en arrière et non un, et ce n’est pas une étourderie. Un moment de 23:55 avec fenêtre de rattrapage tombe, juste après minuit, dans le jour calendaire d’hier ; en ne regardant qu’aujourd’hui, ce créneau ne viendrait jamais par construction.

Deux freins, pour deux raisons différentes

Cinq agents par organisation et par tour, et vingt-cinq au total.

Le frein par organisation concerne la bande passante du client et non la nôtre. Le binaire fait environ vingt-deux mégaoctets, un hébergeur avec quarante agents les a d’ordinaire tous les quarante à 03:00 puisque c’est la valeur par défaut, et quarante téléchargements simultanés sortent par le même lien. Cinq par minute étalent une flotte de quarante sur huit minutes, largement dans la fenêtre de quinze.

Le frein global existe parce que le premier agit par organisation : avec cent organisations, cela ferait cinq cents mises à jour en une minute. Le reste arrive à la minute suivante, toujours dans la fenêtre.

Il n’y a aucun drapeau de déploiement sur tout ceci, ce qui est inhabituel chez nous. L’interrupteur est déjà chez le client, sur la colonne ; un second drapeau, à l’échelle de la plateforme, promettrait ce que la colonne promet déjà. Ce qui existe, c’est un frein d’urgence pour nous : mettre l’intervalle d’interrogation à zéro arrête la minuterie sans changer un seul réglage de client.

La nuit aux deux trois heures

Le dernier dimanche d’octobre, l’heure de pendule 03:00 existe deux fois, et le dernier dimanche de mars, 02:30 n’existe pas du tout. Les deux sont traitées par une même fonction auxiliaire partagée, non recopiée dans ce module, précisément parce qu’un algorithme d’heure d’été entretenu à deux endroits diverge un jour par an.

Cette fonction répond à une heure de pendule ambiguë par le premier des deux instants, et à une heure inexistante par l’instant suivant qui, lui, existe. Les conséquences sont exactement celles que l’on veut. Lors de la nuit longue, 03:00 tourne une fois et non deux ; mesuré : un créneau. Lors de la nuit courte, 02:30 devient simplement 03:30 locales au lieu d’être sauté, car ne pas sauter est le bon côté de cette pièce. Les deux sont fixés par des tests.

Les jours sont parcourus comme des jours calendaires et non en ajoutant un nombre fixe de millisecondes : ajouter vingt-quatre heures à travers un changement saute un jour ou en fait un deux fois.

À qui on le dit, et à quelle fréquence

Le rapprochement qui rapporte les issues vit dans le travailleur et non sur la voie d’envoi, et seul l’horaire prévient. Un clic à la main avait déjà quelqu’un qui regardait.

Les avis sont regroupés par organisation, car quarante agents le jour d’une publication ne doivent pas devenir quarante lignes dans une boîte. Un échec est signalé au plus une fois par agent et par jour calendaire. Et l’horodatage qui enregistre qu’un avis a été envisagé est écrit même lorsque la décision a été de se taire, afin que le silence se distingue d’un travailleur qui n’est jamais arrivé.

Une mise à jour planifiée n’apparaît volontairement pas comme opération racine dans le tiroir des tâches : à trois heures du matin personne n’attend, et un tiroir plein de lignes nocturnes est inutile pour ce à quoi il sert. La trace est une ligne d’exécution, un événement et un avis. Cet événement a une sorte à lui, car la chronologie ne doit pas prétendre que le client a lui-même lancé quelque chose à trois heures.

Ce que nous noterions

  1. « Par défaut pour tout le monde » est une valeur par défaut de colonne. Dans le code applicatif, cela signifie « pour tout le monde à partir de maintenant », ce qui exclut précisément les gens auxquels vous pensiez.
  2. Unknown n’est pas faux. En chemin JSON, un prédicat sur une valeur du mauvais type n’est ni vrai ni faux, et le nier n’aide pas. Vérifiez d’abord le type.
  3. Décidez pour chaque situation si elle consomme le créneau. Rien à faire et injoignable sont des réponses opposées, et les deux sont invisibles si on les échange.
  4. Une fenêtre de rattrapage est une promesse sur l’interruption, pas sur la complétude. Reprenez le nombre d’un système au comportement de panne différent et vous aurez le mauvais.
  5. Freinez là où cela se ressent vraiment. Quarante agents dans un réseau de client partagent un lien, et c’est là la limite, pas notre route de téléchargement.
  6. Résolvez l’heure d’été une seule fois. Deux copies du même raisonnement sont d’accord jusqu’au dernier dimanche d’octobre.

Pour être clair sur ce dont il s’agit : l’horaire est en production et actif par défaut. Plusieurs choses ne sont pas prouvées. Le travailleur n’a pas été exercé dans une passerelle réellement en marche, aucun agent réel n’a encore exécuté de mise à jour planifiée, l’avis d’échec et sa déduplication quotidienne n’ont pas été observés de bout en bout, et l’écran a été relu dans deux des neuf langues.

Issu du travail de plateforme de Provibr. Pourquoi un agent refuse une mise à jour qu’il ne peut pas vérifier est expliqué dans quand une mise à jour signée se refuse, et pourquoi l’agent se connecte vers l’extérieur au lieu d’écouter est expliqué dans un agent sortant plutôt qu’une redirection de ports.

Commencez avec un hôte et un agent

Créez un compte, installez l’agent avec une seule commande et connectez votre premier hôte Proxmox. La période d’essai dure 14 jours et ne demande aucune coordonnée de paiement.