# Atminties ribų nustatymas su systemd ir cgroups

> systemd MemoryMax PHP-FPM: atminties riba per cgroup, kaip php.net rekomenduoja dėl PHP2Shell, su MemoryHigh, OOMPolicy ir patikros komandomis.

Šaltinis: https://phpsos.de/lt/blog/speichergrenzen-systemd-cgroups/  
Būsena: 2026-10-25  
Teikėjas: PHP SOS, PHP avarinė pagalba ir serverio sauga. Skubi pagalba per „WhatsApp“: https://wa.me/491736494914?text=Sveiki%2C%20PHP%20SOS%2C%20mums%20reikia%20pagalbos%20su%20m%C5%ABs%C5%B3%20PHP%20serveriu.%20%C4%AEmon%C4%97%3A%20%E2%80%A6%20Svetain%C4%97%3A%20%E2%80%A6

**Trumpai:** PHP2Shell pereinamajai priemonei `memory_limit=-1` PHP-FPM reikia atminties ribos už PHP ribų. Su systemd ją nustatote naudodami `MemoryHigh=` kaip stabdį ir `MemoryMax=` kaip kietą viršutinę ribą FPM paslaugos papildymo faile. Pasak php.net, `ulimit`, `LimitAS=` ar `LimitDATA=` tam netinka. Be to, patikrinkite `OOMPolicy=`, kad vienas darbinis procesas (worker) nenutrauktų visos paslaugos.

## Rekomendacija, kuri iš pradžių stebina

Tarp pereinamųjų priemonių, kurias php.net paskelbė 2026 m. spalio 21 d. dėl PHP2Shell, yra viena, privertusi daugelį administratorių sustoti: `memory_limit=-1`, taigi jokios atminties ribos pačiame PHP. Daug metų gera praktika buvo laikoma priešingai.

Ryšys su pažeidžiamumo aprašymu akivaizdus. CVE-2026-109618 atveju atminties trūkumo (Out-of-Memory) būsena gali nutraukti PHP, kol užklausos duomenys inicializuoti tik iš dalies. Pereinamoji priemonė atminties ribą iš PHP perkelia į operacinę sistemą. php.net ją aiškiai sieja su dviem sąlygomis: reikšmė turi būti užrakinta nuo perrašymo, o riba turi ateiti per konteinerį ar cgroup, o ne per `RLIMIT_AS` ar `RLIMIT_DATA`.

Kas įgyvendina tik pirmąją pusę, turi serverį be jokios atminties ribos. Šis straipsnis parodo antrąją pusę. Net ir kartu priemonė lieka pereinamoji: spraga ištaisyta tik su PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35 arba patvirtintu atgaliniu pataisų perkėlimu (backport).

## cgroups dviem sakiniais

Valdymo grupės (Control Groups), trumpai cgroups, yra Linux branduolio funkcija, kuri grupuoja procesus ir riboja jų resursus. systemd kiekvienai paslaugai automatiškai sukuria savo cgroup, ir visi paslaugos procesai, FPM paslaugos atveju master ir visi darbiniai procesai, jai priklauso.

systemd atminties parinktys nustato cgroup v2 atminties valdiklio reikšmes. Ar jūsų sistema naudoja cgroup v2, parodo trumpa komanda:

```bash
# "cgroup2fs" reiškia: cgroup v2 aktyvus
stat -fc %T /sys/fs/cgroup/
```

Dabartinės populiarių distribucijų versijos numatytai naudoja cgroup v2. Jei komanda rodo ką nors kita, tai išsiaiškinkite prieš perėjimą, nes systemd dokumentacija senas v1 parinktis apibūdina kaip pasenusias.

## systemd MemoryMax PHP-FPM nustatymas

Riba įrašoma papildymo faile, vadinamajame drop-in. Taip paketo Unit failas lieka nepakeistas, o atnaujinimas jūsų nustatymo neperrašo. Paslaugų pavadinimai skiriasi pagal distribuciją; pavyzdyje naudojamas Debian ir Ubuntu su PHP 8.3.

```ini
# Atidaro redaktorių drop-in failui: sudo systemctl edit php8.3-fpm
# Rezultatas: /etc/systemd/system/php8.3-fpm.service.d/override.conf
[Service]
MemoryAccounting=yes
MemoryHigh=1536M
MemoryMax=2G
MemorySwapMax=0
OOMPolicy=continue
```

Parinkčių apžvalga pagal systemd dokumentaciją:

