ลดความยุ่งยากในการใช้คลาวด์ พูดคุยกับเจ้าหน้าที่

แก้ปัญหา Too many open files ด้วย ulimit และ systemd
Home แก้ปัญหา Too many open files ด้วย ulimit และ systemd

แก้ปัญหา Too many open files ด้วย ulimit และ systemd

ข้อความ Too many open files ใน Log ของ Nginx, MySQL หรือแอป Node.js เป็นสัญญาณว่าโปรเซสนั้นชนเพดานจำนวนไฟล์ที่เปิดได้พร้อมกัน บน Linux ทุกอย่างนับเป็นไฟล์ รวมถึงการเชื่อมต่อเครือข่ายด้วย เว็บที่มีผู้ใช้พร้อมกันหลักพันจึงชนเพดานได้ง่ายกว่าที่คิด

ความสับสนของเรื่องนี้คือมีเพดานอยู่สามชั้นที่แยกกันคนละที่ ตั้งผิดชั้นแล้วค่าจะไม่มีผลเลย คู่มือนี้ไล่ทั้งสามชั้นตามลำดับที่ควรตรวจ

สิ่งที่ต้องเตรียม

  • Linux Server ที่ใช้ systemd และสิทธิ์ sudo
  • ชื่อบริการที่มีปัญหา เช่น nginx, mysql หรือชื่อ service ของแอปคุณ

ขั้นตอนที่ 1: ยืนยันว่าชนเพดานจริง

ค้นหาข้อความใน Log ก่อน

sudo journalctl --since '1 day ago' | grep -i 'too many open files'
sudo grep -i 'too many open files' /var/log/nginx/error.log

ดูตัวเลขรวมทั้งระบบ ค่าสามตัวคือ จำนวนที่เปิดอยู่ จำนวนที่จัดสรรแล้วแต่ยังไม่ใช้ และเพดานรวม

cat /proc/sys/fs/file-nr

หาโปรเซสที่เปิดไฟล์มากที่สุด

sudo lsof 2>/dev/null | awk '{print $2}' | sort | uniq -c | sort -rn | head -10

นำ PID ที่ได้ไปดูชื่อโปรเซสและเพดานจริงของมัน

ps -p 1234 -o pid,comm,user
cat /proc/1234/limits | grep 'open files'
ls /proc/1234/fd | wc -l

บรรทัดสุดท้ายคือจำนวนไฟล์ที่โปรเซสนั้นเปิดอยู่จริงในขณะนี้ เทียบกับ Soft Limit ที่เห็นในบรรทัดก่อนหน้า

ขั้นตอนที่ 2: เข้าใจเพดานสามชั้น

  • ชั้นทั้งระบบ คือ fs.file-max เพดานรวมของทุกโปรเซสในเครื่อง ปกติสูงมากอยู่แล้วและไม่ค่อยเป็นสาเหตุ
  • ชั้น systemd service คือ LimitNOFILE ใช้กับบริการที่เริ่มโดย systemd ซึ่งก็คือเกือบทุกอย่างบนเซิร์ฟเวอร์ยุคนี้ นี่คือชั้นที่ถูกมองข้ามบ่อยที่สุด
  • ชั้นผู้ใช้ คือ /etc/security/limits.conf ใช้เมื่อผู้ใช้ล็อกอินเข้ามาแล้วรันโปรแกรมเอง ไม่มีผลกับบริการที่ systemd เป็นคนเริ่ม

สาเหตุที่คนแก้ไม่หายส่วนใหญ่คือไปแก้ limits.conf ทั้งที่ปัญหาอยู่ที่ LimitNOFILE ของ systemd

ขั้นตอนที่ 3: ตั้งค่าที่ระดับ systemd service

อย่าแก้ไฟล์ service ต้นฉบับ ให้ใช้ไฟล์เสริมแทน ซึ่งจะไม่ถูกเขียนทับเมื่ออัปเดตแพ็กเกจ

sudo systemctl edit nginx

พิมพ์เนื้อหานี้ลงไปในส่วนที่ว่าง

[Service]
LimitNOFILE=65535

บันทึกแล้วโหลดใหม่

sudo systemctl daemon-reload
sudo systemctl restart nginx

ยืนยันผล

systemctl show nginx -p LimitNOFILE
cat /proc/$(pgrep -f 'nginx: master' | head -1)/limits | grep 'open files'

ทำแบบเดียวกันกับบริการอื่นได้ เช่น sudo systemctl edit mysql หรือ sudo systemctl edit php8.3-fpm

ขั้นตอนที่ 4: ตั้งค่าเฉพาะของแอปเพิ่ม

บางโปรแกรมมีเพดานของตัวเองซ้อนอีกชั้น ต้องตั้งให้สอดคล้องกัน

Nginx

sudo nano /etc/nginx/nginx.conf
worker_rlimit_nofile 65535;

events {
    worker_connections 16384;
}

