SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-08-28

Jak ograniczyć zużycie CPU i RAM przez systemd

Dowiedz się, jak poprawnie skonfigurować MemoryMax, MemoryHigh oraz CPUQuota w plikach drop-in. Poznaj parametry cgroup v2, które zapobiegają zawieszaniu systemu przez procesy.

Ograniczanie pamięci i procesora procesu za pomocą pliku drop-in systemd

Ograniczenie pamięci i procesora procesu na serwerze VPS z systemem Linux polega na dodaniu kilku linii do jednostki (unit), która uruchamia dany proces. MemoryMax= stanowi twardy limit pamięci. CPUQuota= stanowi limit czasu procesora. Oba parametry są wymuszane przez cgroup v2 (control groups, wersja 2), czyli funkcję jądra, z której systemd korzysta do rozliczania każdej usługi w systemie.

sudo systemctl edit myapp.service

To polecenie otwiera plik drop-in z instrukcjami w komentarzach. Należy dodać poniższą treść powyżej nich:

[Service]
MemoryHigh=512M
MemoryMax=768M
MemorySwapMax=0
CPUQuota=80%
TasksMax=128
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
systemctl show myapp.service -p MemoryHigh -p MemoryMax -p CPUQuotaPerSecUSec -p TasksMax

systemctl show musi zwrócić podane wartości w jednostkach używanych przez jądro: MemoryMax=805306368 oraz CPUQuotaPerSecUSec=800ms. Jeśli polecenie wyświetli MemoryMax=infinity, oznacza to, że plik drop-in nie został wczytany. Należy sprawdzić, czy plik znajduje się w /etc/systemd/system/myapp.service.d/override.conf oraz czy zaczyna się od nagłówka [Service], ponieważ linia z ustawieniami bez poprzedzającej sekcji powoduje, że systemd rejestruje Assignment outside of section. Ignoring. i uruchamia usługę bez żadnych ograniczeń.

Dalsza część tego przewodnika wyjaśnia, jak dobrać te wartości oraz co może pójść nie tak po ich ustawieniu.

Dlaczego proces typu runaway zamraża VPS, mimo że nie wyczerpuje pamięci

Proces, który osiąga twardy limit pamięci, kończy działanie w ciągu sekundy, a usługa jest restartowana. To wariant optymistyczny. Wariant pesymistyczny występuje, gdy nic nie zostaje przerwane: serwer odpowiada na ping, SSH akceptuje połączenie, ale znak zachęty powłoki nigdy się nie pojawia. Maszyna jest aktywna i zajęta, lecz żadna z wykonywanych operacji nie jest użyteczna.

Oto mechanizm tego zjawiska, ponieważ nie jest on oczywisty. Gdy ilość wolnej pamięci spada, jądro odzyskuje strony zamiast przydzielać nowe. Najtańszymi do odzyskania stronami są te oparte na plikach, a pamięć podręczna stron (page cache) przechowuje kod wykonywalny wszystkich uruchomionych procesów. Jądro usuwa strony kodu sshd, a każda kolejna instrukcja wykonywana przez sshd powoduje błąd strony (page fault), który wymaga odczytania tych bajtów z nośnika. Każdy proces kończy oczekiwaniem na dysk, zamiast wykonywać obliczenia. Te same strony są usuwane i wczytywane w pętli, co nazywamy thrashingiem.

Dwie rzeczy sprawiają, że na VPS jest to bardziej odczuwalne niż na laptopie. Pamięć masowa jest często sieciowa lub współdzielona, więc każdy błąd strony kosztuje więcej milisekund niż w przypadku lokalnego urządzenia NVMe. Ponadto jądro nie mierzy czasu, lecz niepowodzenia: dopóki proces odzyskiwania stron zwraca stronę, niezależnie od tego, jak wolno to przebiega, jądro uznaje, że czyni postępy i nie uruchamia mechanizmu OOM (out of memory) killer. Serwer może pozostawać w tym stanie przez wiele minut, zanim cokolwiek zostanie przerwane.

