crossorigin="anonymous">
외과전담간호사의 조용한 복습시간

리눅스

리눅스에서 시스템 리소스 제한하기 – ulimit, cgroups 활용

bluefrog 2025. 4. 25. 08:53

리눅스 서버를 운영하다 보면, 어느 날 갑자기 특정 프로세스 하나가 메모리를 다 먹거나, 무한히 자식 프로세스를 만들어서 서버 전체가 멈춰버리는 일이 생긴다. 나도 예전에 개발용 서버에서 Python 스크립트 테스트하다가 fork 폭탄을 터뜨린 적 있었는데, ssh 연결도 끊기고, 아무 키도 안 먹히는 경험은 정말 잊을 수가 없다. 그때부터 진지하게 생각하게 된 게 시스템 리소스를 사전에 제한하는 방법, 즉 ulimit과 cgroups였다.

이 두 가지는 리눅스에서 각각 사용자 수준과 시스템 수준에서 자원 사용량을 제한할 수 있게 해준다. 쉽게 말해, ‘이 이상은 못 써’라는 리소스 한도를 걸어주는 도구라고 보면 된다.

먼저 가장 간단하게 쓸 수 있는 게 ulimit이다. ulimit은 사용자 단위로 열 수 있는 파일 수, 생성 가능한 프로세스 수, 메모리, 스택 크기 등을 제한하는 기능이다. 예를 들어 ulimit -n이라고 치면 현재 세션에서 열 수 있는 최대 파일 디스크립터 수를 확인할 수 있다. 기본값은 1024인 경우가 많다.

근데 요즘 웹 서버나 대용량 네트워크 프로그램은 이 파일 핸들 수가 부족할 수 있다. nginx나 Node.js 같은 서비스는 커넥션 하나당 파일 디스크립터 하나를 쓰니까, 연결 수가 많아지면 금방 한도에 도달한다. 이럴 땐 ulimit -n 65535처럼 값을 올려주면 된다. 다만 이건 현재 쉘 세션에서만 적용되기 때문에, 시스템 전역으로 설정하려면 /etc/security/limits.conf 파일을 수정해야 한다.

예를 들어 nginx 유저에 대해 파일 디스크립터 제한을 높이고 싶으면 아래처럼 작성한다:

nginx
복사편집
nginx soft nofile 65535 nginx hard nofile 65535

여기서 soft는 기본 적용값, hard는 최대 한도다. 사용자가 soft 제한을 변경할 수는 있지만, hard 제한을 넘을 수는 없다. 이 설정이 적용되려면 PAM과 SSH도 함께 설정해줘야 하는 경우가 있으니 /etc/pam.d/common-session이나 /etc/pam.d/sshd에 다음 라인도 확인해야 한다:

swift
복사편집
session required pam_limits.so

ulimit으로 제한할 수 있는 항목은 정말 많다. ulimit -a로 보면 현재 설정된 한도들이 쭉 뜨는데, max user processes, open files, stack size, core file size 등등 다양하다. 특히 fork 폭탄 방지를 위해 ulimit -u로 프로세스 수를 제한하는 것도 아주 효과적이다. 일반 사용자에 대해 300개 이하로 잡아두면, 실수로 무한 fork를 시도해도 시스템 전체가 먹통 되는 건 막을 수 있다.

하지만 ulimit의 한계도 있다. 이건 어디까지나 “사용자” 기준이다. 즉, 개별 유저가 동시에 몇 개의 파일, 프로세스, 리소스를 쓸 수 있는지를 조절할 뿐이고, 컨테이너나 다중 서비스 환경에서는 좀 더 세밀한 통제가 필요하다. 그때 쓰는 게 바로 **cgroups(Control Groups)**다.

cgroups는 리눅스 커널이 제공하는 기능으로, 프로세스 그룹 단위로 CPU, 메모리, 디스크 I/O, 네트워크 사용량을 제한하거나 모니터링할 수 있다. 말 그대로 자원을 “컨테이너처럼” 격리시키는 기능이다. docker도 내부적으로는 cgroups를 활용해서 리소스를 제한하고 관리한다.

cgroups는 v1과 v2가 있는데, 요즘은 대부분 시스템에서 cgroups v2를 기본으로 쓰거나 지원하고 있다. 설정 방법은 복잡할 수도 있지만, 개념은 단순하다. 특정 서비스나 프로세스를 하나의 그룹으로 묶고, 그 그룹에 대해 자원 한도를 정해주는 방식이다.

예를 들어 systemd 기반 시스템에서는 유닛 파일에 MemoryMax=, CPUQuota= 같은 설정을 추가하면 cgroup 제한이 적용된다. 예시로 nginx에 메모리 500MB 한도, CPU 50% 제한을 주고 싶다면 이렇게 설정한다:

ini
복사편집
[Service] MemoryMax=500M CPUQuota=50%

이렇게 설정하고 나서 systemctl daemon-reexec && systemctl restart nginx 하면, nginx는 지정된 범위 안에서만 자원을 사용할 수 있다. 메모리를 초과하면 OOM(Out of Memory)으로 프로세스가 종료되고, CPU는 50%까지만 할당된다.

더 강력하게 컨트롤하고 싶을 땐 cgcreate, cgset, cgexec 같은 도구를 쓰는 cgroup-tools도 있다.
예를 들어 cgcreate -g memory,cpu:/mygroup 으로 cgroup을 만들고, cgset -r memory.limit_in_bytes=500000000 mygroup처럼 설정한 다음, cgexec -g memory,cpu:mygroup ./myscript.sh 이렇게 실행하면 된다.

물론 이건 좀 더 저수준의 접근이고, 실무에서는 systemd나 docker 환경에서 cgroup을 직접적으로 건드릴 일은 드물긴 하다. 하지만 어떤 서비스가 자원을 초과해서 문제를 일으키는지 모니터링하고, 방지하기 위한 정책을 짤 땐 꼭 알아야 할 부분이다.


정리하자면,

  • ulimit은 사용자가 직접 자원 제한을 설정할 수 있는 가장 간단한 방식이다.
  • limits.conf, PAM 설정 등을 통해 시스템적으로 강제할 수도 있다.
  • cgroups는 프로세스 그룹 단위로 좀 더 정밀하게 리소스를 제한할 수 있고, 도커, 시스템 서비스 등과 결합해 실무에서 매우 많이 쓰인다.

한마디로 말해서, ulimit은 “한 명에게 줄 리소스 조절”, cgroups는 “작업 단위(서비스나 컨테이너)에 줄 리소스 조절”이라고 보면 된다.

처음엔 이런 거 신경 안 쓰고 서버만 돌리지만, 실전에서 한 번이라도 메모리 폭주, fork 폭탄, CPU 점유율 100% 같은 걸 경험하면, 이 리소스 제한 설정이 얼마나 중요한지 절실히 느껴진다. 지금이라도 테스트 서버에서 ulimit 한 번 바꿔보고, systemd 유닛에 MemoryMax나 CPUQuota 넣어서 실험해보면 금방 감이 잡힌다. 사전 방어가 결국 서버를 살리는 법이다.