본문 바로가기

컨테이너/쿠버네티스 네트워크

[KANS] 쿠버네티스 네트워크 (2) 컨테이너 격리

cloudNet@ 팀의 가시다 님이 진행하는 쿠버네티스 네트워크 스터디 1주차 정리입니다.

 

 


대주제

도커 컨테이너의 격리 요약

  • 컨테이너는 독립된 리눅스 환경(pivot-root, namespace, Overlay filesystem, cgroup)을 보장받는 프로세스
  • chroot와 탈옥
  • 네임스페이스
  • overlay filesystem
  • cgroup

 

1. chroot와 탈옥

  • chroot는 경로를 모으고 패키징 해주는 리눅스 기능이다 
# [터미널1] 관리자 전환
sudo su -
whoami

#
cd /tmp
mkdir myroot

# chroot 사용법 : [옵션] NEWROOT [커맨드]
chroot myroot /bin/sh
chroot: failed to run command ‘/bin/sh’: No such file or directory

#
tree myroot
which sh
ldd /bin/sh

# 바이러리 파일과 라이브러리 파일 복사
mkdir -p myroot/bin
cp /usr/bin/sh myroot/bin/
mkdir -p myroot/{lib64,lib/x86_64-linux-gnu}
tree myroot

cp /lib/x86_64-linux-gnu/libc.so.6 myroot/lib/x86_64-linux-gnu/
cp /lib64/ld-linux-x86-64.so.2 myroot/lib64
tree myroot/

#
w
--------------------
ls
exit
--------------------

#
which ls
ldd /usr/bin/ls
	
#
cp /usr/bin/ls myroot/bin/
mkdir -p myroot/bin
cp /lib/x86_64-linux-gnu/{libselinux.so.1,libc.so.6,libpcre2-8.so.0} myroot/lib/x86_64-linux-gnu/
cp /lib64/ld-linux-x86-64.so.2 myroot/lib64
tree myroot

#
chroot myroot /bin/sh
--------------------
ls /

## 탈출 가능한지 시도
cd ../../../
ls /

# 아래 터미널2와 비교 후 빠져나오기
exit
--------------------
# chroot 요약 : 경로를 모으고(패키징), 경로에 가둬서 실행(격리)


# [터미널2]
# chroot 실행한 터미널1과 호스트 디렉터리 비교
ls /

  • 탈옥 코드 작성
#include <sys/stat.h>
#include <unistd.h>

int main(void)
{
  mkdir(".out", 0755);
  chroot(".out");
  chdir("../../../../../");
  chroot(".");

  return execl("/bin/sh", "-i", NULL);
}
  • 컴파일 하고 복사  -> 탈출되서 못쓰겠군 
# 컴파일
gcc -o myroot/escape_chroot escape_chroot.c
tree -L 1 myroot
file myroot/escape_chroot

# chroot 실행
chroot myroot /bin/sh
-----------------------
ls /
cd ../../
cd ../../
ls /

# 탈출!
./escape_chroot
ls /

# 종료
exit
exit
-----------------------

# [터미널2]
## 루트 디렉터리 비교 및 확인
ls /

 


2. 피벗 + 마운트 네임스페이스


chroot와 pivot_root의 차이점은 chroot가 해커들의 침입을 막기 위해 개발된 기능인데 반해서 pivot_root는 부팅 파일시스템과 루트 파일시스템의 pivot을 위해 개발된 기능이라는 점입니다.

따라서 pivot_root 기능을 활용하여 보다 호스트와 격리된 환경을 제공할수 있습니다.

 

장점

✅ 탈옥 NO

✅ 프로세스에서의 사건은 호스트에 영향 없음 - 마운트 네임스페이스

✅  완전한 격리가 가능하다 ( mount, network, cgroup 등 )

✅  권한 제어가 가능하다 / 프로세스의 자원 사용의 제한을 줄 수 있다. (cgroup)

주요 명령어
pivot_root
# pivot_root [new-root] [old-root]
## 사용법은 심플합니다 ~ new-root와 old-root 경로를 주면 됩니다

mount
# mount -t [filesystem type] [device_name] [directory - mount point]
## root filesystem tree에 다른 파일시스템을 붙이는 명령
## -t : filesystem type  ex) -t tmpfs  (temporary filesystem : 임시로 메모리에 생성됨)               
## -o  : 옵션  ex) -o size=1m  (용량 지정 등 …)
## 참고) * /proc/filesystems 에서 지원하는 filesystem type 조회 가능

