[infra] Le job silver n'est planifié nulle part, et ne pourrait pas l'être en l'état #136

Closed
opened 2026-09-04 07:51:18 +00:00 by lenaic · 3 comments
Owner

Exigence couverte

ENF-08, EF-04

Épreuve servie

EC04 · Cloud et sécurisation

Charge estimée

0,5 j.h

Ce qu'on veut obtenir

Le job de la zone argent n'est appelé par rien, et une ligne de crontab posée aujourd'hui échouerait à chaque passage. Trois obstacles, dont deux invisibles tant qu'on ne cherche pas.

1. Le lanceur ignore le python du venv. services/etl/bin/silver-daily.sh fait exec python3, là où collecte-current.sh fait exec "${ENERVISION_PYTHON:-python3}". Sur le serveur :

$ python3 -c "import duckdb"
ModuleNotFoundError: No module named 'duckdb'

C'est le mode de panne relevé par Olivier en relecture de la #114 pour le collecteur, reproduit à l'identique dans le lanceur de l'ETL : la tâche sort en erreur chaque heure sans rien dire d'utile.

2. La tâche Ansible code le service en dur. « Planifier les relèves que le dépôt fournit » construit son chemin avec services/collector/bin/{{ item.script }}, et la tâche de stat qui la précède fait pareil. Le lanceur silver vit dans services/etl/bin/. On ne peut donc pas l'ajouter à collector_taches en l'état. Ce chemin était juste tant qu'il n'y avait que des collecteurs, il ne l'est plus.

3. Aucune planification n'existe. Vérifié sur le serveur : crontabs de root, deploy et lenaic, /etc/crontab, /etc/cron.d, /etc/cron.hourly, /etc/cron.daily, minuteries systemd et unités nommées. Seules les deux lignes du collecteur existent. Rien pour l'ETL.

Conséquence directe : le seau silver est à 0 objet alors que le code du #34 est fusionné et déployé, et que bronze contient 9090 objets.

Critères d'acceptation

  • silver-daily.sh honore ENERVISION_PYTHON comme les lanceurs du collecteur, et sort en erreur explicite si l'interpréteur n'a pas ses dépendances plutôt que de lever une trace brute.
  • La liste des tâches planifiées porte le service de chaque lanceur au lieu de le supposer, et la variable est renommée : elle ne décrit plus le seul collecteur.
  • La ligne silver est posée sous deploy avec ENERVISION_RACINE et le python du venv, sa sortie dans {{ collector_log_dir }}/silver.log.
  • Un second déploiement ne duplique aucune ligne : les trois tâches restent trois.
  • Le banc d'essai du rôle, tests/ci/test-role-app.yml, gagne un contrôle qui échoue si le chemin du service redevient codé en dur.
  • Après une passe planifiée, silver contient la partition du jour.

Comment on le vérifie

Commande sudo crontab -u deploy -l
Attendu trois lignes, dont silver, avec ENERVISION_PYTHON renseigné
Commande mc ls --recursive ev/silver/
Attendu les trois tables Parquet de la partition du jour
Commande le déploiement rejoué une seconde fois
Attendu aucune ligne dupliquée, aucune tâche en changed sur la planification

Manuel d'exploitation à mettre à jour

docs/runbooks/etl.md s'il existe, sinon la section ETL du manuel d'exploitation : où va le journal, comment rejouer une journée à la main, et comment lire un refus du garde-fou sans le confondre avec une panne.

Risque et retour arrière

Risque faible. La modification touche la planification et un lanceur, pas la transformation elle-même. Le pire cas est une tâche horaire qui échoue et remplit silver.log, déjà couvert par la rotation *.log en place.

Retour arrière : passer l'entrée silver à state: absent dans la liste des tâches et rejouer le rôle retire la ligne, sans toucher aux deux du collecteur.

