본문 바로가기

IT

cgroups 소개와 실제 적용 예제

cgroup이란?

Cpu, Disk I/O, Memory, Network 등 자원 사용을 제한/격리 시키는 커널 기능

cgroups인가? cgroup 인가?

위키는 cgroups라고 한다.

하지만 리눅스 커널 공식문서를 보면 허태준님께서 이미 정의를 하고 시작을 하셨다.

여러 개의 control group을 명시적으로 언급할 때는 복수형인 "cgroups"을 사용한다고 한다

하지만 언제 사용해야 할지 헷갈리므로 혼합해서 쓰겠다...

“cgroup” stands for “control group” and is never capitalized. The singular form is used to designate the whole feature and also as a qualifier as in “cgroup controllers”. When explicitly referring to multiple individual control groups, the plural form “cgroups” is used.

history

2006년 구글에서 “프로세스 컨테이너”라는 이름으로 시작
2007년 컨테이너와의 이름 충돌을 피하기 위해 cgroup (control groups)로 변경
2008년 kernel 2.6.24에 cgroup v1 내장
2016년 kernel 4.5에 cgroup v2 내장
짧게 히스토리를 알아보았다. 생각보다 v2는 따끈한 신상이다

cgroup v1 과 v2의 큰 차이점

v1에서는 서브 시스템을 통해 시스템 리소스를 특정 프로세스에 할당하는 형태로 제한한다

  • 서브 시스템 예시 : cpu, blkio, memory 등
  • 도움되는 레드햇 문서

가장 큰 변화는 v2에서는 단일 계층구조를 사용한다(single hierarchy)

v1에서는 서브 시스템를 통해 연결되어 있어서 독립적인 계층구조로 연결되어 있었다.

 

조금 더 자세하게 설명하면 v1에서는 cpu를 제한하려면 cpu, memory를 제한하려면 memory 서브 시스템을 사용 해야해서 복잡하고 관리가 어려웠다.

그래서 v2에서는 이를 단일 계층 구조로 전환하면서 리소스 컨트롤러가 cgroup과 연결되어 여러 리소스 컨트롤러와 동시에 연결되었다.

이로써 마침내 v2에서는 모든 자원 제어를 한곳에서 설정하고 관리할 수가 있어졌다.

 

말은 어려우니까 그림과 예시로 알아보자

V1 V2

https://www.redhat.com/en/blog/world-domination-cgroups-rhel-8-welcome-cgroups-v2

 

kernel.org의 v1v2 문서를 살펴보면 더 많은 방식으로 자원제어가 가능한 차이도 있으며 

더 나은 보안과 격리가 가능하다고도 한다(미확인)

 

하지만 가장 중요한 계층구조와 실제 적용되는 예시를 확인하는데 초점을 맞춘다

v1과 v2의 차이 비교 예시

우분투 22.04에서 실습을 진행했다

cgroup 확인

cgroup v1, v2 모두 사용가능하지만 cgroup2가 활성화 되어 있다.

ubuntu@MyServer:~$ mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
ubuntu@MyServer:~$ cat /proc/filesystems | grep cgroup
nodev    cgroup
nodev    cgroup2

cgroup V1 설정 예시

앞의 계층구조를 쉽게 이해하기 위해 cgroup v1에서의 cpu와 메모리를 제한하는 설정을 해본다. 하지만 v1은 실제 적용되는지 보지 않고 넘어가도록 한다

  • my_cgroup_v1_cpu 디렉터리를 생성하고 cpu를 제한한다
  • my_cgroup_v1_cpu 디렉터리를 생성하고 memory를 제한한다
sudo mkdir -p /sys/fs/cgroup/cpu/my_cgroup_v1_cpu
sudo mkdir -p /sys/fs/cgroup/memory/my_cgroup_v1_memory

echo 512 | sudo tee /sys/fs/cgroup/cpu/my_cgroup_v1_cpu/cpu.shares
echo 500M | sudo tee /sys/fs/cgroup/memory/my_cgroup_v1_memory/memory.limit_in_bytes
echo 7263 | sudo tee /sys/fs/cgroup/cpu/my_cgroup_v1_cpu/tasks
echo 7263 | sudo tee /sys/fs/cgroup/memory/my_cgroup_v1_memory/tasks

cgroup v2 설정 예시

  • my_cgroup_v2 디렉터리를 생성하고 cpu와 메모리를 제한한다
  • 메모리는 확인 하지 않지만 v1과의 차이를 비교하기 위해 설정
ubuntu@MyServer:~$ 
ubuntu@MyServer:~$ sudo mkdir -p /sys/fs/cgroup/my_cgroup_v2
ubuntu@MyServer:~$ echo "1000 100000" | sudo tee /sys/fs/cgroup/my_cgroup_v2/cpu.max
1000 100000
ubuntu@MyServer:~$ echo 500M | sudo tee /sys/fs/cgroup/my_cgroup_v2/cpu.max
500M

