SSD Nodes Learn 🎉 VPS od $5.50/mies.
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-08-13

Ograniczanie pamięci i CPU procesu przez systemd

Dowiedz się, jak skonfigurować MemoryMax oraz CPUQuota w systemd, aby zapobiec zawieszaniu VPS przez procesy. Sprawdź, jak weryfikować limity cgroup v2 i unikać błędu Unit not loaded.

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 uruchamiającej 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, którą systemd wykorzystuje 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ć wprowadzone 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 rozpoczyna się od nagłówka [Service], ponieważ linia z ustawieniami bez poprzedzającej sekcji powoduje, że systemd loguje Assignment outside of section. Ignoring. i uruchamia usługę bez żadnych ograniczeń.

Dalsza część tego przewodnika opisuje sposób doboru tych wartości oraz problemy, które mogą wystąpić po ich ustawieniu.

Dlaczego proces typu runaway zamraża VPS, mimo że nie wyczerpuje zasobów

Proces, który osiąga twardy limit pamięci, kończy działanie w około sekundę, a usługa jest restartowana. To scenariusz optymistyczny. Scenariusz pesymistyczny występuje wtedy, gdy żaden proces nie zostaje zakończony: serwer odpowiada na ping, SSH akceptuje połączenie, ale znak zachęty powłoki nigdy się nie pojawia. Maszyna jest aktywna i obciążona, ale ż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ńsze do odzyskania są strony oparte na plikach, a pamięć podręczna stron (page cache) przechowuje kod wykonywalny wszystkich uruchomionych procesów. Jądro usuwa strony tekstowe sshd, a kolejna instrukcja, którą sshd próbuje wykonać, wywołuje błąd strony (page fault), co wymusza odczyt tych bajtów z nośnika. Każdy proces kończy oczekiwaniem na dysk zamiast wykonywaniem obliczeń. Te same strony są usuwane i wczytywane w pętli, co nazywamy thrashingiem.

Dwie rzeczy sprawiają, że na VPS sytuacja ta jest gorsza niż na laptopie. Pamięć masowa często jest 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 wywołuje mechanizmu OOM (out of memory) killer. Serwer może pozostawać w tym stanie przez wiele minut, zanim jakikolwiek proces zostanie zakończony.

