From 93e7abfc574ff48d2bba135fae8ad44ec2aabf04 Mon Sep 17 00:00:00 2001 From: Gabriel Radureau Date: Wed, 26 Aug 2026 22:55:24 +0200 Subject: [PATCH] =?UTF-8?q?fix(ci,crowdsec):=20=C3=A9carter=20le=20runner?= =?UTF-8?q?=20de=20pi1,=20figer=20la=20r=C3=A9solution=20de=20la=20forge,?= =?UTF-8?q?=20couper=20le=20cache=20Redis=20du=20bouncer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trois correctifs issus d'une même soirée de panne. 1. Runner hors du control-plane (03_cicd.yml) `capacity: 1` ne borne que CHAQUE runner, pas le cluster : avec deux runners, deux builds lourds tournent en parallèle. Le 2026-08-26 ils ont saturé pi1 (charge 7,38 sur 15 min, 5,7 Go/8), le démon Docker a cessé de répondre et les DEUX jobs sont morts à la même seconde — 19:49:27, runs #152 et #154, `context deadline exceeded` sur docker.sock. pi1 porte l'apiserver et l'ingress. Même famille que 2026-07-23 et 2026-08-15. Le parallélisme tombe à 1 (pi3 seul), assumé : le runner du Mac (label `laptop`) absorbe les jobs lourds et va ~10× plus vite — mesuré sur kadans, 184 s contre 1 065 s sur pi3 et 1 915 s sur pi1. 2. Résolution de la forge figée (03_cicd.yml, group_vars/all/gitea.yml) Les hôtes à runner résolvent `.lab` via un Pi-hole, puis retombent sur les nameservers IPv6 du routeur, qui répondent NXDOMAIN — réponse valide, donc retenue. D'où des pulls qui échouent par intermittence pendant que `getent hosts` réussit. `extra_hosts` + `--add-host` court-circuitent le DNS. L'IP est dérivée de l'inventaire (premier hôte du groupe `gitea`) plutôt qu'écrite en dur. Vérifié que c'est équivalent : le registre répond 401 sur /v2/ via .202 comme via .201, le svclb Traefik écoutant sur 443 de chaque nœud. ⚠ Ça ne couvre PAS le namespace réseau de buildkit, qui n'hérite d'aucun des deux — corrigé séparément côté cms (pull côté démon, build sans `--pull`). 3. Cache Redis du bouncer coupé (roles/crowdsec) `redisCacheUnreachableBlock: false` n'est pas honoré par le plugin : redis-0 étant 0/1, TOUTES les requêtes externes prenaient un 403 alors que `cscli decisions list` était vide. Logs Traefik : `isBanned:false redis:unreachable` → 403. Le cache mémoire suffit — en mode `stream` les décisions viennent de la LAPI de toute façon. ⚠ NE PAS « réparer » Redis en `--save ""` : il sert aussi kadans-jobs (DB 1, file de jobs) et telegram-gateway (DB 0, sessions d'auth 24 h). Co-Authored-By: Claude Opus 5 (1M context) --- .../inventory/group_vars/all/gitea.yml | 34 +++++++++++++++++++ .../arcodange/factory/playbooks/03_cicd.yml | 29 ++++++++++++++-- .../tools/roles/crowdsec/tasks/main.yml | 31 ++++++++++++++--- 3 files changed, 88 insertions(+), 6 deletions(-) diff --git a/ansible/arcodange/factory/inventory/group_vars/all/gitea.yml b/ansible/arcodange/factory/inventory/group_vars/all/gitea.yml index 8691392..9ad52f2 100644 --- a/ansible/arcodange/factory/inventory/group_vars/all/gitea.yml +++ b/ansible/arcodange/factory/inventory/group_vars/all/gitea.yml @@ -86,3 +86,37 @@ gitlab_personal_namespace_id: ~ # qu'ils n'annoncent pas. gitea_runner_image: "gitea/runner" gitea_runner_version: "2.3.0" + +# ══════════════════════════════════════════════════════════════════════════ +# RÉSOLUTION DE LA FORGE DEPUIS LA CI — FIGÉE, ET C'EST LE POINT. +# +# `gitea.arcodange.lab` est servi par l'ingress k3s (Traefik) sur pi1. Les +# hôtes à runner (pi1, pi3) le résolvent via un Pi-hole qui tourne... sur pi1 +# et pi3. Leur /etc/resolv.conf liste ensuite les nameservers IPv6 du routeur, +# qui ignorent `.lab` et répondent NXDOMAIN — une réponse VALIDE, donc retenue. +# +# Résultat mesuré le 2026-08-26 (run #147, job 7373) : le pull de l'image de +# base a échoué en 16 s sur +# failed to fetch oauth token: dial tcp: lookup gitea.arcodange.lab +# on 127.0.0.11:53: no such host +# alors que `getent hosts` réussissait sur les deux hôtes au même moment. La +# panne est INTERMITTENTE : elle dépend de quel nameserver du chaînage répond +# en premier. Un `nslookup` qui réussit ne prouve donc rien. +# +# La CI de la forge ne peut pas dépendre d'un résolveur qui vacille : on fige +# la correspondance nom → IP dans les conteneurs (extra_hosts / --add-host, +# cf. playbooks/03_cicd.yml), ce qui court-circuite le DNS entièrement. +# +# L'IP est DÉRIVÉE de l'inventaire — premier hôte du groupe `gitea` — plutôt +# qu'écrite en dur : déplacer Gitea dans l'inventaire suffit alors à corriger +# la CI, sans qu'on ait à se souvenir de cette variable. +# +# Vérifié le 2026-08-26 que ce choix est valide : le registre répond +# identiquement sur les deux adresses (401 sur /v2/, réponse normale d'un +# registre non authentifié), le svclb Traefik écoutant sur 443 de chaque nœud. +# --resolve gitea.arcodange.lab:443:192.168.1.202 → 401 (pi2, groupe gitea) +# --resolve gitea.arcodange.lab:443:192.168.1.201 → 401 (pi1, ingress k3s) +# Passer par pi2 raccourcit d'ailleurs le chemin : on vise l'hôte de Gitea +# lui-même plutôt qu'un rebond par l'ingress. +gitea_lab_fqdn: "gitea.arcodange.lab" +gitea_lab_ingress_ip: "{{ hostvars[groups['gitea'][0]].preferred_ip }}" diff --git a/ansible/arcodange/factory/playbooks/03_cicd.yml b/ansible/arcodange/factory/playbooks/03_cicd.yml index 532ddf8..728e62f 100644 --- a/ansible/arcodange/factory/playbooks/03_cicd.yml +++ b/ansible/arcodange/factory/playbooks/03_cicd.yml @@ -1,7 +1,20 @@ --- - name: Deploy Gitea Action - hosts: raspberries:&local:!gitea # do not deploy on machine with gitea instance + # `!gitea` : ne pas déployer sur la machine qui héberge Gitea (pi2). + # `!pi1` : NI sur le control-plane k3s. Ajouté le 2026-08-26 après que deux + # builds lourds simultanés — un par runner, `capacity: 1` ne borne + # que CHAQUE runner, pas le cluster — aient saturé pi1 : charge 7,38 + # sur 15 min, 5,7 Go/8 utilisés, puis le démon Docker cesse de + # répondre et les DEUX jobs meurent à la même seconde (19:49:27, + # runs #152 et #154, `context deadline exceeded` sur docker.sock). + # pi1 porte l'apiserver et l'ingress : un `nuxt generate` n'a rien à + # y faire. Même famille d'incident que 2026-07-23 et 2026-08-15. + # + # Le parallélisme tombe donc à 1 (pi3 seul). C'est assumé : le runner du Mac + # (label `laptop`) absorbe les jobs lourds, et il est ~10× plus rapide — + # mesuré sur kadans : 184 s sur le Mac contre 1 065 s sur pi3 et 1 915 s sur pi1. + hosts: raspberries:&local:!gitea:!pi1 roles: - arcodange.factory.gitea_token # generate gitea_api_token used to replace generated token with set name if required @@ -49,6 +62,13 @@ GITEA_RUNNER_LABELS: ubuntu-latest:docker://gitea.arcodange.lab/arcodange-org/runner-images:ubuntu-latest-ca,ubuntu-latest-ca:docker://gitea.arcodange.lab/arcodange-org/runner-images:ubuntu-latest-ca,ci-node-playwright:docker://ci-node-playwright:latest ports: - "43707:43707" + # Résolution figée de la forge : le runner tire lui-même des + # images depuis gitea.arcodange.lab. Voir le commentaire de + # gitea_lab_ingress_ip (group_vars/all/gitea.yml) — le DNS du + # homelab répond NXDOMAIN par intermittence selon le nameserver + # du chaînage qui répond en premier. + extra_hosts: + - "{{ gitea_lab_fqdn }}:{{ gitea_lab_ingress_ip }}" networks: - gitea_action_network volumes: @@ -143,7 +163,12 @@ # And other options to be used when the container is started (eg, --add-host=my.gitea.url:host-gateway). # Plafonds durs : un build ne doit jamais pouvoir affamer l'hôte (incident 2026-07-23 : # nuxt generate à 3,5 Go RSS sur pi1 → load 150, ingress+API k3s morts → gitea.arcodange.lab injoignable). - options: "--memory=3g --memory-swap=3g --cpus=2 --pids-limit=512" + # `--add-host` : même raison que l'`extra_hosts` du service + # ci-dessus, mais pour les conteneurs de JOB, qui sont ceux + # qui lancent `docker build --pull` contre la forge. + options: >- + --memory=3g --memory-swap=3g --cpus=2 --pids-limit=512 + --add-host={{ gitea_lab_fqdn }}:{{ gitea_lab_ingress_ip }} # The parent directory of a job's working directory. # NOTE: There is no need to add the first '/' of the path as act_runner will add it automatically. # If the path starts with '/', the '/' will be trimmed. diff --git a/ansible/arcodange/factory/playbooks/tools/roles/crowdsec/tasks/main.yml b/ansible/arcodange/factory/playbooks/tools/roles/crowdsec/tasks/main.yml index b26e262..5f6b126 100644 --- a/ansible/arcodange/factory/playbooks/tools/roles/crowdsec/tasks/main.yml +++ b/ansible/arcodange/factory/playbooks/tools/roles/crowdsec/tasks/main.yml @@ -142,10 +142,33 @@ captchaSiteKey: "{{ crowdsec_captcha_secret.resources[0].data.sitekey | b64decode }}" captchaSecretKey: "{{ crowdsec_captcha_secret.resources[0].data.secret | b64decode }}" captchaHTMLFilePath: "/data/captcha.html" - redisCacheEnabled: true - redisCacheHost: "redis.tools:6379" - redisCacheDatabase: "0" - redisCacheUnreachableBlock: false + # ⚠ CACHE REDIS DÉSACTIVÉ — le bouncer retombe sur son cache mémoire. + # + # `redisCacheUnreachableBlock: false` est censé dire « ne bloque pas si + # Redis est injoignable ». Le plugin ne l'honore PAS : mesuré le + # 2026-08-26, redis-0 étant 0/1, TOUTES les requêtes externes prenaient + # un 403 alors que `cscli decisions list` ne montrait aucune décision + # active. Les logs Traefik sont sans ambiguïté : + # ServeHTTP:Get ip: isBanned:false redis:unreachable + # ERROR ServeHTTP:Get ip: redis:unreachable → 403 + # `isBanned:false` ET 403 : c'est l'indisponibilité du cache qui bloque, + # pas une décision. + # + # Redis stalle parce qu'il tourne avec `--save 60 1` sur un volume + # Longhorn (réplication réseau) monté sur un Pi : le BGSAVE bloque le + # process assez longtemps pour que `redis-cli ping` dépasse les 5 s de + # timeout, six fois de suite → le kubelet le tue. 41 échecs de liveness + # sur 11 jours. + # + # Le cache mémoire suffit ici : en mode `stream`, le bouncer récupère de + # toute façon les décisions de la LAPI périodiquement, et un redémarrage + # de Traefik les repeuple. On échange un cache partagé contre le fait de + # ne plus faire dépendre TOUT l'ingress de la santé d'un Redis. + # + # ⚠ NE PAS « réparer » ça en passant Redis en `--save ""` : il sert AUSSI + # kadans-jobs (DB 1, file de jobs) et telegram-gateway (DB 0, sessions + # d'auth, TTL 24 h). Supprimer sa persistance les impacterait tous deux. + redisCacheEnabled: false - name: Supprimer les pods crowdsec en état Error pour forcer leur redémarrage ansible.builtin.shell: |