fix(ops): run heavy commands under a hard memory cap to stop host OOM kills
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 35s
CI / tests-integration (push) Successful in 2m5s
CI / tests-unit (push) Successful in 2m30s
CI / tests-ui (push) Successful in 3m3s
CI / preflight (push) Skipped
CI / deploy (push) Failing after 44s
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 35s
CI / tests-integration (push) Successful in 2m5s
CI / tests-unit (push) Successful in 2m30s
CI / tests-ui (push) Successful in 3m3s
CI / preflight (push) Skipped
CI / deploy (push) Failing after 44s
The host runs with vm.overcommit_memory=0 and no swap, so a process that
grows past free memory makes the kernel OOM-kill across the whole machine
-- the Turbopack build (commit 3d828a61) could take out the database,
nginx or the live release.
Add scripts/with-memory-cap.sh: it moves a command into its own systemd
scope with MemoryMax, so only that cgroup gets OOM-killed (verified: a
Turbopack build died at its 6GB cap, host untouched). Build/analyze/dev/
test*/typecheck now run under explicit caps; ulimit -v is only an explicit
opt-in because it bounds virtual address space per process and 10g/20g both
break V8-based builds. Docker and GitLab builds run in their own isolated
containers with a read-only cgroupfs and opt out explicitly (webpack +
--max-old-space-size stay their bound).
Measured: webpack build peaks ~6.5GB RSS, so 10GB leaves headroom within
the 23.5GB host.
This commit is contained in:
1 parent
3265c149da
commit
0845f80768
8 files changed
+563
-393
No files matched your search
Executable
+166
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env bash
|
||||
# Voer een zwaar commando uit onder een harde geheugenplafond.
|
||||
#
|
||||
# Waarom dit bestaat:
|
||||
# De host draait met `vm.overcommit_memory=0` en ZONDER swap. Vraagt een
|
||||
# proces meer geheugen dan er vrij is, dan geeft de kernel niets weg en
|
||||
# roept hij meteen de OOM-killer aan. Die kiest zijn slachtoffer over de
|
||||
# héle machine, niet alleen in het schuldige proces — dus een build kan de
|
||||
# database, nginx of de live release meenemen.
|
||||
#
|
||||
# `next build` op Turbopack groeit op dit 329-route app voorbij 12GB RSS
|
||||
# en is ook met 4GB swap nog steeds dood. Zie commit 3d828a61.
|
||||
#
|
||||
# Wat dit doet:
|
||||
# Het commando komt in zijn eigen cgroup met `MemoryMax`. Als het door die
|
||||
# grens heen groeit krijgt alleen die cgroup een OOM-signaal: het commando
|
||||
# zelf sterft, de rest van de machine leeft. Dat is precies het gedrag dat
|
||||
# je wilt — de build faalt, de site blijft staan.
|
||||
#
|
||||
# Met `--max-old-space-size` lukt dat niet. Die limiet zit op de V8-heap en
|
||||
# Turbopack-geheugen is native Rust-geheugen; met een 2GB cap piekte de RSS
|
||||
# alsnog op 8GB. Zie het commentaar in de Dockerfile.
|
||||
#
|
||||
# Backends:
|
||||
#
|
||||
# systemd (cgroup MemoryMax)
|
||||
# Meet RSS over de héle procesboom. Dit is de echte garantie en wordt
|
||||
# overal gebruikt waar systemd beschikbaar is (de host waar deze
|
||||
# CMS draait). De RSS-plafonds in package.json zijn hierop gekozen:
|
||||
# `next build` piekte op 6,5GB, dus 10GB laat ruimte over terwijl er
|
||||
# 6GB basislast naast blijft passen binnen de 23,5GB van deze machine.
|
||||
#
|
||||
# ulimit -v (per proces, virtuele adresruimte)
|
||||
# Alleen als expliciet gevraagd. Meet virtuele adresruimte, NIET RSS, en
|
||||
# kan de boom helemaal niet begrenzen: elke worker krijgt z'n eigen
|
||||
# limiet. Op moderne V8 is het bovendien een vergiftigde gift: `-v 10g`
|
||||
# laat V8 de heaplimiet terugbrengen naar 2,25GB (webpack sterft met
|
||||
# std::bad_alloc), en `-v 20g` laat de v8-wasm-memory-toewijzing falen
|
||||
# tijdens `next build`. Zet CMS_MEMORY_CAP_VIRTUAL ruim boven de fysieke
|
||||
# RAM als je het echt wilt gebruiken.
|
||||
#
|
||||
# Geen van beide -> weigeren. Stil onbegrensd doorlopen zou precies de
|
||||
# valse geruststelling zijn waar 3d828a61 voor waarschuwt. Omgevingen
|
||||
# zonder systemd (de Docker-build, de GitLab-runner) kiezen daarom
|
||||
# expliciet voor CMS_MEMORY_CAP_BACKEND=none — met een waarschuwing,
|
||||
# en met als rechtvaardiging dat die builds al begrensd zijn door
|
||||
# `next build --webpack` + `--max-old-space-size` en in hun eigen
|
||||
# geïsoleerde container draaien, niet op de host.
|
||||
#
|
||||
# Gebruik: bash scripts/with-memory-cap.sh 10g <command...>
|
||||
# Backend kiezen: CMS_MEMORY_CAP_BACKEND=systemd|ulimit|none|auto
|
||||
# ulimit-waarde kiezen: CMS_MEMORY_CAP_VIRTUAL=40g
|
||||
set -Eeuo pipefail
|
||||
|
||||
usage() {
|
||||
echo "gebruik: $0 <plafond, bv. 10g> <command...>" >&2
|
||||
exit 64
|
||||
}
|
||||
|
||||
[ "$#" -ge 2 ] || usage
|
||||
cap="$1"
|
||||
shift
|
||||
|
||||
# Zet 10g / 512m / 2G / 1234567 om in bytes. Alleen bytes gaan naar
|
||||
# systemd: MemoryMax accepteert `10G` maar weigert `10g`, en die
|
||||
# hoofdletterval is te makkelijk om per ongeluk te treffen.
|
||||
to_bytes() {
|
||||
local value="$1" number suffix
|
||||
if [[ "$value" =~ ^([0-9]+)([kKmMgGtT]?)$ ]]; then
|
||||
number="${BASH_REMATCH[1]}"
|
||||
suffix="${BASH_REMATCH[2]}"
|
||||
else
|
||||
echo "onbekend plafond-formaat: $value" >&2
|
||||
return 1
|
||||
fi
|
||||
case "$suffix" in
|
||||
k | K) echo $((number * 1024)) ;;
|
||||
m | M) echo $((number * 1024 * 1024)) ;;
|
||||
g | G) echo $((number * 1024 * 1024 * 1024)) ;;
|
||||
t | T) echo $((number * 1024 * 1024 * 1024 * 1024)) ;;
|
||||
*) echo "$number" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
bytes="$(to_bytes "$cap")" || exit 64
|
||||
kilobytes=$((bytes / 1024))
|
||||
|
||||
# De virtuele waarde voor ulimit -v. Bewust los van `cap`: zie de toelichting
|
||||
# hierboven, 10g -v breekt de webpack-build.
|
||||
virtual_bytes="$(to_bytes "${CMS_MEMORY_CAP_VIRTUAL:-40g}")" || exit 64
|
||||
virtual_kb=$((virtual_bytes / 1024))
|
||||
|
||||
backend="${CMS_MEMORY_CAP_BACKEND:-auto}"
|
||||
systemd_cmd=()
|
||||
|
||||
# Echt proberen, niet alleen uitzoeken of het bestand bestaat: `systemd-run`
|
||||
# zonder rechten faalt met "Access denied", en dat moet dan een nette
|
||||
# terugval naar ulimit worden in plaats van een kapotte build.
|
||||
probe_systemd() {
|
||||
command -v systemd-run >/dev/null 2>&1 || return 1
|
||||
[ -d /run/systemd/system ] || return 1
|
||||
if systemd-run --scope --quiet true 2>/dev/null; then
|
||||
systemd_cmd=(systemd-run --scope --quiet)
|
||||
return 0
|
||||
fi
|
||||
if systemd-run --user --scope --quiet true 2>/dev/null; then
|
||||
systemd_cmd=(systemd-run --user --scope --quiet)
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
run_systemd() {
|
||||
echo "[mem-cap] systemd cgroup MemoryMax=$((bytes / 1024 / 1024 / 1024))GB: $*" >&2
|
||||
exec "${systemd_cmd[@]}" -p "MemoryMax=$bytes" "$@"
|
||||
}
|
||||
|
||||
run_ulimit() {
|
||||
echo "[mem-cap] ulimit -v ${virtual_kb}KB per proces (geen systemd; RSS-plafond ${cap} niet meetbaar zonder cgroup): $*" >&2
|
||||
if [ "$virtual_bytes" -lt $((16 * 1024 * 1024 * 1024)) ]; then
|
||||
echo "[mem-cap] let op: -v onder 16g verlaagt V8's heaplimiet en breekt de build; verhoog CMS_MEMORY_CAP_VIRTUAL" >&2
|
||||
fi
|
||||
ulimit -v "$virtual_kb" || {
|
||||
echo "[mem-cap] ulimit -v $virtual_kb werd geweigerd" >&2
|
||||
return 1
|
||||
}
|
||||
exec "$@"
|
||||
}
|
||||
|
||||
run_refuse() {
|
||||
echo "[mem-cap] geen systemd hier; weiger onbegrensd te draaien." >&2
|
||||
echo "[mem-cap] zet CMS_MEMORY_CAP_BACKEND=none om dit bewust te accepteren, of =ulimit voor een per-proces vangnet." >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
run_opted_out() {
|
||||
echo "[mem-cap] WAARSCHUWING: plafond bewust uitgeschakeld, dit commando kan de machine laten OOM-killed worden: $*" >&2
|
||||
exec "$@"
|
||||
}
|
||||
|
||||
case "$backend" in
|
||||
systemd)
|
||||
probe_systemd || {
|
||||
echo "[mem-cap] CMS_MEMORY_CAP_BACKEND=systemd maar systemd-run reageert niet" >&2
|
||||
exit 70
|
||||
}
|
||||
run_systemd "$@"
|
||||
;;
|
||||
ulimit)
|
||||
run_ulimit "$@"
|
||||
;;
|
||||
none | off)
|
||||
run_opted_out "$@"
|
||||
;;
|
||||
auto)
|
||||
if probe_systemd; then
|
||||
run_systemd "$@"
|
||||
else
|
||||
run_refuse "$@"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "[mem-cap] onbekende backend: $backend" >&2
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
Reference in new issue
Block a user