unshare
# unshare [options] [program] [arguments]]
## "새로운 네임스페이스를 만들고 나서 프로그램을 실행" 하는 명령어입니다


# [터미널1]
unshare --mount /bin/sh
-----------------------
# 아래 터미널2 호스트 df -h 비교 : mount unshare 시 부모 프로세스의 마운트 정보를 복사해서 자식 네임스페이스를 생성하여 처음은 동일
df -h
-----------------------

# [터미널2]
df -h


# [터미널1]
-----------------------
#
mkdir new_root
mount -t tmpfs none new_root
ls -l
tree new_root

## 마운트 정보 비교 : 마운트 네임스페이스를 unshare
df -h
mount | grep new_root
findmnt -A

## 파일 복사 후 터미널2 호스트와 비교
cp -r myroot/* new_root/
tree new_root/

-----------------------

# [터미널2]
cd /tmp
ls -l
tree new_root
df -h
mount | grep new_root
findmnt -A

## 안보이는 이유 : 마운트 네임스페이스를 unshare 된 상태
tree new_root/
# 터미널1
-----------------------
mkdir new_root/put_old

## pivot_root 실행
cd new_root # pivot_root 는 실행 시, 변경될 root 파일시스템 경로로 진입
pivot_root . put_old # [신규 루트] [기존 루트]

##
cd /
ls / # 터미널2와 비교
ls put_old

-----------------------

# 터미널2
ls /


탈옥 시도
# 터미널1
-----------------------
./escape_chroot
cd ../../../
ls /
exit
exit
-----------------------

목표

 

[ 네임스페이스와 관련된 프로세스의 특징 ]

  1. 모든 프로세스들은 네임스페이스 타입별로 특정 네임스페이스에 속합니다
  2. Child 는 Parent 의 네임스페이스를 상속받습니다
  3. 프로세스는 네임스페이스 타입별로 일부는 호스트(root) 네임스페이스를 사용하고 일부는 컨테이너의 네임스페이스를 사용할 수 있습니다.
    • (mount 네임스페이스는 컨테이너의 것으로 격리하고, network 네임스페이스는 호스트 것을 사용)

 

 

# [터미널 1,2] 관리자
sudo su -
cd /tmp

# 네임스페이스 확인 방법 1 : 프로세스 별 네임스페이스 확인
ls -al /proc/$$/ns
lrwxrwxrwx 1 root root 0 Aug 25 13:45 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 mnt -> 'mnt:[4026531841]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 net -> 'net:[4026531840]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 uts -> 'uts:[4026531838]'

## 특정 네임스페이스의 inode 값만 확인
readlink /proc/$$/ns/mnt
readlink /proc/$$/ns/net


# 네임스페이스 확인 방법 2 : lsns - List system namespaces
lsns -h
lsns -p 1
lsns -p $$

## -t 네임스페이스 타입 , -p 조회할 PID
## NPROCS : 해당 네임스페이스에 속해있는 프로세스 갯수
## PID : 해당 네임스페이스의 (최초) 주인 프로세스
lsns -t mnt -p 1
lsns -t mnt -p $$

 

 

마운트 네임스페이스

  • unshare -m
# PID 1과 현재 Shell 속한 프로세스의 MNT NS 정보 확인
lsns -t mnt -p 1
lsns -t mnt -p $$

# [터미널1] /tmp 디렉터리
# unshare -m [명령어] : -m 옵션을 주면 [명령어]를 mount namespace 를 isolation 하여 실행합니다
unshare -m  # *[명령어]를 지정하지 않으면 환경변수 $SHELL 실행
-----------------------------------
# NPROCS 값과 PID 값의 의미 확인
lsns -p $$
        NS TYPE   NPROCS   PID USER COMMAND
4026531834 time      112     1 root /sbin/init
4026531835 cgroup    112     1 root /sbin/init
4026531836 pid       112     1 root /sbin/init
4026531837 user      112     1 root /sbin/init
4026531838 uts       108     1 root /sbin/init
4026531839 ipc       112     1 root /sbin/init
4026531840 net       112     1 root /sbin/init
4026532206 mnt         2  5834 root -bash

# PID 1과 비교
lsns -p 1

# 빠져나오기
exit
-----------------------------------

UTS 네임스페이스 :  여러 사용자 작업 환경 제공하고자 서버 시분할 나눠쓰기), 호스트명, 도메인명 격리

# unshare -u [명령어]
# -u 옵션을 주면 [명령어]를 UTS namespace 를 isolation 하여 실행

# [터미널1] /tmp 디렉터리
unshare -u
-----------------------------------
lsns -p $$
lsns -p 1

## 기본은 부모 네임스페스의 호스트 네임을 상속
hostname

## 호스트 네임 변경
hostname KANS

## 아래 터미널2에서 hostname 비교
hostname

exit
-----------------------------------

# [터미널2] /tmp 디렉터리
hostname

IPC 네임스페이스 :  Inter-Process Communication 격리, 프로세스 간 통신 자원 분리 관리 - Shared Memory, Pipe, Message Queue 등

# [터미널1] /tmp 디렉터리
unshare -i
-----------------------------------
lsns -p $$
lsns -p 1

exit
-----------------------------------

# [터미널1]
ipcs -m
docker run --rm --name test1 --ipc=shareable -it ubuntu bash
---------------------
ipcs -m
ipcmk -M 2000
Shared memory id: 0

ipcs -m
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x6427dde0 0          root       644        2000       0

lsns -p $$

# 아래 실습 확인 후 종료
exit
---------------------

# [터미널2]
# 호스트에서 확인
ipcs -m

# 컨테이너 생성 시 IPC 공유 : 해당 컨테이너는 test1 컨테이너와 IPC 네임스페이스를 공유
docker run --rm --name test2 --ipc=container:test1 -it ubuntu bash
---------------------
ipcs -m
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x6427dde0 0          root       644        2000       0

lsns -p $$

# 실습 확인 후 종료
exit
---------------------

 

PID 네임스페이스

# unshare -p [명령어]
## -p 옵션을 주면 [명령어]를 PID namespace 를 isolation 하여 실행합니다
## -f(fork) : PID namespace 는 child 를 fork 하여 새로운 네임스페이스로 격리함
## --mount-proc : namespace 안에서 ps 명령어를 사용하려면 /proc 를 mount 하기위함 

# [터미널1] /proc 파일시스템 마운트
echo $$

unshare -fp --mount-proc /bin/sh
--------------------------------
# 터미널2 호스트와 비교
echo $$
ps -ef
ps aux


# 내부에서 PID NS 확인 : 아래 터미널2에서 lsns -t pid -p <위 출력된 PID>와 비교
lsns -t pid -p 1

--------------------------------

# [터미널2]
ps -ef
ps aux
ps aux | grep '/bin/sh'
root        6186  0.0  0.0   6192  1792 pts/2    S    15:08   0:00 unshare -fp --mount-proc /bin/sh
root        6187  0.0  0.0   2892  1664 pts/2    S+   15:08   0:00 /bin/sh

# 터미널1 PID NS와 비교
lsns -t pid -p <위 출력된 PID>
lsns -t pid -p 6187

USER 네임스페이스

# 터미널1
unshare -U --map-root-user /bin/sh
-----------------------------
# 내부에서는 여전히 root로 보임
whoami
id

# User 네임스페이스를 호스터(터미널2)와 비교
readlink /proc/$$/ns/user
lsns -p $$

# 아래 동작 확인 후 종료
exit
-----------------------------

# 터미널2
readlink /proc/$$/ns/user
lsns -p $$

## ubuntu 로 실행됨
ps -ef |grep "/bin/sh"
ubuntu      6874    5348  0 15:42 pts/0    00:00:00 /bin/sh

 


목표
  • OverlayFS : 무엇일까? 
    • OverlayFS (오버레이 파일 시스템) 은 2010년에 개발된 유니온 마운트 파일 시스템으로, 2014년에 리눅스 커널 Mainline에 통합되었다.

오버레이 파일 시스템은 "유니언 파일 시스템" 또는 "유니언 마운트"라고도 하며, 두 개의 디렉토리("하위" 디렉토리 및 "상위" 디렉토리)를 사용하여 파일 시스템을 마운트할 수 있습니다.

  • 파일 시스템의 하위 디렉토리  읽기 전용입니다
  • 파일 시스템의 상위 디렉토리는 읽고 쓸 수 있습니다 .

프로세스가 파일을 읽을  , overlayfs 파일 시스템 드라이버는 상위 디렉토리를 보고 거기에 파일이 있으면 읽습니다. 그렇지 않으면 하위 디렉토리를 봅니다.

프로세스가 파일을   , overlayfs는 해당 파일을 상위 디렉토리에 씁니다.

 

 

overlayfs 실습1 

 

# OverlayFS를 활용하여 마운트 하기 전에 각 역할별로 디렉터리를 생성하고, lower dir들을 위해 미리 파일들을 준비해 보자.

mkdir lower1 lower2 upper merge work
echo CloudNeta > lower1/file1
echo CloudNetaKoo > lower2/file2
# 두개의 lower dir을 준비했고, 각 디렉터리에 file1, file2를 생성해 두었다.

mount -t overlay overlay -o lowerdir=lower1/:lower2/,upperdir=upper/,workdir=work/ merge/
# 이제 merge 디렉터리에서 파일들을 수정해보면서 변화들을 관찰해 보자.

df -h
# overlay file system 생성된것 확인 /root/merge

ls -al /root/merge
# file1 file2 
# lowerdir의 file1과 upperdir의 file2가 merge 디렉토리에 위치함

echo koo >> /root/merge/file1
# merge dir과 / upper dir에만 변경점이 저장됨
# 왜 lowerdir 1,2에는 변경점을 저장하지 않는다 ReadOnly 이기 때문

echo Cloudneta >> /root/lower1/file3
tree -L 3 

# lower 디렉토리에 변경은 불가할까? No 변경점은 merge directory에 반영됨
# 왜 이럴까요? 이유를 설명하면 다음과 같습니다. 오버레이 파일시스템은 merge에 마운트 되었고, 
#오버레이 파일시스템 내에서 Lower Dir 에 대한 원본 유지는 보장됩니다. 
#하지만 lower1 디렉터리 자체는 호스트의 루트파일시스템에 속해 있으므로 권한을 가진 유저가 
# 루트파일시스템 상에서 직접 수정을 하면 막을 방법은 없습니다. 
# 그리고 merge/b 파일은 오버레이 파일시스템 상에서 발생한 변경이 아니므로 (Copy-On-Write가 발생하지 않아서) 
# Lower Dir 상의 image1/b를 그대로 출력하게 됩니다.



root@MyServer:~# cat /root/lower1/file3
Cloudneta
Cloudneta
root@MyServer:~# cat /root/merge/file3
Cloudneta
Cloudneta
root@MyServer:~# echo Cloudneta123 >> /root/lower1/file3
root@MyServer:~# cat /root/lower1/file3
Cloudneta
Cloudneta
Cloudneta123
root@MyServer:~# cat /root/merge/file3
Cloudneta
Cloudneta
Cloudneta123



### file3이라는 파일이 중복되는데 해당 내용이 다르면 어떻게 될까?
### 먼저 만들어진 lower1 디렉토리의 file3의 내용을 따른다.

root@MyServer:~# echo Beta >> /root/lower2/file3
root@MyServer:~# tree -L 3
.
├── lower1
│   ├── file1
│   └── file3
├── lower2
│   ├── file2
│   └── file3
├── merge
│   ├── file1
│   ├── file2
│   └── file3
├── upper
│   └── file1
└── work
    └── work

6 directories, 8 files
root@MyServer:~#
root@MyServer:~# cat /root/merge/file3
Cloudneta
Cloudneta
Cloudneta123

overlayfs 실습2

## vi Dockerfile
FROM alpine:3.16.3
RUN echo "Hellow koo2" > /tmp/test1


docker build -t koo2/parseimage:v1 . # 이미지 빌드
docker save -o parseimage.tar koo2/parseimage:v1
mkdir contents
tar xf parseimage.tar -C ./contents

 

root@MyServer:~/contents# cat manifest.json | jq .
[
  {
    "Config": "blobs/sha256/3366851afa805f562cd064234421c84b31c80d488922dabff33febb4f72ddcb5",
    "RepoTags": [
      "koo/parseimage:v1"
    ],
    "Layers": [
      "blobs/sha256/e5e13b0c77cbb769548077189c3da2f0a764ceca06af49d8d558e759f5c232bd",
      "blobs/sha256/f1802d109de9032b9370755b6b191e3e2a74262c8a491db42f571feaf6b6e386"
    ],
    "LayerSources": {
      "sha256:e5e13b0c77cbb769548077189c3da2f0a764ceca06af49d8d558e759f5c232bd": {
        "mediaType": "application/vnd.oci.image.layer.v1.tar",
        "size": 5827584,
        "digest": "sha256:e5e13b0c77cbb769548077189c3da2f0a764ceca06af49d8d558e759f5c232bd"
      },
      "sha256:f1802d109de9032b9370755b6b191e3e2a74262c8a491db42f571feaf6b6e386": {
        "mediaType": "application/vnd.oci.image.layer.v1.tar",
        "size": 3072,
        "digest": "sha256:f1802d109de9032b9370755b6b191e3e2a74262c8a491db42f571feaf6b6e386"
      }
    }
  }
]

  • manifest.json : 최상위 이미지에 관한 정보
  • sha256:e5e13b0c77{} : 각각의 Layer에 대한 정보
  • layer디렉토리 : 각각의 디렉토리들이 레이어를 의미한다.
    • VERSION : json file에 관한 스키마 버전
    • json : 레이어에 관한 메타데이터
    • layer.tar : 파일시스템 변경사항이 반영된 tar파일. 실질적인 layer. - overlayFS 에선 그랬었다
기존에는 각 layer 디렉토리 아래 경로에 각 레이어에 대한 image.tar 파일을 위치 시켰는데 
Overlayfs2 registry 부터는 blobs/sha256/아래의 폴더에는 file만 위치시키고 해당 파일명을 아래 디렉토리에서 찾아서 비교해 보면  이미지 tar 파일을 찾을수 있다.

이는 Blob 디렉토리의 주소 지정 방식으로 관리하는것인데 
: 이 용어는 Docker와 같은 컨테이너화 기술에서 이미지 레이어를 효율적으로 저장하기 위한 방식입니다. Docker에서 이미지 레이어는 블롭(blob) 형태로 저장되며, 이는 내용 주소 지정(content-addressable) 방식으로 관리됩니다. 즉, 파일 시스템 레이어가 SHA256 해시를 기반으로 한 고유한 Blob으로 저장되고, 여러 이미지에서 동일한 레이어를 사용할 경우 중복 저장을 방지합니다.

root@MyServer:~/contents# tree /var/lib/docker/image/overlay2/layerdb/sha256 -L 3
/var/lib/docker/image/overlay2/layerdb/sha256

├── e5e13b0c77cbb769548077189c3da2f0a764ceca06af49d8d558e759f5c232bd
│   ├── cache-id
│   ├── diff
│   ├── size
│   └── tar-split.json.gz

레이어 db란? 

 

도커에서 레이어를 관리하기 위해 사용하는 id 체계를 알아보겠습니다.

 

레이어 DB

도커는 다운로드한 이미지를 로컬에 저장하고 관리하기 위해 "레이어 DB"를 사용합니다.

레이어 DB는 [그림 ]와 같이 {도커루트}/image/overlay2/layerdb에 있습니다.

 

도커는 레이어 DB에 레이어들이 저장된 로컬 경로 정보(cache-id)를 기록하고,  컨테이너 기동 시 레이어 DB를 참고하여 해당 이미지의 레이어들을 정해진 순서대로 쌓아서 오버레이 마운트하게 됩니다. 레이어 DB가 관리하는 주요 정보는 다음과 같습니다.

 

 


목표:

Cgroup : 무엇일까? 

  • Control groups : 프로세스들의 자원의 사용(CPU, 메모리, 디스크 입출력, 네트워크 등)을 제한 / 격리 (리눅스 커널 기능) 
#
mount -t cgroup
mount -t cgroup2
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)

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

# cgroup2 이외에 proc, bpf 도 있음
findmnt -A
TARGET                         SOURCE           FSTYPE      OPTIONS
/                              /dev/nvme0n1p1   ext4        rw,relatime,discard,errors=remount-ro
...
├─/proc                        proc             proc        rw,nosuid,nodev,noexec,relatime
  ...
├─/sys                         sysfs            sysfs       rw,nosuid,nodev,noexec,relatime
│ ├─/sys/kernel/security       securityfs       securityfs  rw,nosuid,nodev,noexec,relatime
│ ├─/sys/fs/cgroup             cgroup2          cgroup2     rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot
│ ├─/sys/fs/pstore             pstore           pstore      rw,nosuid,nodev,noexec,relatime
│ ├─/sys/firmware/efi/efivars  efivarfs         efivarfs    rw,nosuid,nodev,noexec,relatime
│ ├─/sys/fs/bpf                bpf              bpf         rw,nosuid,nodev,noexec,relatime,mode=700
...

# cgroupv1 만 지원 시, cgroup2 출력되지 않음
grep cgroup /proc/filesystems
nodev	cgroup
nodev	cgroup2

stat -fc %T /sys/fs/cgroup/
cgroup2fs

 
# 터미널2
sleep 100000
 
# /proc에 cgroup 정보 확인
cat /proc/cgroups
cat /proc/$(pgrep sleep)/cgroup
0::/user.slice/user-1000.slice/session-7.scope

tree /proc/$(pgrep sleep) -L 2
...
├── ns
│   ├── cgroup -> cgroup:[4026531835]
│   ├── ipc -> ipc:[4026531839]
│   ├── mnt -> mnt:[4026531841]
│   ├── net -> net:[4026531840]
...

# cgroup 목록 확인
ls /sys/fs/cgroup
cat /sys/fs/cgroup/cgroup.controllers

#
tree /sys/fs/cgroup/ -L 1
tree /sys/fs/cgroup/ -L 2
tree /sys/fs/cgroup/user.slice -L 1
tree /sys/fs/cgroup/user.slice/user-1000.slice -L 1
tree /sys/fs/cgroup/user.slice/user-1000.slice -L 2
# 터미널1,2 관리자로 실습 진행
sudo su -
whoami

# 툴 설치
apt install cgroup-tools stress -y

# 터미널2 : 아래 stress 실행 후 CPU 사용률 확인
htop

# 터미널1에서 실습 진행

# 먼저 부하 발생 확인
stress -c 1

# 디렉터리 이동
cd /sys/fs/cgroup

# 서브 디렉터리 생성 후 확인 확인
mkdir test_cgroup_parent && cd test_cgroup_parent
tree

# 제어 가능 항목 확인
cat cgroup.controllers

# cpu를 subtree이 추가하여 컨트롤 할 수 있도록 설정 : +/-(추가/삭제) 
cat cgroup.subtree_control
echo "+cpu" >> /sys/fs/cgroup/test_cgroup_parent/cgroup.subtree_control

# cpu.max 제한 설정 : 첫 번쨰 값은 허용된 시간(마이크로초) 두 번째 값은 총 기간 길이 > 1/10 실행 설정
echo 100000 1000000 > /sys/fs/cgroup/test_cgroup_parent/cpu.max

# test용 자식 디렉토리를 생성하고, pid를 추가하여 제한을 걸어
mkdir test_cgroup_child && cd test_cgroup_child
echo $$ > /sys/fs/cgroup/test_cgroup_parent/test_cgroup_child/cgroup.procs
cat /sys/fs/cgroup/test_cgroup_parent/test_cgroup_child/cgroup.procs
cat /proc/$$/cgroup

# 부하 발생 확인 : 터미널2에 htop 확인
stress -c 1

# 값 수정
echo 1000000 1000000 > /sys/fs/cgroup/test_cgroup_parent/cpu.max

# 부하 발생 확인 : 터미널2에 htop 확인
stress -c 1

# cgroup 삭제
exit
sudo su -
rmdir /sys/fs/cgroup/test_cgroup_parent/test_cgroup_child
rmdir /sys/fs/cgroup/test_cgroup_parent


# 아래는 cgroup v1 경우
---------------------------
## 1. 제어그룹 생성 : mycgroup
### -a : owner 설정 (control group's file)
### -g : cgroup 설정 <controllers>:<path>
### -g cpu:mycgroup ~ cpu controller 를 사용하고 path 는 mycgroup
cgcreate -a root -g cpu:mycgroup
tree /sys/fs/cgroup/cpu/mycgroup

## 2. 제어그룹 리소스 설정 : CPU 사용률 설정
### cpu 사용률(%CPU)
### cpu.cfs_quota_us / cat cpu.cfs_period_us * 100
### 참고 1000us = 1ms
### cpu사용률(30%)을 설정 (30,000/100,000)x100=30%
cgset -r cpu.cfs_quota_us=30000 mycgroup
cat /sys/fs/cgroup/cpu/mycgroup/cpu.cfs_quota_us

## 3. 제어그룹 프로세스 할당 : stress 실행
cgexec -g cpu:mycgroup stress -c 1