리눅스에서 ulimit은 각 프로세스가 사용할 수 있는 자원의 상한선을 설정하는 기능이다. 여기서 중요한 포인트는 이 제한이 프로세스 한 개에만 머무는 것이 아니라 해당 셸에서 실행되는 모든 자식 프로세스까지 그대로 이어진다는 점이다.
즉 운영자가 로그인한 셸에서 ulimit 설정을 바꾸면 그 셸에서 실행하는 서버 데몬과 스크립트 전체에 영향을 준다. 반대로 시스템 부팅 과정에서 서비스가 어떤 환경에서 실행되느냐에 따라 ulimit 값이 이미 정해져 있는 경우도 많다.
ulimit 설정이 시스템 전체에 어떤 식으로 영향을 주는지 이해하려면 먼저 자원 종류와 적용 범위를 나눠 보는 것이 도움이 된다.
목차
소프트 제한과 하드 제한의 차이
ulimit에는 소프트 제한과 하드 제한이라는 두 가지 개념이 있다.
- 소프트 제한
현재 프로세스에 실제로 적용되는 값이다. 사용자가 세션 안에서 낮춰서 설정할 수 있다. - 하드 제한
해당 자원에 대해 설정할 수 있는 최대 상한이다. 일반 사용자는 이 값을 더 높이는 것이 보통 불가능하고 낮추는 것만 허용되는 경우가 많다.
예를 들어 파일 디스크립터를 1024에서 4096으로 늘리고 싶다면 하드 제한이 4096 이상으로 설정되어 있어야 한다. 시스템 레벨에서 너무 낮은 하드 제한을 잡아 놓으면 애플리케이션 튜닝에도 제약이 생긴다.
파일 디스크립터 제한이 시스템에 미치는 영향
ulimit 설정 중 가장 많이 다루게 되는 값이 open files 항목이다. 흔히 ulimit n 옵션으로 확인하는 값이다.
이 값은 한 프로세스가 동시에 열 수 있는 파일 디스크립터의 개수를 의미한다. 여기에는 실제 파일뿐 아니라 소켓과 파이프 등도 포함된다.
이 값이 너무 낮게 잡혀 있으면 다음과 같은 문제가 발생한다.
- 웹 서버가 동시에 처리할 수 있는 연결 수가 제한된다
- 데이터베이스나 메시지 큐 프로세스가 연결을 더 열지 못하고 에러를 반환한다
- 로그 파일과 임시 파일을 많이 여는 배치 작업이 중간에 실패한다
반대로 이 값을 무작정 크게 늘리는 것도 위험하다. 시스템 전체가 동시에 사용할 수 있는 파일 디스크립터 상한은 커널의 fs file max 등으로 정해져 있다. 개별 프로세스에 과도한 상한을 주면 특정 애플리케이션이 버그나 공격에 의해 디스크립터를 계속 소모해 전체 시스템이 파일을 더 열지 못하는 상황이 올 수 있다.
결국 파일 디스크립터 관련 ulimit 설정은 시스템 전역 상한과 애플리케이션 특성을 함께 고려해 균형 있게 설정해야 한다.
프로세스 수 제한과 포크 폭주 방지
ulimit의 nproc 항목은 한 사용자가 만들 수 있는 프로세스 수를 제한한다. 이 값은 특히 멀티 유저 시스템이나 컨테이너 환경에서 중요하다.
만약 nproc 제한이 없다면 다음과 같은 상황이 벌어질 수 있다.
- 버그가 있는 스크립트가 무한 포크를 하면서 프로세스가 폭발적으로 늘어난다
- 특정 계정이 실수 혹은 악의적으로 대량의 프로세스를 생성해 다른 사용자 프로세스를 실행하지 못하게 만든다
- 커널이 프로세스 관리 테이블을 모두 소진하면서 시스템 전체 응답이 멈춘다
nproc 제한을 적절히 걸어 두면 한 계정이 사용할 수 있는 프로세스 수를 강제로 제한해 이런 폭주를 막을 수 있다. 반대로 이 값을 너무 낮게 잡으면 애플리케이션이 필요로 하는 워커 프로세스를 충분히 띄우지 못해 정상적인 서비스 운영이 어려워질 수 있다.
메모리와 스택 크기 제한이 주는 영향
ulimit을 통해 메모리 관련 자원도 제어할 수 있다. 대표적으로
- 주소 공간 크기
- 개별 프로세스의 가상 메모리 크기
- 스택 크기
등을 제한할 수 있다.
스택 크기 제한은 재귀 호출이 많은 프로그램에서 스택 오버플로를 일으킬 수 있다. 예를 들어 ulimit s 값이 너무 작으면 평소에는 잘 동작하던 프로그램이 특정 입력에서 갑자기 세그멘테이션 폴트로 종료될 수 있다.
반대로 메모리 관련 상한을 너무 느슨하게 두면 잘못 작성된 프로그램이 메모리를 무제한으로 요구하면서 시스템 전체가 스왑을 사용하는 상황으로 빠질 수 있다. 이 경우 다른 서비스들도 함께 느려지거나 응답하지 않게 된다.
따라서 메모리 관련 ulimit은
- 개발 단계에서 애플리케이션의 평균 사용량과 피크 사용량을 측정하고
- 시스템 전체 메모리와 스왑 정책을 고려한 뒤
- 적당한 보호선 역할을 할 수준으로 정해 두는 것이 좋다
코어 덤프 제한과 장애 분석
ulimit c 값은 프로세스가 비정상 종료할 때 코어 덤프 파일을 생성할 수 있는지 여부와 크기 상한을 결정한다.
- 0이면 코어 덤프를 생성하지 않는다
- 양수면 그 크기까지 코어 덤프 파일을 남긴다
- 무제한으로 두면 디버깅에는 편리하지만 디스크를 빠르게 소모할 수 있다
운영 환경에서 코어 덤프를 완전히 막아 두면 보안상 이점이 있을 수 있지만 실제 장애가 발생했을 때 원인을 분석하기가 매우 어렵다. 반대로 모든 프로세스에서 무제한 코어 덤프를 허용하면 백그라운드에서 반복적으로 죽는 프로세스가 대용량 코어 파일을 여러 개 생성해 디스크를 가득 채우는 문제가 생길 수 있다.
보통은 중요한 데몬에 대해서만 별도로 코어 덤프 경로와 크기를 관리하고 나머지 프로세스는 제한하는 방식으로 절충한다. 이때에도 ulimit 값이 기본 토대가 된다.
로그인 셸과 서비스에서의 ulimit 전파
ulimit 설정은 현재 셸 프로세스에 적용되고 그 셸에서 실행하는 모든 자식 프로세스에 그대로 상속된다. 여기서 시스템 전체에 미치는 효과가 발생한다.
예를 들어 운영자가 직접 로그인해 ulimit n을 65535로 올린 뒤 그 셸에서 웹 서버를 수동으로 실행했다고 하자. 이 경우 웹 서버 프로세스와 그 자식 프로세스들은 모두 65535의 파일 디스크립터 상한을 갖게 된다. 같은 서버에서 다른 사람이 기본값 1024인 상태에서 같은 프로그램을 띄우면 전혀 다른 동작을 하게 된다.
반대로 systemd 같은 서비스 관리자는 서비스 유닛 파일에서 별도로 LimitNOFILE 같은 항목을 지정할 수 있다. 이 값은 서비스 프로세스의 ulimit 값으로 반영된다. 부팅 시 이런 설정이 자동으로 적용되기 때문에 사용자가 로그인해서 ulimit을 바꾸더라도 이미 떠 있는 서비스에는 영향을 주지 않는다.
결국 시스템 전체 동작에 영향을 주는 ulimit은
- 로그인 셸에서 일시적으로 변경한 값인지
- pam이나 limits 설정에서 계정별 기본값으로 지정된 값인지
- 서비스 유닛이나 초기화 스크립트에서 별도로 설정된 값인지
를 구분해 이해해야 한다.
시스템 전역 설정과 ulimit의 관계
많은 배포판에서는 etc security limits conf나 limits d 아래 파일을 통해 사용자나 그룹별 기본 ulimit을 설정한다. 로그인을 거치는 세션에서는 이 설정이 소프트 제한과 하드 제한의 기본값으로 주입된다.
또한 커널 레벨에는 이미 별도의 전역 자원 상한이 있다. 예를 들어
- 전체 파일 디스크립터 개수 상한
- 시스템 전체 프로세스 수 상한
등은 sysctl을 통해 조정하는 값이다. ulimit 값은 이런 전역 상한 안에서만 의미를 가진다. 예를 들어 커널 전역 파일 디스크립터 상한이 100만인데 개별 프로세스 ulimit을 1억으로 설정해도 실제로는 전역 상한을 넘어설 수 없다.
따라서 시스템 전체 자원 정책을 설계할 때는
- 커널 전역 상한
- 사용자와 그룹 별 기본 ulimit
- 개별 서비스에서 오버라이드하는 ulimit
이 세 계층을 함께 조율해야 한다. 한쪽에서만 값을 조정하면 예상과 다른 동작이 나오기 쉽다.
실전에서 ulimit 설계하는 방법
실제 운영 환경에서 ulimit 설정이 시스템 전체에 미치는 영향을 최소화하면서도 보호 장치를 갖추려면 다음과 같은 흐름으로 설계하는 것이 좋다.
첫째 현재 값을 정확히 파악한다
- ulimit a 명령으로 기본값을 확인한다
- 주요 서비스 프로세스의 실제 제한값을 proc 아래 limits 파일로 확인한다
둘째 서비스별 요구 사항을 정리한다
- 웹 서버 데이터베이스 메시지 브로커 등 주요 서비스의 동시 접속 수와 워커 수를 기준으로 필요한 파일 디스크립터와 프로세스 수를 계산한다
- 벤치마크나 부하 시험을 통해 메모리 사용량과 코어 덤프 정책을 정한다
셋째 전역과 개별 설정의 균형을 맞춘다
- 커널 전역 상한을 여유 있게 잡되 무한정 높이지 않는다
- limits 설정에서 일반 사용자와 서비스 계정의 기본값을 분리한다
- systemd 유닛 파일이나 서비스 스크립트에서 필요한 값만 추가로 상향 조정한다
넷째 모니터링과 피드백을 적용한다
- 파일 디스크립터 부족 에러나 포크 실패 로그가 발생하는지 확인한다
- 코어 덤프 생성 여부와 디스크 사용량을 주기적으로 점검한다
- 필요할 때마다 값의 상향 또는 하향 조정을 반복해 안정 구간을 찾는다
이 과정을 거치면 ulimit 설정이 시스템 전체에 미치는 영향을 의도적으로 통제할 수 있고 특정 프로세스의 오동작이 전체 서버를 끌어내리는 상황을 미리 막을 수 있다.
정리
ulimit은 단순한 셸 옵션 수준이 아니라 프로세스가 사용할 수 있는 자원을 제한하는 중요한 보안 장치이자 안정성 도구다. 파일 디스크립터 프로세스 수 메모리 스택 코어 덤프와 같은 설정은 모두 시스템 전체 자원 소비 패턴과 직접적으로 연결된다.
로그인 환경과 서비스 환경에서 ulimit이 어떤 형태로 적용되는지 이해하고 전역 상한과의 관계를 고려해 값을 설계하면 예기치 않은 자원 고갈과 시스템 전체 장애를 예방할 수 있다. 결국 ulimit을 제대로 이해하는 것이 곧 리눅스 서버 자원 관리의 기본기를 다지는 길이다.