Dépendance : l'horaire retenu dépend du #135. Tant qu'il n'est pas corrigé, une tâche horaire sur la journée courante refusera avant 17 h. On peut poser la planification quand même, la sortie du job est explicite, mais le journal portera des refus qu'il ne faudra pas lire comme des pannes.

### Exigence couverte ENF-08, EF-04 ### Épreuve servie EC04 · Cloud et sécurisation ### Charge estimée 0,5 j.h ### Ce qu'on veut obtenir Le job de la zone argent n'est appelé par rien, et une ligne de crontab posée aujourd'hui échouerait à chaque passage. Trois obstacles, dont deux invisibles tant qu'on ne cherche pas. **1. Le lanceur ignore le python du venv.** `services/etl/bin/silver-daily.sh` fait `exec python3`, là où `collecte-current.sh` fait `exec "${ENERVISION_PYTHON:-python3}"`. Sur le serveur : ``` $ python3 -c "import duckdb" ModuleNotFoundError: No module named 'duckdb' ``` C'est le mode de panne relevé par Olivier en relecture de la #114 pour le collecteur, reproduit à l'identique dans le lanceur de l'ETL : la tâche sort en erreur chaque heure sans rien dire d'utile. **2. La tâche Ansible code le service en dur.** « Planifier les relèves que le dépôt fournit » construit son chemin avec `services/collector/bin/{{ item.script }}`, et la tâche de `stat` qui la précède fait pareil. Le lanceur silver vit dans `services/etl/bin/`. On ne peut donc pas l'ajouter à `collector_taches` en l'état. Ce chemin était juste tant qu'il n'y avait que des collecteurs, il ne l'est plus. **3. Aucune planification n'existe.** Vérifié sur le serveur : crontabs de `root`, `deploy` et `lenaic`, `/etc/crontab`, `/etc/cron.d`, `/etc/cron.hourly`, `/etc/cron.daily`, minuteries systemd et unités nommées. Seules les deux lignes du collecteur existent. Rien pour l'ETL. Conséquence directe : le seau `silver` est à **0 objet** alors que le code du #34 est fusionné et déployé, et que bronze contient 9090 objets. ### Critères d'acceptation - [x] `silver-daily.sh` honore `ENERVISION_PYTHON` comme les lanceurs du collecteur, et sort en erreur explicite si l'interpréteur n'a pas ses dépendances plutôt que de lever une trace brute. - [x] La liste des tâches planifiées porte le service de chaque lanceur au lieu de le supposer, et la variable est renommée : elle ne décrit plus le seul collecteur. - [x] La ligne silver est posée sous `deploy` avec `ENERVISION_RACINE` et le python du venv, sa sortie dans `{{ collector_log_dir }}/silver.log`. - [ ] Un second déploiement ne duplique aucune ligne : les trois tâches restent trois. - [x] Le banc d'essai du rôle, `tests/ci/test-role-app.yml`, gagne un contrôle qui échoue si le chemin du service redevient codé en dur. - [x] Après une passe planifiée, `silver` contient la partition du jour. ### Comment on le vérifie Commande sudo crontab -u deploy -l Attendu trois lignes, dont silver, avec ENERVISION_PYTHON renseigné Commande mc ls --recursive ev/silver/ Attendu les trois tables Parquet de la partition du jour Commande le déploiement rejoué une seconde fois Attendu aucune ligne dupliquée, aucune tâche en changed sur la planification ### Manuel d'exploitation à mettre à jour `docs/runbooks/etl.md` s'il existe, sinon la section ETL du manuel d'exploitation : où va le journal, comment rejouer une journée à la main, et comment lire un refus du garde-fou sans le confondre avec une panne. ### Risque et retour arrière **Risque faible.** La modification touche la planification et un lanceur, pas la transformation elle-même. Le pire cas est une tâche horaire qui échoue et remplit `silver.log`, déjà couvert par la rotation `*.log` en place. **Retour arrière** : passer l'entrée silver à `state: absent` dans la liste des tâches et rejouer le rôle retire la ligne, sans toucher aux deux du collecteur. **Dépendance** : l'horaire retenu dépend du #135. Tant qu'il n'est pas corrigé, une tâche horaire sur la journée courante refusera avant 17 h. On peut poser la planification quand même, la sortie du job est explicite, mais le journal portera des refus qu'il ne faudra pas lire comme des pannes.
lenaic self-assigned this 2026-09-04 07:51:18 +00:00
Author
Owner