Można obserwować ten proces. Jądro udostępnia informacje o zatorach (pressure stall information – 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 48% czasu każde uruchomione zadanie na serwerze było wstrzymane w oczekiwaniu na operacje pamięciowe, więc nic nie było przetwarzane. Sprawny 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 to stan, który określa się jako zamrożenie.

Dlatego też sam limit nie stanowi gwarancji. Jednostka ograniczona przez MemoryHigh= jest dławiona zamiast kończona, więc pozostaje aktywna, działa wolno i 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 koszt niedoboru zasobów, ale nie zwiększają dostępnej pojemności.

Sprawdzenie, czy VPS korzysta z cgroup v2

stat -fc %T /sys/fs/cgroup

cgroup2fs to ujednolicona hierarchia, której wymagają wszystkie poniższe ustawienia. 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 używają v2 domyślnie. Stary obraz systemu lub jądro uruchomione z parametrem systemd.unified_cgroup_hierarchy=0 nie korzystają z tego rozwiązania.

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

systemd-cgtop -m

To polecenie wyświetla listę cgroups posortowaną 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 i zapory sieciowej opisana w pierwszych dziesięciu minutach na nowym VPS powinna zostać wykonana przed tymi krokami.

Ograniczenia MemoryHigh i MemoryMax.

Różnica między tymi dwoma ustawieniami pamięci decyduje o sposobie obsługi awarii.

  • MemoryHigh= to miękki limit. Po jego przekroczeniu jądro systemu agresywnie odzyskuje pamięć z danej grupy kontrolnej (cgroup) i celowo spowalnia przydzielanie zasobów. Zużycie może przekroczyć tę wartość, a procesy nie są przerywane.
  • 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 przerywa jeden z procesów danej jednostki.

To drugie rozwiązanie jest głównym powodem, 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 proces zużywający najwięcej pamięci. Największym procesem jest zazwyczaj baza danych, a nie skrypt, w którym wystąpił wyciek. Dzięki limitowi przerwanie 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 tworzy strefę ostrzegawczą: powolny wyciek przekracza High i objawia się spowolnieniem usługi, podczas gdy nagły skok zużycia przebija Max i powoduje jej przerwanie.

Wartości procentowe są obliczane względem zainstalowanej pamięci fizycznej, więc MemoryMax=25% na serwerze z 4 GB RAM oznacza 1 GB i pozostaje jedną czwartą zasobów po zmianie planu na większy. MemorySwapMax=0 całkowicie wyklucza jednostkę z korzystania ze swapu, co zamienia długotrwałe spowolnienie w szybkie i czytelne przerwanie procesu.

Limit wymaga zdefiniowania polityki restartu, w przeciwnym razie przerwanie procesu pozostawi usługę w stanie zatrzymanym.

[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5

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

StartLimit* należy umieścić 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 świadczy o wycieku, a nie o chwilowym błędzie, dlatego po tym czasie systemd przerywa próby i pozostawia jednostkę w stanie awarii. Jest to stan, który pozwala później zdiagnozować problem, zamiast ukrywać go w pętli ciągłych restartów.

Ograniczanie procesora za pomocą CPUQuota lub współdzielenie zasobów przez CPUWeight

CPUQuota= pobiera procent czasu dostępnego na jednym rdzeniu procesora. CPUQuota=50% to połowa jednego rdzenia. CPUQuota=200% to odpowiednik dwóch rdzeni, które jednostka może rozdzielić między dowolną liczbę wątków. W planie z 2 vCPU, CPUQuota=200% oznacza całą maszynę.

CPUWeight= jest lepszym ustawieniem domyślnym dla większości usług. Jest to względny udział w zakresie od 1 do 10000, przy czym wartością domyślną jądra jest 100. Mechanizm ten aktywuje się tylko w przypadku rywalizacji o zasoby: zadanie kopii zapasowej z CPUWeight=20 ustępuje serwerowi WWW z wartością 100 w warunkach obciążenia, ale w stanie bezczynności wykorzystuje całą dostępną moc maszyny. Sztywny limit (quota) powoduje utratę tej niewykorzystanej wydajności.

Należy realistycznie 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ęć operacyjna jest czynnikiem prowadzącym do awarii systemu. Warto sięgnąć po CPUQuota= w celu ustalenia przewidywalnego górnego limitu, na przykład dla 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ń jest osobnym zagadnieniem, omówionym w ile pamięci RAM i CPU potrzebuje VPS dla agenta programistycznego.

Jeśli odczyty wskazują na wysokie obciążenie procesora, mimo że żaden z procesów nie wykonuje intensywnych zadań, przyczyna może leżeć po stronie hypervisora. Jest to czas kradzieży CPU (steal time) od sąsiadującego użytkownika, a żadne ustawienie limitu nie zmieni tej sytuacji.

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 napisana 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 zapisuje 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ślnie stosuje menedżer za pomocą systemctl show -p DefaultTasksMax.

Ograniczanie jednorazowych zadań za pomocą systemd-run

Nie ma potrzeby tworzenia pliku jednostki, aby korzystać z tych funkcji. 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 komunikatu 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 usługa tymczasowa i będzie logować dane do 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 oddelegowane, więc właściwość może zostać odrzucona. W takim przypadku należy uruchomić polecenie z sudo. Gdy zadanie zyska stałe miejsce, 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, co powoduje natychmiastowe zakończenie procesu. Awaria jest głośna, krótka i łatwa do zdiagnozowania w dziennikach systemowych. Dzięki swapowi jądro zapisuje nieużywane strony pamięci anonimowej na dysk, zyskując czas. Jeśli proces miał się ustabilizować, swap ratuje sytuację. Jeśli jednak proces jest niekontrolowany, swap zamienia pięciosekundową awarię w dwudziestominutowe zawieszenie systemu. Zawieszenie jest gorsze, ponieważ martwy proces pozwala na dostęp do powłoki, podczas gdy system w stanie thrashingu przestaje odpowiadać.

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 nieużywane, oraz ustawienie MemorySwapMax=0 dla jednostek, których utrata jest akceptowalna. Kluczowe usługi zachowują dostęp do swapu. Te nieprzewidywalne szybko osiągają limit i restartują się.

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 swapowaniem pamięci anonimowej, a oba te działania wiążą się z późniejszym odczytem z dysku. Zmienia to jedynie rodzaj stron podlegających thrashingowi, a nie sam fakt występowania tego zjawiska.

Wczesny demon OOM przerywa procesy przed wystąpieniem zatoru

Jądro systemu czeka, aż odzyskiwanie pamięci całkowicie zawiedzie. Na małym serwerze VPS ten czas oczekiwania jest dokładnie momentem, w którym tracisz dostęp do 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 o najwyższym wyniku 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 sprawdź journalctl -u earlyoom, aby zobaczyć, 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 zatorach (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 wymagają aktywacji 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 aktualnie monitorowane zasoby, co na obrazach serwerowych często nie zwraca żadnych danych, ponieważ ustawienia wymagają ręcznej aktywacji dla każdej jednostki. Wybierz jednego demona i na nim poprzestań. Uruchomienie obu oznacza rywalizację dwóch narzędzi o wybór ofiary, co utrudnia ustalenie przyczyny przerwania procesu.

Która jednostka była odpowiedzialna?

Należy zacząć od jądra systemu, 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 prefiks, 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 prefiks stanowi większość diagnozy. Memory cgroup out of memory oznacza, że jedna jednostka osiągnęła limit MemoryMax=, który został jej nadany, 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, podobnie jak 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 zużycie osiągnięte przez cgroup; jest to wartość, w oparciu o którą należy ustawić MemoryMax=. Oba pliki są resetowane przy restarcie jednostki, ponieważ systemd tworzy cgroup od nowa.

Podstawowym warunkiem jest odpowiednia konfiguracja. Jeśli katalog /var/log/journal nie istnieje, dziennik znajduje się w pamięci RAM, a wszystkie wpisy znikają po restarcie wymuszonym w celu odzyskania dostępu do serwera.

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 więcej niż bieżący rozruch oznacza, że historia jest zapisywana, dzięki czemu journalctl -k -b -1 pozwala wyświetlić komunikaty jądra z sesji, która uległa awarii.

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). Nie należy sumować limitów do pełnych 2 GB, ponieważ każda jednostka może osiągnąć szczytowe zużycie w tym samym momencie. Największy przydział zasobów należy nadać kluczowej usłudze, a pozostałe, mniej istotne procesy ograniczyć.

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

Zapewnienie dostępu do serwera wymaga dodatkowej konfiguracji. Ustawienie OOMScoreAdjust=-500 w pliku typu drop-in dla ssh.service sprawia, że globalny mechanizm OOM killer znacznie rzadziej wybiera proces SSH jako cel. Decyduje to o możliwości naprawy serwera bez konieczności jego restartu z poziomu panelu sterowania. Zmiana ta wpływa jedynie na wybór ofiary przez jądro systemu, nie skraca czasu zawieszenia usługi.

Kontenery działają we własnych grupach cgroups, tworzonych przez środowisko uruchomieniowe kontenerów, a nie przez pliki jednostek. Dlatego 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 opisane 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 operacja odzyskiwania pamięci zwraca strony, a nie na podstawie czasu jej trwania. W warunkach braku pamięci jądro usuwa cache stron, w tym strony wykonywalne uruchomionych programów, a następnie wczytuje je ponownie przy kolejnej instrukcji. Wszystko oczekuje na nośnik danych, a technicznie żadna alokacja nie zakończyła się niepowodzeniem, więc mechanizm OOM killer nie jest wywoływany. Sprawdź /proc/pressure/memory w trakcie występowania problemu: wartość full avg10 powyżej 40 oznacza, że w ciągu ostatnich dziesięciu sekund niemal żadne zadanie nie otrzymało czasu procesora. Demon 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, więc ginie proces, który spowodował problem, a nie największy proces w systemie. Ustaw MemoryHigh= poniżej MemoryMax= i traktuj różnicę między nimi jako strefę ostrzegawczą.

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

Uruchom 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=, podczas gdy zwykły wpis Out of memory oznacza wyczerpanie pamięci w całym systemie. Następnie uruchom journalctl -u <unit> -n 50 i wyszukaj 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; utwórz ten katalog przed kolejnym incydentem.

Czy powinienem dodać swap do małego VPS?

Niewielki plik wymiany pomaga w przypadku "zimnych" stron, które są alokowane raz i nigdy więcej nieużywane. Nie pomaga on w przypadku procesu wymykającego się spod kontroli: opóźnia ubicie procesu i zamienia krótką przerwę w długie zawieszenie, które uniemożliwia zalogowanie się w celu naprawy. Utrzymuj swap na skromnym poziomie i ustaw MemorySwapMax=0 dla jednostek, których utrata jest akceptowalna, aby osiągnęły swój limit i szybko się zrestartowały, podczas gdy ważne usługi zachowają swój swap.

Czy mogę 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ą. Pomiń --scope i dodaj --unit=name, aby uruchomić zadanie w tle z wyjściem przekierowanym do dziennika.