Skip to content

필사 모드: [Linux] cgroup 완전 가이드: 컨테이너 리소스 제어의 핵심

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

목차

1. cgroup이란?

2. cgroup v1 vs v2

3. 주요 컨트롤러 심층 분석

4. Docker와 cgroup

5. Kubernetes와 cgroup

6. cgroup 실전 트러블슈팅

7. cgroup과 보안

8. 정리

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 점수 확인

**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

현재 단락 (1/554)

1. cgroup이란?

작성 글자: 0원문 글자: 21,153작성 단락: 0/554