Views: 0
서버 운영에서 “ulimit”는 단순한 쉘 명령이 아니라, 커널 자원 관리와 서비스 안정성을 잇는 핵심 관문이다. 잘못 설정하면 “Too many open files”나 예기치 않은 프로세스 종료가 잦아지고, 반대로 과도하게 풀면 장애 전파 범위가 커진다. 이 글은 ulimit의 동작 원리부터 사용자·서비스별 영구 설정, systemd와 PAM의 연결, 커널 한도와의 관계, 그리고 현장에서 자주 겪는 함정과 해결책까지 체계적으로 정리한다.
목차
ulimit의 기본 개념
ulimit는 현재 셸(프로세스)과 그 자식 프로세스에 적용되는 “프로세스 자원 한도”를 조회·변경하는 인터페이스다. 대부분의 셸에서 내장(builtin) 명령으로 제공된다.
- soft limit: 현재 세션이 즉시 사용하는 값. 사용자/프로세스가 하드 한도 범위 내에서 올릴 수 있다.
- hard limit: 상한선. 보통 관리자만 변경 가능하며, soft를 올릴 수 있는 최대치다.
대표 옵션
-nnofile: 한 프로세스가 동시에 열 수 있는 FD(파일 디스크립터) 수-unproc: 사용자별 생성 가능한 프로세스/스레드 수(커널/배포판에 따라 스레드 포함)-ccore: 코어덤프 파일 크기-sstack: 스택 크기-lmemlock: 잠금 메모리 크기-ffsize: 생성 파일 최대 크기-vas: 가상 메모리 한도
현재 한도 확인
ulimit -a
cat /proc/$$/limits
“한도”는 누가 최종 결정하나: 적용 순서 이해
동일한 서버에서도 터미널, SSH, 서비스마다 한도가 달라질 수 있다. 이유는 적용 지점이 다르기 때문이다. 실무 감각으로 기억할 순서는 다음과 같다.
- 커널·cgroup 전역 상한
- 커널 sysctl(예:
fs.file-max,kernel.pid_max)과 cgroup(Task/PID/Memory 등) 한도가 절대 상한을 잡는다. - systemd 기반 시스템에서는 PID/Tasks 상한을
TasksMax=로, FD 상한을 각 서비스의LimitNOFILE=로도 제어한다.
- 로그인 경로의 PAM limits
- SSH, 콘솔 로그인 등 PAM을 통과하는 세션은
/etc/security/limits.conf및limits.d/*.conf의 규칙을 적용받는다. - SSH에서 적용하려면
sshd_config에UsePAM yes가 필수다.
- 셸 내 ulimit
- 셸 프로파일(
~/.bashrc,~/.zshrc,/etc/profile등)에서 ulimit를 올리면 해당 셸과 자식에게만 적용된다.
- 프로세스 상속
- 부모가 가진 한도를 자식이 상속한다. 서비스 관리자는 부모가 systemd인지, 사용자인지에 따라 결과가 달라진다.
핵심: 전역 커널 상한 > cgroup/systemd > PAM > 셸 순서로 생각하면 충돌 원인 파악이 빨라진다.
로그인 사용자에 대한 영구 설정(PAM)
대부분의 사용자 세션은 PAM을 경유한다. 아래 방식이 표준적이다.
# /etc/security/limits.d/99-custom.conf
# <도메인> <타입> <아이템> <값>
@developers soft nofile 65535
@developers hard nofile 65535
@developers soft nproc 4096
@developers hard nproc 8192
- 도메인: 사용자명 또는 그룹명(@그룹)
- 타입: soft/hard
- 아이템: nofile, nproc 등
- SSH에 적용하려면
/etc/ssh/sshd_config에UsePAM yes유지 su와su -의 차이:su -는 로그인 셸처럼 PAM과 프로파일을 초기화하므로 한도 적용 결과가 달라질 수 있다.
검증
ssh user@host
ulimit -n
cat /proc/$$/limits | grep -E 'Max open files|Max processes'
서비스 단위 영구 설정(systemd)
데몬·백그라운드 서비스는 PAM 경로를 거치지 않는다. 따라서 systemd 유닛에 직접 한도를 명시해야 한다.
예시: Nginx의 FD 상한 조정
# /etc/systemd/system/nginx.service.d/limits.conf
[Service]
LimitNOFILE=200000
LimitNPROC=16384
TasksMax=infinity
적용
systemctl daemon-reload
systemctl restart nginx
systemctl show nginx -p LimitNOFILE -p TasksMax
중요 포인트
- 소켓도 FD를 소비하므로 트래픽 높은 프록시/DB/메시징 서버는
LimitNOFILE을 넉넉히 잡는다. - 스레드 풀형 서버는
LimitNPROC(스레드 포함으로 계산되는 환경 다수)에 걸려 스레드 생성 실패가 날 수 있다. - 많은 서비스가 상위 슬라이스의
TasksMax를 상속한다. 전역 완화가 필요하면/etc/systemd/system.conf또는user.conf의DefaultTasksMax=로 조정한다. 단, 무한대는 신중하게 사용한다.
커널·시스템 전역 한도와의 관계
fs.file-max: 시스템 전체에서 열 수 있는 FD 총량 상한. 프로세스별nofile합보다 작으면 병목이 생긴다.
sysctl fs.file-max
sysctl -w fs.file-max=2097152
kernel.pid_max: 시스템 전체 PID 상한. 사용자별nproc을 키워도 PID 전역 상한에 막히면 프로세스/스레드 생성 실패가 난다.
sysctl kernel.pid_max
- cgroup v2 환경: 많은 배포판이 기본 v2를 사용한다. 프로세스 수는 “PIDs 컨트롤러”로 제한되며, systemd의
TasksMax=로 쉽게 제어한다. 컨테이너 런타임도 cgroup 한도를 동시에 건다.
컨테이너 환경에서의 ulimit
- Docker: 컨테이너 시작 시
--ulimit nofile=...,--ulimit nproc=...로 지정 가능. Docker 데몬의 기본 ulimit을 설정할 수도 있다. - Kubernetes: 컨테이너 런타임/CRI마다 노출 방법이 다르며, 종종 파드 보안 설정과 상호작용한다. 높은 FD가 필요한 애플리케이션은 노드 단(systemd) 한도와 런타임 한도를 함께 점검한다.
- 컨테이너 내부에서
ulimit를 올려도, 상위 호스트의 cgroup/systemd 한도를 초과할 수 없다.
실무 설정 레시피
대용량 연결 처리용 FD 상향
목표: 웹 프록시/DB 프런트엔드의 “Too many open files” 제거
- 커널 전역 FD 풀 점검·확대
sysctl fs.file-max
sysctl -w fs.file-max=2097152
- 서비스 유닛에 한도 설정
[Service]
LimitNOFILE=200000
- 소켓/워커 설정 동반 조정
애플리케이션의 워커 수, 이벤트 루프, DB 커넥션 풀도 함께 늘리지 않으면 기대 효과가 제한된다. - 검증
systemctl show <svc> -p LimitNOFILE
lsof -p <pid> | wc -l
빌드 서버의 폭주 방지(nproc)
목표: CI 빌드/테스트가 노드를 고갈시키지 않도록 사용자별 프로세스 상한 설정
- PAM limits로 그룹 제한
@ci soft nproc 4096
@ci hard nproc 8192
- systemd 서비스(에이전트)가 스폰하는 작업이면
LimitNPROC=와TasksMax=도 세팅 - 부작용 점검: 스레드 많은 JVM/Go/Node 작업이 에러를 내면 상한을 단계적으로 올린다.
디버깅을 위한 코어덤프 허용
목표: 장애 분석 시 코어 파일 확보
- 시스템 정책과 보안 요건 확인 후 제한 완화
ulimit -c unlimited
- 영구화는 PAM 또는 프로파일에 설정, systemd 서비스는
LimitCORE=infinity - 저장 위치와 크기 정책:
coredumpctl또는kernel.core_pattern활용
자주 겪는 함정과 해결법
- 셸에서 올렸는데 서비스에는 반영되지 않음
서비스는 PAM/셸을 거치지 않는다. systemd 유닛의Limit*항목을 수정해야 한다. - SSH에선 적용되는데
su로 전환하면 초기화됨su는 비로그인 셸이라 PAM 규칙을 재적용하지 않는다.su -를 사용하거나/etc/pam.d/su구성을 확인한다. - FD를 크게 올렸는데 여전히 에러
애플리케이션 자체의 FD 상한(예: JVM-XX:MaxFDLimit)이나 워커 수, 파일 핸들 캐시가 병목일 수 있다. 또한fs.file-max가 충분한지 확인한다. - nproc이 스레드까지 묶여 OOM 비슷한 현상
많은 배포판에서 스레드=프로세스로 계산한다. 스레드 많은 서버는LimitNPROC과TasksMax를 함께 조정한다. - 컨테이너에서만 실패
호스트의 cgroup 한도 또는 런타임의--ulimit기본값이 낮을 수 있다. 노드 단 설정부터 확인한다.
운영 점검 체크리스트
- 현재 프로세스 한도:
cat /proc/$$/limits - 서비스 한도:
systemctl show <svc> -p LimitNOFILE -p LimitNPROC -p TasksMax - 실패 로그:
journalctl -u <svc> -b -p warning..emerg - 전역 자원:
sysctl fs.file-max,sysctl kernel.pid_max - 실제 사용량 관찰:
lsof -p <pid> | wc -l,ps -o nlwp= -p <pid>(스레드 수)
보안과 안정성 관점
- 제한은 “문제의 확산”을 막는 안전장치다. 특히 멀티테넌시 서버, 공용 빌드 노드, 공개 API 프런트엔드는 합리적 상한이 필수다.
- 반대로 핵심 인프라(DB, 로드밸런서)는 한도를 낮게 잡으면 서비스 중단으로 직결될 수 있다. 트래픽 패턴과 피크 시나리오에 맞춰 여유치를 둔다.
- 모든 변경은 스테이징에서 부하 테스트로 검증하고, 운영 반영 시 롤백 계획을 준비한다.
빠르게 써먹는 명령 모음
# 세션 한도 전체 보기
ulimit -a
cat /proc/$$/limits
# 셸에서 임시 상향
ulimit -n 200000
ulimit -u 8192
# systemd 서비스 한도 설정(드롭인)
mkdir -p /etc/systemd/system/myapp.service.d
cat >/etc/systemd/system/myapp.service.d/limits.conf <<'EOF'
[Service]
LimitNOFILE=200000
LimitNPROC=16384
TasksMax=infinity
EOF
systemctl daemon-reload && systemctl restart myapp && systemctl show myapp -p LimitNOFILE -p TasksMax
# PAM limits: 로그인 사용자/그룹
echo '@web soft nofile 65535' >> /etc/security/limits.d/99-web.conf
echo '@web hard nofile 65535' >> /etc/security/limits.d/99-web.conf
# 전역 커널 FD 풀
sysctl -w fs.file-max=2097152
마무리
ulimit는 단독으로 존재하지 않는다. 커널 전역 한도, cgroup(systemd), PAM, 셸이 층층이 얽혀 실제 유효값을 만든다. 어떤 경로로 프로세스가 시작되는지부터 파악하고, 전역-서비스-사용자 순서로 논리적으로 맞춰가면 “열린 파일 부족”이나 “프로세스 생성 실패” 같은 고질적 장애를 설계 단계에서 차단할 수 있다.