Sáu tháng trước, một VPS chạy ứng dụng Node.js của tôi bị đứng hoàn toàn lúc 3 giờ sáng. SSH kết nối không được. Dashboard monitoring phẳng lặng. Cách duy nhất để khắc phục là reboot cứng. Thủ phạm? Server hết RAM, và Linux OOM killer — cơ chế xử lý cuối cùng của kernel — bị đình trệ khi đang cố quyết định nên kill process nào. Đến lúc nó ra tay thì máy đã không còn phản hồi nữa.
Sau hơn ba năm vận hành 10+ VPS Linux, tôi nhận ra tình huống này phổ biến hơn nhiều người nghĩ. Tôi bắt đầu thử nghiệm systemd-oomd như một giải pháp, và sau sáu tháng chạy thực tế trên droplet DigitalOcean 2 vCPU / 2 GB RAM, tôi có thể cho bạn thấy rõ nó thực sự làm được gì.
Vấn đề thực sự: Tại sao server bị treo thay vì tự phục hồi
Khi server Linux hết RAM, lý thuyết rất đơn giản: kernel kill một process nào đó, bộ nhớ được giải phóng, mọi thứ tiếp tục. Nhưng thực tế lại diễn ra khác hẳn:
- Các ứng dụng dùng cạn RAM khả dụng
- Hệ thống bắt đầu dùng swap nặng nề — nếu swap tồn tại
- Disk I/O tăng vọt vì mọi thứ đều đang swap vào ra liên tục
- Hệ thống bận swap đến mức không thể phản hồi input
- Cuối cùng OOM killer mới kích hoạt — nhưng lúc đó máy đã đứng đơ rồi
Khoảng thời gian giữa “bộ nhớ cạn kiệt nghiêm trọng” và “OOM killer cuối cùng ra tay” chính là lúc server chết. Trên một VPS dùng SSD, tôi từng thấy khoảng này kéo dài từ 30 giây đến hơn hai phút, tùy vào dung lượng swap và tốc độ đọc ghi của disk. Trong suốt khoảng thời gian đó, SSH không phản hồi, cron job bị đình trệ, và traffic web phẳng lặng. Rốt cuộc bạn vẫn phải reboot thôi.
Phân tích nguyên nhân gốc rễ: Điểm yếu chí mạng của OOM Killer
Phản ứng thụ động — đó là bản chất của kernel OOM killer. Nó chỉ kích hoạt sau khi áp lực bộ nhớ đạt đến mức khủng hoảng thực sự: cụ thể là sau khi kernel không thể cấp phát bộ nhớ cho một yêu cầu mới. Đến thời điểm đó, hệ thống đã ngập trong swap I/O đến mức gần như không thể thực thi chính đoạn code OOM killer nữa.
Vấn đề thứ hai là cách chọn mục tiêu. OOM killer chấm điểm từng process dựa trên heuristic kết hợp mức dùng bộ nhớ, thời gian chạy và các yếu tố khác. Kết quả hoàn toàn không thể đoán trước. Trên một instance của tôi, nó kill một process PostgreSQL trong khi một cron job chạy rông ngốn 600 MB lại sống sót — hoàn toàn ngược với điều tôi muốn.
Kiểm tra áp lực hiện tại của hệ thống:
cat /proc/meminfo | grep -E "MemAvailable|SwapFree|MemTotal"
vmstat -s | head -20
dmesg | grep -i "oom\|kill"
Đã thấy OOM kill trong dmesg? Tức là kernel đang phản ứng trong chế độ khủng hoảng toàn diện. Đó chính là khoảng trống mà systemd-oomd được xây dựng để lấp đầy.
So sánh giải pháp: Ba cách xử lý áp lực bộ nhớ
Tùy chọn 1: Tinh chỉnh trực tiếp OOM Killer của Kernel
Bạn có thể điều chỉnh oom_score_adj cho từng process để ảnh hưởng đến thứ tự bị kill:
# Giảm khả năng bị kill của một process (-1000 = miễn nhiễm)
echo -500 > /proc/$(pidof your-critical-service)/oom_score_adj
# Tăng khả năng bị kill của một process (1000 = mục tiêu đầu tiên)
echo 1000 > /proc/$(pidof disposable-worker)/oom_score_adj
Chọn mục tiêu tốt hơn, đúng vậy. Nhưng vấn đề về thời điểm vẫn không thay đổi. OOM killer vẫn kích hoạt quá muộn, sau khi hệ thống đã chật vật để duy trì phản hồi.
Tùy chọn 2: earlyoom
earlyoom là một daemon userspace theo dõi mức dùng bộ nhớ và kill process trước khi OOM killer của kernel kịp ra tay. Đơn giản, hiệu quả, và đã được kiểm chứng qua nhiều năm:
sudo apt install earlyoom
sudo systemctl enable --now earlyoom
Hoạt động tốt cho các máy đơn lẻ. Có hai nhược điểm đáng lưu ý: đây là dependency của bên thứ ba, và nó không hiểu cgroup hierarchy của systemd. earlyoom kill từng process riêng lẻ, không kill toàn bộ service unit. Nếu app của bạn fork nhiều worker process, kill một worker không dừng được việc rò rỉ bộ nhớ — các worker còn lại vẫn tiếp tục chạy.
Tùy chọn 3: systemd-oomd
Đây là lựa chọn tôi đã quyết định sau sáu tháng. systemd-oomd đi kèm với systemd (có từ phiên bản 247), tích hợp native với cgroups v2, và kill toàn bộ service unit thay vì từng process riêng lẻ. Đó là điểm khác biệt then chốt. Khi một service hoạt động bất thường, bạn muốn loại bỏ hoàn toàn nó — không phải kill một worker trong khi bốn worker khác vẫn tiếp tục ngốn bộ nhớ.
Nó cũng theo dõi áp lực bộ nhớ bằng PSI (Pressure Stall Information) — đo lường thời gian hệ thống bị đình trệ chờ bộ nhớ. PSI cảnh báo sớm hơn so với đếm bộ nhớ thô. oomd có thể hành động khi hệ thống vẫn còn đủ phản hồi để thực sự làm được việc hữu ích.
Cấu hình systemd-oomd: Setup cho môi trường production
Kiểm tra điều kiện tiên quyết
systemd-oomd yêu cầu cgroups v2 và hỗ trợ PSI. Xác minh cả hai trước khi làm bất cứ điều gì khác:
# Xác nhận cgroups v2 đã được mount
mount | grep cgroup2
# Kết quả mong đợi: cgroup2 on /sys/fs/cgroup type cgroup2 ...
# Xác nhận PSI khả dụng
cat /proc/pressure/memory
# Nếu file này tồn tại, PSI đã được bật
cgroups v2 là mặc định trên Fedora 31+, Ubuntu 21.10+, và Debian 11+. Với các hệ thống cũ hơn, thêm systemd.unified_cgroup_hierarchy=1 vào kernel command line trong /etc/default/grub, sau đó chạy update-grub và reboot.
Cài đặt và kích hoạt
# Ubuntu 22.04+ / Debian 12+
sudo apt install systemd-oomd
# Fedora / RHEL 9+
sudo dnf install systemd-oomd
# Bật và khởi động
sudo systemctl enable --now systemd-oomd
# Kiểm tra trạng thái
systemctl status systemd-oomd
Cấu hình /etc/systemd/oomd.conf
Đây là cấu hình tôi đang chạy trên production. Ngưỡng thận trọng — tôi thà để oomd kích hoạt hơi sớm còn hơn ngồi yên trong khi hệ thống đứng đơ:
[OOM]
# Bắt đầu theo dõi khi swap dùng 80% (còn lại 20%)
SwapUsedLimit=80%
# Kill một cgroup khi áp lực bộ nhớ vượt 60% trong 30 giây
DefaultMemoryPressureLimit=60%
# Cửa sổ 30 giây tránh false positive từ các đợt tăng đột biến ngắn
DefaultMemoryPressureDurationSec=30s
Sau khi chỉnh sửa, reload lại:
sudo systemctl daemon-reload
sudo systemctl restart systemd-oomd
Đánh dấu service là ứng viên OOM
systemd-oomd chỉ tác động lên các cgroup đã đăng ký tham gia rõ ràng. Với mỗi service bạn sẵn sàng hy sinh khi áp lực bộ nhớ cao, hãy tạo systemd override:
sudo systemctl edit your-background-service.service
Thêm nội dung sau vào file override:
[Service]
# Cho phép oomd kill service này nếu swap ở mức nguy hiểm
ManagedOOMSwap=kill
# Cho phép oomd kill service này khi áp lực bộ nhớ kéo dài
ManagedOOMMemoryPressure=kill
# Ghi đè ngưỡng áp lực toàn cục cho service này (tùy chọn)
# Thấp hơn = nhạy hơn; hữu ích cho các background job không quan trọng
ManagedOOMMemoryPressureLimit=40%
Với user session và các tác vụ tương tác, cấu hình user slice để có thể kill theo nhóm:
sudo systemctl edit user.slice
[Slice]
ManagedOOMSwap=kill
ManagedOOMMemoryPressure=kill
Bảo vệ các service quan trọng
Với các service bạn tuyệt đối không muốn oomd đụng vào — database, SSH daemon, monitoring agent — hãy đặt tường minh về auto. Điều này để mọi quyết định kill hoàn toàn cho OOM killer của kernel xử lý như phương án cuối cùng:
sudo systemctl edit postgresql.service
[Service]
ManagedOOMSwap=auto
ManagedOOMMemoryPressure=auto
Kiểm tra những gì oomd đang theo dõi
# Hiển thị tất cả cgroup đang được oomd theo dõi
oomctl
# Theo dõi hoạt động của oomd theo thời gian thực
journalctl -u systemd-oomd -f
# Kiểm tra giá trị PSI hiện tại trực tiếp
cat /proc/pressure/memory
# Kết quả: some avg10=0.03 avg60=0.15 avg300=0.08 total=12345678
# avg10 vượt 60% liên tục trong 30 giây sẽ kích hoạt kill với cấu hình trên
oomctl hiển thị những cgroup nào là ứng viên và chỉ số áp lực hiện tại của chúng. Nếu một service bạn đã cấu hình không xuất hiện, kiểm tra hai điều: xác nhận cgroups v2 đang hoạt động cho unit đó, và kiểm tra override đã được áp dụng đúng chưa bằng systemctl cat your-service.
Kiểm thử trước khi tin tưởng
Hai tuần trên VPS staging trước khi deploy lên production — đó là quy tắc tôi tuân theo. Tôi chạy stress-ng trên bản clone của môi trường production để mô phỏng áp lực bộ nhớ liên tục trước khi đụng vào hệ thống thật. Bạn có thể làm tương tự:
# Cài đặt công cụ kiểm thử tải
sudo apt install stress-ng
# Mô phỏng áp lực bộ nhớ nặng trong 60 giây
stress-ng --vm 2 --vm-bytes 80% --timeout 60s
Chạy journalctl -u systemd-oomd -f trong một terminal riêng trong lúc này. Nếu oomd được cấu hình đúng và các service mục tiêu đã đăng ký, bạn sẽ thấy nó xác định ứng viên và kill chúng — trong khi SSH session của bạn vẫn hoạt động bình thường xuyên suốt. Đó chính là điều quan trọng nhất. Sự khác biệt giữa một hệ thống hấp thụ được sự kiện OOM và một hệ thống chỉ đơn giản là chết ồn ào hơn.
Sau sáu tháng: Đánh giá thực tế
Hai sự kiện OOM xảy ra trên các instance production kể từ khi triển khai oomd. Cả hai lần, nó đều kill background job queue trong vòng vài giây kể từ khi áp lực vượt ngưỡng. Bộ nhớ được giải phóng, ứng dụng chính tiếp tục phục vụ traffic, và không có incident nào cần reboot. Trước khi có oomd, cả hai trường hợp đều sẽ là cuộc gọi reboot cứng lúc 3 giờ sáng.
Giám sát dựa trên PSI phát hiện áp lực sớm — thường là khi hệ thống vẫn còn đủ dư địa để thực hiện kill một cách sạch sẽ. Cửa sổ 30 giây của tôi lọc bỏ các đợt tăng đột biến ngắn mà không để áp lực kéo dài tích lũy mà không ai hay. Trong sáu tháng, oomd chưa bao giờ kích hoạt với một đợt tăng đột biến mà không phải vấn đề thực sự.
Phần lớn công việc nằm ở giai đoạn đầu: phân loại service nào có thể hy sinh và service nào cần bảo vệ. Đây là việc đáng làm dù không dùng oomd — nó buộc bạn phải suy nghĩ rõ ràng về mức độ ưu tiên của từng service trên mỗi host. Sau đó, oomd biến mất vào nền. Bạn không còn lo lắng về những cuộc gọi đứng đơ lúc 3 giờ sáng nữa. Đó là kết quả đáng để tối ưu hóa.