Traité par la #143, poussée et en attente de relecture. À fusionner après
la #142
, dont l'entrée de crontab suppose la correction de la grille.

Les trois obstacles sont levés. Le banc du rôle gagne un contrôle sur le chemin
du lanceur, éprouvé dans les deux sens : vert sur le rôle corrigé, rouge sur le
chemin en dur réintroduit.

Une précision qui vaut d'être notée, parce qu'elle m'a fait écrire deux fois le
même préflight. J'avais remplacé import duckdb, minio par un
import etl.silver.job plus général : ça ne marche pas, entrepot.py importe
ces deux modules tardivement pour que les tests unitaires tournent sans le
SDK. Importer le module réussit donc sans eux, et on retombe sur la trace brute.
Le contrôle explicite est délibéré, et le commentaire du script le dit.

La chaîne est rouge sur cette demande, mais pour le #147.

Traité par la **#143**, poussée et en attente de relecture. **À fusionner après la #142**, dont l'entrée de crontab suppose la correction de la grille. Les trois obstacles sont levés. Le banc du rôle gagne un contrôle sur le chemin du lanceur, éprouvé dans les deux sens : vert sur le rôle corrigé, rouge sur le chemin en dur réintroduit. Une précision qui vaut d'être notée, parce qu'elle m'a fait écrire deux fois le même préflight. J'avais remplacé `import duckdb, minio` par un `import etl.silver.job` plus général : ça ne marche pas, `entrepot.py` importe ces deux modules **tardivement** pour que les tests unitaires tournent sans le SDK. Importer le module réussit donc sans eux, et on retombe sur la trace brute. Le contrôle explicite est délibéré, et le commentaire du script le dit. La chaîne est rouge sur cette demande, mais pour le **#147**.
lenaic added this to the EnerVision project 2026-09-04 08:57:39 +00:00
Author
Owner

Fusionné par la #143 et déployé. Quatre critères sur six sont prouvés, les
deux autres attendent la passe de 10 h 00 UTC et un second déploiement.

Avant et après, sur le serveur

avant   crontab deploy    2 lignes
        silver-daily.sh   0 occurrence d'ENERVISION_PYTHON
        journal silver    absent
        clone             370ba43

après   crontab deploy    3 lignes
        silver-daily.sh   ENERVISION_PYTHON en ligne 38, honoré en 53
        clone             eb12df2

Critère 3, la ligne posée

0 * * * * ENERVISION_RACINE=/opt/enervision/.repo
          ENERVISION_PYTHON=/opt/enervision/venv/bin/python
          /opt/enervision/.repo/services/etl/bin/silver-daily.sh
          >> /var/log/enervision/silver.log 2>&1

Sous deploy, avec la racine et le python du venv, la sortie dans son journal.

Critère 1, le lanceur

38: PYTHON="${ENERVISION_PYTHON:-python3}"
47: if ! "$PYTHON" -c "import duckdb, minio" 2>/dev/null; then
53: exec "$PYTHON" -m etl.silver.job "$@"

Éprouvé dans les deux sens avant fusion : code 3 avec un interpréteur sans
les dépendances, silencieux avec le bon.

Les deux dépendances sont nommées explicitement, et c'est délibéré :
entrepot.py importe minio et duckdb tardivement, pour que les tests
unitaires tournent sans le SDK. Un import etl.silver.job plus général réussit
donc sans eux. J'avais écrit cette version là d'abord, le banc local l'a montrée
inopérante.

