Sửa lỗi ‘Too many open files’ trên Linux: Hướng dẫn về File Descriptor và ulimit

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

Bài học thực tế: Mở rộng giới hạn tài nguyên trên Linux

Đội ngũ của chúng tôi vừa chuyển đổi một cụm microservices xử lý khoảng 15.000 yêu cầu mỗi giây. Trong vài tháng đầu, hệ thống hoạt động ổn định. Tuy nhiên, trong một đợt lưu lượng truy cập tăng đột biến 40% vào lúc 2 giờ sáng thứ Sáu, các cảnh báo giám sát đã bắt đầu reo vang.

Các bản ghi log tràn ngập lỗi java.io.IOException: Too many open files. Nginx ngừng chấp nhận các kết nối TCP mới và cơ sở dữ liệu PostgreSQL bắt đầu từ chối các truy vấn. Nếu bạn đang quản lý các hệ thống có độ chịu tải cao (high-concurrency) như Node.js, Go hoặc Elasticsearch, nút thắt cổ chai này gần như là điều không thể tránh khỏi.

Kiến trúc Linux dựa trên triết lý rằng hầu như mọi thứ đều là file. Từ network socket, log hệ thống đến index của cơ sở dữ liệu, tất cả đều tiêu tốn một File Descriptor (FD).

Theo mặc định, nhiều bản phân phối Linux đặt giới hạn khá khiêm tốn—thường chỉ 1.024 FD cho mỗi tiến trình—để bảo vệ tài nguyên hệ thống khỏi các script chạy lỗi. Trong môi trường production hiện đại, các giá trị mặc định này trở thành một rủi ro. Sau nhiều tháng tinh chỉnh trên 12 cụm máy chủ khác nhau, tôi đã đúc kết được quy trình chuẩn để chuyển từ các thiết lập mặc định mong manh sang một cấu hình mạnh mẽ, hiệu năng cao.

Ba lớp quản lý tài nguyên

Quá trình xử lý sự cố đã hé lộ ba cách khác nhau để quản lý giới hạn tài nguyên. Việc chọn sai lớp quản lý là lý do chính khiến các thay đổi của lập trình viên bị “biến mất” sau khi khởi động lại hệ thống hoặc dịch vụ.

1. Phiên làm việc của Shell (lệnh ulimit)

Đây là phương pháp sửa lỗi nhanh thường thấy trên các diễn đàn. Chạy lệnh ulimit -n 65535 trong terminal sẽ có tác dụng ngay lập tức. Tuy nhiên, thay đổi này chỉ áp dụng cho phiên shell đó và các tiến trình con của nó. Khi bạn đăng xuất hoặc máy chủ khởi động lại, giới hạn sẽ quay về mức mặc định 1024. Nó hoàn hảo để kiểm tra nhanh, nhưng vô dụng đối với các giải pháp production lâu dài.

2. Cấu hình cấp người dùng (/etc/security/limits.conf)

Đây là cách tiếp cận truyền thống để xác định giới hạn cứng (hard limit) và giới hạn mềm (soft limit) cho người dùng hoặc nhóm cụ thể. Dù nó hoạt động tốt cho các phiên SSH, nhưng đây là một “cái bẫy” phổ biến. Các dịch vụ hiện đại được quản lý bởi systemd hoàn toàn bỏ qua limits.conf. Nếu bạn cố gắng mở rộng dịch vụ Nginx bằng cách chỉnh sửa file này, các thay đổi sẽ không bao giờ có hiệu lực.

3. Ghi đè cấp dịch vụ (systemd Unit Files)

Đối với các bản phân phối hiện đại như Ubuntu 22.04+, Debian hoặc AlmaLinux, systemd là nơi quản lý chính xác nhất. Để thay đổi giới hạn cho một dịch vụ như Nginx hoặc MySQL, bạn phải sử dụng cơ chế service override. Đây là phương pháp chính xác và đáng tin cậy nhất cho môi trường production vì nó gắn liền với dịch vụ bất kể dịch vụ đó được khởi động như thế nào.

So sánh các phương pháp quản lý

Phương pháp Ưu điểm Nhược điểm
lệnh ulimit Tức thì, không cần quyền root khi giảm giới hạn. Mất khi đăng xuất; không duy trì lâu dài.
limits.conf Tốt cho môi trường nhiều người dùng và lập trình viên. Bị systemd bỏ qua; yêu cầu phiên đăng nhập mới.
systemd override Chuẩn cho các dịch vụ hiện đại; duy trì sau khi khởi động lại. Yêu cầu reload daemon; áp dụng cho từng dịch vụ cụ thể.
sysctl (kernel) Đặt mức trần tuyệt đối cho toàn bộ hệ điều hành. Sẽ không hiệu quả nếu giới hạn cho mỗi tiến trình vẫn ở mức thấp.