Można obserwować ten proces. Jądro udostępnia informacje o zatorach (PSI) w systemie Linux 4.20 i nowszych:

cat /proc/pressure/memory
cat /proc/pressure/io
some avg10=63.72 avg60=41.02 avg300=12.33 total=13729481
full avg10=48.15 avg60=30.44 avg300=8.90 total=9114233

Linia full jest kluczowa. full avg10=48.15 oznacza, że w ciągu ostatnich dziesięciu sekund przez 48% czasu każde zadanie gotowe do uruchomienia na serwerze było wstrzymane, oczekując na operacje pamięciowe, więc nic nie było przetwarzane. Zdrowy serwer wykazuje wartości bliskie zeru w full. Powyżej 10 system staje się odczuwalnie wolny dla użytkownika, a wartość 40 lub wyższa odpowiada stanowi, który określa się jako zamrożenie.

Dlatego sam limit nie stanowi gwarancji. Jednostka ograniczona przez MemoryHigh= jest dławiona zamiast przerywana, więc pozostaje aktywna i działa wolno, a nic jej nie restartuje, ponieważ z punktu widzenia systemd nie wystąpiła awaria. Ograniczona jednostka, która nadal może korzystać ze swapu, generuje operacje odczytu i zapisu, które są przypisywane do tej jednostki, ale obsługiwane przez jedno współdzielone urządzenie, co może podnieść /proc/pressure/io dla każdej innej usługi na serwerze. Limity decydują o tym, kto ponosi koszty niedoboru, ale nie tworzą dodatkowej wydajności.

Sprawdzenie, czy VPS korzysta z cgroup v2

stat -fc %T /sys/fs/cgroup

cgroup2fs to ujednolicona hierarchia, która jest wymagana dla wszystkich poniższych ustawień. tmpfs oznacza, że system uruchomił się w starszym układzie v1, w którym MemoryHigh= oraz MemorySwapMax= nie istnieją, a zachowanie OOM dla poszczególnych jednostek jest inne. Systemy Ubuntu 22.04 i nowsze oraz Debian 11 i nowsze domyślnie korzystają z v2. Starsze obrazy lub jądro uruchomione z parametrem systemd.unified_cgroup_hierarchy=0 nie obsługują tego standardu.

W systemie cgroup v2 systemd domyślnie włącza rozliczanie pamięci dla każdej jednostki, więc odpowiednie dane są już dostępne:

systemd-cgtop -m

Powyższe polecenie wyświetla grupy cgroup posortowane według zużycia pamięci. Jest to najszybszy sposób na ustalenie, co obciąża serwer, dopóki system pozostaje responsywny. Jeśli serwer jest nowy, konfiguracja konta oraz zapory sieciowej opisana w pierwsze dziesięć minut na nowym VPS powinna zostać wykonana przed tymi krokami.

MemoryHigh ogranicza, MemoryMax zabija.

Różnica między tymi dwoma ustawieniami pamięci decyduje o tym, jak wygląda awaria.

  • MemoryHigh= to miękki limit. Powyżej tej wartości jądro agresywnie odzyskuje pamięć z danej grupy kontrolnej (cgroup) i celowo spowalnia przydzielanie zasobów. Zużycie może przekroczyć tę wartość i żaden proces nie zostanie zabity.
  • MemoryMax= to twardy limit. Gdy przydział pamięci nie może zostać zrealizowany w ramach tego limitu, mechanizm OOM killer uruchamia się wewnątrz tej grupy i zabija jeden z procesów należących do danej jednostki.

Ta druga część to główny powód, dla którego warto ustawić MemoryMax= dla każdego procesu, któremu nie ufasz w pełni. Bez limitu niedobór pamięci staje się problemem całego serwera, a globalny OOM killer wybiera ofiarę na podstawie oom_score, co zazwyczaj oznacza największy proces. Największym procesem jest zazwyczaj baza danych, a nie skrypt, w którym wystąpił wyciek. Dzięki limitowi proces zabijania dotyczy tylko jednostki, która spowodowała problem.