Critère 2, le rôle ne suppose plus le service

312:    path: "{{ app_repo_dir }}/services/{{ item.service }}/bin/{{ item.script }}"
338:      {{ app_repo_dir }}/services/{{ item.item.service }}/bin/{{ item.item.script }}

Critère 5, le banc

Vert sur le rôle corrigé, rouge sur le chemin en dur réintroduit, et la
chaîne le joue à chaque demande. ansible-lint à 0 défaut en profil production.

Ce qui reste

Critère 6 : la première passe planifiée tombe à 10 h 00 UTC. La preuve sera
l'existence de /var/log/enervision/silver.log écrit par cron, et la partition
du jour dans le seau silver. Le lanceur déployé a déjà produit 3885 lignes en
essai isolé, donc il n'y a pas de doute sur le résultat, seulement sur le
déclenchement.

Critère 4 : demande un second déploiement pour vérifier que les trois lignes
restent trois.

Fusionné par la #143 et déployé. **Quatre critères sur six sont prouvés**, les deux autres attendent la passe de 10 h 00 UTC et un second déploiement. ### Avant et après, sur le serveur ``` avant crontab deploy 2 lignes silver-daily.sh 0 occurrence d'ENERVISION_PYTHON journal silver absent clone 370ba43 après crontab deploy 3 lignes silver-daily.sh ENERVISION_PYTHON en ligne 38, honoré en 53 clone eb12df2 ``` ### Critère 3, la ligne posée ``` 0 * * * * ENERVISION_RACINE=/opt/enervision/.repo ENERVISION_PYTHON=/opt/enervision/venv/bin/python /opt/enervision/.repo/services/etl/bin/silver-daily.sh >> /var/log/enervision/silver.log 2>&1 ``` Sous `deploy`, avec la racine et le python du venv, la sortie dans son journal. ### Critère 1, le lanceur ``` 38: PYTHON="${ENERVISION_PYTHON:-python3}" 47: if ! "$PYTHON" -c "import duckdb, minio" 2>/dev/null; then 53: exec "$PYTHON" -m etl.silver.job "$@" ``` Éprouvé dans les deux sens avant fusion : code **3** avec un interpréteur sans les dépendances, silencieux avec le bon. Les deux dépendances sont nommées explicitement, et c'est délibéré : `entrepot.py` importe minio et duckdb **tardivement**, pour que les tests unitaires tournent sans le SDK. Un `import etl.silver.job` plus général réussit donc sans eux. J'avais écrit cette version là d'abord, le banc local l'a montrée inopérante. ### Critère 2, le rôle ne suppose plus le service ``` 312: path: "{{ app_repo_dir }}/services/{{ item.service }}/bin/{{ item.script }}" 338: {{ app_repo_dir }}/services/{{ item.item.service }}/bin/{{ item.item.script }} ``` ### Critère 5, le banc Vert sur le rôle corrigé, **rouge sur le chemin en dur réintroduit**, et la chaîne le joue à chaque demande. `ansible-lint` à 0 défaut en profil production. ### Ce qui reste **Critère 6** : la première passe planifiée tombe à 10 h 00 UTC. La preuve sera l'existence de `/var/log/enervision/silver.log` écrit par cron, et la partition du jour dans le seau `silver`. Le lanceur déployé a déjà produit 3885 lignes en essai isolé, donc il n'y a pas de doute sur le résultat, seulement sur le déclenchement. **Critère 4** : demande un second déploiement pour vérifier que les trois lignes restent trois.
Author
Owner

Critère 6 atteint. La première passe planifiée a tourné seule à 10 h 00 UTC.

/var/log/enervision/silver.log, écrit par cron, jamais à la main :