Chiến lược Production hiệu quả

Chiến lược ổn định nhất là sử dụng phương pháp kết hợp. Bạn phải nâng mức trần ở cấp kernel trước bằng cách tối ưu hóa tham số Kernel, sau đó thiết lập các giá trị mặc định hợp lý cho người dùng, và cuối cùng là cấp giới hạn cao một cách rõ ràng cho các ứng dụng quan trọng. Trong kinh nghiệm quản lý máy chủ tải cao của mình, tôi đã học được rằng luôn phải kiểm tra giới hạn từ góc nhìn của chính tiến trình đang chạy. Tôi từng mất 3 giờ hệ thống ngừng hoạt động vì giới hạn “Cứng” (Hard) được đặt thấp hơn giới hạn “Mềm” (Soft), khiến hệ điều hành âm thầm loại bỏ toàn bộ cấu hình.

Cấu hình khuyến nghị:

  • Kernel: Đặt fs.file-max thành 2.097.152 (2 triệu) để đảm bảo phần cứng có thể xử lý tổng tải toàn hệ thống.
  • Systemd: Áp dụng LimitNOFILE=65535 cho mọi unit của các dịch vụ có lưu lượng truy cập cao.
  • Người dùng: Đặt giới hạn 10.000 FD trong limits.conf cho các lập trình viên để ngăn một script lỗi làm treo toàn bộ node.

Hướng dẫn triển khai

Bước 1: Các lệnh chẩn đoán

Đừng đoán; hãy thực hiện chẩn đoán hệ thống bằng cách kiểm tra hệ thống file /proc. Nếu Nginx đang chạy, bạn có thể xem giới hạn thực tế của nó bất kể các file cấu hình ghi gì:

# Lấy giới hạn thực tế của tiến trình Nginx
cat /proc/$(pidof nginx | awk '{print $1}')/limits | grep "Max open files"

Để xem một tiến trình hiện đang mở bao nhiêu file, hãy sử dụng lsof. Nếu con số này gần mức 1024, bạn đang ở trong vùng nguy hiểm:

# Đếm số lượng file descriptor đang hoạt động cho một PID cụ thể
lsof -n -p <PID> | wc -l

Bước 2: Nâng giới hạn Kernel

Nếu giới hạn trên toàn hệ thống quá thấp, việc điều chỉnh ở cấp tiến trình sẽ không có tác dụng. Kiểm tra mức tối đa toàn cầu hiện tại:

cat /proc/sys/fs/file-max

Để tăng giới hạn này lên 2 triệu một cách lâu dài, hãy chỉnh sửa /etc/sysctl.conf:

# Thêm vào /etc/sysctl.conf
fs.file-max = 2097152

# Tải thiết lập mới ngay lập tức
sysctl -p

Bước 3: Systemd Override (Tiêu chuẩn hiện đại)

Đối với các dịch vụ như Nginx hoặc Redis, hãy tạo một file override. Cách này sạch sẽ hơn việc chỉnh sửa trực tiếp file dịch vụ chính, vốn có thể bị ghi đè khi cập nhật gói phần mềm.

# Mở trình soạn thảo override cho Nginx
systemctl edit nginx

Dán đoạn mã sau:

[Service]
LimitNOFILE=65535

Áp dụng các thay đổi bằng cách reload daemon và khởi động lại dịch vụ:

systemctl daemon-reload
systemctl restart nginx

Bước 4: Giới hạn người dùng cho các script

Nếu bạn có một người dùng deploy chuyên chạy các script migration thủ công, hãy cập nhật /etc/security/limits.conf:

# /etc/security/limits.conf
deploy         soft      nofile         10000
deploy         hard      nofile         65535

Lưu ý: Người dùng có thể tự tăng giới hạn “Mềm” (Soft) của mình lên đến mức giới hạn “Cứng” (Hard). Giới hạn “Cứng” đóng vai trò là mức trần mà chỉ root mới có thể thay đổi.

Tổng kết

Quản lý tài nguyên hiệu quả bắt đầu từ việc nắm rõ thông tin. Sau sáu tháng chạy các thiết lập tối ưu này, các lỗi “Too many open files” của chúng tôi đã biến mất hoàn toàn. Bài học lớn nhất của tôi là: Đừng bao giờ tin hoàn toàn vào file cấu hình—hãy tin vào kết quả của /proc/[PID]/limits. Nếu file đó hiển thị 1024, tiến trình của bạn đang bị kẹt ở 1024. Bằng cách kết hợp tinh chỉnh cấp kernel với systemd override, bạn sẽ xây dựng được một môi trường máy chủ vững vàng ngay cả khi lưu lượng truy cập tăng đột biến.

Share: