- Authors

- Name
- Youngju Kim
- @fjvbn20031
목차
- cgroup이란?
- cgroup v1 vs v2
- 주요 컨트롤러 심층 분석
- Docker와 cgroup
- Kubernetes와 cgroup
- cgroup 실전 트러블슈팅
- cgroup과 보안
- 정리
1. cgroup이란?
Control Groups의 탄생
cgroup(Control Groups)은 Linux 커널이 제공하는 프로세스 그룹 단위의 리소스 제한 및 격리 메커니즘이다. 2006년 Google 엔지니어 Paul Menage와 Rohit Seth가 "process containers"라는 이름으로 개발을 시작했으며, 2007년 Linux 커널 2.6.24에 merge되면서 기존 커널의 "container"라는 용어와의 혼동을 피하기 위해 cgroup으로 이름이 변경되었다.
cgroup이 해결하는 문제
cgroup이 없던 시절, 하나의 프로세스가 시스템 전체의 CPU나 메모리를 독점하는 것을 막기 어려웠다. cgroup은 다음과 같은 기능을 제공한다:
- 리소스 제한(Resource Limiting): CPU, 메모리, IO, 네트워크 대역폭 등의 사용량 상한 설정
- 우선순위(Prioritization): 프로세스 그룹 간 리소스 할당 비율 조정
- 계정(Accounting): 그룹별 리소스 사용량 측정 및 보고
- 제어(Control): 프로세스 그룹의 일시 중지, 재개, 체크포인트
Namespace와 cgroup의 관계
컨테이너 기술의 두 축은 Namespace와 cgroup이다.
+-------------------------------------------+
| Linux 컨테이너 격리 모델 |
+-------------------------------------------+
| |
| Namespace (격리 - Visibility) |
| ┌─────────────────────────────────────┐ |
| │ PID NS : 프로세스 ID 격리 │ |
| │ NET NS : 네트워크 스택 격리 │ |
| │ MNT NS : 파일시스템 마운트 격리 │ |
| │ UTS NS : 호스트네임 격리 │ |
| │ IPC NS : IPC 리소스 격리 │ |
| │ USER NS : UID/GID 매핑 격리 │ |
| └─────────────────────────────────────┘ |
| |
| cgroup (제한 - Resource Limits) |
| ┌─────────────────────────────────────┐ |
| │ CPU : CPU 시간 제한 │ |
| │ Memory : 메모리 사용량 제한 │ |
| │ IO : 블록 디바이스 IO 제한 │ |
| │ PIDs : 프로세스 수 제한 │ |
| │ Devices : 디바이스 접근 제어 │ |
| └─────────────────────────────────────┘ |
| |
+-------------------------------------------+
핵심 차이를 요약하면:
- Namespace = "무엇을 볼 수 있는가" (격리)
- cgroup = "얼마나 사용할 수 있는가" (제한)
cgroup 파일시스템 기본 구조
cgroup은 가상 파일시스템(VFS)을 통해 관리된다. 모든 설정은 파일 읽기/쓰기로 이루어진다.
# cgroup 마운트 확인
mount | grep cgroup
# cgroup v2가 마운트된 경우
# cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
# 현재 프로세스의 cgroup 확인
cat /proc/self/cgroup
# 0::/user.slice/user-1000.slice/session-1.scope
2. cgroup v1 vs v2
cgroup v1의 구조
cgroup v1에서는 각 컨트롤러(CPU, memory, blkio 등)가 독립적인 hierarchy를 가진다.
cgroup v1 구조:
/sys/fs/cgroup/
├── cpu/ # CPU 컨트롤러
│ ├── docker/
│ │ ├── container-abc/
│ │ │ ├── cpu.cfs_quota_us
│ │ │ ├── cpu.cfs_period_us
│ │ │ └── cpu.shares
│ │ └── container-xyz/
│ └── tasks
├── memory/ # Memory 컨트롤러
│ ├── docker/
│ │ ├── container-abc/
│ │ │ ├── memory.limit_in_bytes
│ │ │ └── memory.usage_in_bytes
│ │ └── container-xyz/
│ └── tasks
├── blkio/ # Block IO 컨트롤러
├── pids/ # PID 컨트롤러
├── devices/ # Device 컨트롤러
└── freezer/ # Freezer 컨트롤러
v1의 특징:
- 각 컨트롤러가 별도의 디렉토리 트리를 가짐
- 하나의 프로세스가 각 컨트롤러에서 서로 다른 그룹에 속할 수 있음
- 유연하지만 관리가 복잡함
cgroup v2의 구조 (Unified Hierarchy)
cgroup v2는 단일 통합 hierarchy를 사용한다.
cgroup v2 구조 (Unified):
/sys/fs/cgroup/ # 루트 cgroup
├── cgroup.controllers # 사용 가능한 컨트롤러 목록
├── cgroup.subtree_control # 자식에게 활성화할 컨트롤러
├── system.slice/ # systemd 시스템 서비스
│ ├── docker.service/
│ └── sshd.service/
├── user.slice/ # 사용자 세션
│ └── user-1000.slice/
└── kubepods.slice/ # Kubernetes pods
├── kubepods-burstable.slice/
└── kubepods-besteffort.slice/
v1 vs v2 비교표
| 특성 | cgroup v1 | cgroup v2 |
|---|---|---|
| Hierarchy | 컨트롤러별 독립 | 단일 통합 (Unified) |
| 마운트 포인트 | /sys/fs/cgroup/cpu/, /sys/fs/cgroup/memory/ 등 | /sys/fs/cgroup/ 하나 |
| 프로세스 배치 | 각 컨트롤러마다 다른 그룹 가능 | 모든 컨트롤러가 동일 그룹 |
| PSI (Pressure Stall Info) | 미지원 | 지원 |
| Threaded mode | 미지원 | 지원 |
| 서브트리 위임 | 제한적 | 완전한 delegation 모델 |
| Memory QoS | memory.limit만 | memory.min/low/high/max 4단계 |
| IO 제어 | blkio (buffered IO 미지원) | io (buffered IO 지원) |
| CPU burst | 미지원 (커널 패치 필요) | 지원 (kernel 5.14+) |
v2 마이그레이션 호환성
| 소프트웨어 | cgroup v2 지원 시작 버전 |
|---|---|
| systemd | 236+ |
| Docker | 20.10+ |
| containerd | 1.4+ |
| Kubernetes | 1.25+ (GA) |
| Podman | 네이티브 지원 |
| RHEL | 9+ (기본값) |
| Ubuntu | 21.10+ (기본값) |
cgroup v2 활성화 확인
# cgroup v2가 사용 중인지 확인
stat -fc %T /sys/fs/cgroup/
# cgroup2fs -> v2
# tmpfs -> v1
# 또는 마운트 정보로 확인
grep cgroup /proc/mounts
# 커널 부트 파라미터로 v2 강제 사용
# GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=1"
3. 주요 컨트롤러 심층 분석
3.1 CPU 컨트롤러
CFS Bandwidth Control
Linux의 CFS(Completely Fair Scheduler)는 CPU 시간을 공정하게 분배한다. cgroup CPU 컨트롤러는 CFS 위에 bandwidth throttling을 추가한다.
CFS Bandwidth Control 원리:
Period = 100ms (기본값)
Quota = 50ms (할당량)
0ms 50ms 100ms 150ms 200ms
|----------|----------|----------|----------|
[##########]...........[##########]...........
^-- 실행 --^ ^- 쓰로틀 -^ ^-- 실행 --^ ^- 쓰로틀 -^
CPU 1개 기준: quota/period = 50ms/100ms = 0.5 CPU (50%)
v1 vs v2 CPU 파라미터
# ===== cgroup v1 =====
# CFS quota: 마이크로초 단위, -1은 무제한
cat /sys/fs/cgroup/cpu/docker/CONTAINER_ID/cpu.cfs_quota_us
# 50000 (50ms)
cat /sys/fs/cgroup/cpu/docker/CONTAINER_ID/cpu.cfs_period_us
# 100000 (100ms)
# CPU shares: 상대적 가중치 (기본 1024)
cat /sys/fs/cgroup/cpu/docker/CONTAINER_ID/cpu.shares
# 512
# ===== cgroup v2 =====
# cpu.max: "quota period" 형식
cat /sys/fs/cgroup/kubepods.slice/.../cpu.max
# 50000 100000 (50ms quota, 100ms period)
# "max 100000" -> 무제한
# cpu.weight: 1-10000 (기본 100)
cat /sys/fs/cgroup/kubepods.slice/.../cpu.weight
# 100
CPU shares/weight 변환
v1 cpu.shares -> v2 cpu.weight 변환:
v2_weight = (1 + ((v1_shares - 2) * 9999) / 262142)
예시:
v1 shares=1024 (기본) -> v2 weight=39 (약)
v1 shares=512 -> v2 weight=20 (약)
v1 shares=2048 -> v2 weight=78 (약)
cpuset: CPU 핀닝
특정 CPU 코어에 프로세스를 고정(pinning)할 수 있다.
# v2: 사용할 CPU 코어 지정
echo "0-3" > /sys/fs/cgroup/mygroup/cpuset.cpus
echo "0" > /sys/fs/cgroup/mygroup/cpuset.mems
# 현재 설정 확인
cat /sys/fs/cgroup/mygroup/cpuset.cpus.effective
실습: CPU 제한 cgroup 직접 생성
# cgroup v2 환경에서 실습
# 1. 새 cgroup 생성
sudo mkdir /sys/fs/cgroup/cpu-test
# 2. CPU 컨트롤러 활성화 (루트에서)
echo "+cpu" | sudo tee /sys/fs/cgroup/cgroup.subtree_control
# 3. CPU 50%로 제한 (50ms quota / 100ms period)
echo "50000 100000" | sudo tee /sys/fs/cgroup/cpu-test/cpu.max
# 4. 현재 쉘을 cgroup에 추가
echo $$ | sudo tee /sys/fs/cgroup/cpu-test/cgroup.procs
# 5. CPU 부하 생성 후 제한 확인
stress --cpu 1 --timeout 10 &
# 6. 쓰로틀링 통계 확인
cat /sys/fs/cgroup/cpu-test/cpu.stat
# usage_usec 4500000
# user_usec 4500000
# system_usec 0
# nr_periods 100
# nr_throttled 50
# throttled_usec 5000000
# 7. 정리
echo $$ | sudo tee /sys/fs/cgroup/cgroup.procs
sudo rmdir /sys/fs/cgroup/cpu-test
CPU Throttling 문제와 burst
CPU 쓰로틀링은 짧은 burst 워크로드에서 심각한 latency spike를 유발할 수 있다.
문제 시나리오: 평균 CPU 사용량 30%인 웹 서버 (limit: 1 CPU)
정상 시: [###-------][###-------][###-------] -> 응답 10ms
버스트 시: [##########][..........][###-------] -> 응답 110ms!
^-- 100ms 전부 사용 --^ ^-- 쓰로틀 --^
해결: CPU burst (kernel 5.14+, cgroup v2)
cpu.max.burst = 허용 burst 마이크로초
이전 period에서 미사용 quota를 저축하여 burst 시 사용
# CPU burst 설정 (cgroup v2, kernel 5.14+)
echo 20000 > /sys/fs/cgroup/mygroup/cpu.max.burst
# 20ms까지 burst 허용
3.2 Memory 컨트롤러
v2의 4단계 메모리 제한
cgroup v2는 v1보다 세밀한 4단계 메모리 제어를 제공한다.
Memory 제어 4단계 (cgroup v2):
memory.min : 절대 보호 (이 이하로 reclaim 불가)
↑ 최소 보장 영역
memory.low : 소프트 보호 (가능한 reclaim 방지, best-effort)
↑ 선호 영역
memory.high : 소프트 제한 (초과 시 throttle, OOM 아님)
↑ 경고 영역
memory.max : 하드 제한 (초과 시 OOM Killer 발동)
↑ 금지 영역
0 MB 1024 MB
|=====[min]==[low]==========[high]====[max]========|
|<-보호됨->|<-선호->|<-정상 사용->|<-쓰로틀->|<-OOM->|
v1 vs v2 메모리 파라미터
# ===== cgroup v1 =====
# 하드 제한
echo 536870912 > memory.limit_in_bytes # 512MB
# 소프트 제한 (reclaim 압력)
echo 268435456 > memory.soft_limit_in_bytes # 256MB
# Swap 포함 제한
echo 1073741824 > memory.memsw.limit_in_bytes # 1GB (mem+swap)
# OOM 제어
echo 1 > memory.oom_control # OOM Killer 비활성화
# ===== cgroup v2 =====
echo 512M > memory.max # 하드 제한
echo 256M > memory.high # 소프트 제한 (throttle)
echo 128M > memory.low # best-effort 보호
echo 64M > memory.min # 절대 보호
echo 256M > memory.swap.max # swap 제한
memory.stat 분석
cat /sys/fs/cgroup/kubepods.slice/.../memory.stat
# anon 104857600 # 익명 메모리 (heap, stack)
# file 52428800 # 파일 캐시
# kernel 8388608 # 커널 메모리 (slab 등)
# shmem 0 # 공유 메모리
# pgfault 250000 # 페이지 폴트 횟수
# pgmajfault 10 # 메이저 페이지 폴트
# workingset_refault 500 # working set에서 evict 후 재참조
# oom_kill 0 # OOM kill 횟수
중요 지표 해석:
anon높음: 애플리케이션 힙 메모리 사용량이 큼file높음: 파일 캐시가 많음 (보통 정상)pgmajfault높음: 디스크에서 페이지를 읽어오는 빈도가 높음 (메모리 부족 신호)oom_kill > 0: OOM이 발생했음
실습: 메모리 제한과 OOM 테스트
# 1. 메모리 제한 cgroup 생성
sudo mkdir /sys/fs/cgroup/mem-test
echo "+memory" | sudo tee /sys/fs/cgroup/cgroup.subtree_control
# 2. 100MB 하드 제한 설정
echo 104857600 | sudo tee /sys/fs/cgroup/mem-test/memory.max
# 3. 80MB 소프트 제한 설정 (초과 시 throttle)
echo 83886080 | sudo tee /sys/fs/cgroup/mem-test/memory.high
# 4. 프로세스를 cgroup에 추가하고 메모리 할당
echo $$ | sudo tee /sys/fs/cgroup/mem-test/cgroup.procs
# 5. 100MB 초과 메모리 할당 시도
python3 -c "
data = []
for i in range(200):
data.append(bytearray(1024 * 1024)) # 1MB씩 할당
print(f'Allocated {i+1} MB')
"
# -> 약 100MB에서 OOM Killed
# 6. OOM 이벤트 확인
cat /sys/fs/cgroup/mem-test/memory.events
# oom 1
# oom_kill 1
# high 150
# 7. dmesg에서 OOM 로그 확인
dmesg | grep -i "killed process"
Swap 제어
# cgroup v2에서 swap 제한
echo 0 > /sys/fs/cgroup/mygroup/memory.swap.max # swap 사용 금지
echo max > /sys/fs/cgroup/mygroup/memory.swap.max # swap 무제한
# v1에서는 memory + swap 합산으로 제어
echo 1073741824 > memory.memsw.limit_in_bytes # mem + swap = 1GB
3.3 IO 컨트롤러
v1 (blkio) vs v2 (io)
v1의 blkio 컨트롤러는 Direct IO만 제어할 수 있었다. Buffered IO는 page cache를 거치므로 blkio가 추적하지 못했다. v2의 io 컨트롤러는 writeback 메커니즘을 통해 Buffered IO도 제어한다.
# ===== cgroup v2: io.max =====
# 형식: MAJ:MIN rbps=NUM wbps=NUM riops=NUM wiops=NUM
# 디바이스 번호 확인
lsblk -o NAME,MAJ:MIN
# sda 8:0
# 읽기 10MB/s, 쓰기 5MB/s로 제한
echo "8:0 rbps=10485760 wbps=5242880" > /sys/fs/cgroup/mygroup/io.max
# IOPS 제한: 읽기 1000 IOPS, 쓰기 500 IOPS
echo "8:0 riops=1000 wiops=500" > /sys/fs/cgroup/mygroup/io.max
io.weight (상대적 가중치)
# io.weight: 1-10000 (기본 100)
echo "default 200" > /sys/fs/cgroup/mygroup/io.weight
# 특정 디바이스에 대해 가중치 설정
echo "8:0 500" > /sys/fs/cgroup/mygroup/io.weight
io.latency (v2 전용)
latency target을 설정하여 IO 지연시간을 보장한다.
# 5ms latency target 설정
echo "8:0 target=5000" > /sys/fs/cgroup/mygroup/io.latency
3.4 PID 컨트롤러
Fork Bomb 방지
# PID 제한 설정
echo 100 > /sys/fs/cgroup/mygroup/pids.max
# 현재 PID 수 확인
cat /sys/fs/cgroup/mygroup/pids.current
# 5
# Fork bomb 테스트 (안전한 환경에서만!)
# 제한이 없으면 시스템 전체가 멈출 수 있다
# pids.max가 설정되어 있으면 100개에서 fork() 실패
Fork Bomb 방지 원리:
pids.max = 100
Process Tree:
init(1)
├── bash(2)
│ ├── worker(3)
│ ├── worker(4)
│ │ ├── child(5)
│ │ └── child(6)
│ ...
│ └── worker(100) <- pids.max 도달
│ └── fork() -> EAGAIN (실패!)
3.5 기타 컨트롤러
devices 컨트롤러
디바이스 접근을 화이트리스트/블랙리스트 방식으로 제어한다.
# v1: devices.allow / devices.deny
echo 'c 1:3 rmw' > devices.allow # /dev/null 접근 허용
echo 'b 8:0 r' > devices.allow # /dev/sda 읽기 허용
echo 'a' > devices.deny # 모든 디바이스 거부
# GPU 접근 제어 (NVIDIA)
echo 'c 195:* rmw' > devices.allow # NVIDIA 디바이스 허용
freezer 컨트롤러
프로세스 그룹을 일시 중지(freeze)하고 재개(thaw)할 수 있다.
# v2: cgroup.freeze
echo 1 > /sys/fs/cgroup/mygroup/cgroup.freeze # 일시 중지
echo 0 > /sys/fs/cgroup/mygroup/cgroup.freeze # 재개
# 상태 확인
cat /sys/fs/cgroup/mygroup/cgroup.events
# frozen 1
hugetlb 컨트롤러
Huge page 사용량을 제한한다.
# 2MB huge pages 제한
echo 1073741824 > hugetlb.2MB.limit_in_bytes # 1GB
cat hugetlb.2MB.usage_in_bytes
4. Docker와 cgroup
Docker 리소스 제한 옵션
# CPU 제한
docker run -d \
--cpus="1.5" \ # CPU 1.5개 (quota=150000, period=100000)
--cpu-shares=512 \ # CPU shares (상대적 가중치)
--cpuset-cpus="0,1" \ # CPU 0, 1번 코어만 사용
nginx
# 메모리 제한
docker run -d \
--memory=512m \ # 메모리 하드 제한 512MB
--memory-swap=1g \ # 메모리+스왑 합계 1GB (스왑 512MB)
--memory-reservation=256m \ # 소프트 제한
--oom-kill-disable \ # OOM Killer 비활성화 (주의!)
nginx
# IO 제한
docker run -d \
--device-read-bps /dev/sda:10mb \ # 읽기 10MB/s
--device-write-bps /dev/sda:5mb \ # 쓰기 5MB/s
--device-read-iops /dev/sda:1000 \ # 읽기 1000 IOPS
nginx
# PID 제한
docker run -d \
--pids-limit=100 \ # 최대 100개 프로세스
nginx
Docker가 생성하는 cgroup 경로
# systemd cgroup driver 사용 시
# /sys/fs/cgroup/system.slice/docker-CONTAINER_ID.scope/
# 컨테이너 ID 확인
CONTAINER_ID=$(docker ps -q --filter name=my-nginx)
# cgroup 경로 확인
docker inspect --format='{{.HostConfig.CgroupParent}}' $CONTAINER_ID
# 직접 cgroup 파일 확인 (v2)
cat /sys/fs/cgroup/system.slice/docker-${CONTAINER_ID}.scope/cpu.max
cat /sys/fs/cgroup/system.slice/docker-${CONTAINER_ID}.scope/memory.max
cat /sys/fs/cgroup/system.slice/docker-${CONTAINER_ID}.scope/pids.max
docker stats로 실시간 모니터링
# 모든 컨테이너 리소스 사용량
docker stats
# CONTAINER ID NAME CPU % MEM USAGE/LIMIT MEM % NET I/O BLOCK I/O
# abc123 nginx 0.50% 50MiB / 512MiB 9.77% 1.2kB/0B 0B/0B
# xyz789 redis 1.20% 30MiB / 256MiB 11.7% 500B/200B 4kB/0B
# 특정 컨테이너만
docker stats my-nginx --no-stream
Docker cgroup driver: cgroupfs vs systemd
+---------------------------+---------------------+---------------------+
| | cgroupfs | systemd |
+---------------------------+---------------------+---------------------+
| cgroup 관리 방식 | Docker가 직접 관리 | systemd에 위임 |
| init 시스템과의 충돌 | 가능성 있음 | 없음 (통합) |
| Kubernetes 권장 | 비권장 | 권장 (기본값) |
| 설정 위치 | Docker가 직접 생성 | systemd scope/slice |
| cgroup v2 호환성 | 제한적 | 완전 지원 |
+---------------------------+---------------------+---------------------+
// /etc/docker/daemon.json
// systemd cgroup driver 설정
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
5. Kubernetes와 cgroup
kubelet cgroup driver 설정
# kubelet 설정 (/var/lib/kubelet/config.yaml)
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd # 권장
cgroupsPerQOS: true # QoS별 cgroup hierarchy 생성
enforceNodeAllocatable:
- pods
- system-reserved
- kube-reserved
kubeReserved:
cpu: '500m'
memory: '1Gi'
systemReserved:
cpu: '500m'
memory: '1Gi'
Pod QoS 클래스와 cgroup
Kubernetes는 Pod의 requests/limits 설정에 따라 3가지 QoS 클래스를 부여한다.
Pod QoS 클래스 결정 로직:
1. Guaranteed: 모든 컨테이너에 requests == limits 설정
- 가장 높은 우선순위, OOM score = -997
2. Burstable: requests < limits (일부라도)
- 중간 우선순위, OOM score = 2~999
3. BestEffort: requests/limits 모두 미설정
- 가장 낮은 우선순위, OOM score = 1000
# Guaranteed Pod 예시
apiVersion: v1
kind: Pod
metadata:
name: guaranteed-pod
spec:
containers:
- name: app
image: nginx
resources:
requests:
cpu: '500m'
memory: '256Mi'
limits:
cpu: '500m' # requests == limits
memory: '256Mi' # requests == limits
# Burstable Pod 예시
apiVersion: v1
kind: Pod
metadata:
name: burstable-pod
spec:
containers:
- name: app
image: nginx
resources:
requests:
cpu: '250m'
memory: '128Mi'
limits:
cpu: '500m' # requests < limits
memory: '256Mi'
Kubernetes가 생성하는 cgroup hierarchy
/sys/fs/cgroup/
└── kubepods.slice/ # 모든 Pod
├── kubepods-burstable.slice/ # Burstable QoS
│ └── kubepods-burstable-podABCD.slice/ # Pod 단위
│ ├── cri-containerd-XXXX.scope # 컨테이너 단위
│ │ ├── cpu.max # limits.cpu
│ │ ├── cpu.weight # requests.cpu 기반
│ │ ├── memory.max # limits.memory
│ │ ├── memory.min # requests.memory (v2)
│ │ └── pids.max # pod pid limit
│ └── cri-containerd-YYYY.scope
├── kubepods-besteffort.slice/ # BestEffort QoS
│ └── kubepods-besteffort-podEFGH.slice/
└── kubepods-podIJKL.slice/ # Guaranteed QoS
└── cri-containerd-ZZZZ.scope
Kubernetes 리소스 요청이 cgroup에 매핑되는 방식
Kubernetes -> cgroup v2 매핑:
resources.requests.cpu: 500m
-> cpu.weight = (500m / 총 allocatable CPU) 비례
-> Pod가 경쟁 시 보장받는 CPU 비율
resources.limits.cpu: 1000m
-> cpu.max = "100000 100000" (100ms/100ms = 1 CPU)
resources.requests.memory: 256Mi
-> memory.min = 268435456 (Guaranteed QoS in v2)
resources.limits.memory: 512Mi
-> memory.max = 536870912
pid limit (kubelet 설정):
-> pids.max = podPidsLimit (기본 -1, 무제한)
노드 리소스 예약
노드 리소스 분배:
총 노드 리소스 (예: 16 CPU, 64Gi Memory)
├── kube-reserved : kubelet, kube-proxy 등 (0.5 CPU, 1Gi)
├── system-reserved : sshd, journald 등 (0.5 CPU, 1Gi)
├── eviction-threshold: 회수 임계값 (memory.available=100Mi)
└── allocatable : Pod에 할당 가능한 리소스 (15 CPU, 61.9Gi)
allocatable = total - kube-reserved - system-reserved - eviction-threshold
cgroup v2 + Kubernetes: MemoryQoS
Kubernetes 1.22+에서 MemoryQoS 기능(alpha/beta)은 cgroup v2의 memory.high를 활용한다.
MemoryQoS 동작:
Burstable Pod (requests: 256Mi, limits: 512Mi)
cgroup v2 설정:
memory.min = 0 (BestEffort 보호 없음)
memory.low = 0 (기본)
memory.high = 268435456 (requests 기반, throttle point)
memory.max = 536870912 (limits 기반, OOM point)
메모리 사용이 requests(256Mi)를 넘으면:
-> memory.high에 의해 throttle (할당 속도 저하)
-> 바로 OOM kill 되지 않음
-> limits(512Mi)를 넘으면 OOM kill
6. cgroup 실전 트러블슈팅
6.1 CPU Throttling 확인
# 컨테이너의 CPU throttling 확인
# Pod의 cgroup 경로 찾기
CGROUP_PATH=$(cat /proc/1/cgroup | grep -oP '(?<=::).*')
# cpu.stat 확인
cat /sys/fs/cgroup${CGROUP_PATH}/cpu.stat
# usage_usec 45000000
# user_usec 40000000
# system_usec 5000000
# nr_periods 1000
# nr_throttled 350 <- 35% throttle 비율!
# throttled_usec 15000000
# throttle 비율 계산
# throttle_ratio = nr_throttled / nr_periods
# 350 / 1000 = 35% -> 높음! limits 증가 검토 필요
CPU Throttling 해결 전략
CPU Throttling 진단 플로우:
1. nr_throttled / nr_periods > 5% 인가?
├── Yes -> 2로 진행
└── No -> CPU throttling은 문제가 아님
2. 평균 CPU 사용량이 limits에 가까운가?
├── Yes -> limits 증가 필요
└── No -> burst 패턴 문제 -> 3으로 진행
3. 짧은 시간에 CPU burst가 발생하는가?
├── Yes -> cpu.max.burst 설정 (kernel 5.14+)
│ 또는 CPU limits 제거 검토
└── No -> period 조정 검토
6.2 OOM Killer 트러블슈팅
# 1. dmesg에서 OOM 이벤트 확인
dmesg | grep -i "killed process"
# [12345.678] Killed process 1234 (java) total-vm:4096000kB,
# anon-rss:524288kB, file-rss:8192kB, shmem-rss:0kB,
# UID:1000 pgrp:1234
# 2. cgroup 메모리 이벤트 확인
cat /sys/fs/cgroup/kubepods.slice/.../memory.events
# low 0
# high 500 # memory.high 초과 횟수
# max 10 # memory.max 초과 횟수
# oom 2 # OOM 발생 횟수
# oom_kill 2 # OOM kill 횟수
# 3. 현재 메모리 사용량 vs 제한
cat /sys/fs/cgroup/kubepods.slice/.../memory.current
# 524288000 (500MB)
cat /sys/fs/cgroup/kubepods.slice/.../memory.max
# 536870912 (512MB) <- 거의 도달!
# 4. 메모리 사용 상세 분석
cat /sys/fs/cgroup/kubepods.slice/.../memory.stat | head -10
OOM 해결 전략
OOM Kill 진단 플로우:
1. memory.events에서 oom_kill > 0 확인
├── oom_kill 증가 중 -> 2로 진행
└── oom_kill = 0 -> OOM 문제 아님
2. memory.current vs memory.max 비교
├── current >= max * 0.9 -> 메모리 부족
│ ├── anon 높음 -> 힙 메모리 누수 가능성 -> 프로파일링
│ ├── file 높음 -> 파일 캐시 과다 -> 정상일 수 있음
│ └── shmem 높음 -> 공유 메모리 사용 확인
└── current << max -> 급격한 할당 패턴 확인
3. 해결책:
a) memory limits 증가
b) 애플리케이션 메모리 누수 수정
c) JVM: -XX:MaxRAMPercentage=75 설정
d) memory.high 설정으로 soft throttle 적용
6.3 프로세스의 cgroup 확인
# 특정 프로세스의 cgroup 확인
cat /proc/PID/cgroup
# v2 출력: 0::/kubepods.slice/kubepods-burstable.slice/...
# v1 출력:
# 12:pids:/docker/abc123
# 11:memory:/docker/abc123
# 10:cpu,cpuacct:/docker/abc123
# systemd-cgls: cgroup 트리 표시
systemd-cgls
# Control group /:
# -.slice
# ├─user.slice
# │ └─user-1000.slice
# ├─system.slice
# │ ├─docker.service
# │ └─sshd.service
# └─kubepods.slice
# ├─kubepods-burstable.slice
# └─kubepods-besteffort.slice
6.4 systemd-cgtop: 실시간 cgroup 리소스 모니터링
# systemd-cgtop: top과 유사한 cgroup 모니터링
systemd-cgtop
# Control Group Tasks %CPU Memory Input/s Output/s
# / 235 15.2 3.5G - -
# /system.slice 45 5.1 1.2G - -
# /kubepods.slice 120 8.3 2.0G - -
# /kubepods.slice/burstable 80 6.1 1.5G - -
6.5 cAdvisor와 Prometheus 메트릭
주요 cAdvisor Prometheus 메트릭:
CPU:
container_cpu_usage_seconds_total # 누적 CPU 사용 시간
container_cpu_cfs_throttled_seconds_total # 누적 throttle 시간
container_cpu_cfs_periods_total # 총 CFS period 수
container_cpu_cfs_throttled_periods_total # throttle된 period 수
Memory:
container_memory_usage_bytes # 현재 메모리 사용량
container_memory_working_set_bytes # working set (OOM 판단 기준)
container_memory_rss # RSS (실제 물리 메모리)
container_memory_cache # 페이지 캐시
OOM:
container_oom_events_total # OOM 이벤트 횟수
kube_pod_container_status_last_terminated_reason # OOMKilled 등
유용한 PromQL 쿼리
# CPU throttle 비율 (5분 평균)
rate(container_cpu_cfs_throttled_periods_total[5m])
/ rate(container_cpu_cfs_periods_total[5m])
# 메모리 사용률 (limits 대비)
container_memory_working_set_bytes
/ container_spec_memory_limit_bytes
# OOM Kill 발생 Pod
kube_pod_container_status_last_terminated_reason{reason="OOMKilled"}
7. cgroup과 보안
컨테이너 격리의 3요소
컨테이너 보안 격리 계층:
+--------------------------------------------------+
| Host Kernel |
| |
| ┌──────────┐ ┌──────────┐ ┌──────────┐ |
| │ Namespace│ │ cgroup │ │ Seccomp/ │ |
| │ (격리) │ │ (제한) │ │ AppArmor │ |
| │ │ │ │ │ (보안) │ |
| │ - PID │ │ - CPU │ │ - syscall│ |
| │ - NET │ │ - Memory │ │ 필터링 │ |
| │ - MNT │ │ - IO │ │ - MAC │ |
| │ - USER │ │ - PIDs │ │ 정책 │ |
| └──────────┘ └──────────┘ └──────────┘ |
| |
+--------------------------------------------------+
3가지가 모두 갖추어져야 안전한 격리!
CVE-2022-0492: cgroup escape 취약점
CVE-2022-0492는 cgroup v1의 release_agent 메커니즘을 악용한 컨테이너 탈출 취약점이다.
공격 원리:
1. release_agent는 cgroup의 마지막 프로세스가 종료될 때 실행되는 프로그램
2. 취약점: cgroup_release_agent_write()에서 권한 검증 누락
3. 공격자가 unshare()로 새 user/cgroup namespace를 생성
4. writable cgroupfs를 마운트
5. release_agent에 호스트에서 실행할 바이너리 경로 설정
6. 호스트 권한으로 임의 코드 실행
방어:
- Seccomp + AppArmor/SELinux가 적용되어 있으면 방어됨
- unshare() syscall을 차단하거나
- cgroupfs 마운트를 제한
rootless containers와 cgroup v2 delegation
# cgroup v2에서 rootless 컨테이너가 작동하려면
# 사용자에게 cgroup 서브트리를 위임해야 함
# systemd에서 위임 설정
sudo systemctl edit user@1000.service
# [Service]
# Delegate=cpu memory pids io
# 또는 사용자 단위로
mkdir -p /etc/systemd/system/user@.service.d/
cat > /etc/systemd/system/user@.service.d/delegate.conf << 'CONF'
[Service]
Delegate=cpu cpuset io memory pids
CONF
systemctl daemon-reload
Kubernetes Pod Security Standards와 cgroup
# Pod Security Standard: restricted
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginx
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ['ALL']
readOnlyRootFilesystem: true
resources:
limits: # cgroup 리소스 제한 필수
cpu: '500m'
memory: '256Mi'
requests:
cpu: '250m'
memory: '128Mi'
8. 정리
cgroup v1에서 v2 마이그레이션 체크리스트
- 커널 버전 확인: 5.8+ 권장 (PSI, CPU burst 등)
- systemd 버전 확인: 236+
- 컨테이너 런타임 확인: Docker 20.10+, containerd 1.4+
- Kubernetes 버전 확인: 1.25+ (GA)
- GRUB 부트 파라미터 설정
- kubelet cgroup driver를 systemd로 변경
- 모니터링 도구 호환성 확인 (cAdvisor, Prometheus)
- 커스텀 cgroup 스크립트가 있다면 v2 인터페이스로 업데이트
- 테스트 환경에서 충분히 검증 후 프로덕션 적용
프로덕션 추천 설정
# Kubernetes 노드 권장 설정
# kubelet config
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
cgroupsPerQOS: true
podPidsLimit: 1024 # Fork bomb 방지
enforceNodeAllocatable:
- pods
- system-reserved
- kube-reserved
kubeReserved:
cpu: '500m'
memory: '1Gi'
ephemeral-storage: '1Gi'
systemReserved:
cpu: '500m'
memory: '1Gi'
ephemeral-storage: '1Gi'
evictionHard:
memory.available: '100Mi'
nodefs.available: '10%'
imagefs.available: '15%'
핵심 명령어 치트시트
# === 정보 확인 ===
cat /proc/self/cgroup # 현재 프로세스 cgroup
stat -fc %T /sys/fs/cgroup/ # v1(tmpfs) vs v2(cgroup2fs)
cat /sys/fs/cgroup/cgroup.controllers # 사용 가능 컨트롤러 목록
systemd-cgls # cgroup 트리 표시
systemd-cgtop # 실시간 cgroup 모니터링
# === CPU ===
cat /sys/fs/cgroup/PATH/cpu.max # CPU quota/period
cat /sys/fs/cgroup/PATH/cpu.weight # CPU weight
cat /sys/fs/cgroup/PATH/cpu.stat # throttle 통계
# === Memory ===
cat /sys/fs/cgroup/PATH/memory.max # 하드 제한
cat /sys/fs/cgroup/PATH/memory.current # 현재 사용량
cat /sys/fs/cgroup/PATH/memory.stat # 상세 통계
cat /sys/fs/cgroup/PATH/memory.events # OOM 이벤트
# === IO ===
cat /sys/fs/cgroup/PATH/io.max # IO 제한
cat /sys/fs/cgroup/PATH/io.stat # IO 통계
# === PID ===
cat /sys/fs/cgroup/PATH/pids.max # PID 제한
cat /sys/fs/cgroup/PATH/pids.current # 현재 PID 수
# === Docker ===
docker stats # 실시간 리소스 모니터링
docker inspect CONTAINER | grep -i cgroup # cgroup 설정 확인
# === 트러블슈팅 ===
dmesg | grep -i "killed process" # OOM kill 로그
cat /proc/PID/cgroup # 프로세스 cgroup 확인
cat /proc/PID/oom_score # OOM 점수 확인
퀴즈: cgroup 이해도 테스트
Q1. cgroup v1과 v2의 가장 큰 구조적 차이는?
A: v1은 컨트롤러별로 독립적인 hierarchy를 가지지만, v2는 단일 통합(unified) hierarchy를 사용한다. v2에서는 하나의 프로세스가 모든 컨트롤러에서 동일한 cgroup에 속한다.
Q2. Kubernetes Pod QoS 클래스 중 "Guaranteed"가 되려면 어떤 조건이 필요한가?
A: 모든 컨테이너에서 CPU와 메모리의 requests와 limits가 동일하게 설정되어야 한다.
Q3. cgroup v2의 memory.high와 memory.max의 차이는?
A: memory.high는 소프트 제한으로, 초과 시 프로세스를 throttle(속도 제한)한다. memory.max는 하드 제한으로, 초과 시 OOM Killer가 발동한다.
Q4. CPU throttle 비율이 35%일 때 의미하는 것은?
A: 전체 CFS period 중 35%에서 CPU quota를 모두 소진하여 프로세스가 실행되지 못했다는 의미이다. CPU limits 증가 또는 CPU burst 설정을 검토해야 한다.
Q5. Docker에서 systemd cgroup driver가 cgroupfs보다 권장되는 이유는?
A: systemd가 init 시스템으로 동작할 때, cgroupfs와 systemd 두 개의 cgroup 관리자가 동시에 존재하면 리소스 압박 상황에서 시스템이 불안정해질 수 있다. systemd driver를 사용하면 단일 cgroup 관리자로 통합된다.
Q6. CVE-2022-0492는 어떤 cgroup 메커니즘을 악용하는가?
A: cgroup v1의 release_agent 메커니즘을 악용한다. release_agent는 cgroup의 마지막 프로세스 종료 시 실행되는 프로그램인데, 권한 검증이 누락되어 컨테이너에서 호스트 권한으로 임의 코드를 실행할 수 있었다.
참고 자료
- Linux Kernel Documentation: Control Group v2
- Kubernetes Documentation: About cgroup v2
- Red Hat: Migrating from CGroups V1 to V2
- CFS Bandwidth Control - Linux Kernel Documentation