위 두 예시로 cgroup v2의 구조가 왜 편한지 이해할 수 있다

cgroup v2 실제 적용

  • cpu 제한값만 설정하고 현재 stress 툴을 통해 부하를 주고 있는 cpu 상태를 확인한다.
ubuntu@MyServer:~$ top
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                  
   7263 ubuntu    20   0    3708    256    256 R 100.0   0.0  16:35.10 stress
  • 스트레스 주고 있는 PID를 cgroup에 할당 후 cpu를 확인한다.
ubuntu@MyServer:~$ echo 7263 | sudo tee /sys/fs/cgroup/my_cgroup_v2/cgroup.procs
ubuntu@MyServer:~$ top
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                  
   7263 ubuntu    20   0    3708    256    256 R  10.3   0.0  16:43.74 stress
  • 제한값을 기본 으로 변경한 뒤 cpu 사용량이 복구되는 것을 확인한다
# 기본값으로 복구                                      
ubuntu@MyServer:/sys/fs/cgroup/my_cgroup_v2$ echo "max 100000" | sudo tee /sys/fs/cgroup/my_cgroup_v2/cpu.max
max 100000

ubuntu@MyServer:~$ top
# cpu 확인
    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                  
   7263 ubuntu    20   0    3708    256    256 R 100.0   0.0  16:53.10 stress       

cgroup 적용 확인

  • systemd-cgls를 사용하면 쉽게 확인할 수 있다
  • 일반 경로에서 실행하면 전체를 볼 수 있고, 특정 cgroup 폴더에서 실행하면 해당 cgroup에 대한 내용만 출력한다
ubuntu@MyServer:/sys/fs/cgroup/my_cgroup_v2$ systemd-cgls
Working directory /sys/fs/cgroup/my_cgroup_v2:
└─7263 stress -c 1

cgroup은 재부팅 후에도 이해가 될까?

안된다

  • 재부팅 후에는 삭제되지만 init.d에 쉘스트립트형태로 등록은 가능하다
  • 하지만 PID를 직접 넣어야 하므로 의미가 없다

systemd에서 설정하는 CPUQuota값은 cgroup일까?

위의 재부팅 관련해서 systemd이 생각나서 systemd에서도 cpu제한 값이 있던 게 생각나서 한번 확인해 보기로 했다

  • 적당히 stress툴을 실행하는 systemd 파일을 생성한다
  • CPUQuota를 50%로 설정한다
sudo vim /etc/systemd/system/stress.service

[Unit]
Description=Stress Test Service
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/stress -c 1
CPUQuota=50%

[Install]
WantedBy=multi-user.target
  • daemon-reload 후 잘 등록되었는지 확인한다
ubuntu@MyServer:~$ sudo systemctl daemon-reload
ubuntu@MyServer:~$ sudo systemctl status stress
○ stress.service - Stress Test Service
     Loaded: loaded (/etc/systemd/system/stress.service; disabled; vendor preset: enabled)
     Active: inactive (dead)
ubuntu@MyServer:~$                                
  • 시작한 뒤 cpu가 잘 올라오고 50%로 제한되는지 확인한다
ubuntu@MyServer:~$ sudo systemctl start stress
ubuntu@MyServer:~$ sudo sys
top

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                  
   1041 root      20   0    3708    256    256 R  49.8   0.0   0:40.61 stress    
  • systemd-cgls로 확인해보면 stress.service를 확인할 수 있고, cgroup 폴더 내에도 존재함을 확인 할 수 있다
ubuntu@MyServer:~$ systemd-cgls
...
└─system.slice 
  ├─stress.service 
  │ ├─1040 /usr/bin/stress -c 1
  │ └─1041 /usr/bin/stress -c 1
...

ubuntu@MyServer:~$ cd /sys/fs/cgroup/system.slice/stress.service
ubuntu@MyServer:/sys/fs/cgroup/system.slice/stress.service$ systemd-cgls
Working directory /sys/fs/cgroup/system.slice/stress.service:
├─1040 /usr/bin/stress -c 1
└─1041 /usr/bin/stress -c 1
  • cpu 제한값을 확인해보면 50%를 타겟으로 설정된 것을 볼 수 있다
ubuntu@MyServer:/sys/fs/cgroup/system.slice/stress.service$ cat cpu.max
50000 100000
  • 직접 수정해도 바로 적용된다
ubuntu@MyServer:/sys/fs/cgroup/system.slice/stress.service$ echo 10000 100000 | sudo tee cpu.max
10000 100000

ubuntu@MyServer:/sys/fs/cgroup/system.slice/stress.service$ top

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                  
   1041 root      20   0    3708    256    256 R  10.0   0.0   2:38.85 stress           
  • 그리고 서비스를 내리면 바로 cgroup도 삭제 되는 걸 확인할 수 있다
