cgroups / namespace
컨테이너는 가상 머신처럼 운영체제를 따로 띄우지 않고도 프로세스를 독립된 환경처럼 보이게 만듭니다. 이 마법의 바탕에는 리눅스 커널의 두 가지 기능이 있습니다. namespace는 프로세스가 볼 수 있는 시스템 자원의 범위를 나누고, cgroups(control groups)는 프로세스 그룹이 쓸 수 있는 자원의 양을 제한하고 측정합니다. 한마디로 namespace는 "무엇이 보이는가", cgroups는 "얼마나 쓸 수 있는가"를 다룹니다.
namespace 덕분에 컨테이너 안의 프로세스는 자기만의 프로세스 목록, 네트워크, 파일 시스템을 가진 것처럼 보입니다. cgroups 덕분에 한 컨테이너가 호스트의 CPU나 메모리를 독점하지 못합니다. 이 둘은 컨테이너 전용 기능이 아니라 리눅스 커널의 일반 기능이고, Docker 같은 도구가 이를 조합해 컨테이너를 만듭니다. 이 토픽에서는 PID namespace가 보이는 범위를 나누는 방식, cgroups가 메모리와 CPU를 제한하는 방식, 둘이 합쳐져 컨테이너가 되는 과정을 봅니다.
핵심 개념
namespace
커널의 전역 자원을 추상화해, 한 namespace 안의 프로세스에게는 그 자원이 자기만의 독립된 인스턴스처럼 보이게 합니다. 같은 namespace 안의 프로세스끼리만 그 변화를 봅니다.
핵심 개념
namespace의 종류
PID(프로세스 번호), Mount(파일 시스템 마운트), Network(네트워크 인터페이스와 포트), UTS(호스트 이름), IPC(System V IPC, POSIX 메시지 큐), User(사용자와 그룹 ID), Cgroup, Time 등이 있습니다.
핵심 개념
PID namespace
프로세스 ID 공간을 분리합니다. 새 PID namespace의 첫 프로세스는 PID 1을 받고, 호스트에서는 같은 프로세스가 보통 다른 PID로 보입니다. 그 첫 프로세스가 끝나면 커널이 namespace 안의 나머지 프로세스를 종료합니다.
핵심 개념
cgroups
프로세스들을 계층적인 그룹으로 묶고, 그룹 단위로 CPU, 메모리, I/O 같은 자원의 사용량을 제한하고 계측합니다. 현재 리눅스는 통합 계층을 쓰는 cgroup v2를 권장합니다.
핵심 개념
메모리 상한과 OOM
그룹의 메모리 사용이 상한에 닿으면 커널은 먼저 메모리를 회수하려 하고, 줄일 수 없으면 그 그룹 안에서 OOM killer가 프로세스를 종료합니다. 종료 대상은 그 그룹 안에서 고르므로 다른 그룹의 프로세스는 보통 영향받지 않습니다.
핵심 개념
CPU 상한
주기마다 쓸 수 있는 CPU 시간(쿼터)을 정하면 그 이상 쓰려는 프로세스는 다음 주기까지 멈춥니다. 예를 들어 100,000µs 주기에 50,000µs면 코어 하나의 50%입니다.
namespace와 cgroups의 배경
namespace는 마운트 namespace를 시작으로 UTS, IPC, PID, 네트워크, 사용자 namespace 등이 리눅스 커널에 차례로 더해졌습니다. cgroups는 구글의 엔지니어(Paul Menage 등)가 시작해 리눅스 2.6.24에 처음 포함되었습니다. 이후 이 기능들을 조합해 쉽게 쓰게 해 주는 도구(LXC, Docker 등)가 나오면서 컨테이너가 널리 쓰이게 되었습니다.
숫자로 확인하기
자주 하는 오해
면접에서 이렇게 물어요
면접 질문
namespace와 cgroups의 차이는?
namespace는 프로세스가 볼 수 있는 자원의 범위를 분리해 독립된 환경처럼 보이게 하고, cgroups는 프로세스 그룹이 쓸 수 있는 CPU, 메모리 등의 양을 제한하고 측정합니다.
면접 질문
컨테이너는 어떻게 격리되나요?
호스트 커널의 PID, 마운트, 네트워크 같은 namespace로 시야를 나누고, cgroups로 자원 상한을 걸어 프로세스를 격리합니다. 별도의 게스트 커널은 없습니다.
면접 질문
컨테이너가 메모리 상한을 넘으면?
커널이 먼저 메모리를 회수하려 하고, 줄일 수 없으면 그 cgroup 안에서 OOM killer가 프로세스를 종료합니다. 컨테이너가 예기치 않게 종료되는 대표 원인입니다.
면접 질문
컨테이너 안에서 PID 1은 무엇인가요?
PID namespace의 첫 프로세스입니다. 이 프로세스가 종료되면 커널이 namespace 안의 나머지 프로세스를 종료하므로 컨테이너도 끝납니다.
실제 시스템에서는
함께 보면 좋은 토픽
참고 자료
셸 — PID namespace 만들어 보기 (unshare)
# 새 PID, mount namespace 안에서 셸 실행 (root 권한 필요) sudo unshare --pid --fork --mount-proc bash # 이 셸 안에서 echo $$ # 1 ← 새 namespace의 첫 프로세스 ps aux # 이 namespace 안의 프로세스만 보임 # 다른 터미널(호스트)에서 namespace 목록 lsns -t pid
unshare는 새 namespace를 만들어 그 안에서 명령을 실행합니다. --pid와 --fork로 만든 셸은 새 PID namespace의 첫 프로세스라서 자기 PID가 1로 보입니다. --mount-proc은 이 namespace 전용으로 /proc을 다시 마운트해서, ps가 호스트 전체가 아니라 namespace 안의 프로세스만 보여 주게 합니다. 호스트의 다른 터미널에서 lsns -t pid를 실행하면 같은 셸이 호스트 PID로 보이는 것도 확인할 수 있습니다. 셸을 종료하면 namespace도 함께 사라집니다.
셸 — cgroup v2로 메모리와 CPU 상한 걸기
# cgroup v2 계층에 그룹 만들기 (root 권한, memory와 cpu 컨트롤러가 활성이어야 함) sudo mkdir /sys/fs/cgroup/demo echo 536870912 | sudo tee /sys/fs/cgroup/demo/memory.max # 512MiB echo "50000 100000" | sudo tee /sys/fs/cgroup/demo/cpu.max # 주기 100ms당 50ms = 50% # 현재 셸을 이 그룹에 넣기 echo $$ | sudo tee /sys/fs/cgroup/demo/cgroup.procs # 사용량과 제한 횟수 확인 cat /sys/fs/cgroup/demo/memory.current cat /sys/fs/cgroup/demo/cpu.stat
cgroup v2에서는 /sys/fs/cgroup 아래에 디렉터리를 만드는 것이 그룹을 만드는 일이고, 그 안의 파일에 값을 써서 설정합니다. memory.max는 바이트 단위의 메모리 상한이고, cpu.max는 쿼터와 주기를 마이크로초로 적습니다. cgroup.procs에 PID를 쓰면 그 프로세스가 그룹으로 옮겨져 상한을 적용받습니다. memory.current는 현재 메모리 사용량이고, cpu.stat에는 CPU 쿼터 때문에 멈춘 횟수(nr_throttled) 같은 통계가 있습니다. 하위 그룹에 컨트롤러를 켜려면 상위 그룹의 cgroup.subtree_control 설정이 필요할 수 있고, 배포판에서는 systemd가 이 계층을 관리하므로 환경에 따라 직접 쓰기 전에 확인해야 합니다.
Docker · 쿠버네티스 — 자원 상한 설정
# Docker: 메모리 512MB, CPU 0.5개 docker run -d --name web --memory 512m --cpus 0.5 nginx # 컨테이너 안의 PID 1 확인 docker exec web cat /proc/1/comm # 쿠버네티스 파드의 자원 요청과 상한 resources: requests: cpu: "250m" memory: "256Mi" limits: cpu: "500m" memory: "512Mi"
docker run의 --memory와 --cpus는 컨테이너의 cgroup에 메모리 상한과 CPU 상한을 설정하며, 상한을 넘는 메모리를 쓰면 컨테이너 안의 프로세스가 OOM으로 종료될 수 있습니다. docker exec로 컨테이너 안에서 PID 1이 무엇인지 보면 컨테이너의 첫 프로세스가 PID namespace의 PID 1임을 확인할 수 있습니다. 쿠버네티스에서 requests는 스케줄러가 파드를 배치할 때 쓰는 값이며(CPU request는 경합 시 가중치로도 쓰입니다), limits는 런타임이 지키는 상한입니다. 메모리 limit을 넘으려 하면 OOM으로 종료될 수 있고, CPU limit은 throttling으로 제한됩니다.
Q1
리눅스 cgroups(control groups)의 역할은?
Q2
리눅스 namespace의 역할은?
Q3
컨테이너 안에서 실행 중인 첫 프로세스가 자기 PID를 1로 인식하게 해 주는 기능은?