Cách giới hạn CPU và RAM cho tiến trình bằng systemd
Sử dụng MemoryMax và CPUQuota trong file drop-in để kiểm soát tài nguyên trên VPS. Bài viết hướng dẫn cách cấu hình cgroup v2 và xử lý lỗi systemd không load cấu hình đúng.
Giới hạn bộ nhớ và CPU của tiến trình bằng systemd drop-in
Bạn giới hạn bộ nhớ và CPU của tiến trình trên VPS Linux bằng cách thêm vài dòng vào unit chạy tiến trình đó. MemoryMax= là giới hạn cứng về bộ nhớ. CPUQuota= là giới hạn về thời gian xử lý của bộ vi xử lý. Cả hai đều được thực thi bởi cgroup v2 (control groups, phiên bản 2), tính năng kernel mà systemd đã sử dụng để quản lý mọi service trên máy chủ.
sudo systemctl edit myapp.serviceLệnh đó mở một file drop-in với các hướng dẫn trong phần chú thích. Hãy thêm nội dung này vào phía trên chúng:
[Service]
MemoryHigh=512M
MemoryMax=768M
MemorySwapMax=0
CPUQuota=80%
TasksMax=128sudo systemctl daemon-reload
sudo systemctl restart myapp.service
systemctl show myapp.service -p MemoryHigh -p MemoryMax -p CPUQuotaPerSecUSec -p TasksMaxsystemctl show phải hiển thị lại các con số của bạn theo đơn vị của kernel: MemoryMax=805306368 và CPUQuotaPerSecUSec=800ms. Nếu nó in ra MemoryMax=infinity, nghĩa là file drop-in chưa được load. Hãy kiểm tra xem file đã nằm tại /etc/systemd/system/myapp.service.d/override.conf chưa, và đảm bảo nó bắt đầu bằng header [Service], vì một dòng cấu hình không có section phía trên sẽ khiến systemd ghi log Assignment outside of section. Ignoring. và khởi động service mà không áp dụng bất kỳ giới hạn nào.
Phần còn lại của hướng dẫn này giải thích cách chọn các con số đó và những lỗi vẫn có thể xảy ra sau khi đã thiết lập.
Tại sao một tiến trình chạy quá tải làm treo VPS dù không đầy bộ nhớ
Một tiến trình chạm ngưỡng giới hạn bộ nhớ cứng sẽ bị kill trong khoảng một giây và service sẽ tự khởi động lại. Đó là trường hợp tốt. Trường hợp xấu là khi không có gì bị kill: máy chủ vẫn phản hồi ping, SSH chấp nhận kết nối, nhưng shell prompt không bao giờ xuất hiện. Máy vẫn sống và đang bận rộn, nhưng không có công việc nào hữu ích được thực hiện.
Đây là cơ chế hoạt động, vì nó không hiển nhiên. Khi bộ nhớ trống xuống thấp, kernel sẽ thu hồi các page thay vì cấp phát page mới. Các page rẻ nhất để thu hồi là file-backed, và page cache chứa mã thực thi của mọi thứ đang chạy. Vì vậy, kernel đẩy các text page của sshd ra ngoài, và lệnh tiếp theo mà sshd thực thi là một page fault, buộc hệ thống phải đọc lại các byte đó từ bộ nhớ lưu trữ. Mọi tiến trình cuối cùng đều phải chờ đợi ổ cứng thay vì được chạy. Các page này cứ rời đi rồi quay lại trong một vòng lặp, hiện tượng này gọi là thrashing.
Có hai lý do khiến tình trạng này trên VPS tệ hơn trên laptop. Lưu trữ thường là network attached hoặc shared, nên mỗi lỗi fault tốn nhiều mili giây hơn so với thiết bị NVMe cục bộ. Và kernel không đo thời gian, nó đo sự thất bại: chừng nào việc thu hồi vẫn trả về được một page, dù chậm đến đâu, kernel vẫn tin rằng nó đang tiến triển và không gọi out of memory (OOM) killer. Một máy chủ có thể nằm trong trạng thái đó nhiều phút trước khi có bất kỳ tiến trình nào bị kill.
Bạn có thể quan sát hiện tượng này. Kernel xuất thông tin pressure stall information (PSI) trên Linux 4.20 trở lên:
cat /proc/pressure/memory
cat /proc/pressure/iosome avg10=63.72 avg60=41.02 avg300=12.33 total=13729481
full avg10=48.15 avg60=30.44 avg300=8.90 total=9114233Dòng full là dòng quan trọng. full avg10=48.15 có nghĩa là trong mười giây vừa qua, 48% thời gian mọi tác vụ có thể chạy trên máy đều bị treo chờ đợi xử lý bộ nhớ, nên không có gì được thực thi. Một server khỏe mạnh sẽ có chỉ số gần bằng 0 trên full. Trên 10, con người sẽ cảm thấy chậm, và từ 40 trở lên là trạng thái mà mọi người mô tả là bị treo.
Đây cũng là lý do tại sao một giới hạn (limit) đơn thuần không phải là một lời hứa. Một unit bị giữ dưới MemoryHigh= sẽ bị điều tiết (throttle) thay vì bị kill, nên nó vẫn sống và vẫn chậm, và không có gì khởi động lại nó vì từ góc nhìn của systemd, nó chưa bao giờ thất bại. Một unit bị giới hạn mà vẫn được phép swap sẽ tạo ra các thao tác đọc và ghi được tính cho unit đó nhưng lại được phục vụ bởi một thiết bị dùng chung, vì vậy nó có thể đẩy /proc/pressure/io lên cao đối với mọi service khác trên máy. Các giới hạn chỉ quyết định ai là người phải trả giá cho sự thiếu hụt, chúng không thể tạo ra thêm dung lượng.
Kiểm tra xem VPS của bạn có chạy cgroup v2 không
stat -fc %T /sys/fs/cgroupcgroup2fs là phân cấp hợp nhất, đây là yêu cầu bắt buộc cho mọi thiết lập bên dưới. tmpfs có nghĩa là máy chủ đã khởi động với cấu trúc v1 cũ, nơi mà MemoryHigh= và MemorySwapMax= không tồn tại và hành vi OOM trên mỗi unit sẽ khác biệt. Ubuntu 22.04 trở lên, và Debian 11 trở lên, sử dụng v2 theo mặc định. Một image cũ, hoặc kernel được khởi động với systemd.unified_cgroup_hierarchy=0, sẽ không sử dụng v2.
Trên cgroup v2, systemd tự động bật tính năng ghi nhận bộ nhớ (memory accounting) cho mọi unit theo mặc định, vì vậy các con số đã có sẵn:
systemd-cgtop -mLệnh này liệt kê các cgroup được sắp xếp theo mức sử dụng bộ nhớ, đây là cách nhanh nhất để trả lời câu hỏi "cái gì đang ngốn tài nguyên máy chủ" khi hệ thống vẫn còn phản hồi. Nếu đây là server mới, các công việc thiết lập tài khoản và firewall trong mười phút đầu tiên trên một VPS mới cần được thực hiện trước bước này.
MemoryHigh gây nghẽn. MemoryMax gây kill.
Sự khác biệt giữa hai thiết lập bộ nhớ này quyết định cách thức xảy ra lỗi.
MemoryHigh=là giới hạn mềm. Khi vượt quá mức này, kernel sẽ chủ động thu hồi bộ nhớ từ cgroup đó và cố tình làm chậm các yêu cầu cấp phát. Mức sử dụng vẫn có thể vượt quá con số này và không có tiến trình nào bị kill.MemoryMax=là giới hạn cứng. Khi một yêu cầu cấp phát không thể đáp ứng trong phạm vi này, OOM killer sẽ chạy bên trong cgroup đó và kill một trong các tiến trình của unit đó.
Vế thứ hai chính là lý do thực sự để thiết lập MemoryMax= cho bất kỳ thứ gì bạn không hoàn toàn tin tưởng. Nếu không có giới hạn, tình trạng thiếu hụt bộ nhớ sẽ trở thành vấn đề của toàn bộ máy chủ, và OOM killer toàn cục sẽ chọn nạn nhân dựa trên oom_score, thường là tiến trình chiếm nhiều bộ nhớ nhất. Tiến trình lớn nhất thường là database của bạn, chứ không phải script bị leak. Với giới hạn, việc kill chỉ xảy ra bên trong unit gây ra lỗi.
Hãy thiết lập cả hai, với MemoryHigh= thấp hơn MemoryMax= khoảng 20 đến 30 phần trăm. Khoảng cách này là vùng cảnh báo: một lỗi leak chậm sẽ vượt qua High và biểu hiện thành một dịch vụ bị chậm, trong khi một spike đột ngột sẽ vượt thẳng qua Max và bị kill.
Giá trị phần trăm được tính dựa trên bộ nhớ vật lý đã cài đặt, vì vậy MemoryMax=25% trên gói 4 GB là 1 GB và vẫn giữ nguyên tỷ lệ một phần tư sau khi bạn nâng cấp gói. MemorySwapMax=0 giúp unit đó hoàn toàn không sử dụng swap, biến tình trạng chạy chậm chạp kéo dài thành một cú kill nhanh chóng và rõ ràng.
Một vài dịch vụ cho phép bạn quyết định mức tiêu thụ trước thay vì phải đo lường: một unit Ollama sẽ định cỡ KV cache dựa trên context window mà bạn cung cấp, vì vậy hãy đọc chi phí RAM khi tăng num_ctx trước khi chọn mức trần cho nó.
Một giới hạn cần đi kèm với chính sách khởi động lại, nếu không việc kill chỉ để lại cho bạn một dịch vụ đã dừng.
[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Restart=on-failure
RestartSec=5sStartLimit* thuộc về [Unit] và Restart= thuộc về [Service]. Đặt sai phần sẽ khiến systemd bỏ qua thiết lập đó. Năm lần khởi động lại trong năm phút là dấu hiệu của lỗi leak thay vì chỉ là một sự cố tạm thời, vì vậy sau đó systemd sẽ từ bỏ và để unit ở trạng thái failed. Đây là trạng thái bạn muốn thấy sau này thay vì một crash loop che giấu vấn đề.
Giới hạn CPU với CPUQuota, hoặc chia sẻ tài nguyên với CPUWeight
CPUQuota= lấy một tỷ lệ phần trăm thời gian khả dụng trên một CPU. CPUQuota=50% tương đương với một nửa nhân. CPUQuota=200% tương đương với hai nhân, đơn vị này có thể phân bổ trên bao nhiêu luồng tùy ý. Trên gói 2 vCPU, CPUQuota=200% là toàn bộ máy chủ.
CPUWeight= là giá trị mặc định tốt hơn cho hầu hết các dịch vụ. Đây là tỷ lệ chia sẻ tương đối từ 1 đến 10000, với mặc định của kernel là 100. Nó chỉ phát huy tác dụng khi có sự cạnh tranh tài nguyên: một tác vụ backup ở mức CPUWeight=20 sẽ nhường tài nguyên cho web server ở mức 100 khi có tải, nhưng vẫn sử dụng toàn bộ máy chủ khi máy chủ đang rảnh. Một hạn mức cứng (hard quota) sẽ làm lãng phí công suất nhàn rỗi đó.
Hãy nhìn nhận thực tế về lợi ích của việc giới hạn CPU. Một tiến trình chiếm dụng CPU hiếm khi làm treo Linux, vì bộ lập lịch (scheduler) luôn phân bổ thời gian cho mọi tiến trình. Bộ nhớ (RAM) mới là thứ làm sập máy chủ. Hãy dùng CPUQuota= khi bạn muốn một mức trần có thể dự đoán được, ví dụ như cho một tác vụ build hoặc một agent mà nếu không giới hạn sẽ chạy hết công suất trong cả giờ đồng hồ. Việc định cỡ cho loại workload đó là một vấn đề riêng, được đề cập trong dung lượng RAM và CPU cần thiết cho một VPS chạy coding agent.
Nếu CPU báo bận trong khi không có tiến trình nào của bạn hoạt động nhiều, nguyên nhân có thể nằm ở phía bên kia của hypervisor. Đó là CPU steal time từ một noisy neighbour, và không hạn mức nào bạn thiết lập có thể thay đổi được điều đó.
TasksMax ngăn chặn vòng lặp fork
TasksMax= là số lượng tiến trình và luồng (thread) mà một unit được phép duy trì. Vì các luồng cũng được tính, một service chạy Java hoặc Go cần nhiều không gian hơn so với danh sách tiến trình thông thường. Đây là cơ chế bảo vệ rẻ nhất chống lại các script tạo tiến trình con (fork) trong vòng lặp, vì lệnh fork sẽ thất bại ngay bên trong unit thay vì làm cạn kiệt ID tiến trình của toàn bộ hệ thống.
TasksMax=128Khi một unit chạm ngưỡng giới hạn, kernel sẽ ghi log một dòng chỉ rõ cgroup đó:
cgroup: fork rejected by pids controller in /system.slice/myapp.serviceBản thân chương trình thường sẽ báo lỗi fork: retry: Resource temporarily unavailable. Hãy kiểm tra giá trị mặc định mà trình quản lý áp dụng bằng lệnh systemctl show -p DefaultTasksMax.
Giới hạn một tác vụ chạy một lần với systemd-run
Bạn không cần file unit để sử dụng các tính năng này. systemd-run tự động tạo một unit tạm thời xung quanh một lệnh duy nhất.
sudo systemd-run --scope -p MemoryMax=1G -p MemorySwapMax=0 -p CPUQuota=50% -p TasksMax=64 ./import-data.sh--scope chạy lệnh ngay trong terminal của bạn sau khi in ra Running scope as unit: run-r7c1a....scope. Output vẫn hiển thị trên màn hình và các giới hạn sẽ mất đi khi lệnh kết thúc. Mọi thuộc tính từ systemd.resource-control đều hoạt động sau -p.
Đối với một tác vụ chạy lâu, hãy bỏ --scope và đặt tên cho nó. Nó sẽ chạy dưới nền như một service tạm thời và ghi log vào journal:
sudo systemd-run --unit=nightly-import -p MemoryMax=1G -p CPUWeight=20 ./import-data.sh
journalctl -u nightly-import -fCác tùy chọn tương tự cũng hoạt động với --user khi bạn không phải là root, mặc dù trình quản lý người dùng của bạn chỉ có các controller được ủy quyền cho nó, vì vậy một thuộc tính có thể bị từ chối. Hãy chạy nó với sudo nếu điều đó xảy ra. Khi một tác vụ cần một vị trí cố định, các thiết lập có thể được chuyển nguyên trạng sang một unit thực sự: xem chạy script dưới dạng systemd service và timer.
Câu hỏi về swap, trả lời một cách trung thực
Swap làm thay đổi hình thái của sự cố thay vì ngăn chặn nó.
Nếu không có swap, khi bộ nhớ bị leak chạm ngưỡng tối đa, một tiến trình sẽ chết trong vài giây. Sự cố này diễn ra nhanh, rõ ràng và dễ đọc trong log sau đó. Với swap, kernel ghi các anonymous page ít dùng ra đĩa và kéo dài thời gian. Nếu tiến trình đó tự ổn định, swap sẽ cứu bạn. Nếu đó là một tiến trình bị lỗi runaway, swap biến sự cố 5 giây thành 20 phút hệ thống bị treo. Trạng thái treo này tệ hơn, vì một tiến trình chết vẫn cho phép bạn dùng shell, còn một máy chủ bị thrashing thì không.
swapon --show
free -hMột giải pháp trung gian khả thi trên VPS nhỏ: duy trì một swap file vừa phải cho các page được cấp phát một lần và không bao giờ dùng lại, đồng thời thiết lập MemorySwapMax=0 trên các unit mà bạn chấp nhận cho phép dừng. Các dịch vụ quan trọng vẫn giữ swap. Các dịch vụ không ổn định sẽ chạm ngưỡng nhanh chóng và tự khởi động lại.
Giảm vm.swappiness là một biện pháp yếu, và cần hiểu rõ lý do tại sao. Nó chỉ thay đổi sự cân bằng giữa việc xóa page cache và swap các anonymous page, cả hai đều gây tốn kém khi phải đọc đĩa sau đó. Nó chỉ thay đổi việc page nào bị thrash, chứ không ngăn được tình trạng máy chủ bị thrashing.
Một daemon OOM sớm sẽ kill tiến trình trước khi hệ thống bị treo
Kernel đợi cho đến khi quá trình reclaim thất bại hoàn toàn, và trên một VPS nhỏ, khoảng thời gian chờ đó chính là lúc bạn mất quyền kiểm soát máy chủ. Hai daemon userspace giải quyết vấn đề này bằng cách tự theo dõi bộ nhớ và kill tiến trình sớm hơn.
earlyoom theo dõi bộ nhớ khả dụng và swap trống, sau đó kill tiến trình có điểm số cao nhất khi một trong hai chỉ số này giảm xuống dưới ngưỡng cho phép.
sudo apt install earlyoom
systemctl status earlyoomGói cài đặt trên Debian và Ubuntu sẽ tự khởi động service ngay khi cài đặt. Các tùy chọn của nó nằm trong /etc/default/earlyoom:
EARLYOOM_ARGS="-m 5,2 -s 5,2 --avoid '^(sshd|systemd)$' --prefer '^(node|python3)$'"-m PERCENT thiết lập mức tối thiểu cho bộ nhớ khả dụng và -s PERCENT thiết lập mức tối thiểu cho swap trống, mặc định cả hai đều là 10 phần trăm. Số thứ hai trong mỗi cặp là điểm kích hoạt SIGKILL: earlyoom gửi SIGTERM khi bạn giảm xuống dưới giá trị thứ nhất, sau đó gửi SIGKILL khi xuống dưới giá trị thứ hai, mặc định bằng một nửa giá trị thứ nhất. Áp dụng thay đổi bằng sudo systemctl restart earlyoom, và đọc journalctl -u earlyoom để xem tiến trình nào đã bị kill và tiến trình đó đang chiếm bao nhiêu bộ nhớ.
systemd-oomd là tùy chọn còn lại. Trang manual của nó mô tả đây là "một service hệ thống sử dụng cgroups-v2 và pressure stall information (PSI) để giám sát và thực hiện hành động khắc phục trước khi tình trạng OOM xảy ra trong kernel space". Nó tác động lên toàn bộ cgroup thay vì từng tiến trình riêng lẻ, vì vậy nó sẽ kill cả unit thay vì chỉ một tiến trình con. Các unit cần được chọn tham gia (opt-in) bằng ManagedOOMMemoryPressure=kill hoặc ManagedOOMSwap=kill, và các ngưỡng được cấu hình trong /etc/systemd/oomd.conf.
systemctl status systemd-oomd
oomctloomctl in ra những gì nó đang giám sát, thường là không có gì trên các image máy chủ vì cài đặt này yêu cầu opt-in cho từng unit. Hãy chọn một daemon và chỉ dùng một loại. Chạy cả hai sẽ dẫn đến việc hai tiến trình tranh giành nhau để chọn "nạn nhân", và lý do dẫn đến việc kill tiến trình sẽ trở nên khó xác định hơn.
Unit nào đã gây ra lỗi?
Hãy bắt đầu với kernel, vì nó ghi lại mọi tiến trình mà nó đã kill.
journalctl -k --grep "Killed process" --since "2 hours ago"Một lệnh kill từ OOM killer toàn cục sẽ trông như thế này:
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:0anon-rss là dung lượng bộ nhớ mà tiến trình đó chiếm giữ trong RAM khi bị kết thúc, ở đây là khoảng 1.8 GB. Hãy chú ý đến tên nằm trong ngoặc vuông. Đó là nạn nhân mà kernel đã chọn, và kernel luôn chọn tiến trình chiếm nhiều bộ nhớ nhất, vốn không phải lúc nào cũng là tiến trình gây ra tình trạng thiếu hụt.
Một lệnh kill do giới hạn cgroup sẽ có tiền tố khác, và báo cáo in phía trên nó sẽ nêu tên cgroup đã chạm ngưỡng giới hạn của chính nó:
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:0Tiền tố đó là phần quan trọng nhất để chẩn đoán. Memory cgroup out of memory nghĩa là một unit đã chạm ngưỡng MemoryMax= mà bạn thiết lập, trong khi phần còn lại của máy chủ vẫn ổn định. Một thông báo Out of memory đơn thuần nghĩa là toàn bộ máy chủ đã cạn kiệt bộ nhớ, do đó các giới hạn của bạn hoặc là chưa được thiết lập, hoặc là quá rộng so với tổng tài nguyên.
Sau đó, hãy hỏi systemd về những gì nó đã ghi nhận:
systemctl status myapp.service
journalctl -u myapp.service -n 50myapp.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 cho biết cùng một thông tin trên một dòng, dưới dạng Active: failed (Result: oom-kill).
Các bộ đếm cgroup là nguồn thứ ba, và là nguồn duy nhất ghi lại tình trạng throttling (bóp nghẹt tài nguyên), vốn không bao giờ tạo ra dòng log nào:
cat /sys/fs/cgroup/system.slice/myapp.service/memory.events
cat /sys/fs/cgroup/system.slice/myapp.service/memory.peaklow 0
high 4213
max 118
oom 12
oom_kill 12high đếm số lần unit bị đẩy vượt quá MemoryHigh= và bị throttled. max đếm số lần nó chạm ngưỡng giới hạn cứng (hard cap), và oom_kill đếm số tiến trình thực sự bị kill. Một giá trị high lớn đi kèm với oom_kill 0 chính là trường hợp "im lặng" đã đề cập trước đó: service vẫn đang chạy, bị chậm đến mức bò, và không báo cáo lỗi cho bất kỳ ai. memory.peak (từ Linux 5.19 trở lên) lưu giữ mức sử dụng cao nhất mà cgroup từng đạt tới, đây là con số để bạn đối chiếu khi cấu hình MemoryMax=. Cả hai file này đều reset khi unit khởi động lại, vì systemd tạo lại cgroup mới.
Một điều kiện tiên quyết nằm dưới tất cả những điều này. Nếu /var/log/journal không tồn tại, journal sẽ nằm trên RAM, và mọi dòng log sẽ mất sạch sau khi bạn reboot để khôi phục máy chủ.
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
journalctl --list-bootsjournalctl --list-boots hiển thị nhiều hơn phiên boot hiện tại nghĩa là lịch sử log đã được lưu lại, vì vậy journalctl -k -b -1 có thể cho bạn thấy các thông báo kernel từ phiên boot đã bị treo.
Điểm khởi đầu cho một VPS nhỏ
Trên gói 2 GB, hãy dành ra 300 đến 400 MB cho kernel và page cache, và đừng để tổng các giới hạn cộng lại bằng đúng 2 GB, vì mọi đơn vị đều có thể đạt đỉnh cùng một lúc. Hãy ưu tiên cấp tài nguyên lớn nhất cho dịch vụ quan trọng, sau đó giới hạn mọi dịch vụ phụ xung quanh nó.
[Service]
MemoryHigh=256M
MemoryMax=384M
MemorySwapMax=0
CPUWeight=20
TasksMax=64
Restart=on-failure
RestartSec=5sViệc giữ một lối vào là xứng đáng với thêm một thiết lập nữa. OOMScoreAdjust=-500 trong một file drop-in cho ssh.service giúp OOM killer của hệ thống ít có khả năng chọn SSH daemon của bạn làm nạn nhân hơn, đây chính là sự khác biệt giữa việc sửa lỗi máy chủ và phải reboot từ bảng điều khiển. Nó chỉ thay đổi lựa chọn nạn nhân của kernel. Nó không làm giảm thời gian treo máy.
Các container chạy trong cgroup riêng của chúng, được tạo bởi container runtime thay vì các unit file của bạn, vì vậy giới hạn trên docker.service không trở thành giới hạn cho một container. Các thiết lập tương đương cho từng container của MemoryMax= và CPUQuota= được đề cập trong thiết lập giới hạn bộ nhớ và CPU trong Docker Compose.
FAQ
Tại sao VPS của tôi bị treo thay vì kill tiến trình đang chạy quá tải?
Vì kernel đánh giá tiến trình dựa trên việc reclaim có trả về các page hay không, chứ không dựa vào thời gian thực hiện. Khi thiếu bộ nhớ, nó sẽ giải phóng page cache, bao gồm cả các page thực thi của chương trình đang chạy, sau đó đọc lại chúng ở lệnh tiếp theo. Mọi thứ đều phải chờ storage và về mặt kỹ thuật thì chưa có yêu cầu cấp phát nào thất bại, nên OOM killer không được kích hoạt. Hãy kiểm tra /proc/pressure/memory khi sự cố xảy ra: full avg10 trên 40 nghĩa là hầu như không có tác vụ nào được chạy trong 10 giây vừa qua. Một daemon ở userspace như earlyoom sẽ kill tiến trình trước khi máy chủ rơi vào trạng thái đó.
Sự khác biệt giữa MemoryHigh và MemoryMax là gì?
MemoryHigh= là giới hạn mềm (soft cap) dùng để điều tiết. Kernel sẽ reclaim mạnh từ unit đó và làm chậm quá trình cấp phát, nhưng mức sử dụng vẫn có thể vượt quá con số này và không có tiến trình nào bị kill. MemoryMax= là giới hạn cứng (hard cap): một yêu cầu cấp phát không thể đáp ứng trong giới hạn này sẽ kích hoạt OOM killer ngay bên trong cgroup của unit đó, vì vậy tiến trình gây ra vấn đề sẽ bị kill thay vì tiến trình lớn nhất trên máy. Hãy đặt MemoryHigh= thấp hơn MemoryMax= và coi khoảng cách giữa chúng là vùng cảnh báo.
Làm thế nào để tìm xem service nào bị OOM killer xử lý?
Chạy journalctl -k --grep "Killed process" --since "2 hours ago". Một dòng bắt đầu bằng Memory cgroup out of memory nghĩa là một unit đã chạm ngưỡng MemoryMax= của chính nó, trong khi Out of memory đơn thuần nghĩa là toàn bộ máy chủ đã cạn kiệt bộ nhớ. Sau đó chạy journalctl -u <unit> -n 50 và tìm Failed with result 'oom-kill'. Nếu /var/log/journal không tồn tại trên máy chủ của bạn, nghĩa là journal được lưu trong RAM và bằng chứng đã mất sau khi reboot, vì vậy hãy tạo thư mục đó trước sự cố tiếp theo.
Tôi có nên thêm swap cho VPS nhỏ không?
Một file swap nhỏ giúp ích cho các cold page được cấp phát một lần và không bao giờ đụng đến nữa. Nó không giúp ích gì với tiến trình chạy quá tải: nó chỉ làm chậm việc kill tiến trình và thay thế một sự cố ngắn bằng một trạng thái treo dài khiến bạn không thể đăng nhập để sửa lỗi. Hãy giữ swap ở mức vừa phải và đặt MemorySwapMax=0 trên các unit mà bạn chấp nhận cho phép dừng, để chúng đạt ngưỡng giới hạn và khởi động lại nhanh chóng trong khi các service quan trọng vẫn giữ được swap của mình.
Tôi có thể giới hạn một lệnh mà không cần viết unit file không?
Có. sudo systemd-run --scope -p MemoryMax=1G -p CPUQuota=50% ./script.sh chạy lệnh trong terminal của bạn bên trong một transient scope với các giới hạn đó, và các giới hạn này sẽ mất đi khi lệnh kết thúc. Mọi thuộc tính từ systemd.resource-control đều khả dụng sau -p, vì vậy MemorySwapMax=, TasksMax= và CPUWeight= cũng hoạt động ở đó. Bỏ --scope và thêm --unit=name để chạy job ở chế độ nền với output được ghi vào journal.