Ustaw oba parametry, zachowując MemoryHigh= na poziomie około 20 do 30 procent poniżej MemoryMax=. Ta różnica stanowi strefę ostrzegawczą: powolny wyciek przekracza High i objawia się spowolnieniem usługi, podczas gdy nagły skok zużycia przebija się przez Max i powoduje natychmiastowe zakończenie procesu.

Wartości procentowe są obliczane względem zainstalowanej pamięci fizycznej, więc MemoryMax=25% na planie 4 GB oznacza 1 GB i pozostaje jedną czwartą zasobów po zmianie rozmiaru planu. MemorySwapMax=0 całkowicie chroni jednostkę przed użyciem swapu, co zamienia długotrwałe spowolnienie w szybkie i oczywiste zakończenie procesu.

Niektóre usługi pozwalają z góry określić zapotrzebowanie na pamięć zamiast je mierzyć: jednostka Ollama ustala rozmiar pamięci podręcznej KV na podstawie podanego okna kontekstowego, więc przeczytaj ile RAM-u kosztuje zwiększenie num_ctx przed ustaleniem limitu dla takiej usługi.

Limit wymaga zdefiniowania polityki restartu, w przeciwnym razie zakończenie procesu pozostawi usługę w stanie zatrzymania.

[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5

[Service]
Restart=on-failure
RestartSec=5s

StartLimit* powinno znaleźć się w [Unit], a Restart= w [Service]. Umieszczenie któregokolwiek z nich w niewłaściwej sekcji spowoduje, że systemd je zignoruje. Pięć restartów w ciągu pięciu minut oznacza wyciek, a nie chwilowy błąd, dlatego po tym czasie systemd poddaje się i pozostawia jednostkę w stanie failed. Jest to stan, który chcesz zastać później, zamiast pętli restartów (crash loop), która maskuje problem.

Ograniczanie procesora za pomocą CPUQuota lub przydzielanie zasobów przez CPUWeight

CPUQuota= przyjmuje wartość procentową czasu dostępnego na jednym rdzeniu CPU. CPUQuota=50% to połowa jednego rdzenia. CPUQuota=200% odpowiada dwóm rdzeniom, które jednostka może rozdzielić między dowolną liczbę wątków. W planie z 2 vCPU, CPUQuota=200% oznacza wykorzystanie całej maszyny.

CPUWeight= stanowi lepsze ustawienie domyślne dla większości usług. Jest to względny udział w zakresie od 1 do 10000, przy czym domyślna wartość jądra wynosi 100. Ograniczenie to aktywuje się tylko w przypadku rywalizacji o zasoby: zadanie kopii zapasowej z CPUWeight=20 ustąpi serwerowi WWW z wartością 100 w warunkach obciążenia, ale w czasie bezczynności systemu wykorzysta pełną moc obliczeniową. Sztywny limit (quota) powoduje utratę tej niewykorzystanej wydajności.

Należy realnie ocenić korzyści płynące z limitowania procesora. Proces obciążający CPU rzadko powoduje zawieszenie systemu Linux, ponieważ harmonogram zadań stale przydziela czas wszystkim procesom. To pamięć RAM jest czynnikiem, który doprowadza do awarii serwera. Warto sięgnąć po CPUQuota=, gdy wymagany jest przewidywalny górny limit, na przykład w przypadku procesu budowania lub agenta, który w przeciwnym razie pracowałby z pełną wydajnością przez godzinę. Dobór rozmiaru dla tego typu obciążeń to osobne zagadnienie, omówione w ile pamięci RAM i CPU potrzebuje VPS dla agenta programistycznego.

Jeśli procesor wykazuje wysokie zużycie, mimo że uruchomione procesy nie wykonują intensywnych zadań, przyczyna może leżeć po stronie hypervisora. Jest to zjawisko CPU steal time od hałaśliwego sąsiada, którego nie wyeliminuje żadne ustawienie limitu.

TasksMax zatrzymuje pętlę fork

TasksMax= to liczba procesów i wątków, które może posiadać jednostka. Wątki są wliczane, więc usługa w Java lub Go wymaga większego zapasu, niż sugerowałaby sama lista procesów. Jest to najtańsza ochrona przed skryptem, który wykonuje fork w pętli, ponieważ operacja fork kończy się niepowodzeniem wewnątrz jednostki, zamiast wyczerpywać identyfikatory procesów w całym systemie.

TasksMax=128

Gdy jednostka osiągnie limit, jądro systemu rejestruje w dzienniku linię wskazującą na cgroup:

cgroup: fork rejected by pids controller in /system.slice/myapp.service

Sam program zazwyczaj zgłasza fork: retry: Resource temporarily unavailable. Sprawdź, jakie wartości domyślne stosuje menedżer za pomocą systemctl show -p DefaultTasksMax.

Ograniczanie jednorazowych zadań za pomocą systemd-run

Nie ma potrzeby tworzenia pliku jednostki, aby skorzystać z tych rozwiązań. systemd-run tworzy tymczasową jednostkę wokół pojedynczego polecenia.

sudo systemd-run --scope -p MemoryMax=1G -p MemorySwapMax=0 -p CPUQuota=50% -p TasksMax=64 ./import-data.sh

--scope uruchamia polecenie w terminalu po wyświetleniu Running scope as unit: run-r7c1a....scope. Dane wyjściowe pozostają na ekranie, a limity znikają po zakończeniu działania polecenia. Każda właściwość z systemd.resource-control działa po użyciu -p.

W przypadku długotrwałego zadania należy pominąć --scope i nadać mu nazwę. Zadanie uruchomi się wtedy w tle jako tymczasowa usługa, a dziennik zostanie zapisany w journal:

sudo systemd-run --unit=nightly-import -p MemoryMax=1G -p CPUWeight=20 ./import-data.sh
journalctl -u nightly-import -f

Te same opcje działają z --user, gdy użytkownik nie posiada uprawnień root, jednak menedżer użytkownika dysponuje tylko tymi kontrolerami, które zostały mu delegowane, więc właściwość może zostać odrzucona. W takim przypadku należy uruchomić polecenie z sudo. Gdy zadanie wymaga stałej konfiguracji, ustawienia można przenieść bez zmian do właściwej jednostki: zobacz uruchamianie skryptu jako usługi i timera systemd.

Kwestia swapu, wyjaśniona szczerze

Swap zmienia charakter awarii, zamiast jej zapobiegać.

Bez swapu wyciek pamięci szybko osiąga limit i proces zostaje przerwany w ciągu kilku sekund. Awaria jest głośna, krótka i łatwa do zdiagnozowania w dzienniku systemowym. Dzięki swapowi jądro zapisuje nieużywane strony pamięci anonimowej na dysk, co pozwala zyskać czas. Jeśli proces miał się ustabilizować, swap ratuje sytuację. Jeśli jednak jest to niekontrolowany wyciek, swap zamienia pięciosekundową awarię w dwudziestominutowe zawieszenie systemu. Zawieszenie jest gorsze, ponieważ martwy proces pozwala zachować dostęp do powłoki, podczas gdy system w stanie thrashingu przestaje reagować.

swapon --show
free -h

Rozsądnym rozwiązaniem na małym VPS jest utrzymywanie niewielkiego pliku swap dla stron, które są alokowane jednorazowo i nigdy więcej nieużywane, oraz ustawienie MemorySwapMax=0 dla jednostek, których utrata jest akceptowalna. Ważne usługi zachowują dostęp do swapu. Te nieprzewidywalne szybko osiągają limit i są restartowane.

Obniżanie vm.swappiness jest mało skutecznym narzędziem i warto wiedzieć dlaczego. Zmienia ono jedynie balans między usuwaniem pamięci podręcznej stron (page cache) a przenoszeniem stron anonimowych do swapu, a oba te działania wiążą się z późniejszym odczytem z dysku. Zmienia to jedynie rodzaj stron powodujących thrashing, a nie sam fakt jego wystąpienia.

Wczesny demon OOM przerywa procesy przed wystąpieniem blokady

Jądro systemu czeka na całkowite niepowodzenie odzyskiwania pamięci, a na małym serwerze VPS ten czas oczekiwania jest momentem, w którym tracona jest responsywność maszyny. Dwa demony działające w przestrzeni użytkownika rozwiązują ten problem, samodzielnie monitorując pamięć i przerywając procesy wcześniej.

earlyoom monitoruje dostępną pamięć oraz wolną przestrzeń wymiany (swap) i przerywa proces z najwyższym wynikiem punktowym, gdy którykolwiek z tych parametrów spadnie poniżej progu.

sudo apt install earlyoom
systemctl status earlyoom

Pakiety w systemach Debian i Ubuntu uruchamiają usługę automatycznie podczas instalacji. Opcje konfiguracyjne znajdują się w /etc/default/earlyoom:

EARLYOOM_ARGS="-m 5,2 -s 5,2 --avoid '^(sshd|systemd)$' --prefer '^(node|python3)$'"

-m PERCENT określa minimalną ilość dostępnej pamięci, a -s PERCENT minimalną ilość wolnego swapu; domyślnie obie wartości wynoszą 10 procent. Druga liczba w każdej parze to punkt wysłania sygnału SIGKILL: earlyoom wysyła SIGTERM po spadku poniżej pierwszej wartości, a następnie SIGKILL poniżej drugiej, która domyślnie stanowi połowę pierwszej. Zastosuj zmiany za pomocą sudo systemctl restart earlyoom i odczytaj journalctl -u earlyoom, aby sprawdzić, który proces został przerwany i ile pamięci zajmował.

systemd-oomd to druga opcja. Strona podręcznika systemowego opisuje go jako "usługę systemową wykorzystującą cgroups-v2 oraz informacje o blokadach ciśnienia (PSI) do monitorowania i podejmowania działań naprawczych, zanim wystąpi stan OOM w przestrzeni jądra". Narzędzie to działa na całych grupach cgroups, a nie na pojedynczych procesach, więc przerywa całą jednostkę, a nie tylko pojedynczy proces potomny. Jednostki zgłaszają chęć uczestnictwa za pomocą ManagedOOMMemoryPressure=kill lub ManagedOOMSwap=kill, a progi konfiguracyjne znajdują się w /etc/systemd/oomd.conf.

systemctl status systemd-oomd
oomctl

oomctl wyświetla listę aktualnie monitorowanych elementów, co na obrazie serwerowym często oznacza brak wyników, ponieważ ustawienie wymaga aktywacji dla każdej jednostki z osobna. Wybierz jednego demona i na nim poprzestań. Uruchomienie obu narzędzi jednocześnie oznacza rywalizację dwóch mechanizmów w wyborze ofiary, co utrudnia ustalenie przyczyny przerwania procesu.

Która jednostka była odpowiedzialna?

Należy zacząć od jądra, ponieważ rejestruje ono każde wymuszone zakończenie procesu.

journalctl -k --grep "Killed process" --since "2 hours ago"

Zakończenie procesu przez globalny mechanizm OOM killer wygląda następująco:

Out of memory: Killed process 4127 (node) total-vm:2731084kB, anon-rss:1874232kB, file-rss:0kB, shmem-rss:0kB, UID:1000 pgtables:4212kB oom_score_adj:0

anon-rss to ilość pamięci RAM zajmowana przez proces w momencie jego zakończenia, w tym przypadku około 1.8 GB. Nazwę w nawiasach należy traktować jako wskazówkę. Jest to ofiara wybrana przez jądro, które wybiera proces o największym zużyciu pamięci, co nie zawsze oznacza, że to właśnie ten proces spowodował niedobór.

Zakończenie procesu z powodu limitu cgroup posiada inny przedrostek, a raport wyświetlony powyżej wskazuje nazwę cgroup, która osiągnęła swój limit:

Memory cgroup out of memory: Killed process 8811 (python3) total-vm:1044320kB, anon-rss:769112kB, file-rss:0kB, shmem-rss:0kB, UID:998 pgtables:1720kB oom_score_adj:0

Ten przedrostek stanowi większość diagnozy. Memory cgroup out of memory oznacza, że jedna jednostka osiągnęła przypisany jej MemoryMax=, podczas gdy reszta systemu działała poprawnie. Zwykły komunikat Out of memory oznacza, że pamięć wyczerpała się w całym systemie, co sugeruje, że limity były nieobecne lub zbyt wysokie.

Następnie należy sprawdzić informacje w systemd:

systemctl status myapp.service
journalctl -u myapp.service -n 50
myapp.service: A process of this unit has been killed by the OOM killer.
myapp.service: Main process exited, code=killed, status=9/KILL
myapp.service: Failed with result 'oom-kill'.

systemctl status przekazuje tę samą informację w jednej linii, jako Active: failed (Result: oom-kill).

Liczniki cgroup stanowią trzecie źródło informacji i jedyne, które rejestruje dławienie (throttling), które nie generuje żadnego wpisu w dzienniku:

cat /sys/fs/cgroup/system.slice/myapp.service/memory.events
cat /sys/fs/cgroup/system.slice/myapp.service/memory.peak
low 0
high 4213
max 118
oom 12
oom_kill 12

high zlicza, ile razy jednostka przekroczyła MemoryHigh= i została zdławiona. max zlicza, jak często osiągnięto twardy limit, a oom_kill zlicza procesy, które zostały faktycznie zakończone. Wysoka wartość high przy oom_kill 0 to wspomniany wcześniej przypadek cichej awarii: usługa działa, jest drastycznie spowolniona i nie zgłasza żadnego błędu. memory.peak (Linux 5.19 i nowsze) przechowuje najwyższe odnotowane zużycie pamięci przez cgroup; jest to wartość, w oparciu o którą należy dobrać MemoryMax=. Oba pliki są resetowane przy restarcie jednostki, ponieważ systemd tworzy cgroup od nowa.

Podstawowym warunkiem jest odpowiednia konfiguracja. Jeśli /var/log/journal nie istnieje, dziennik znajduje się w pamięci RAM, a wszystkie wpisy znikają po restarcie wymuszonym w celu przywrócenia działania systemu.

sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
journalctl --list-boots

journalctl --list-boots wskazujące na więcej niż jeden bieżący rozruch oznacza, że historia jest zachowywana, dzięki czemu journalctl -k -b -1 może wyświetlić komunikaty jądra z rozruchu, podczas którego wystąpiła awaria.

Punkt wyjścia dla małego VPS

W planie 2 GB należy pozostawić od 300 do 400 MB dla jądra systemu i pamięci podręcznej stron (page cache). Suma limitów nie powinna osiągać pełnych 2 GB, ponieważ wszystkie jednostki mogą osiągnąć szczytowe zużycie w tym samym momencie. Największy przydział zasobów należy nadać kluczowej usłudze, a następnie ograniczyć wszystkie pozostałe, mniej istotne procesy.

[Service]
MemoryHigh=256M
MemoryMax=384M
MemorySwapMax=0
CPUWeight=20
TasksMax=64
Restart=on-failure
RestartSec=5s

Zapewnienie dostępu do serwera wymaga dodatkowej konfiguracji. Użycie OOMScoreAdjust=-500 w pliku typu drop-in dla ssh.service sprawia, że globalny mechanizm OOM killer znacznie rzadziej wybierze demona SSH jako swoją ofiarę. Decyzja ta stanowi różnicę między naprawą systemu a koniecznością jego restartu z poziomu panelu sterowania. Ustawienie to zmienia jedynie wybór ofiary przez jądro systemu, nie skraca natomiast czasu zawieszenia procesu.

Kontenery działają we własnych grupach cgroups, tworzonych przez środowisko uruchomieniowe kontenerów, a nie przez pliki jednostek systemd. Z tego powodu limit nałożony na docker.service nie staje się limitem dla pojedynczego kontenera. Odpowiedniki MemoryMax= oraz CPUQuota= dla poszczególnych kontenerów zostały omówione w ustawianiu limitów pamięci i procesora w Docker Compose.

FAQ

Dlaczego mój VPS zawiesił się zamiast ubić proces zużywający zasoby?

Ponieważ jądro ocenia postęp na podstawie tego, czy odzyskiwanie pamięci zwraca strony, a nie na podstawie czasu trwania tej operacji. Gdy pamięci brakuje, system usuwa strony z pamięci podręcznej (page cache), w tym strony wykonywalne działających programów, a następnie wczytuje je ponownie przy kolejnej instrukcji. Wszystko oczekuje na operacje dyskowe, a żadna alokacja technicznie nie zakończyła się błędem, więc mechanizm OOM killer nie jest wywoływany. W takiej sytuacji należy sprawdzić /proc/pressure/memory: wartość full avg10 powyżej 40 oznacza, że w ciągu ostatnich dziesięciu sekund niemal żadne zadanie nie otrzymało czasu procesora. Demon działający w przestrzeni użytkownika, taki jak earlyoom, ubija procesy, zanim serwer osiągnie taki stan.

Jaka jest różnica między MemoryHigh a MemoryMax?

MemoryHigh= to miękki limit, który powoduje dławienie. Jądro agresywnie odzyskuje pamięć z jednostki i spowalnia jej alokacje, ale zużycie może przekroczyć tę wartość i żaden proces nie jest ubijany. MemoryMax= to twardy limit: alokacja, której nie można zrealizować w jego ramach, wywołuje OOM killer wewnątrz cgroup danej jednostki. Dzięki temu proces, który spowodował problem, zostaje zakończony, zamiast największego procesu w całym systemie. Należy ustawić MemoryHigh= poniżej MemoryMax= i traktować różnicę między nimi jako strefę ostrzegawczą.

Jak sprawdzić, którą usługę ubił OOM killer?

Należy wykonać journalctl -k --grep "Killed process" --since "2 hours ago". Linia zaczynająca się od Memory cgroup out of memory oznacza, że jednostka przekroczyła własny limit MemoryMax=, natomiast zwykły wpis Out of memory oznacza, że pamięć wyczerpała się w całym systemie. Następnie należy uruchomić journalctl -u <unit> -n 50 i wyszukać Failed with result 'oom-kill'. Jeśli /var/log/journal nie istnieje na serwerze, dziennik był przechowywany w pamięci RAM i dowody przepadły wraz z restartem; należy utworzyć ten katalog przed wystąpieniem kolejnego incydentu.

Czy warto dodać swap na małym VPS?

Niewielki plik wymiany pomaga w przypadku "zimnych" stron pamięci, które są alokowane raz i nigdy więcej nieużywane. Nie pomaga on jednak w przypadku procesu wymykającego się spod kontroli: opóźnia ubicie procesu i zamienia krótką awarię na długie zawieszenie, które uniemożliwia zalogowanie się w celu naprawy. Swap powinien być niewielki, a na jednostkach, których utrata jest akceptowalna, należy ustawić MemorySwapMax=0. Dzięki temu osiągną one swój limit i szybko się zrestartują, podczas gdy ważne usługi zachowają dostępny swap.

Czy można ograniczyć polecenie bez tworzenia pliku jednostki?

Tak. sudo systemd-run --scope -p MemoryMax=1G -p CPUQuota=50% ./script.sh uruchamia polecenie w terminalu wewnątrz tymczasowego zakresu (transient scope) z określonymi limitami, które znikają po zakończeniu pracy. Każda właściwość z systemd.resource-control jest dostępna po -p, więc MemorySwapMax=, TasksMax= oraz CPUWeight= również tam działają. Należy pominąć --scope i dodać --unit=name, aby uruchomić zadanie w tle z wyjściem przekierowanym do dziennika.