| Parinktis | Poveikis | Pastaba PHP-FPM |
|---|---|---|
| `MemoryHigh=` | slopinimo riba: procesai stipriai stabdomi, atmintis susigrąžinama | tikrasis valdymo dydis, šiek tiek žemiau `MemoryMax` |
| `MemoryMax=` | absoliuti riba: jei jos neįmanoma išlaikyti, paslaugoje įsijungia OOM-killer | paskutinė gynybos linija |
| `MemorySwapMax=` | iškėlimo riba | `0` neleidžia paslaugai vietoj to trauktis į swap |
| `OOMPolicy=` | elgsena po OOM įvykio: `continue`, `stop` ar `kill` | `continue` leidžia likusiems darbiniams procesams veikti toliau |
| `LimitAS=`, `LimitDATA=` | rlimits kiekvienam procesui | pasak php.net, šiai priemonei netinka |

`OOMPolicy=` nusipelno ypatingo dėmesio. Esant `stop` ar `kill`, systemd po OOM įvykio nutraukia visą paslaugą, ir visos šios FPM paslaugos svetainės neveikia. Kuri reikšmė galioja be savo nurodymo, nustato sistemos numatytoji `DefaultOOMPolicy=`. Todėl veiksmingą reikšmę patikrinkite, o ne ją prisiimkite.

Po to paslaugą paleiskite iš naujo ir baseine užrakinkite `memory_limit`:

```bash
sudo systemctl restart php8.3-fpm
systemctl show php8.3-fpm -p MemoryHigh -p MemoryMax -p MemorySwapMax -p OOMPolicy -p MemoryCurrent
```

```ini
; Baseino failas, pvz., /etc/php/8.3/fpm/pool.d/seite1.conf
php_admin_value[memory_limit] = -1
```

