Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions scripts/ci/docker/linux-runner/persist/coursia-ci.slice
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,24 @@
#
# COMMENT LES CONTENEURS Y ENTRENT
# --------------------------------
# Par le defaut du daemon, pas par un drapeau que chaque appelant devrait
# penser a passer -- `/etc/docker/daemon.json` porte `"cgroup-parent":
# "coursia-ci.slice"` (copie de reference dans persist/daemon.json). Tout
# conteneur du daemon docker-ce y atterrit, y compris ceux lances a la main
# pour un debug, y compris ceux d'une famille future que personne n'aura pense
# a cabler. Un garde qui depend de la memoire de son appelant n'est pas un
# garde.
# Par le drapeau EXPLICITE --cgroup-parent pose par supervise.sh ; et, sur
# le daemon docker-ce seulement, par le defaut de son daemon.json. Ce defaut
# NE COUVRE PAS LA FLOTTE (#15157, mesure ai-01 2026-09-08) : les conteneurs
# de la CI tournent sur un AUTRE daemon (Docker Desktop, pilote cgroupfs)
# qui ne porte aucun cgroup-parent par defaut. Pour lui, l'entree depend
# entierement du drapeau : un conteneur lance a la main pour un debug y
# echappe. supervise.sh rend cette evasion visible -- report_slice_membership
# (cmd_status) confronte les sous-groupes de la slice aux conteneurs
# label=coursia-ci=1 et crie EVASION sur decalage.
#
# PIEGE DE RECOPIE : sous pilote systemd, "coursia-ci.slice" se resout en
# slice imbriquee sous coursia.slice -- correct, et c'est pourquoi le defaut
# ne pose aucun probleme sur docker-ce. Sous pilote CGROUPFS (Docker
# Desktop), la meme chaine est un chemin LITTERAL : docker cree
# /sys/fs/cgroup/coursia-ci.slice A PLAT, SANS AUCUN plafond -- un leurre
# homonyme un niveau au-dessus du vrai mur. La valeur correcte pour un
# daemon cgroupfs est le chemin imbrique complet :
# "coursia.slice/coursia-ci.slice".
#
# systemd imbrique une slice sur son nom : `coursia-ci.slice` vit sous
# `coursia.slice`. Le chemin cgroup reel est donc
Expand Down
64 changes: 59 additions & 5 deletions scripts/ci/docker/linux-runner/supervise.sh
Original file line number Diff line number Diff line change
Expand Up @@ -207,15 +207,21 @@ LEAN_MEMORY_SWAP="${COURSIA_LEAN_RUNNER_MEMORY_SWAP:-12g}"
# monopoliser le disque. Mesure ai-01 2026-09-07 : dd 256 Mio oflag=direct
# rend 7,4 GB/s sans cap et 21,2 MB/s sous --device-write-bps 20 Mio/s --
# facteur 350, a 1 % de la valeur demandee. Le cap est REEL.
# 2. AGREGE -- la slice systemd coursia-ci.slice, appliquee par defaut du
# daemon (/etc/docker/daemon.json "cgroup-parent"). C'est la seule borne
# qui somme les familles ; voir persist/coursia-ci.slice.
# 2. AGREGE -- la slice systemd coursia-ci.slice. Entree par le drapeau
# --cgroup-parent que ce script pose : le defaut de daemon.json ne
# couvre que le daemon docker-ce, qui n'heberge AUCUN conteneur de la
# flotte -- le daemon reel (Docker Desktop, #15157) n'a pas de defaut.
# C'est la seule borne qui somme les familles ; voir
# persist/coursia-ci.slice.
# 3. CPU INTER-FAMILLES -- assert_cpu_budget() ci-dessous, qui ferme le trou
# que cmd_lean documente depuis #14337 (« la somme des caps CPU des
# familles actives n'est gardee par RIEN »).
#
# La borne 2 est daemon-wide et donc independante de l'appelant ; ce script
# n'a pas a la re-imposer, il a a VERIFIER qu'elle est en vigueur. La
# La borne 2 ne depend de la memoire de l'appelant que TANT QUE l'appelant
# passe le drapeau -- supervise.sh le fait ; un conteneur lance a la main y
# echappe, et report_slice_membership rend cette evasion visible (#15157).
# Ce script n'a pas a re-imposer la borne, il a a VERIFIER qu'elle est en
# vigueur. La
# difference n'est pas cosmetique : re-passer --cgroup-parent sur une machine
# ou la slice n'existe pas cree un cgroup vide qui a l'air d'un garde et n'en
# est pas -- exactement la classe de defaut ou un outil manquant rend un garde
Expand Down Expand Up @@ -1601,6 +1607,51 @@ Le mur agrege serait decoratif tout en paraissant actif. Deployer :
echo "[slice] ($CI_SLICE_PATH ; conteneurs places via --cgroup-parent=$CI_CGROUP_PARENT)"
}

