fix(ci,crowdsec): écarter le runner de pi1, figer la résolution de la forge, couper le cache Redis du bouncer
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) <[email protected]>
This commit is contained in:
@@ -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 }}"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:<ip> isBanned:false redis:unreachable
|
||||
# ERROR ServeHTTP:Get ip:<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: |
|
||||
|
||||
Reference in New Issue
Block a user