คำนวณคร่าว ๆ จำนวนการเชื่อมต่อสูงสุด = worker_processes คูณ worker_connections และ worker_rlimit_nofile ควรมากกว่า worker_connections อย่างน้อยสองเท่า เพราะหนึ่งคำขอที่ผ่าน Proxy ใช้ทั้งการเชื่อมต่อขาเข้าและขาออก

MySQL / MariaDB

sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
open_files_limit = 65535

ตรวจค่าที่ใช้จริงหลังรีสตาร์ต

mysql -e "SHOW VARIABLES LIKE 'open_files_limit';"

PHP-FPM

sudo nano /etc/php/8.3/fpm/php-fpm.conf
rlimit_files = 65535

ขั้นตอนที่ 5: ตั้งค่าระดับผู้ใช้ (เมื่อจำเป็น)

จำเป็นเมื่อคุณรันโปรแกรมจาก Shell เอง เช่น สคริปต์ที่สั่งด้วยมือหรือรันผ่าน screen

sudo nano /etc/security/limits.d/99-nofile.conf
*        soft  nofile  65535
*        hard  nofile  65535
root     soft  nofile  65535
root     hard  nofile  65535

ต้องออกจากระบบแล้วล็อกอินใหม่จึงมีผล ตรวจด้วย

ulimit -n
ulimit -Hn

บนบางระบบต้องมั่นใจว่าโมดูล PAM เปิดใช้งานอยู่ ตรวจว่ามีบรรทัด session required pam_limits.so ใน /etc/pam.d/common-session

ขั้นตอนที่ 6: ตั้งค่าเพดานทั้งระบบ

sudo nano /etc/sysctl.d/99-file-max.conf
fs.file-max = 2097152
fs.nr_open = 1048576
sudo sysctl --system
cat /proc/sys/fs/file-max

fs.nr_open คือเพดานสูงสุดที่โปรเซสหนึ่งตัวจะขอได้ หากตั้ง LimitNOFILE สูงกว่าค่านี้ บริการจะเริ่มไม่ขึ้น

ขั้นตอนที่ 7: หาต้นเหตุจริง ไม่ใช่แค่ขยายเพดาน

เพดานที่ชนบ่อยอาจเป็นอาการ ไม่ใช่โรค ตรวจสองอย่างนี้ก่อนตัดสินใจเพิ่มค่าไปเรื่อย ๆ

# ดูว่าโปรเซสเปิดอะไรค้างไว้
sudo lsof -p 1234 | awk '{print $5}' | sort | uniq -c | sort -rn | head

# ดูการเชื่อมต่อค้างสถานะ CLOSE_WAIT ซึ่งแปลว่าแอปไม่ปิด socket
ss -tan state close-wait | wc -l

ถ้ามี CLOSE_WAIT สะสมเรื่อย ๆ แปลว่าโค้ดของแอปเปิดการเชื่อมต่อแล้วไม่ปิด การเพิ่มเพดานจะแค่เลื่อนเวลาที่จะพังออกไป ต้องแก้ที่โค้ด

ปัญหาที่พบบ่อย

ตั้ง limits.conf แล้วแต่บริการยังชนเพดานเดิม

เพราะบริการถูกเริ่มโดย systemd ซึ่งไม่อ่านไฟล์นั้น ต้องใช้ systemctl edit ตั้ง LimitNOFILE แทน

ulimit -n แสดงค่าใหม่แล้วแต่ยังมีปัญหา

ค่าที่เห็นคือของ Shell ปัจจุบัน ไม่ใช่ของโปรเซสที่รันอยู่ ตรวจของจริงที่ /proc/<PID>/limits เสมอ

ตั้งค่าสูงมากแล้วบริการเริ่มไม่ขึ้น

ค่าเกิน fs.nr_open ให้เพิ่ม fs.nr_open ก่อน หรือลด LimitNOFILE ลงมา ดูสาเหตุได้จาก sudo journalctl -u ชื่อบริการ -n 50

ปัญหาเกิดเฉพาะตอนมีคนเข้าเยอะ

เป็นเรื่องปกติ ให้เผื่อเพดานไว้อย่างน้อยสองเท่าของจุดพีคที่เคยวัดได้ และเฝ้าดู /proc/sys/fs/file-nr ต่อเนื่องด้วยระบบมอนิเตอร์

หากต้องการให้ทีมงานช่วยตรวจว่าเพดานปัจจุบันเพียงพอกับทราฟฟิกจริงหรือไม่ ติดต่อ THAI DATA CLOUD ได้ที่ https://thaidata.cloud/contact/

ผู้ให้บริการคลาวด์ไทย
เพื่อธุรกิจของคนไทย

"มุ่งมั่น" และ "มั่นคง"
พร้อมรับมือทุกการเติบโต
Trust Cloud
คลาว์ที่ปลอดภัย
คือรากฐานที่มั่นคง
cloud security