[네떡스터디🔥kans] cgroup v1, v2

최신 리눅스 환경에서는 여러 애플리케이션과 프로세스가 동시에 실행되면서 CPU, 메모리, 네트워크 등 다양한 자원을 효율적으로 관리하는 것이 매우 중요합니다.
특히, Docker나 Kubernetes와 같은 컨테이너 환경에서는 하나의 노드에서 여러 애플리케이션이 운영되므로, 특정 프로세스가 자원을 과도하게 사용하면 전체 시스템에 영향을 줄 수 있습니다.

이를 방지하기 위해 리눅스 커널은 Cgroup(Control Groups)이라는 기능을 제공합니다.
Cgroup을 통해 각 프로세스 그룹이 사용할 수 있는 자원의 양을 제한하고, 실시간으로 모니터링하며, 프로세스 제어나 우선순위 조정을 보다 효율적으로 할 수 있습니다.

이 글에서는 Cgroup의 개념과 함께, Cgroup v1과 v2의 차이점, 그리고 실제 환경에서 어떻게 적용할 수 있는지를 살펴보겠습니다.

 

 

https://wizardzines.com/comics/cgroups/

 

100ms 동안 할당량을 다 썼다는건
운영체제에서 특정 프로세스에 대해 CPU 사용 시간을 제한할 때, 예를 들어 100ms 동안 CPU를 사용할 수 있는 할당량이 주어졌다면, 그 시간 내에 CPU를 모두 사용해버리면 더 이상 CPU를 사용할 수 없고, 이후에 다시 사용할 수 있을 때까지 기다려야 한다는 뜻입니다.

 

 

cgroup 이란 

Cgroup(Control Groups)은 프로세스를 그룹으로 묶고, 그룹별로 리소스(CPU, 메모리 등)를 제한하는 기능입니다.
즉, 여러 개의 컨테이너(혹은 프로세스)가 실행될 때, 어떤 컨테이너가 얼마만큼의 리소스를 사용할지를 설정하는 역할을 합니다.

 

✔ CPU 사용량을 제한하고 싶다면? → cpu 컨트롤러 사용
✔ 메모리 제한을 걸고 싶다면? → memory 컨트롤러 사용
✔ 디스크 I/O 속도를 조절하고 싶다면? → blkio 컨트롤러 사용

 

각 컨트롤러를 활용하면 컨테이너별로 리소스를 균형 있게 할당할 수 있습니다.

 

cgroup v1

cgroup v1은 리눅스 커널 2.6.24에서 처음 등장한 리소스 관리 시스템인데, 여기서 중요한 포인트는 각 리소스를 따로따로 관리한다는 점이에요. CPU는 CPU대로, 메모리는 메모리대로, 네트워크는 네트워크대로 각각 제어됩니다.

 

이걸 쉽게 설명해볼게요.

A가 회사에서 프로젝트를 진행하는데, 각 부서에서 이런저런 규칙을 정해준다고 해봅시다.

  • IT팀: "CPU 사용률 50% 이하로 제한해야 해!"
  • 회계팀: "메모리는 2GB까지만 써!"
  • 보안팀: "네트워크 트래픽을 제한해야 해!"

이러면 A 입장에선 각 부서의 요구 사항을 따로따로 신경 써야 해서 좀 번거롭겠죠? cgroup v1도 마찬가지입니다. CPU를 조절하는 cpu 서브시스템, 메모리를 관리하는 memory 서브시스템, 네트워크를 제한하는 net_cls 서브시스템 등이 각각 독립적으로 동작합니다.

즉, 여러 개의 제한을 걸려면 각 서브시스템을 개별적으로 설정해야 해서 관리가 좀 불편할 수 있다는 거죠. 이런 구조를 그림으로 나타내면 다음과 같은 계층 형태가 됩니다.

 

 

 

아래 그림을 보면 cgroup v1의 계층 구조를 이해하는 데 도움이 됩니다.

 

  • 왼쪽 (cgroup hierarchies)
    • /sys/fs/cgroup 아래에 여러 개의 최상위 cgroup(TL, Top Level)이 존재합니다.
    • /cpu 그룹은 cpu와 cpuacct 서브시스템을 포함하고 있으며, 그 아래에 high-priority, normal, experiment_1 같은 하위 그룹이 있습니다.
    • /mem 그룹은 memory 서브시스템을 포함하고 있으며, 마찬가지로 여러 하위 그룹이 존재합니다.
    • 중요한 점: 각 서브시스템(컨트롤러)은 하나의 계층에서만 사용할 수 있습니다.
  • 오른쪽 (subsystems, controllers)
    • cpuset, cpu, cpuacct, memory, blkio, net_cls 등 다양한 컨트롤러가 존재합니다.
    • 빨간 삼각형(🔺)이 붙은 net_cls와 net_prio는 커널 모듈로 로드해야만 사용할 수 있는 컨트롤러입니다.

이 그림을 보면 cgroup v1의 가장 큰 특징을 알 수 있습니다.
각 서브시스템이 독립적인 계층을 사용하기 때문에, 여러 개의 제한을 조합하려면 개별적인 그룹을 따로 만들어야 합니다. 즉, CPU와 메모리 제한을 동시에 적용하려면 별도의 cgroup을 따로 관리해야 하는 불편함이 생길 수 있습니다.

 

 

 