10:00:34 site=SITE001 mesurées=506 imputées=94 trous=0 disponibilité=100.0%
10:00:34 site=SITE002 mesurées=522 imputées=78 trous=0 disponibilité=100.0%
10:00:34 site=SITE003 mesurées=532 imputées=68 trous=0 disponibilité=100.0%
10:00:34 site=SITE004 mesurées=520 imputées=76 trous=4 disponibilité=99.3%
10:00:34 site=SITE005 mesurées=523 imputées=77 trous=0 disponibilité=100.0%
10:00:34 site=SITE006 mesurées=522 imputées=73 trous=5 disponibilité=99.2%
10:00:34 site=SITE007 mesurées=520 imputées=80 trous=0 disponibilité=100.0%
passe silver-2026-09-04-20260904T100001Z terminée : 4200 lignes sur 7 sites

Et la partition du jour dans le seau de production, qui était à 0 objet
depuis le déploiement du #34 :

domain=energy/table=mesure/dt=2026-09-04/data_0.parquet               116 KiB
domain=energy/table=journal_capteur/dt=2026-09-04/data_0.parquet       30 KiB
domain=energy/table=disponibilite_jour/dt=2026-09-04/data_0.parquet   3,8 KiB

4200 lignes, soit 7 sites × 600 minutes à 10 h 00 UTC. Le compte est juste.
Cinq sites sur sept à 100 % de disponibilité, aucun en dessous de 99,2 %.

La zone argent se remplit maintenant toute seule, toutes les heures.

Ce qui reste

Le critère 4 seul, qui demande un second déploiement pour vérifier que les
trois lignes de crontab restent trois. Il se cochera au prochain déploiement,
quel qu'il soit.

Une fausse alerte, pour mémoire

J'ai signalé ce matin que la passe « n'avait rien produit », en regardant à
09 h 37 UTC une tâche planifiée à 10 h 00. Il n'y avait rien à chercher.

**Critère 6 atteint.** La première passe planifiée a tourné seule à 10 h 00 UTC. `/var/log/enervision/silver.log`, écrit par cron, jamais à la main : ``` 10:00:34 site=SITE001 mesurées=506 imputées=94 trous=0 disponibilité=100.0% 10:00:34 site=SITE002 mesurées=522 imputées=78 trous=0 disponibilité=100.0% 10:00:34 site=SITE003 mesurées=532 imputées=68 trous=0 disponibilité=100.0% 10:00:34 site=SITE004 mesurées=520 imputées=76 trous=4 disponibilité=99.3% 10:00:34 site=SITE005 mesurées=523 imputées=77 trous=0 disponibilité=100.0% 10:00:34 site=SITE006 mesurées=522 imputées=73 trous=5 disponibilité=99.2% 10:00:34 site=SITE007 mesurées=520 imputées=80 trous=0 disponibilité=100.0% passe silver-2026-09-04-20260904T100001Z terminée : 4200 lignes sur 7 sites ``` Et la partition du jour dans le seau de production, qui était à **0 objet** depuis le déploiement du #34 : ``` domain=energy/table=mesure/dt=2026-09-04/data_0.parquet 116 KiB domain=energy/table=journal_capteur/dt=2026-09-04/data_0.parquet 30 KiB domain=energy/table=disponibilite_jour/dt=2026-09-04/data_0.parquet 3,8 KiB ``` 4200 lignes, soit 7 sites × 600 minutes à 10 h 00 UTC. Le compte est juste. Cinq sites sur sept à 100 % de disponibilité, aucun en dessous de 99,2 %. La zone argent se remplit maintenant toute seule, toutes les heures. ### Ce qui reste Le **critère 4** seul, qui demande un second déploiement pour vérifier que les trois lignes de crontab restent trois. Il se cochera au prochain déploiement, quel qu'il soit. ### Une fausse alerte, pour mémoire J'ai signalé ce matin que la passe « n'avait rien produit », en regardant à 09 h 37 UTC une tâche planifiée à 10 h 00. Il n'y avait rien à chercher.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
g2/enervision#136
No description provided.