Devin.KR
로그인

리눅스 ulimit 사용법 - Too many open files 장애의 원인과 영구 설정

개발자 조회 1

이 명령어를 언제 쓰는가

nginx나 자바 애플리케이션 로그에 Too many open files가 찍히면서 요청이 거부되기 시작할 때 쓴다. 이 오류는 디스크가 찬 것도 메모리가 없는 것도 아니고, 프로세스가 동시에 열 수 있는 파일 디스크립터 개수 한도에 걸린 것이다. 소켓도 파일 디스크립터라서 동시 접속이 늘면 똑같이 걸린다.

ulimit는 그 한도를 보고 바꾸는 셸 내장 명령이다. 진단과 처방을 같은 명령으로 한다는 점에서 특이하지만, 실무에서 중요한 것은 명령 자체보다 어디에 설정해야 실제로 먹히는가다. 대부분의 실패는 여기서 발생한다.

기본 형식

ulimit [-H | -S] [-리소스] [새로운_값]

ulimit는 /usr/bin에 있는 실행 파일이 아니라 셸 내장 명령이다. 그래서 man ulimit이 아니라 bash에서는 help ulimit으로 확인한다. 값을 생략하면 현재 값을 보여주고, 값을 주면 그 셸과 그 셸이 앞으로 띄울 자식 프로세스에만 적용된다.

한도는 두 종류다. soft는 실제로 적용되는 값이고, hard는 soft를 올릴 수 있는 상한이다. 일반 사용자는 soft를 hard 이하 범위에서 자유롭게 올리고 내릴 수 있지만, hard는 낮추기만 가능하고 다시 올릴 수 없다. root만 hard를 올릴 수 있다.

자주 쓰는 옵션

옵션의미예시
-a모든 리소스 한도를 한 번에 표시ulimit -a
-n열 수 있는 파일 디스크립터 개수ulimit -n 65535
-u사용자당 프로세스(스레드) 개수ulimit -u
-s스택 크기(KB). 자바 스레드 수에 영향ulimit -s
-c코어 덤프 크기(0이면 덤프 안 남음)ulimit -c unlimited
-v가상 메모리 총량(KB)ulimit -v
-f만들 수 있는 파일의 최대 크기ulimit -f
-Hhard 한도를 대상으로ulimit -Hn
-Ssoft 한도를 대상으로(기본)ulimit -Sn

Too many open files 진단 순서

1단계 - 셸의 한도가 아니라 문제가 난 프로세스의 한도를 본다. 이게 가장 중요하다. 내 셸에서 ulimit -n이 65535로 나와도 이미 떠 있는 nginx에는 아무 의미가 없다.

pgrep -f nginx
grep 'Max open files' /proc/1234/limits
Max open files            1024                 4096                 files

왼쪽이 soft, 가운데가 hard다. soft가 1024면 이 프로세스는 1024개를 넘길 수 없다.

2단계 - 실제로 몇 개를 쓰고 있는지 센다.

ls /proc/1234/fd | wc -l

이 값이 soft 한도에 붙어 있으면 확정이다. 시스템 전체로 열린 디스크립터는 cat /proc/sys/fs/file-nr로 본다.

3단계 - 누수인지 부족인지 가른다. lsof -p 1234 | awk '{print $5, $9}' | sort | uniq -c | sort -rn | head로 같은 파일이 수천 개 열려 있으면 애플리케이션의 디스크립터 누수이지 한도 문제가 아니다. 한도를 올려도 시간만 벌 뿐 다시 터진다.

실전 예제

1) 현재 셸의 전체 한도를 확인한다.

ulimit -a
ulimit -Hn    # 올릴 수 있는 상한
ulimit -Sn    # 지금 적용 중인 값

2) 지금 이 셸에서 띄울 프로세스만 한도를 올린다(임시 조치).

ulimit -n 65535
./start-server.sh

3) systemd 서비스에 영구 적용한다. 실무에서 이게 정답인 경우가 가장 많다.

sudo systemctl edit nginx.service
[Service]
LimitNOFILE=65535
sudo systemctl daemon-reload
sudo systemctl restart nginx
grep 'Max open files' /proc/$(pgrep -o -f 'nginx: master')/limits

4) 로그인 세션에 영구 적용한다(/etc/security/limits.conf).

sudo tee /etc/security/limits.d/90-nofile.conf >/dev/null <<'EOF'
deploy  soft  nofile  65535
deploy  hard  nofile  65535
EOF

적용은 다음 로그인부터다. * 와일드카드는 root에 적용되지 않으므로 root가 필요하면 root 줄을 따로 써야 한다.

5) 재시작 없이 실행 중인 프로세스의 한도를 바꾼다.

sudo prlimit --pid 1234 --nofile=65535:65535
grep 'Max open files' /proc/1234/limits

prlimit은 util-linux에 포함된 별도 명령으로, 서비스를 내릴 수 없는 상황의 응급 조치다. 다만 프로세스가 재시작되면 원래대로 돌아가므로 반드시 설정 파일도 같이 고쳐야 한다.

6) 코어 덤프를 남겨 크래시를 분석한다.

ulimit -c unlimited
cat /proc/sys/kernel/core_pattern

함정과 주의점

limits.conf는 systemd 서비스에 적용되지 않는다. 이것이 가장 많은 시간을 잡아먹는 함정이다. /etc/security/limits.conf는 PAM의 pam_limits 모듈이 로그인 세션을 만들 때 적용하는 설정이다. systemd가 부팅 시 직접 띄우는 서비스는 로그인 세션을 거치지 않으므로 이 파일을 아예 읽지 않는다. 서비스에는 유닛 파일의 LimitNOFILE=을, 모든 서비스 기본값을 바꾸려면 /etc/systemd/system.confDefaultLimitNOFILE=을 쓴다.

ulimit로 올린 값은 그 셸에서만 산다. 터미널에서 ulimit -n 65535를 치고 서비스를 systemctl restart로 재시작하면 한도는 그대로 1024다. 새 프로세스는 셸이 아니라 systemd(PID 1)의 자식으로 뜨기 때문이다. 반드시 /proc/PID/limits로 결과를 확인하는 습관을 들여야 한다.

hard 한도는 내리면 되돌릴 수 없다. 일반 사용자가 ulimit -Hn 1024를 실행하면 그 셸에서는 다시 못 올린다. 스크립트에서 습관적으로 hard까지 낮추지 않도록 주의한다.

커널 전체 상한이 따로 있다. 프로세스별 한도를 아무리 올려도 fs.nr_open(프로세스가 가질 수 있는 최대 fd 번호, 기본 1048576)을 넘을 수 없고, 시스템 전체 합계는 fs.file-max가 제한한다. 수십만 단위로 올릴 때는 sysctl -w fs.nr_open=2097152도 함께 봐야 한다.

macOS는 체계가 다르다. 이 문서의 /proc 경로와 limits.conf는 리눅스 전용이다. macOS에서는 launchctl limit maxfiles로 시스템 한도를 보고 launchd plist로 설정한다. 컨테이너 환경에서는 도커의 --ulimit nofile=65535:65535 또는 쿠버네티스 노드 런타임 설정이 우선한다.

함께 보면 좋은 명령어

  • lsof - 한도에 걸린 프로세스가 무엇을 그렇게 많이 열었는지 실제로 들여다본다.
  • systemctl - 서비스의 한도는 결국 유닛 파일의 Limit* 지시자로 결정된다.
  • ps - 한도에 걸린 프로세스의 PID를 먼저 찾는 데 쓴다.