실제로 cgroup v1이 어떻게 구성되어 있는지 확인하려면 아래 명령어를 실행해 볼 수 있습니다.

 

blkio: 블록 장치의 I/O(입출력) 성능을 제한합니다.
cpu: 스케줄러를 통해 사용 가능한 CPU 자원의 총량을 제어합니다.
cpuacct: CPU 자원의 사용량을 기록하고 보고합니다.
cpuset: 프로세스가 사용할 수 있는 CPU와 메모리를 제어합니다.
devices: 장치(디바이스)에 대한 접근을 제어합니다.
freezer: cgroup 내의 프로세스를 일시 중지하거나 재개합니다.
hugetlb: 프로세스가 사용할 수 있는 대용량 페이지 메모리를 제어합니다.
memory: 프로세스가 사용할 수 있는 메모리의 총량을 제어하며, 메모리 사용 상황을 모니터링합니다.
net_cls: cgroup 내의 네트워크 패킷을 분류합니다.
net_prio: 네트워크 트래픽의 우선순위를 제어합니다.
perf_event: cgroup의 성능을 모니터링합니다.
pids: 생성할 수 있는 프로세스의 총수를 제어합니다.

 

Cgroup v2

cgroup v2는 v1의 단점을 보완하고 보다 통합적이고 일관된 방식으로 리소스를 관리할 수 있도록 설계되었습니다.

 

cgroup v1에서는 CPU 제한을 위해 cpu 계층을 만들고, 메모리 제한을 위해 memory 계층을 따로 만들었어야 했어요.
하지만 cgroup v2에서는 하나의 계층에서 모든 리소스를 한꺼번에 제어할 수 있습니다.

 

 

트리 구조에서 상속이 가능해짐

cgroup v1에서는 하위 그룹(child group)이 상위 그룹의 제한을 반드시 따를 필요가 없었어요. 즉, 상위 그룹에서 CPU 사용량을 제한해도, 하위 그룹에서 다시 무제한으로 설정할 수 있었습니다.

 

하지만 cgroup v2에서는 하위 그룹이 상위 그룹의 정책을 자동으로 따르도록 설계되었습니다. 즉, 부모 그룹이 설정한 리소스 제한이 하위 그룹에도 강제 적용됩니다.

보다 직관적인 인터페이스 제공

cgroup v1에서는 cpu.cfs_quota_us, memory.limit_in_bytes처럼 설정해야 하는 파일 이름이 서브시스템마다 다 달랐어요.
반면 cgroup v2에서는 인터페이스가 일관되도록 정리되어 설정이 훨씬 직관적입니다.

 

📌 예제 (CPU 제한 설정)

  • cgroup v1: cpu.cfs_quota_us, cpu.cfs_period_us
  • cgroup v2: cpu.max

📌 예제 (메모리 제한 설정)

  • cgroup v1: memory.limit_in_bytes
  • cgroup v2: memory.max

 

cgroup v2 구성 확인하기

 

 

내가 사용하는 버전 확인하기

$ findmnt /sys/fs/cgroup/
TARGET         SOURCE FSTYPE  OPTIONS
/sys/fs/cgroup cgroup cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate

$ stat -fc %T /sys/fs/cgroup/
cgroup v2 -> cgroup2fs
cgroup v1 -> tmpfs

 

저는 ubuntu 22.04에서 테스트 해서 cgroup2로 나왔습니다.

 

어떤 컨트롤러가 활성화 되어있는지 확인하는 방법

findmnt -R /sys/fs/cgroup
TARGET         SOURCE  FSTYPE  OPTIONS
/sys/fs/cgroup cgroup2 cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot

 

계층 정보 확인하기

systemd-cgls --no-pager 명령을 사용하면 cgroup 계층 구조를 확인할 수 있습니다.

Control group /:
-.slice
├─init.scope (#7253)
│ ├─  1 /sbin/init
│ ├─360 bash
│ ├─711 systemd-cgls --no-pager
│ └─712 head -n 30
├─system.slice (#9248)
│ ├─containerd.service … (#11054)
│ │ → user.invocation_id: baa3f1519c1d40e09ee4880c29f1bc42
│ │ → user.delegate: 1
│ │ → trusted.invocation_id: baa3f1519c1d40e09ee4880c29f1bc42
│ │ → trusted.delegate: 1
│ │ ├─102 /usr/local/bin/containerd
│ │ ├─277 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 6ab866f4…
│ │ └─291 /usr/local/bin/containerd-shim-runc-v2 -namespace k8s.io -id 612f1fe7…
│ └─systemd-journald.service (#10074)
│   → user.invocation_id: 5afa0abdf32e4df8a6afef2ab17c8904
│   → trusted.invocation_id: 5afa0abdf32e4df8a6afef2ab17c8904
│   └─88 /lib/systemd/systemd-journald
└─kubelet.slice (#9199)
  → user.invocation_id: 0554ce5b81ba4f88986dc88b2d99beee
  → trusted.invocation_id: 0554ce5b81ba4f88986dc88b2d99beee
  ├─kubelet.service (#13069)
  │ → user.invocation_id: fa073782955b4301b344931473195892
  │ → trusted.invocation_id: fa073782955b4301b344931473195892
  │ └─223 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kub…
  └─kubelet-kubepods.slice (#12781)
    → user.invocation_id: be5c383963124a82ba4193bb178f01bf
    → trusted.invocation_id: be5c383963124a82ba4193bb178f01bf