Kaip veikia užrakinimas, aprašo [php_admin_value: nustatymų užrakinimas nuo perrašymo](https://phpsos.de/lt/blog/php-admin-value-sperren/).

## Tinkamos reikšmės radimas

Per siaura riba sukelia nutraukimus įprastu režimu, per dosni beveik neapsaugo. Atspirties taškas yra tikrasis naudojimas.

1. Kelias dienas stebėti dabartinį visų FPM procesų atminties poreikį, taip pat apkrovos pikų metu.
2. Nustatyti tipinį vieno darbinio proceso poreikį ir padauginti iš visų paslaugos baseinų `pm.max_children`.
3. Numatyti priedą master procesui, OPcache ir pikams.
4. `MemoryHigh=` nustatyti šiek tiek virš išmatuotos piko reikšmės, `MemoryMax=` su šiek tiek didesniu atstumu virš jos.
5. Po savaitės reikšmes patikslinti pagal matavimus ir OOM įvykius.

```bash
# Visų PHP-FPM procesų atmintis MB (Resident Set Size)
ps -C php-fpm8.3 -o rss= | awk '{s+=$1} END {printf "%.0f MB\n", s/1024}'
# Nuolatinė visų cgroups apžvalga, surikiuota pagal atmintį
systemd-cgtop -m
```

`memory_limit` reikšmę prieš perėjimą turėtumėte užsirašyti. Po atnaujinimo galite grįžti prie fiksuotos ribos PHP; kaip ją prasmingai pasirinkti, aprašyta skyriuje [memory_limit teisingas nustatymas](https://phpsos.de/lt/blog/memory-limit-richtig-setzen/).

## Stebėsena: kada riba suveikia

Atminties riba, kurios suveikimo niekas nepastebi, yra tylus klaidų šaltinis. Branduolys skaičiuoja OOM įvykius kiekvienai cgroup, o systemd ir branduolio žurnalas juos užfiksuoja.

```bash
# Paslaugos cgroup OOM skaitiklis (kelias esant cgroup v2)
cat /sys/fs/cgroup/system.slice/php8.3-fpm.service/memory.events
# OOM-killer pranešimai branduolio žurnale
journalctl -k --since "-1 day" | grep -i "out of memory"
```

Jei skaitiklis `oom_kill` auga, branduolys nutraukia darbinius procesus. Tai gali rodyti per siaurą ribą, klaidingą programą arba neįprastas užklausas. Dabartinėje padėtyje verta tokius įvykius palyginti su prieigos žurnalais tuo pačiu laiku.

> **Svarbu:**
> Atminties riba yra pereinamoji priemonė ir nepakeičia PHP atnaujinimo. Atnaujinimą planuokite lygiagrečiai ir po jo iš naujo paleiskite visus PHP procesus. Visų pereinamųjų priemonių apžvalga pateikta skyriuje [Skubios priemonės, kai dar negalite pataisyti](https://phpsos.de/lt/php2shell/sofortmassnahmen-ohne-patch/).

Kas vyksta PHP viduje trūkstant atminties, paaiškina [memory_limit ir Out-of-Memory](https://phpsos.de/lt/php/out-of-memory/). Su Apache ir mod_php ta pati technika galioja Apache paslaugai, tačiau PHP tada ribą dalijasi su pačiu žiniatinklio serveriu.

## Išvada: nepamirškite antros pusės

`memory_limit=-1` yra pateisinama pereinamoji priemonė tik kartu su išorine riba. systemd šią ribą įgalina keliomis eilutėmis: `MemoryHigh` kaip stabdį, `MemoryMax` kaip kietą viršutinę ribą, `MemorySwapMax` nuo traukimosi į swap ir sąmoningai pasirinktą `OOMPolicy`. Išmatuokite prieš tai, stebėkite po to ir turėkite omenyje, kad visi paslaugos baseinai ribą dalijasi.

Visų pirma: suplanuokite priemonės pabaigą. Po atnaujinimo į ištaisytą PHP versiją grįžkite prie fiksuoto `memory_limit`. cgroup ribą galite palikti kaip papildomą apsaugą nuo išsiplečiančių procesų.

## Kaip PHP SOS padeda konkrečiai?

- **Matuoti:** užfiksuojame jūsų PHP paslaugų atminties poreikį ir iš jo išvedame tinkamas ribas.
- **Įgyvendinti:** nustatome drop-in failus su `MemoryHigh`, `MemoryMax` ir tinkama `OOMPolicy`, užrakiname `memory_limit` baseine ir kontroliuojamai iš naujo paleidžiame paslaugas.
- **Stebėti:** OOM įvykius įtraukiame į stebėseną ir koreguojame reikšmes.
- **Nuvesti iki pataisos:** per [PHP pataisų paslaugą](https://phpsos.de/lt/leistungen/php-patch-service/) įdiegiame atnaujinimą ir po to tvarkingai atšaukiame pereinamąsias reikšmes.


## Dažni klausimai

**Kodėl memory_limit turi būti -1? Ar tai nepavojinga?**  
Vienas pats būtų. php.net memory_limit=-1 rekomenduoja kaip pereinamąją priemonę aiškiai kartu su atminties riba per konteinerį ar cgroup. Apribojimas taip iš PHP persikelia į operacinę sistemą. Be šios išorinės ribos reikšmės nenustatykite.

**Ar vietoj to galiu naudoti ulimit ar LimitAS=?**  
Ne. php.net aiškiai atkalba nuo RLIMIT_AS ir RLIMIT_DATA, o būtent šias ribas nustato ulimit -v, LimitAS= ir LimitDATA=. systemd dokumentacija taip pat pažymi, kad LimitAS= riboja adresų erdvę, o ne atminties naudojimą.

**Kas atsitinka, kai PHP-FPM pasiekia ribą?**  
Esant MemoryHigh branduolys stabdo procesus ir susigrąžina atmintį. Esant MemoryMax paslaugos viduje įsijungia OOM-killer ir nutraukia procesą, dažniausiai didelį darbinį procesą (worker). Ar po to sustoja visa paslauga, nustato OOMPolicy=.

**Ar riba galioja kiekvienam baseinui atskirai?**  
Ne. Visi vienos FPM paslaugos baseinai dalijasi viena cgroup ir todėl bendra riba. Atskiroms ribos kiekvienai svetainei reikia atskirų FPM paslaugų ar konteinerių.

**Kaip nustatyti ribą Docker ar Kubernetes?**  
Per konteinerio atminties apribojimą, pavyzdžiui, parinktį --memory su docker run arba resources.limits.memory Kubernetes. Abu taip pat naudoja cgroups. Principas tas pats: riba už PHP ribų, memory_limit PHP užrakintas.

## Šaltiniai

- [systemd.resource-control (MemoryMax, MemoryHigh)](https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html)
- [systemd.service (OOMPolicy)](https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html)
- [php.net: saugumo pranešimai](https://www.php.net/)
- [PHP vadovas: memory_limit](https://www.php.net/manual/en/ini.core.php)
