Un wrapper care caută memory.limit_in_bytes sub /sys/fs/cgroup/memory nu are ce găsi pe o gazdă configurată exclusiv cu cgroups v2. Soluția sigură nu este să reactivezi orbește ierarhiile v1, ci să identifici exact dependența, să muți controlul resurselor în unitatea systemd și să păstrezi o cale de întoarcere. Tutorialul construiește un laborator reproductibil pentru un indexator intern numit legacy-indexer: aplicația rămâne funcțională după restart, primește limite de CPU, memorie și procese, iar un timer verifică periodic cgroup-ul și scrie o alertă locală.
Ținta operațională: un indexator limitat și observabil
La final, legacy-indexer.service rulează ca utilizatorul fără login legacyindexer, citește fișiere din /var/lib/legacy-indexer/inbox și scrie sumele SHA-256 în /var/lib/legacy-indexer/state. systemd îi aplică CPUQuota=25%, MemoryHigh=192M, MemoryMax=256M și TasksMax=64. Acestea sunt valori de laborator, nu praguri universale; dimensionarea din producție trebuie bazată pe măsurători.
Serviciul nu mai cheamă cgexec, nu mai scrie direct în pseudo-fișiere v1 și nu primește capabilități Linux. Un timer rulează verificarea la cinci minute. La abatere, /usr/local/sbin/legacy-indexer-health scrie mesajul în /var/lib/legacy-indexer/monitor/alert.txt, îl trimite în journal cu prioritatea daemon.err și încheie cu status nenul. Jurnalul persistent este plafonat printr-un drop-in journald; această politică este globală pentru gazdă, fapt care trebuie acceptat înainte de instalare.
Scenariul folosește un singur server. Nu pretinde că comenzile au fost rulate pe infrastructura cititorului. Toate blocurile etichetate „output așteptat” sunt exemple; PID-ul, calea cgroup-ului, lista controllerelor, consumul și versiunile cu sufix Ubuntu pot varia.
Platforma fixată înainte de prima schimbare
Varianta documentată este Ubuntu Server 24.04.4 LTS amd64, instalare systemd, 2 vCPU, 2 GiB RAM și cel puțin 2 GiB liberi. Manifestul imaginii fixează systemd 255.4-1ubuntu8.12, Python 3.12.3-0ubuntu2.1 și Bash 5.2.21-2ubuntu4; pachetul Noble pentru stress-ng este 0.17.06-1build1. Actualizările ulterioare pot schimba sufixul complet, de aceea comenzile de mai jos rămân obligatorii. Rezervă aproximativ 45 de minute, plus fereastra aprobată de restart.
Verifică mediul, nu îl deduce din numele distribuției:
. /etc/os-release
printf 'OS=%s VERSION_ID=%sn' "$PRETTY_NAME" "$VERSION_ID"
uname -m
systemd --version | sed -n '1p'
python3 --version
bash --version | sed -n '1p'
findmnt -no FSTYPE,OPTIONS /sys/fs/cgroup
stat -fc '%T' /sys/fs/cgroup
free -h
df -h /var /etc
Output așteptat, exemplu:
OS=Ubuntu 24.04.4 LTS VERSION_ID=24.04
x86_64
systemd 255 (255.4-1ubuntu8.12)
Python 3.12.3
GNU bash, version 5.2.21(1)-release
cgroup2 rw,nosuid,nodev,noexec,relatime
cgroup2fs
Mem: 1.9Gi 412Mi 1.1Gi 5.0Mi 512Mi 1.4Gi
/dev/mapper/ubuntu--vg-ubuntu--lv 20G 8.0G 11G 43% /
/dev/mapper/ubuntu--vg-ubuntu--lv 20G 8.0G 11G 43% /
Oprește-te dacă VERSION_ID nu este 24.04, PID 1 nu este systemd sau findmnt nu raportează cgroup2. Nu adapta comenzile prin presupunere. Pentru operațiuni administrative folosește un cont nominal cu sudo, nu sesiuni obișnuite ca root; ghidul despre utilizatorul de administrare fără root direct explică separarea.
Fișierele care formează migrarea
/root/cgroup-v2-migration/
├── indexer.py # aplicația: indexează local și gestionează SIGTERM
├── legacy-indexer-health # validează serviciul și fișierele cgroup v2
├── legacy-indexer.service # identitate, izolare și limite de resurse
├── legacy-indexer-monitor.service # verificare oneshot fără privilegii
├── legacy-indexer-monitor.timer # execuție periodică a verificării
├── 60-legacy-indexer.conf # plafon global pentru jurnalul persistent
├── install.sh # backup, validare, instalare și restart controlat
└── rollback.sh # restaurare verificată și arhivarea datelor noi
Fișierele sunt legate: unitatea pornește indexer.py; timerul activează unitatea de monitorizare; aceasta execută scriptul de sănătate; instalatorul le validează și le copiază numai după backup. Aplicația scrie în journal prin ieșirea standard, deci nu creează loguri paralele greu de rotit.
Pasul 1 — Dovedește dependența de v1 înainte să atingi unitatea
De ce: prezența unei erori cu „cgroup” nu demonstrează automat o incompatibilitate v1/v2. Trebuie corelate mount-ul, controllerele v2, unitatea efectivă și referințele legacy. Comenzile sunt doar de citire.
sudo systemctl status legacy-indexer.service --no-pager --full || true
sudo systemctl cat legacy-indexer.service
sudo systemctl show legacy-indexer.service
--property=FragmentPath
--property=ControlGroup
--property=Delegate
--property=MemoryCurrent
--property=MemoryMax
--property=TasksCurrent
--property=TasksMax
printf '%sn' '--- controllere v2 disponibile ---'
cat /sys/fs/cgroup/cgroup.controllers
printf '%sn' '--- mount-uri cgroup ---'
findmnt -t cgroup,cgroup2
printf '%sn' '--- referințe legacy ---'
sudo grep -RnsE
'/sys/fs/cgroup/(cpu|memory|blkio|pids)|memory.limit_in_bytes|cpu.cfs_quota_us|cgexec|cgcreate'
/etc/systemd/system/legacy-indexer.service
/usr/local/sbin/legacy-cgexec
/opt/legacy-indexer 2>/dev/null || true
sudo journalctl -u legacy-indexer.service -b -n 50 --no-pager
-t cgroup,cgroup2 filtrează tipurile de filesystem; --property limitează răspunsul systemd la indicatorii utili; expresia grep caută căi și nume de interfețe v1. || true este intenționat doar pentru comenzile de diagnostic: absența serviciului sau a potrivirilor nu trebuie să întrerupă colectarea celorlalte dovezi.
Output așteptat în laborator, exemplu:
ControlGroup=/system.slice/legacy-indexer.service
Delegate=no
MemoryMax=[not set]
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
/usr/local/sbin/legacy-cgexec:CGROUP=/sys/fs/cgroup/memory/legacy-indexer
/usr/local/sbin/legacy-cgexec:LIMIT_FILE="$CGROUP/memory.limit_in_bytes"
legacy-cgexec: /sys/fs/cgroup/memory is unavailable
Lista din cgroup.controllers poate conține alt set de controllere, în funcție de kernel și configurație. Punct de control: continuă numai dacă gazda este v2, iar unitatea, wrapperul sau aplicația conține o referință v1. Dacă există și mount-uri v1, ai o configurație hibridă; acest tutorial nu o modifică.
Pasul 2 — Instalează uneltele într-o singură operațiune
De ce: proiectul folosește Python pentru worker, stress-ng numai pentru o verificare izolată a mecanismului de limitare și utilitarele systemd deja furnizate de sistem. Actualizarea indexului APT și instalarea sunt grupate într-un singur bloc.
sudo apt-get update
sudo apt-get install --yes python3 stress-ng
python3 --version
stress-ng --version
systemd-analyze --version | sed -n '1p'
--yes confirmă instalarea pachetelor enumerate, nu upgrade-ul întregului sistem. stress-ng nu va fi lansat în serviciul aplicației. Output așteptat, exemplu:
Python 3.12.3
stress-ng, version 0.17.06
systemd 255 (255.4-1ubuntu8.12)
Versiunea stress-ng și sufixele pachetelor variază cu actualizările repository-ului. Punct de control: toate cele trei comenzi de versiune trebuie să se încheie cu codul zero. Dacă APT raportează erori, nu continua cu fișiere parțial instalate.
Pasul 3 — Scrie workerul și verificarea periodică
De ce: aplicația nu trebuie să cunoască topologia cgroup. Ea procesează date; systemd gestionează resursele. Scriptul de sănătate citește calea reală raportată de systemd, în loc să ghicească /system.slice.
Fișierul /root/cgroup-v2-migration/indexer.py:
#!/usr/bin/python3
import hashlib
import signal
import sys
import time
from pathlib import Path
INBOX = Path("/var/lib/legacy-indexer/inbox")
STATE = Path("/var/lib/legacy-indexer/state")
stop_requested = False
seen: dict[Path, tuple[int, int]] = {}
def request_stop(signum: int, frame: object) -> None:
global stop_requested
stop_requested = True
def digest_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
while chunk := handle.read(1024 * 1024):
digest.update(chunk)
return digest.hexdigest()
def process_once() -> None:
for source in sorted(INBOX.iterdir()):
if not source.is_file():
continue
stat = source.stat()
marker = (stat.st_mtime_ns, stat.st_size)
if seen.get(source) == marker:
continue
value = digest_file(source)
destination = STATE / f"{source.name}.sha256"
temporary = destination.with_suffix(destination.suffix + ".tmp")
temporary.write_text(f"{value} {source}n", encoding="utf-8")
temporary.replace(destination)
seen[source] = marker
print(f"indexed file={source.name!r} sha256={value}", flush=True)
def main() -> int:
INBOX.mkdir(parents=True, exist_ok=True)
STATE.mkdir(parents=True, exist_ok=True)
while not stop_requested:
try:
process_once()
except OSError as exc:
print(f"indexer error: {exc}", file=sys.stderr, flush=True)
return 1
time.sleep(2)
print("indexer stopped cleanly", flush=True)
return 0
signal.signal(signal.SIGTERM, request_stop)
signal.signal(signal.SIGINT, request_stop)
raise SystemExit(main())
Citirea în bucăți de un MiB evită încărcarea întregului fișier în RAM; fișierul temporar urmat de replace() evită publicarea unui rezultat incomplet. SIGTERM permite oprirea controlată de systemd.
Fișierul /root/cgroup-v2-migration/legacy-indexer-health:
#!/usr/bin/env bash
set -euo pipefail
readonly UNIT="legacy-indexer.service"
readonly ALERT_DIR="/var/lib/legacy-indexer/monitor"
readonly ALERT_FILE="${ALERT_DIR}/alert.txt"
fail() {
local message="$1"
install -d -m 0750 "${ALERT_DIR}"
printf '%s ERROR %sn' "$(date --iso-8601=seconds)" "${message}" >"${ALERT_FILE}"
logger --priority daemon.err --tag legacy-indexer-monitor -- "${message}"
printf 'ERROR: %sn' "${message}" >&2
exit 1
}
systemctl is-active --quiet "${UNIT}" || fail "${UNIT} is not active"
[[ -r /sys/fs/cgroup/cgroup.controllers ]] || fail "unified cgroup v2 root is missing"
control_group="$(systemctl show --property=ControlGroup --value "${UNIT}")"
[[ "${control_group}" == /* ]] || fail "invalid ControlGroup for ${UNIT}"
cgroup_path="/sys/fs/cgroup${control_group}"
for metric in memory.current memory.high memory.max cpu.max cpu.stat pids.current pids.max; do
[[ -r "${cgroup_path}/${metric}" ]] || fail "missing cgroup metric: ${cgroup_path}/${metric}"
done
memory_current="$(<"${cgroup_path}/memory.current")"
memory_high="$(<"${cgroup_path}/memory.high")"
pids_current="$(<"${cgroup_path}/pids.current")"
pids_max="$(<"${cgroup_path}/pids.max")"
[[ "${memory_current}" =~ ^[0-9]+$ ]] || fail "memory.current is not numeric"
[[ "${pids_current}" =~ ^[0-9]+$ ]] || fail "pids.current is not numeric"
if [[ "${memory_high}" != "max" && "${memory_current}" -gt "${memory_high}" ]]; then
fail "memory.current exceeds memory.high"
fi
if [[ "${pids_max}" != "max" && "${pids_current}" -gt "${pids_max}" ]]; then
fail "pids.current exceeds pids.max"
fi
install -d -m 0750 "${ALERT_DIR}"
printf '%s OK cgroup=%s memory.current=%s pids.current=%sn'
"$(date --iso-8601=seconds)" "${control_group}"
"${memory_current}" "${pids_current}" >"${ALERT_DIR}/status.txt"
rm -f "${ALERT_FILE}"
set -euo pipefail oprește scriptul la comandă eșuată, variabilă nedefinită sau pipeline defect. logger creează alerta locală în journal; fișierul alert.txt rămâne ușor de inspectat. Output așteptat al workerului este absent până apare un fișier; monitorul sănătos nu scrie pe stdout. Punct de control:
python3 -m py_compile /root/cgroup-v2-migration/indexer.py
bash -n /root/cgroup-v2-migration/legacy-indexer-health
(nicio ieșire; cod de ieșire 0 pentru ambele comenzi)
Pasul 4 — Definește unitățile și politica de jurnal
De ce: limitele declarative sunt aplicate întregului cgroup al serviciului, inclusiv proceselor copil. MemoryHigh este pragul principal de control, iar MemoryMax este ultima limită. CPUQuota=25% înseamnă cel mult un sfert din timpul unui CPU, nu 25% din toate procesoarele gazdei.
Fișierul /root/cgroup-v2-migration/legacy-indexer.service:
[Unit]
Description=Local legacy document indexer migrated to cgroups v2
After=local-fs.target
[Service]
Type=simple
User=legacyindexer
Group=legacyindexer
ExecStart=/usr/bin/python3 /opt/legacy-indexer/indexer.py
Restart=on-failure
RestartSec=5s
CPUAccounting=yes
MemoryAccounting=yes
TasksAccounting=yes
CPUQuota=25%
MemoryHigh=192M
MemoryMax=256M
TasksMax=64
UMask=0077
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
ProtectClock=yes
RestrictSUIDSGID=yes
LockPersonality=yes
CapabilityBoundingSet=
AmbientCapabilities=
RestrictAddressFamilies=AF_UNIX
ReadWritePaths=/var/lib/legacy-indexer
[Install]
WantedBy=multi-user.target
Fișierul /root/cgroup-v2-migration/legacy-indexer-monitor.service:
[Unit]
Description=Validate legacy-indexer cgroup v2 controls
After=legacy-indexer.service
[Service]
Type=oneshot
User=legacyindexer
Group=legacyindexer
ExecStart=/usr/local/sbin/legacy-indexer-health
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
CapabilityBoundingSet=
RestrictAddressFamilies=AF_UNIX
ReadWritePaths=/var/lib/legacy-indexer/monitor
Fișierul /root/cgroup-v2-migration/legacy-indexer-monitor.timer:
[Unit]
Description=Run legacy-indexer cgroup v2 check every five minutes
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
AccuracySec=30s
Unit=legacy-indexer-monitor.service
[Install]
WantedBy=timers.target
Fișierul /root/cgroup-v2-migration/60-legacy-indexer.conf:
[Journal]
Storage=persistent
SystemMaxUse=500M
MaxRetentionSec=14day
ProtectSystem=strict face filesystemul read-only pentru serviciu, iar ReadWritePaths deschide strict directorul de stare. ProtectControlGroups=yes împiedică aplicația să modifice ierarhia. CapabilityBoundingSet= elimină toate capabilitățile. Timerul pornește după două minute și apoi la cinci minute, cu o precizie de 30 de secunde. Politica journald păstrează cel mult 500 MiB și 14 zile; fiind globală, adapteaz-o la politica organizației și capacitatea discului.
Validează sintaxa:
systemd-analyze verify
/root/cgroup-v2-migration/legacy-indexer.service
/root/cgroup-v2-migration/legacy-indexer-monitor.service
/root/cgroup-v2-migration/legacy-indexer-monitor.timer
Output așteptat:
(nicio ieșire; cod de ieșire 0)
Punct de control: orice diagnostic de la systemd-analyze verify trebuie rezolvat înainte de instalare. Pentru operarea zilnică, comenzile de bază sunt explicate separat în ghidul systemctl pentru pornire, oprire și diagnosticare.
Pasul 5 — Fă backup și execută migrarea controlată
De ce: urmează înlocuirea unității și restartul serviciului. Avertisment: pasul întrerupe temporar indexarea și repornește systemd-journald. Rulează-l numai într-o fereastră aprobată, după ce ai verificat că backupul aplicației este actual. Instalatorul salvează fiecare fișier gestionat și marchează fișierele care nu existau.
Fișierul /root/cgroup-v2-migration/install.sh:
#!/usr/bin/env bash
set -euo pipefail
readonly SOURCE_DIR="/root/cgroup-v2-migration"
readonly BACKUP_ROOT="/var/backups/legacy-indexer-cgroup-v1"
readonly STAMP="$(date +%Y%m%dT%H%M%S%z)"
readonly BACKUP_DIR="${BACKUP_ROOT}/${STAMP}"
readonly ABSENT_FILE="${BACKUP_DIR}/absent.list"
fail() {
printf 'ERROR: %sn' "$1" >&2
exit 1
}
trap 'printf "ERROR: migration stopped at line %sn" "$LINENO" >&2' ERR
[[ "${EUID}" -eq 0 ]] || fail "run install.sh with sudo"
[[ -r /sys/fs/cgroup/cgroup.controllers ]] || fail "legacy indexer requires cgroup v2"
for name in indexer.py legacy-indexer-health legacy-indexer.service
legacy-indexer-monitor.service legacy-indexer-monitor.timer 60-legacy-indexer.conf; do
[[ -f "${SOURCE_DIR}/${name}" ]] || fail "missing source file: ${name}"
done
install -d -m 0700 "${BACKUP_DIR}"
: >"${ABSENT_FILE}"
backup_path() {
local path="$1"
if [[ -e "${path}" || -L "${path}" ]]; then
(cd / && cp -a --parents "${path#/}" "${BACKUP_DIR}")
else
printf '%sn' "${path}" >>"${ABSENT_FILE}"
fi
}
for path in
/etc/systemd/system/legacy-indexer.service
/etc/systemd/system/legacy-indexer-monitor.service
/etc/systemd/system/legacy-indexer-monitor.timer
/etc/systemd/journald.conf.d/60-legacy-indexer.conf
/opt/legacy-indexer/indexer.py
/usr/local/sbin/legacy-indexer-health
/usr/local/sbin/legacy-cgexec; do
backup_path "${path}"
done
ln -sfn "${BACKUP_DIR}" "${BACKUP_ROOT}/latest"
if ! getent group legacyindexer >/dev/null; then
groupadd --system legacyindexer
touch "${BACKUP_DIR}/group.created"
fi
if ! id legacyindexer >/dev/null 2>&1; then
useradd --system --gid legacyindexer --home-dir /var/lib/legacy-indexer
--shell /usr/sbin/nologin legacyindexer
touch "${BACKUP_DIR}/user.created"
fi
install -d -o root -g root -m 0755 /opt/legacy-indexer
install -d -o legacyindexer -g legacyindexer -m 0750
/var/lib/legacy-indexer/inbox
/var/lib/legacy-indexer/state
/var/lib/legacy-indexer/monitor
install -d -o root -g root -m 0755 /etc/systemd/journald.conf.d
install -o root -g root -m 0755 "${SOURCE_DIR}/indexer.py" /opt/legacy-indexer/indexer.py
install -o root -g root -m 0755 "${SOURCE_DIR}/legacy-indexer-health" /usr/local/sbin/legacy-indexer-health
install -o root -g root -m 0644 "${SOURCE_DIR}/legacy-indexer.service" /etc/systemd/system/legacy-indexer.service
install -o root -g root -m 0644 "${SOURCE_DIR}/legacy-indexer-monitor.service" /etc/systemd/system/legacy-indexer-monitor.service
install -o root -g root -m 0644 "${SOURCE_DIR}/legacy-indexer-monitor.timer" /etc/systemd/system/legacy-indexer-monitor.timer
install -o root -g root -m 0644 "${SOURCE_DIR}/60-legacy-indexer.conf" /etc/systemd/journald.conf.d/60-legacy-indexer.conf
python3 -m py_compile /opt/legacy-indexer/indexer.py
bash -n /usr/local/sbin/legacy-indexer-health
systemd-analyze verify
/etc/systemd/system/legacy-indexer.service
/etc/systemd/system/legacy-indexer-monitor.service
/etc/systemd/system/legacy-indexer-monitor.timer
systemctl daemon-reload
systemctl restart systemd-journald.service
systemctl enable --now legacy-indexer.service
systemctl enable --now legacy-indexer-monitor.timer
systemctl start legacy-indexer-monitor.service
systemctl is-active --quiet legacy-indexer.service
systemctl is-active --quiet legacy-indexer-monitor.timer
[[ ! -e /var/lib/legacy-indexer/monitor/alert.txt ]] || fail "health alert was created"
printf 'Migration complete. Backup: %sn' "${BACKUP_DIR}"
cp -a --parents păstrează metadatele și calea relativă în backup. Contul de sistem nu are shell de login. Instalarea folosește permisiuni restrictive, apoi validează Python, Bash și unitățile înainte de restart.
Rulează:
sudo chmod 0700 /root/cgroup-v2-migration/install.sh
sudo /root/cgroup-v2-migration/install.sh
Output așteptat, exemplu:
Created symlink /etc/systemd/system/multi-user.target.wants/legacy-indexer.service → /etc/systemd/system/legacy-indexer.service.
Created symlink /etc/systemd/system/timers.target.wants/legacy-indexer-monitor.timer → /etc/systemd/system/legacy-indexer-monitor.timer.
Migration complete. Backup: /var/backups/legacy-indexer-cgroup-v1/20260926T103000+0300
Timestampul și mesajele despre symlinkuri pot varia. Punct de control: verifică existența directorului afișat, ținta linkului latest și statusul activ; nu șterge backupul după prima pornire.
Pasul 6 — Probează funcția, cgroup-ul și jurnalul după restart
De ce: active confirmă doar că procesul rulează. Testul complet trebuie să dovedească procesarea unui fișier, persistența limitelor și executarea monitorului.
sudo systemctl restart legacy-indexer.service
sudo install -o legacyindexer -g legacyindexer -m 0600 /etc/hostname
/var/lib/legacy-indexer/inbox/hostname.test
sleep 4
sudo sha256sum --check /var/lib/legacy-indexer/state/hostname.test.sha256
sudo systemctl show legacy-indexer.service
--property=ActiveState
--property=SubState
--property=ControlGroup
--property=CPUQuotaPerSecUSec
--property=MemoryCurrent
--property=MemoryHigh
--property=MemoryMax
--property=TasksCurrent
--property=TasksMax
cg="$(systemctl show --property=ControlGroup --value legacy-indexer.service)"
sudo sh -c 'printf "memory.current="; cat "$1/memory.current"; printf "memory.high="; cat "$1/memory.high"; printf "memory.max="; cat "$1/memory.max"; printf "cpu.max="; cat "$1/cpu.max"; printf "pids.current="; cat "$1/pids.current"; printf "pids.max="; cat "$1/pids.max"'
sh "/sys/fs/cgroup${cg}"
sudo systemctl start legacy-indexer-monitor.service
sudo cat /var/lib/legacy-indexer/monitor/status.txt
sudo systemctl list-timers legacy-indexer-monitor.timer --no-pager
sudo journalctl -u legacy-indexer.service -u legacy-indexer-monitor.service
--since '-10 minutes' --no-pager
sudo journalctl --disk-usage
CPUQuotaPerSecUSec=250ms corespunde cotei de 25% într-o perioadă systemd de o secundă. În cpu.max, primul număr este cota, iar al doilea perioada, în microsecunde. memory.current și pids.current sunt consumuri curente, deci variază.
Output așteptat, exemplu:
/var/lib/legacy-indexer/inbox/hostname.test: OK
ActiveState=active
SubState=running
ControlGroup=/system.slice/legacy-indexer.service
CPUQuotaPerSecUSec=250ms
MemoryHigh=201326592
MemoryMax=268435456
TasksMax=64
memory.current=12341248
memory.high=201326592
memory.max=268435456
cpu.max=25000 100000
pids.current=1
pids.max=64
2026-09-26T10:36:00+03:00 OK cgroup=/system.slice/legacy-indexer.service memory.current=12341248 pids.current=1
Valorile curente, timestampul și perioada din cpu.max pot diferi. Pentru o probă separată a limitării de memorie, fără a forța workerul de producție:
sudo systemd-run --wait --collect
--unit=legacy-indexer-memory-test
--property=MemoryMax=64M
/usr/bin/stress-ng --vm 1 --vm-bytes 128M --timeout 20s
sudo systemctl show legacy-indexer-memory-test.service
--property=Result --property=MemoryPeak
Un test care depășește limita poate fi terminat de OOM killer și poate raporta rezultat nereușit; acesta este comportamentul urmărit pentru unitatea tranzitorie, nu pentru legacy-indexer. Punct de control: fișierul trece sha256sum --check, unitatea principală rămâne activă, fișierele cgroup conțin limitele și nu există alert.txt. Include aceste verificări în auditul lunar pentru servicii, backup și loguri.
Cinci defecțiuni pe care le poți reproduce și remedia
ERROR: legacy indexer requires cgroup v2apare dininstall.shcând lipsește/sys/fs/cgroup/cgroup.controllers. Cauza este un mediu v1 sau o expunere incompletă a cgroup-ului într-un container. Remediere: oprește migrarea și verificăfindmnt -t cgroup,cgroup2; nu forța instalatorul.legacy-indexer.service: Failed to determine user credentials: No such processurmat destatus=217/USERindică lipsa utilizatorului sau grupului. Remediere:sudo getent passwd legacyindexer; sudo getent group legacyindexer, apoi recreează identitatea prin rularea instalatorului complet, nu prin eliminareaUser=.indexer error: [Errno 30] Read-only file systemînseamnă că aplicația încearcă să scrie în afara/var/lib/legacy-indexersau lipsește calea dinReadWritePaths. Remediere: păstrează datele în directoarele declarate și ruleazăsudo systemctl daemon-reload && sudo systemctl restart legacy-indexer.servicedupă corectarea unității; nu dezactivaProtectSystem.ERROR: missing cgroup metric: /sys/fs/cgroup/system.slice/legacy-indexer.service/memory.currentarată că monitorul a primit o cale dispărută ori controllerul de memorie nu este disponibil pentru unitate. Remediere:sudo systemctl restart legacy-indexer.serviceși verificăsystemctl show -p ControlGroup legacy-indexer.servicepluscat /sys/fs/cgroup/cgroup.controllers.legacy-indexer.service: Start request repeated too quickly.apare după eșecuri repetate ale workerului, în ciudaRestart=on-failure. Remediere: citește cauza cusudo journalctl -u legacy-indexer.service -b -n 100 --no-pager, corecteaz-o, apoi ruleazăsudo systemctl reset-failed legacy-indexer.service && sudo systemctl start legacy-indexer.service.
Nu trata mesajele prin creșterea arbitrară a limitelor. Corelează jurnalul cu memory.events, cpu.stat și statusul unității. Journalul poate conține nume de fișiere interne; nu îl copia integral într-un ticket public.
Întoarcerea la configurația salvată și curățenia laboratorului
Avertisment: rollbackul oprește indexatorul și timerul, apoi repornește journald și vechea unitate. Confirmă directorul indicat de linkul latest înainte de execuție. Mai întâi arhivează datele create după migrare; ele nu trebuie confundate cu backupul configurației.
Fișierul /root/cgroup-v2-migration/rollback.sh:
#!/usr/bin/env bash
set -euo pipefail
readonly BACKUP_LINK="/var/backups/legacy-indexer-cgroup-v1/latest"
fail() {
printf 'ERROR: %sn' "$1" >&2
exit 1
}
trap 'printf "ERROR: rollback stopped at line %sn" "$LINENO" >&2' ERR
[[ "${EUID}" -eq 0 ]] || fail "run rollback.sh with sudo"
backup_dir="$(readlink -f "${BACKUP_LINK}")"
[[ -d "${backup_dir}" ]] || fail "backup directory is missing"
[[ -f "${backup_dir}/absent.list" ]] || fail "absent.list is missing"
tar -C /var/lib -czf
"${backup_dir}/legacy-indexer-data-before-rollback.tar.gz" legacy-indexer
systemctl disable --now legacy-indexer-monitor.timer || true
systemctl stop legacy-indexer-monitor.service legacy-indexer.service || true
while IFS= read -r path; do
[[ -n "${path}" ]] && rm -f -- "${path}"
done <"${backup_dir}/absent.list"
for relative in
etc/systemd/system/legacy-indexer.service
etc/systemd/system/legacy-indexer-monitor.service
etc/systemd/system/legacy-indexer-monitor.timer
etc/systemd/journald.conf.d/60-legacy-indexer.conf
opt/legacy-indexer/indexer.py
usr/local/sbin/legacy-indexer-health
usr/local/sbin/legacy-cgexec; do
if [[ -e "${backup_dir}/${relative}" || -L "${backup_dir}/${relative}" ]]; then
cp -a "${backup_dir}/${relative}" "/${relative}"
fi
done
systemctl daemon-reload
systemctl restart systemd-journald.service
if [[ -f /etc/systemd/system/legacy-indexer.service ]]; then
systemctl enable --now legacy-indexer.service
fi
systemctl status legacy-indexer.service --no-pager --full || true
printf 'Rollback complete from %sn' "${backup_dir}"
Rulează scriptul numai după ce ai citit ținta linkului:
sudo readlink -f /var/backups/legacy-indexer-cgroup-v1/latest
sudo chmod 0700 /root/cgroup-v2-migration/rollback.sh
sudo /root/cgroup-v2-migration/rollback.sh
absent.list elimină doar fișierele introduse de proiect, iar bucla restaurează copiile existente. Arhiva datelor păstrează inboxul, rezultatele și alertele înainte de curățare. Dacă backupul conține user.created și vechea unitate nu folosește contul, verifică întâi ps -u legacyindexer; apoi poți executa sudo userdel legacyindexer și sudo groupdel legacyindexer. Nu șterge /var/lib/legacy-indexer înainte să verifici arhiva cu sudo tar -tzf "$backup_dir/legacy-indexer-data-before-rollback.tar.gz".
Punct de control: systemctl cat legacy-indexer.service trebuie să arate unitatea veche, iar fișierele restaurate trebuie să aibă proprietarii și permisiunile din backup. Reține însă că revenirea readuce și dependența v1; este o măsură temporară de continuitate, nu rezolvarea finală.
Lista de acceptanță pentru administrator
- ☐ Gazda raportează
cgroup2și are/sys/fs/cgroup/cgroup.controllers. - ☐ Căutarea a identificat referința v1 concretă, nu doar un mesaj generic.
- ☐ Backupul conține unitatea, wrapperul și configurația journald anterioare.
- ☐
legacyindexereste un cont de sistem fără shell de login. - ☐
systemd-analyze verify,python3 -m py_compileșibash -nse încheie cu zero. - ☐ Testul SHA-256 trece după restartul serviciului.
- ☐
memory.high,memory.max,cpu.maxșipids.maxreflectă unitatea. - ☐ Timerul este activ,
status.txteste actual șialert.txtlipsește. - ☐ Jurnalul este inspectabil, iar politica globală de retenție a fost aprobată.
- ☐ Rollbackul și arhiva datelor au fost verificate înainte de închiderea ferestrei.
Extensii utile după stabilizare
Prima direcție este dimensionarea: colectează periodic MemoryCurrent, MemoryPeak, CPUUsageNSec și TasksCurrent, apoi ajustează pragurile pe baza vârfurilor observate, nu a unei singure rulări. A doua este integrarea alertei locale cu sistemul organizației, păstrând scriptul fără secrete; agentul de monitorizare poate citi alert.txt sau evenimentul daemon.err. A treia este eliminarea definitivă a wrapperului v1 din pachetul aplicației și din documentația de deployment, după ce perioada de observație și rollback s-a încheiat.
Întrebări frecvente
De ce nu reactivez pur și simplu cgroups v1?
Tutorialul urmărește o gazdă exclusiv v2 și mută controlul resurselor în systemd. Reactivarea v1 ar ascunde dependența aplicației și ar introduce o altă configurație, netestată aici.
MemoryHigh și MemoryMax fac același lucru?
Nu. MemoryHigh este pragul principal de control sub presiune, iar MemoryMax este limita ultimă. În proiect sunt setate separat la 192 MiB și 256 MiB.
Unde găsesc alerta periodică?
Monitorul scrie abaterea în /var/lib/legacy-indexer/monitor/alert.txt și în journal cu tagul legacy-indexer-monitor. La o verificare reușită actualizează status.txt și șterge alerta veche.
Pot considera migrarea reușită dacă unitatea este active?
Nu. Acceptarea cere și procesarea fișierului de test după restart, verificarea pseudo-fișierelor cgroup v2, rularea monitorului, inspectarea jurnalului și confirmarea backupului.

















































