조회수: 0
리눅스 서버를 운영하다 보면 특정 프로세스 하나가 CPU와 메모리를 독점해 전체 시스템이 느려지는 상황을 자주 만나게 된다. 예전에는 ulimit나 nice 정도만으로 이런 문제를 완화했지만, 컨테이너와 다양한 워크로드가 한 서버에서 공존하는 지금은 더 정교한 자원 제어가 필요하다.
cgroup v2는 커널 수준에서 프로세스 그룹 단위로 자원을 제한하고 모니터링할 수 있도록 해 준다. 컨테이너 오케스트레이션 시스템뿐 아니라 단독 서버에서도 잘 활용하면 장애 예방과 성능 안정에 큰 도움을 준다.
이 글에서는 cgroup v2의 기본 구조를 간단히 짚고, CPU 메모리 프로세스 수 I O 제한을 순서대로 예제로 보여준다. 마지막에는 systemd와 연계해 실서비스 환경에 적용하는 방법과 운영 팁을 정리한다.
목차
cgroup v2 환경 여부 확인과 기본 개념
먼저 지금 사용 중인 서버가 cgroup v2를 사용하는지 확인해야 한다. 일반적으로 다음과 같은 명령으로 마운트 여부를 확인할 수 있다.
mount | grep cgroup2
출력에 cgroup2와 마운트 지점이 보이면 cgroup v2가 활성화된 것이다. 보통은 sys fs cgroup 경로 아래에 단일 트리 형태로 구성된다.
cgroup v2의 핵심 특징은 다음과 같다.
- 모든 컨트롤러가 하나의 통합 계층으로 관리된다
- 각 디렉터리가 하나의 cgroup이 된다
- 각 cgroup 디렉터리 안의 컨트롤러 파일에 값을 쓰면서 제한을 건다
- cgroup procs 파일에 PID를 추가해 프로세스를 그룹에 소속시킨다
즉 디렉터리를 만들고, 컨트롤러 파일에 설정 값을 쓰고, 프로세스를 넣는 흐름만 이해하면 기본 사용에는 어려움이 없다.
cgroup v2 디렉터리와 필수 파일 구조
예를 들어 다음처럼 새로운 cgroup을 만든다고 해 보자.
cd /sys/fs/cgroup
mkdir mybatch
mybatch 디렉터리 안에는 다양한 컨트롤러 파일이 존재한다. 예를 들어
- cpu.max
- memory.max
- memory.swap.max
- pids.max
- io.max
- cgroup.procs
와 같은 파일들이 보일 수 있다. 커널과 설정에 따라 이름이 조금 다를 수 있지만 전반적인 구조는 비슷하다.
이제 이 파일들에 적절한 값을 echo로 쓰면 자원 제한이 적용된다. 그리고 cgroup procs에 대상 프로세스의 PID를 추가하면 해당 프로세스는 그 순간부터 설정된 제한 안에서만 자원을 사용할 수 있다.
CPU 자원 제한 설정 예시
cpu.max로 CPU 사용률 제한하기
cgroup v2에서 CPU 제한은 cpu.max 파일로 제어한다. 이 파일에는 두 개의 값이 들어간다.
echo "20000 100000" > cpu.max
이 설정은 다음을 의미한다.
- 기간 100000 마이크로초 기준
- 그중 20000 마이크로초만 CPU를 쓸 수 있음
즉 논리적으로 최대 20퍼센트 정도의 CPU 시간을 허용하는 셈이다. 값의 조합만 바꾸면 50퍼센트 80퍼센트 등 원하는 비율로 제한할 수 있다.
CPU 사용을 무제한으로 두고 싶다면 다음처럼 설정할 수 있다.
echo "max 100000" > cpu.max
max라는 단어를 사용하면 쿼터 없이 사용 가능한 상태가 된다.
특정 프로세스를 cgroup에 넣어 제한 적용
이제 mybatch 그룹에 특정 작업을 넣어 보자. 예를 들어 PID가 12345인 배치 작업의 CPU를 제한하려면
echo 12345 > cgroup.procs
이 한 줄이면 된다. 이후 해당 프로세스와 그 자식 프로세스는 모두 cpu.max에서 정의한 범위 안에서만 CPU를 사용할 수 있다.
실제 운영에서는 배치 작업을 시작하기 전에 cgroup을 미리 만들고, 작업을 실행하면서 바로 그룹에 편입하는 방식도 많이 쓴다. 간단한 예로는 다음과 같이 할 수 있다.
cd /sys/fs/cgroup
mkdir mybatch
echo "20000 100000" > mybatch/cpu.max
# 쉘에서는 이렇게
echo $$ > mybatch/cgroup.procs
# 이후 이 쉘에서 실행하는 모든 명령은 mybatch 제한을 따르게 된다
현재 쉘의 PID를 그룹에 넣으면, 그 쉘에서 실행하는 모든 명령과 자식 프로세스들이 자동으로 같은 제한을 받는다.
메모리와 스왑 자원 제한 설정
memory.max로 메모리 상한 지정
메모리 제한의 기본은 memory.max 파일이다. 예를 들어 mybatch 그룹의 메모리를 1GiB로 제한하고 싶다면
echo $((1024*1024*1024)) > memory.max
혹은 사람이 읽기 편한 단위로 계산해서 숫자를 넣어도 된다. cgroup 파일은 단위를 지원하지 않으므로 바이트 단위 숫자만 들어간다는 점만 기억하면 된다.
memory.max를 설정하면 해당 그룹에 속한 프로세스들이 사용하는 익명 메모리와 페이지 캐시 등을 포함한 전체 메모리가 상한을 넘지 못하게 된다. 상한을 넘으려 하면 cgroup 내부에서 OOM이 발생해 프로세스가 종료될 수 있다.
메모리 제한을 해제하고 싶다면
echo "max" > memory.max
와 같이 설정한다.
memory.swap.max로 스왑 사용 제한
스왑 사용량까지 함께 제어하고 싶다면 memory.swap.max 파일을 사용한다. 예를 들어 스왑 사용을 아예 허용하지 않으려면
echo 0 > memory.swap.max
로 설정한다. 이렇게 하면 cgroup 내 프로세스는 스왑을 사용하지 못하고, 메모리 한계에 도달했을 때 바로 OOM에 걸리게 된다.
반대로 메모리는 어느 정도 여유를 두고, 스왑을 조금 허용해 완충 장치를 만들고 싶다면 적당한 값을 부여할 수 있다. 예를 들어 메모리 1GiB 스왑 512MiB 같은 조합을 구성하면 특정 작업이 순간적으로 추가 메모리가 필요할 때 시스템 전체에 영향을 덜 주면서도 완전히 죽지는 않도록 만들 수 있다.
프로세스 수와 PIDs 제한 설정
pids.max로 포크 폭주 방지
버그 있는 코드나 악성 스크립트가 포크 폭주를 일으키면 시스템 전체의 PID 공간이 가득 차면서 정상 프로세스까지 생성이 안 되는 문제가 발생할 수 있다. cgroup v2의 pids.max는 이 상황을 예방하는 핵심 장치다.
예를 들어 mybatch 그룹에 최대 프로세스 수 100개 제한을 두고 싶다면
echo 100 > pids.max
와 같이 설정한다. 이 그룹 내부에서 새 프로세스를 생성하려 하면 현재 개수와 비교해 threshold를 넘어가는 시점에 fork 실패를 유도해 더 이상의 확장을 막는다.
pids.max를 max로 두면 제한 없이 생성할 수 있다. 보통은 중요한 서비스마다 적당한 상한을 설정해 전체 시스템을 보호하는 방향이 좋다.
디스크 I O 제한 설정
io.max로 특정 디바이스에 대한 I O 제한
디스크 I O를 많이 사용하는 배치 작업이 있을 때, 이를 방치하면 다른 서비스의 응답 속도가 크게 저하될 수 있다. cgroup v2에서 io.max 파일을 활용하면 디바이스별 제한을 걸 수 있다.
먼저 대상 블록 디바이스의 메이저 마이너 번호를 확인한다.
lsblk --output NAME,MAJ:MIN
이때 명령 결과에 콜론 문자가 포함되므로 실제 환경에서는 그대로 사용하되, 여기서는 표기만 설명으로 대신한다. 예를 들어 장치 이름이 sda이고 메이저 번호와 마이너 번호가 각각 8과 0이라고 하자.
다음처럼 io.max 파일에 값을 쓸 수 있다.
echo "8:0 rbps=10485760 wbps=10485760" > io.max
여기서는 초당 읽기 쓰기 대역폭을 각각 10MiB로 제한하는 예시다. 운영 환경에서는 실제 지표를 보면서 적당한 값을 잡아야 한다.
읽기 쓰기 I O 요청 수 기준으로 제한을 걸 수도 있다. 이때는 iops 항목을 사용한다.
echo "8:0 riops=100 wiops=100" > io.max
이렇게 하면 초당 읽기 쓰기 요청 개수를 각각 100개로 제한한다.
systemd와 함께 cgroup v2 활용하기
서비스 단위로 자원 제한 적용
최근 배포판에서는 systemd가 cgroup v2를 직접 관리한다. 개별 디렉터리를 수동으로 다루지 않고 systemd의 속성만 설정해도 내부적으로 적절한 cgroup 설정이 들어간다.
예를 들어 myapp.service라는 서비스에 메모리 상한 512MiB를 적용하려면 다음처럼 할 수 있다.
systemctl set-property myapp.service MemoryMax=512M
CPU 제어도 비슷하게 가능하다.
systemctl set-property myapp.service CPUQuota=50%
이렇게 설정하면 해당 서비스 유닛은 자동으로 cgroup v2 하위 그룹으로 배치되고, 설정한 자원 제한이 커널 수준에서 적용된다.
슬라이스 단위로 계층 구조 설계
systemd는 슬라이스 단위로도 cgroup을 구성한다. 예를 들어
- system.slice
- user.slice
- custom 슬라이스
등을 만들고, 그 아래에 서비스들을 배치하면 슬라이스 수준에서 상한을 걸고 각 서비스에는 상대적인 비율만 조정하는 식의 설계가 가능하다.
예시 흐름은 다음과 비슷하다.
- team.slice 생성
- team.slice에 CPU 메모리 상한 설정
- 팀 관련 서비스들을 모두 team.slice에 속하도록 설정
이 방식은 여러 팀과 서비스를 한 서버에서 공유할 때 특정 팀이 자원을 과도하게 사용하는 것을 방지하는 데 특히 유용하다.
운영 환경에서의 적용 전략과 주의사항
너무 공격적인 제한은 오히려 위험
cgroup v2 자원 제한은 매우 강력하지만, 수치를 과하게 보수적으로 잡으면 한 서비스만이 아니라 전체 시스템에 부작용이 생길 수 있다.
예를 들어 메모리 상한을 너무 낮게 잡으면 서비스가 자주 OOM에 걸리면서 재시작을 반복할 수 있고, CPU를 지나치게 제한하면 서비스 응답 시간이 급격히 늘어날 수 있다.
따라서 초기에는 여유 있는 값을 설정하고, 모니터링 결과를 보면서 단계적으로 줄여 나가는 방식을 추천한다.
모니터링과 로깅 연계
cgroup v2를 제대로 활용하려면 자원 제한 적용 이후 상태를 꾸준히 모니터링해야 한다.
- cgroup 관련 통계 파일에서 현재 사용량과 최대 사용량 확인
- 서비스 로그와 함께 OOM 발생 여부 확인
- 프로메테우스 같은 모니터링 도구와 연계해 그룹별 자원 사용량 수집
이런 정보를 함께 보면서 어느 그룹이 자원 상한에 자주 닿는지, 어느 서비스가 다른 서비스에 영향을 주는지 파악할 수 있다.
테스트 환경에서 먼저 검증하기
운영 서비스에 바로 cgroup v2 제한을 걸기보다는 테스트 환경이나 카나리 서버에서 먼저 설정을 검증하는 것이 안전하다.
- 동일한 설정으로 부하 테스트 수행
- 부하 시 지연 시간 오류율 패턴 확인
- 예기치 못한 OOM이나 타임아웃이 발생하지 않는지 점검
이 과정을 거친 뒤 운영 환경에 단계적으로 반영하면 자원 제한으로 인한 장애 가능성을 크게 줄일 수 있다.
마무리 정리
cgroup v2는 CPU 메모리 프로세스 수 디스크 I O 등 핵심 자원을 프로세스 그룹 단위로 정교하게 제어할 수 있는 강력한 도구다.
디렉터리 기반 구조와 각 컨트롤러 파일의 의미만 이해하면, cpu.max memory.max pids.max io.max 같은 파일에 값을 쓰는 것만으로 다양한 자원 제한을 설정할 수 있다. 여기에 systemd set property를 활용하면 서비스 단위로 자연스럽게 제어할 수 있어 실제 운영 환경에서도 적용이 수월하다.
궁극적으로 cgroup v2의 목표는 특정 프로세스가 전체 시스템을 먹통으로 만드는 일을 막고, 여러 워크로드가 같은 서버에서 서로 공존하도록 만드는 것이다. 지금 운영 중인 서비스에서 자원 사용 편차가 크거나 특정 배치 작업이 자주 시스템을 끌어내린다면, cgroup v2를 활용해 자원 제한 전략을 천천히 도입해 보는 것이 좋은 출발점이 될 수 있다.