ci(diag): probe the runner cache server from a job container — do not merge (#234) #374

Closed
maximus wants to merge 3 commits from diag/234-cache-probe into main
Owner

Refs #234

PR de diagnostic, à ne jamais merger. Elle ajoute un workflow jetable (.forgejo/workflows/diag-cache-probe.yml) qui ne se déclenche que sur sa propre PR.

Pourquoi

Depuis #232, plus aucun workflow n'appelle le cache Actions. Le dernier symptôme observé date du 2026-07-21 (run 326 : getCacheEntry et reserveCache en Request timeout), et rien ne dit s'il est toujours là. Cette sonde le vérifie depuis l'intérieur d'un conteneur de job, sans accès au VPS.

Ce que fait le job

  1. Il affiche l'adresse du serveur de cache que le runner donne au conteneur (ACTIONS_CACHE_URL), l'adresse IPv4 de la forge, les adresses du conteneur et sa passerelle. Le chemin de l'URL est masqué : il peut porter un jeton propre au run, et les journaux de ce dépôt sont publics.
  2. Il teste en TCP, 10 s par cible : l'adresse annoncée ; le même hôte sur les ports 22 et 443 ; la passerelle du réseau du job, sur le port du cache et sur le 22 ; 172.17.0.1 sur le port du cache.
  3. Il fait l'aller-retour complet avec actions/cache : sauvegarde d'un fichier minuscule sous une clé unique, suppression de la copie locale, restauration avec fail-on-cache-miss, comparaison du contenu.

Le job est vert seulement si le fichier revient du serveur de cache.

Lecture des sondes TCP

Résultat Sens
OPEN la connexion s'établit
REFUSED l'hôte répond, mais rien n'écoute sur ce port
TIMEOUT aucune réponse : paquets jetés en route, ou adresse injoignable depuis ce réseau

Comparer l'adresse annoncée aux ports 22 et 443 du même hôte sépare deux cas : un filtrage du seul port du cache, ou un hôte injoignable dans son ensemble depuis le conteneur.

check-frontend tournera aussi sur cette PR, le nouveau fichier n'étant pas dans sa liste d'exclusion. Ce run est sans rapport avec la sonde.

Vérifié avant le push

Le script de sonde a tourné en local contre des cibles maîtrisées : port ouvert, port fermé, adresse de documentation muette (192.0.2.1), URL absente, adresse IPv6. Le chemin de l'URL n'apparaît jamais dans la sortie.

🤖 Generated with Claude Code

Refs #234 **PR de diagnostic, à ne jamais merger.** Elle ajoute un workflow jetable (`.forgejo/workflows/diag-cache-probe.yml`) qui ne se déclenche que sur sa propre PR. ## Pourquoi Depuis #232, plus aucun workflow n'appelle le cache Actions. Le dernier symptôme observé date du 2026-07-21 (run 326 : `getCacheEntry` et `reserveCache` en `Request timeout`), et rien ne dit s'il est toujours là. Cette sonde le vérifie depuis l'intérieur d'un conteneur de job, sans accès au VPS. ## Ce que fait le job 1. Il affiche l'adresse du serveur de cache que le runner donne au conteneur (`ACTIONS_CACHE_URL`), l'adresse IPv4 de la forge, les adresses du conteneur et sa passerelle. Le chemin de l'URL est masqué : il peut porter un jeton propre au run, et les journaux de ce dépôt sont publics. 2. Il teste en TCP, 10 s par cible : l'adresse annoncée ; le même hôte sur les ports 22 et 443 ; la passerelle du réseau du job, sur le port du cache et sur le 22 ; `172.17.0.1` sur le port du cache. 3. Il fait l'aller-retour complet avec `actions/cache` : sauvegarde d'un fichier minuscule sous une clé unique, suppression de la copie locale, restauration avec `fail-on-cache-miss`, comparaison du contenu. **Le job est vert seulement si le fichier revient du serveur de cache.** ## Lecture des sondes TCP | Résultat | Sens | |---|---| | `OPEN` | la connexion s'établit | | `REFUSED` | l'hôte répond, mais rien n'écoute sur ce port | | `TIMEOUT` | aucune réponse : paquets jetés en route, ou adresse injoignable depuis ce réseau | Comparer l'adresse annoncée aux ports 22 et 443 du même hôte sépare deux cas : un filtrage du seul port du cache, ou un hôte injoignable dans son ensemble depuis le conteneur. `check-frontend` tournera aussi sur cette PR, le nouveau fichier n'étant pas dans sa liste d'exclusion. Ce run est sans rapport avec la sonde. ## Vérifié avant le push Le script de sonde a tourné en local contre des cibles maîtrisées : port ouvert, port fermé, adresse de documentation muette (`192.0.2.1`), URL absente, adresse IPv6. Le chemin de l'URL n'apparaît jamais dans la sortie. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
maximus added 1 commit 2026-10-03 17:49:51 +00:00
ci: add a throwaway probe of the runner cache server (#234)
Some checks failed
PR Check — Frontend / frontend (pull_request) Successful in 1m41s
Diag — cache server reachability (#234) / probe (pull_request) Failing after 12s
1326ba1bcc
Diagnostic only, never to be merged. Since #232 no workflow calls the
Actions cache, so nothing has shown whether a job container can reach the
cache server today. This job prints the cache address the runner
advertises (path redacted, since job logs are public), probes it over TCP
next to other ports and host addresses, and round-trips a tiny payload
through actions/cache. It is green only if the payload comes back.

Refs #234

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maximus added 1 commit 2026-10-03 17:53:47 +00:00
ci: keep the cache probe running past an expected failure (#234)
Some checks failed
PR Check — Frontend / frontend (pull_request) Successful in 1m41s
Diag — cache server reachability (#234) / probe (pull_request) Failing after 1m52s
68aef2a74a
The runner starts `shell: bash` steps as `bash -e -o pipefail`, so run 453
stopped at the first TIMEOUT instead of probing the other addresses. Each
probe now swallows its own exit code, and the env listing and the forge
lookup no longer abort the step when they come back empty.

Refs #234

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Résultat — run 455 (2026-10-03)

Le symptôme de juillet est toujours là, et la sonde le localise : l'adresse du cache est joignable, mais son port est filtré sur l'hôte.

Le runner (linux-builder, v12.7.0, déjà la version du run 326 de juillet) annonce le cache sur http://54.39.97.77:41511/…. C'est l'IPv4 publique du VPS, la même que la forge. Le conteneur du job est en 10.0.4.2, avec la passerelle 10.0.4.1.

Cible Résultat
54.39.97.77:41511 (cache annoncé) TIMEOUT (10 s)
54.39.97.77:22 (sshd, même IP) OPEN (9 ms)
54.39.97.77:443 (Traefik) OPEN (6 ms)
10.0.4.1:41511 (passerelle du job, port du cache) TIMEOUT
10.0.4.1:22 (passerelle du job, sshd) OPEN (7 ms)
172.17.0.1:41511 (docker0, port du cache) TIMEOUT

Aller-retour actions/cache : reserveCache failed: Request timeout à la sauvegarde, puis getCacheEntry failed: Request timeout à la restauration, 20 s chacun. Ce sont les messages exacts de juillet.

Lecture

  • Le conteneur atteint l'hôte : sshd répond sur l'IP publique comme sur la passerelle du réseau du job. Le routage et l'adresse annoncée sont bons.
  • Le port du cache ne répond sur aucune adresse de l'hôte, et il ne refuse pas non plus. Sans pare-feu, un port fermé renvoie un refus immédiat. Les paquets vers 41511 sont donc jetés par un pare-feu de l'hôte, qui laisse passer le 22.
  • C'est la signature d'une politique « refuser l'entrant » avec une exception pour SSH (ufw, ou des règles équivalentes). Le 443 passe parce que Docker publie Traefik par ses propres règles NAT, en dehors des règles d'entrée. Le runner tourne sur l'hôte sous systemd et ne bénéficie d'aucune exception.
  • Le correctif suggéré par le corps de #234 (« fixer cache.host sur une IP joignable ») ne suffirait pas : l'IP est joignable, c'est le port qui est filtré.

À lire sur l'hôte (lecture seule)

sudo ufw status verbose                     # sinon : sudo nft list ruleset ; sudo iptables -S INPUT
sudo ss -ltnp | grep -i forgejo-runner      # le runner écoute-t-il sur 41511, et sur quel autre port ?
systemctl cat forgejo-runner                # un --config est-il passé ? bloc cache: (port, proxy_port)
grep -A3 default-address-pools /etc/docker/daemon.json   # plage des réseaux de jobs

Si le bloc cache: laisse ses ports à 0, le runner en tire un au hasard à chaque démarrage, et aucune règle de pare-feu ne peut le viser.

Correctif probable

  1. Fixer les ports du cache dans la config du runner.
  2. Autoriser ce port depuis les réseaux Docker des jobs seulement, jamais depuis Internet. Un serveur de cache exposé permettrait d'empoisonner le cache de toute la CI du runner.
  3. Redémarrer le runner hors de 06:00 UTC, sans job en file : l'arrêt annule les jobs en attente, qui apparaissent alors en échec.
  4. Relancer cette PR. Attendu : OPEN sur le port du cache, puis ROUND TRIP OK.

Notes

  • Le run 453 s'était arrêté à la première sonde : le runner lance les étapes shell: bash en bash --noprofile --norc -e -o pipefail. Corrigé dans 68aef2a.
  • Le segment de chemin de l'URL du cache (263a97ad) apparaît en clair dans les avertissements d'actions/cache, malgré le masquage de la sonde. Il change d'un run à l'autre (8f50bf31 au run 326) et ce run est terminé.
  • Cette PR reste ouverte pour servir de contrôle après le correctif. Ne pas la merger.
## Résultat — run 455 (2026-10-03) Le symptôme de juillet est toujours là, et la sonde le localise : **l'adresse du cache est joignable, mais son port est filtré sur l'hôte.** Le runner (`linux-builder`, v12.7.0, déjà la version du run 326 de juillet) annonce le cache sur `http://54.39.97.77:41511/…`. C'est l'IPv4 publique du VPS, la même que la forge. Le conteneur du job est en `10.0.4.2`, avec la passerelle `10.0.4.1`. | Cible | Résultat | |---|---| | `54.39.97.77:41511` (cache annoncé) | `TIMEOUT` (10 s) | | `54.39.97.77:22` (sshd, même IP) | `OPEN` (9 ms) | | `54.39.97.77:443` (Traefik) | `OPEN` (6 ms) | | `10.0.4.1:41511` (passerelle du job, port du cache) | `TIMEOUT` | | `10.0.4.1:22` (passerelle du job, sshd) | `OPEN` (7 ms) | | `172.17.0.1:41511` (docker0, port du cache) | `TIMEOUT` | Aller-retour `actions/cache` : `reserveCache failed: Request timeout` à la sauvegarde, puis `getCacheEntry failed: Request timeout` à la restauration, 20 s chacun. Ce sont les messages exacts de juillet. ### Lecture - Le conteneur atteint l'hôte : sshd répond sur l'IP publique comme sur la passerelle du réseau du job. Le routage et l'adresse annoncée sont bons. - Le port du cache ne répond sur aucune adresse de l'hôte, et il ne refuse pas non plus. Sans pare-feu, un port fermé renvoie un refus immédiat. Les paquets vers 41511 sont donc jetés par un pare-feu de l'hôte, qui laisse passer le 22. - C'est la signature d'une politique « refuser l'entrant » avec une exception pour SSH (ufw, ou des règles équivalentes). Le 443 passe parce que Docker publie Traefik par ses propres règles NAT, en dehors des règles d'entrée. Le runner tourne sur l'hôte sous systemd et ne bénéficie d'aucune exception. - Le correctif suggéré par le corps de #234 (« fixer `cache.host` sur une IP joignable ») ne suffirait pas : l'IP est joignable, c'est le port qui est filtré. ### À lire sur l'hôte (lecture seule) ```bash sudo ufw status verbose # sinon : sudo nft list ruleset ; sudo iptables -S INPUT sudo ss -ltnp | grep -i forgejo-runner # le runner écoute-t-il sur 41511, et sur quel autre port ? systemctl cat forgejo-runner # un --config est-il passé ? bloc cache: (port, proxy_port) grep -A3 default-address-pools /etc/docker/daemon.json # plage des réseaux de jobs ``` Si le bloc `cache:` laisse ses ports à `0`, le runner en tire un au hasard à chaque démarrage, et aucune règle de pare-feu ne peut le viser. ### Correctif probable 1. Fixer les ports du cache dans la config du runner. 2. Autoriser ce port **depuis les réseaux Docker des jobs seulement**, jamais depuis Internet. Un serveur de cache exposé permettrait d'empoisonner le cache de toute la CI du runner. 3. Redémarrer le runner hors de 06:00 UTC, sans job en file : l'arrêt annule les jobs en attente, qui apparaissent alors en échec. 4. Relancer cette PR. Attendu : `OPEN` sur le port du cache, puis `ROUND TRIP OK`. ### Notes - Le run 453 s'était arrêté à la première sonde : le runner lance les étapes `shell: bash` en `bash --noprofile --norc -e -o pipefail`. Corrigé dans `68aef2a`. - Le segment de chemin de l'URL du cache (`263a97ad`) apparaît en clair dans les avertissements d'`actions/cache`, malgré le masquage de la sonde. Il change d'un run à l'autre (`8f50bf31` au run 326) et ce run est terminé. - Cette PR reste ouverte pour servir de contrôle après le correctif. **Ne pas la merger.**
maximus added 1 commit 2026-10-03 18:20:53 +00:00
ci: re-run the cache probe after the runner fix (#234)
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m45s
Diag — cache server reachability (#234) / probe (pull_request) Successful in 51s
bbfd32b95e
The runner now reads a config file with a fixed cache proxy port (28100),
and ufw lets the Docker job networks (10.0.0.0/8) reach that port only.

Refs #234

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Correctif appliqué : run 457 vert

Cause confirmée sur l'hôte (lecture du 2026-10-03) :

  1. ufw refuse l'entrant par défaut, et la seule règle ouverte aux conteneurs vise 172.16.0.0/12. Or Docker (daemon.json, réglé par Coolify) alloue ses réseaux dans 10.0.0.0/8 : les conteneurs de jobs ne correspondaient à aucune règle.
  2. Le runner tournait sans fichier de config : cache.proxy_port valait 0, donc un port tiré au hasard à chaque démarrage, qu'aucune règle ne pouvait viser.

Correctif : config produite par forgejo-runner generate-config avec proxy_port: 28100, fichier complémentaire systemd qui passe --config, règle ufw allow proto tcp from 10.0.0.0/8 to any port 28100, redémarrage du runner. Le détail et le retour arrière sont dans la section « Cache Actions : Request timeout » de la-compagnie-maximus/docs/forgejo-ops.md.

Run 457 :

  • le runner annonce le cache sur le port 28100, OPEN sur l'IP publique comme sur la passerelle du job ;
  • Cache saved puis Cache restored : l'aller-retour prend environ 1 s, contre 20 s d'attente par appel avant ;
  • la sonde vers 172.17.0.1 reste sans réponse, sans incidence : ce n'est pas l'adresse qu'annonce le runner.

Les ports du proxy et du serveur interne restent fermés depuis Internet (vérifié de l'extérieur).

La sonde a rempli son rôle : je ferme cette PR sans la merger et je supprime sa branche. Reste pour #234 : remettre Swatinem/rust-cache et le cache npm (la suite reportée de #232). Le deuxième run de cette PR-là prouvera la restauration d'un run à l'autre.

## Correctif appliqué : run 457 vert Cause confirmée sur l'hôte (lecture du 2026-10-03) : 1. ufw refuse l'entrant par défaut, et la seule règle ouverte aux conteneurs vise `172.16.0.0/12`. Or Docker (`daemon.json`, réglé par Coolify) alloue ses réseaux dans `10.0.0.0/8` : les conteneurs de jobs ne correspondaient à aucune règle. 2. Le runner tournait sans fichier de config : `cache.proxy_port` valait `0`, donc un port tiré au hasard à chaque démarrage, qu'aucune règle ne pouvait viser. Correctif : config produite par `forgejo-runner generate-config` avec `proxy_port: 28100`, fichier complémentaire systemd qui passe `--config`, règle ufw `allow proto tcp from 10.0.0.0/8 to any port 28100`, redémarrage du runner. Le détail et le retour arrière sont dans la section « Cache Actions : `Request timeout` » de `la-compagnie-maximus/docs/forgejo-ops.md`. Run 457 : - le runner annonce le cache sur le port 28100, `OPEN` sur l'IP publique comme sur la passerelle du job ; - `Cache saved` puis `Cache restored` : l'aller-retour prend environ 1 s, contre 20 s d'attente par appel avant ; - la sonde vers `172.17.0.1` reste sans réponse, sans incidence : ce n'est pas l'adresse qu'annonce le runner. Les ports du proxy et du serveur interne restent fermés depuis Internet (vérifié de l'extérieur). La sonde a rempli son rôle : je ferme cette PR sans la merger et je supprime sa branche. Reste pour #234 : remettre `Swatinem/rust-cache` et le cache npm (la suite reportée de #232). Le deuxième run de cette PR-là prouvera la restauration d'un run à l'autre.
maximus closed this pull request 2026-10-03 18:26:43 +00:00
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m45s
Diag — cache server reachability (#234) / probe (pull_request) Successful in 51s

Pull request closed

Sign in to join this conversation.
No reviewers
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.

Dependencies

No dependencies set.

Reference: maximus/Simpl-Resultat#374
No description provided.