# Garde d'APPARTENANCE du mur agrege (#15157).
#
# assert_ci_slice verifie que le mur A des plafonds ; jusqu'ici rien ne
# prouvait que les conteneurs Y SONT. Mesure ai-01 2026-09-08 : la flotte
# tourne sur un daemon (Docker Desktop, pilote cgroupfs) qui ne porte
# AUCUN cgroup-parent par defaut -- le defaut de daemon.json ne vit que
# sur le daemon docker-ce, qui heberge zero conteneur. L'entree depend
# donc entierement du drapeau pose par ce script, et un conteneur lance
# a la main y echappe -- en silence : hors du mur, il garde ses caps
# propres et la lecture du mur ne le voit pas.
#
# Chaque conteneur place cree un sous-groupe dans la slice : on confronte
# ce compte au nombre de conteneurs vivants label=coursia-ci=1 sur le
# daemon vise (le meme binaire docker que slot_loop). Advisory et non
# bloquant -- un die ici tuerait un pool sain sur un decalage transitoire
# de creation ; la ligne est le signal, lue depuis cmd_status une fois la
# flotte en vol (hors course de creation).
slice_subgroup_count() {
local d n=0
for d in "$CI_SLICE_PATH"/*/; do
[ -d "$d" ] && n=$((n + 1))
done
echo "$n"
}

running_ci_count() {
docker ps -q --filter 'label=coursia-ci=1' 2>/dev/null | grep -c .
}

report_slice_membership() {
# Sans mur actif, assert_ci_slice a deja parle (et neutralise le
# placement) : l'appartenance n'a rien a mesurer.
local mx
mx="$(slice_read_raw memory.max)"
case "$mx" in ""|max) return 0 ;; esac
local sub n
sub="$(slice_subgroup_count)"
n="$(running_ci_count)"
if [ "$n" -gt "$sub" ]; then
echo "[slice] EVASION : $((n - sub)) conteneur(s) coursia-ci HORS du mur agrege (conteneurs=$n, sous-groupes=$sub) -- un conteneur lance sans --cgroup-parent n'est pas borne par la slice (#15157)"
else
echo "[slice] appartenance OK : $n conteneur(s) coursia-ci, $sub sous-groupe(s) dans le mur"
fi
}

# Affiche pic / courant / plafonds de la slice, et confronte le pic au budget.
# C'est la seule ligne de ce script qui compare une DECLARATION a une MESURE.
cmd_peak() {
Expand Down Expand Up @@ -1695,6 +1746,9 @@ cmd_status() {
echo " plafond par conteneur : non declare (COURSIA_RUNNER_DEVICE_WRITE_BPS vide)"
fi
echo " budget CPU inter-familles : ${CPU_BUDGET:-0} vCPU (0 = pas de garde)"
echo "== mur agrege (post-demarrage, #15157) =="
assert_ci_slice 2>&1 | sed 's/^/ /'
report_slice_membership 2>&1 | sed 's/^/ /'
echo "== conteneurs runner en cours =="
docker ps --filter "name=$NAME_PREFIX" --format ' {{.Names}} {{.Status}} {{.RunningFor}}' 2>/dev/null || true
echo "== runners enregistres cote GitHub =="
Expand Down
58 changes: 58 additions & 0 deletions scripts/ci/docker/linux-runner/test_supervise_guards.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ if [ "\$1" = "run" ]; then
esac
exit 0
fi
# Garde d'appartenance du mur agrege (#15157) : la liste d'IDs rendue au
# filtre label=coursia-ci=1 est pilotable par le test via STUB_CI_CONTAINER_IDS.
if [ "\$1" = "ps" ] && echo "\$*" | grep -q 'label=coursia-ci=1'; then
if [ -n "\$STUB_CI_CONTAINER_IDS" ]; then printf '%s\n' \$STUB_CI_CONTAINER_IDS; fi
exit 0
fi
exit 0
STUB
chmod +x "$TEST_DIR/bin/docker"
Expand Down Expand Up @@ -2462,6 +2468,58 @@ STUB
)
echo ""

# --- Test 53 : garde d'appartenance -- evasion visible (#15157) -------------
#
# Le defaut repare ici n'est pas un plafond manquant mais une appartenance
# INVARIABLEMENT annoncee saine : assert_ci_slice rendait « mur ACTIF » alors
# qu'aucun conteneur n'y etait -- le daemon de la flotte ne porte pas de
# cgroup-parent par defaut (mesure 2026-09-08), l'entree depend du drapeau,
# et un conteneur lance a la main echappe en silence. Deux conteneurs
# declares par le stub docker, zero sous-groupe dans la slice : la ligne
# EVASION doit exister, avec son compte.
echo "Test 53 : conteneurs hors slice -> EVASION visible (#15157)"
(
cd "$SCRIPT_DIR"
unset PS_OUTPUT
source_supervise
mkdir -p "$TEST_DIR/slice-mursansloc"
echo "17179869184" > "$TEST_DIR/slice-mursansloc/memory.max"
echo "12884901888" > "$TEST_DIR/slice-mursansloc/memory.high"
CI_SLICE_PATH="$TEST_DIR/slice-mursansloc"
# EXPORT obligatoire : le stub docker est un PROCESSUS FILS -- une variable
# shell nue ne franchit pas la frontiere, et le garde lirait 0 conteneur.
export STUB_CI_CONTAINER_IDS="aaa111 bbb222"
out="$(report_slice_membership 2>&1)"
if echo "$out" | grep -q "EVASION" && echo "$out" | grep -q "conteneurs=2"; then
ok "evasion nommee avec son compte (conteneurs=2, sous-groupes=0)"
else
ko "ligne EVASION attendue, out=$out"
fi
unset STUB_CI_CONTAINER_IDS
)
echo ""

# --- Test 54 : garde d'appartenance -- cas nominal sans faux positif --------
echo "Test 54 : conteneurs dans la slice -> appartenance OK, pas d'EVASION (#15157)"
(
cd "$SCRIPT_DIR"
unset PS_OUTPUT
source_supervise
mkdir -p "$TEST_DIR/slice-muravecloc/id-aaa111" "$TEST_DIR/slice-muravecloc/id-bbb222"
echo "17179869184" > "$TEST_DIR/slice-muravecloc/memory.max"
echo "12884901888" > "$TEST_DIR/slice-muravecloc/memory.high"
CI_SLICE_PATH="$TEST_DIR/slice-muravecloc"
export STUB_CI_CONTAINER_IDS="aaa111 bbb222"
out="$(report_slice_membership 2>&1)"
if echo "$out" | grep -q "appartenance OK" && ! echo "$out" | grep -q "EVASION"; then
ok "appartenance nominale annoncee, pas de faux positif ($out)"
else
ko "appartenance OK attendue sans EVASION, out=$out"
fi
unset STUB_CI_CONTAINER_IDS
)
echo ""


# --- Verdict agrege ---------------------------------------------------------
# `|| echo 0` serait un piege ici, et il l'a ete : `grep -c` IMPRIME "0" avant
Expand Down
Loading