ubuntu@MyServer:~$ sudo systemctl stop stress
ubuntu@MyServer:~$ cd /sys/fs/cgroup/system.slice/stress.service
-bash: cd: /sys/fs/cgroup/system.slice/stress.service: No such file or directory
ubuntu@MyServer:~$ sudo systemctl start stress
ubuntu@MyServer:~$ cd /sys/fs/cgroup/system.slice/stress.service
ubuntu@MyServer:/sys/fs/cgroup/system.slice/stress.service$

도커도 cgroup이 적용되었을까?

  • 적당히 docker 하나를 만들어서 실행한다
ubuntu@MyServer:~$ vim Dockerfile

FROM ubuntu:22.04
RUN apt-get update && apt-get install -y stress

ubuntu@MyServer:~$ docker build -t stress .
...
ubuntu@MyServer:~$ docker run -ti stress stress -c 1
  • 실행하고 뒤져보기 시작한다.
  • container id를 의심하고 찾아본다.
ubuntu@MyServer:/sys/fs/cgroup$ docker ps
CONTAINER ID   IMAGE     COMMAND         CREATED              STATUS              PORTS     NAMES
9a4bb82e0116   stress    "stress -c 1"   About a minute ago   Up About a minute             dreamy_almeida
ubuntu@MyServer:/sys/fs/cgroup$ find . | grep 9a4bb82e0116
./system.slice/docker-9a4bb82e0116d1ffeb04e3ab4042bf175a60df6187067aee759942dac32cde70.scope
./system.slice/docker-9a4bb82e0116d1ffeb04e3ab4042bf175a60df6187067aee759942dac32cde70.scope/misc.events
./system.slice/docker-9a4bb82e0116d1ffeb04e3ab4042bf175a60df6187067aee759942dac32cde70.scope/cgroup.events
./system.slice/docker-9a4bb82e0116d1ffeb04e3ab4042bf175a60df6187067aee759942dac32cde70.scope/memory.events

...

ubuntu@MyServer:/sys/fs/cgroup/system.slice/docker-9a4bb82e0116d1ffeb04e3ab4042bf175a60df6187067aee759942dac32cde70.scope$ systemd-cgls
Working directory /sys/fs/cgroup/system.slice/docker-9a4bb82e0116d1ffeb04e3ab4042bf175a60df6187067aee759942dac32cde70.scope:
├─2670 stress -c 1
└─2698 stress -c 1

도커에 cgroup 직접 적용

  • 바로 적용해본다
ubuntu@MyServer:/sys/fs/cgroup/system.slice/docker-9a4bb82e0116d1ffeb04e3ab4042bf175a60df6187067aee759942dac32cde70.scope$ cat cpu.max
max 100000
ubuntu@MyServer:/sys/fs/cgroup/system.slice/docker-9a4bb82e0116d1ffeb04e3ab4042bf175a60df6187067aee759942dac32cde70.scope$ echo "10000 100000" | sudo tee cpu.max
10000 100000

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                  
   2698 root      20   0    3708    256    256 R  13.3   0.0   2:47.10 stress        

docker run --cpus 의 실제 값 적용 확인

  • 이제 docker run의 --cpus 인자값도 cgroup으로 적용되는지 찾아보자
  • 프리한 도커를 실행한뒤 값을 저장한다
ubuntu@MyServer:~$ docker run -ti --rm stress stress -c 1
stress: info: [1] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd

ubuntu@MyServer:/sys/fs/cgroup/system.slice/docker-7cddbe327b0c34b3158f7387461c8a2e250f24dfe589e5928e17da51064f17da.scope$ find . -type f | xargs -I {} sh -c 'echo "\n{}" && sudo cat {}' >> ~/original.output
cat: ./cgroup.kill: Invalid argument
cat: ./memory.reclaim: Invalid argument
  • 출력이 엉망이지만 상관없다. 혹시나 cpu.max가 아닐 경우를 대비해 다 뽑아놓는다.
  • 도커 공식 문서 참고해서 확실하게 적용될만한 cpus를 적용하고 출력을 내보낸다
ubuntu@MyServer:~$ docker run --cpus=0.500 -ti --rm stress stress -c 1
stress: info: [1] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd

ubuntu@MyServer:/sys/fs/cgroup/system.slice/docker-9f721d664f20aa1fda2e3cde8a32118523052c4d6e738cdb1c955c48f9eae165.scope$ find . -type f | xargs -I {} sh -c 'echo "\n{}" && sudo cat {}' >> ~/cpus.output
cat: ./cgroup.kill: Invalid argument
cat: ./memory.reclaim: Invalid argument
  • 결과 확인
ubuntu@MyServer:~$ cat original.output | grep -E "cpu.max$" -a1

./cpu.max
max 100000
ubuntu@MyServer:~$ cat cpus.output | grep -E "cpu.max$" -a1

./cpu.max
50000 100000
ubuntu@MyServer:~$ 

 

도커의 --cpus 값도 cgroup의 cpu.max 값을 통해 제어되는 것까지 확인했다.

 

end

 

'IT' 카테고리의 다른 글

KIND 소개와 일부 예제  (1) 2024.09.08