도커 컨테이너를 실행할 때 리소스에 제한을 걸 수 있다.

 

예를 들면, redis 컨테이너의 메모리 사용을 제한해보도록 하자.

sudo docker run --name my-redis -d -p 6379:6379 --memory="1g" --memory-swap="1g" --restart=always redis

 

--memory : The maximum amount of memory the container can use. If you set this option, the minimum allowed value is 4m (4 megabyte).

-- memory-swap : The amount of memory this container is allowed to swap to disk

 

--memory-swap details

--memory-swap is a modifier flag that only has meaning if --memory is also set. Using swap allows the container to write excess memory requirements to disk when the container has exhausted all the RAM that is available to it. There is a performance penalty for applications that swap memory to disk often.

Its setting can have complicated effects:

  • If --memory-swap is set to a positive integer, then both --memory and --memory-swap must be set. --memory-swap represents the total amount of memory and swap that can be used, and --memory controls the amount used by non-swap memory. So if --memory="300m" and --memory-swap="1g", the container can use 300m of memory and 700m (1g - 300m) swap.
  • If --memory-swap is set to 0, the setting is ignored, and the value is treated as unset.
  • If --memory-swap is set to the same value as --memory, and --memory is set to a positive integer, the container does not have access to swap. See Prevent a container from using swap.
  • If --memory-swap is unset, and --memory is set, the container can use as much swap as the --memory setting, if the host container has swap memory configured. For instance, if --memory="300m" and --memory-swap is not set, the container can use 600m in total of memory and swap.
  • If --memory-swap is explicitly set to -1, the container is allowed to use unlimited swap, up to the amount available on the host system.
  • Inside the container, tools like free report the host’s available swap, not what’s available inside the container. Don’t rely on the output of free or similar tools to determine whether swap is present.

 

어떻게 컨테이너 마다 독립된 환경으로 리소스를 제한할 수 있는 것일까?

 

먼저 도커 구조를 살펴보고 이해하는데 도움을 얻자.

도커 구조

https://betterprogramming.pub/docker-for-front-end-developers-c758a44e622f

- 도커는 containerd를 활용하여 컨테이너를 생성, 관리하는 서비스로 docker.sock을 통해 통신한다.

- 도커 서비스는 docker cli를 docker.sock을 통해 받아서 containerd로 요청을 보내주는 역할을 한다.

 

아래 명령어를 통해 --containerd를 명시하고 docker.sock을 통해서 API를 리스닝 하는 것을 살펴볼 수 있다.

$ sudo service docker status
● docker.service - Docker Application Container Engine
   Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
   Active: active (running) since Mon 2021-07-26 07:43:57 UTC; 1 months 10 days ago
     Docs: https://docs.docker.com
 Main PID: 1314 (dockerd)
    Tasks: 89
   CGroup: /system.slice/docker.service
           ├─ 1314 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
$ journalctl -xu docker.service
-- Logs begin at Mon 2021-07-26 05:40:51 UTC, end at Sun 2021-09-05 06:52:46 UTC. --
Jul 26 06:32:36 xxx systemd[1]: Starting Docker Application Container Engine...
-- Subject: Unit docker.service has begun start-up
-- Defined-By: systemd
-- Support: http://www.ubuntu.com/support
-- 
-- Unit docker.service has begun starting up.
...
Jul 26 06:32:37 xx dockerd[3815]: time="2021-07-26T06:32:37.322270147Z" level=info msg="API listen on /var/run/docker.sock"
Jul 26 06:32:37 xx systemd[1]: Started Docker Application Container Engine.

 

결론적으로 containerd는 docker로 부터 요청을 받아서 runc를 통해 컨테이너(프로세스)를 생성하는 구조가 된다.

$ sudo service containerd status
● containerd.service - containerd container runtime
   Loaded: loaded (/lib/systemd/system/containerd.service; enabled; vendor preset: enabled)
   Active: active (running) since Mon 2021-07-26 07:43:55 UTC; 1 months 10 days ago
     Docs: https://containerd.io
 Main PID: 1153 (containerd)
    Tasks: 115
   CGroup: /system.slice/containerd.service

하지만 containerd가 여러가지 이유로 재시작될 수 있는데 직접 runc와 소통을 하게되면 runc 프로세스까지 모두 재시작 될 수 있기 때문에 shim을 중간 매개체로 넣어서 관리하게된다. 즉, shim은 컨테이너 runtime과 containerd 사이에서 소통을 담당하고, runtime이 독립적으로 돌 수 있도록 한다.

 

실습을 위해 메모리 제한을 주고 redis container 실행해보자

sudo docker run -it --name test-redis -d -p 6399:6399 --memory="1g" --memory-swap="1g" --restart=always redis /bin/bash

이미지는 redis를 사용하였고, init 커맨드로 /bin/bash를 사용했다. 즉, 해당 컨테이너 내부로 들어가면 /bin/bash가 init 프로세스가 되는 것이다. 

 

생성 프로세스(/bin/bash)의 부모 프로세스가 containerd-shim(25228)임을 확인할 수 있다.

$ ps -ef | grep container
root     25220  1314  0 06:40 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 6399 -container-ip 172.17.0.5 -container-port 6399
root     25228  1153  0 06:40 ?        00:00:00 containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/2035a84b0f9540774fa05c0e8c920c398fe9ddeefd1e54389dceb36fd9e90424 -address /run/containerd/containerd.sock -c


$ ps -ef | grep /bin/bash
root     25254 25228  0 06:40 pts/0    00:00:00 /bin/bash
...

 


도커는 독립된 환경과 리소스 제한을 위해 namespace와 cgroup을 활용한다.

cgroup은 가용할 수 있는 자원의 제한을 설정하는 것이고, namespace는 볼 수 있는 자원의 범위를 의미한다.

 

Namespace

리눅스에서는 프로세스별로 독립적인 공간을 제공하고 서로가 충돌하지 않도록 하는 기능을 제공한다. 

mnt 홈스트 파일시스템에 구애받지 않고 독립적으로 파일시스템을 마운트하거나 언마운트 가능
pid 독립적인 프로세스 공간을 할당
net namespace간에 네트워크 충돌방지
ipc 프로세스간의 독립적인 통신통로 할당
uts 독립적인 hostname 할당
user 독립적인 사용자 할당

 

컨테이너는 네임스페이스가 분리되어 있는 독립적인 프로세스이고, 실제 아래 명령어로 확인가능하다.

$ sudo ls -al /proc/25254/ns
total 0
dr-x--x--x 2 root root 0 Sep  5 06:40 .
dr-xr-xr-x 9 root root 0 Sep  5 06:40 ..
lrwxrwxrwx 1 root root 0 Sep  5 07:17 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Sep  5 07:17 ipc -> 'ipc:[4026532883]'
lrwxrwxrwx 1 root root 0 Sep  5 07:17 mnt -> 'mnt:[4026532881]'
lrwxrwxrwx 1 root root 0 Sep  5 06:40 net -> 'net:[4026532886]'
lrwxrwxrwx 1 root root 0 Sep  5 07:17 pid -> 'pid:[4026532884]'
lrwxrwxrwx 1 root root 0 Sep  5 07:17 pid_for_children -> 'pid:[4026532884]'
lrwxrwxrwx 1 root root 0 Sep  5 07:17 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Sep  5 07:17 uts -> 'uts:[4026532882]'

 

Cgroup

cgroup은 자원에 대한 제어를 가능하게 해주는 리눅스 커널의 기능이다. 다음의 리소스를 제어할 수 있다.

- memory

- cpu

- I/O

- network

- device 노드

 

컨테이너를 띄우면 각각 고유한 컨테이너 ID를 부여받는다. runc는 이 ID를 기반으로 cgroup내의 디렉토리를 구성한다.

(/sys/fs/cgroup/memory/docker/container ID)

 

$ sudo cat /proc/25254/cgroup 
12:rdma:/
11:memory:/docker/2035a84b0f9540774fa05c0e8c920c398fe9ddeefd1e54389dceb36fd9e90424
10:hugetlb:/docker/2035a84b0f9540774fa05c0e8c920c398fe9ddeefd1e54389dceb36fd9e90424
9:perf_event:/docker/2035a84b0f9540774fa05c0e8c920c398fe9ddeefd1e54389dceb36fd9e90424
8:cpu,cpuacct:/docker/2035a84b0f9540774fa05c0e8c920c398fe9ddeefd1e54389dceb36fd9e90424
7:devices:/docker/2035a84b0f9540774fa05c0e8c920c398fe9ddeefd1e54389dceb36fd9e90424
6:pids:/docker/2035a84b0f9540774fa05c0e8c920c398fe9ddeefd1e54389dceb36fd9e90424
5:freezer:/docker/2035a84b0f9540774fa05c0e8c920c398fe9ddeefd1e54389dceb36fd9e90424
4:cpuset:/docker/2035a84b0f9540774fa05c0e8c920c398fe9ddeefd1e54389dceb36fd9e90424
3:net_cls,net_prio:/docker/2035a84b0f9540774fa05c0e8c920c398fe9ddeefd1e54389dceb36fd9e90424
2:blkio:/docker/2035a84b0f9540774fa05c0e8c920c398fe9ddeefd1e54389dceb36fd9e90424
1:name=systemd:/docker/2035a84b0f9540774fa05c0e8c920c398fe9ddeefd1e54389dceb36fd9e90424
0::/system.slice/containerd.service

 

이제 redis에 메모리 제한을 주어 cgroup내에 디렉토리가 어떻게 구성되는지 살펴보자.

- memory: 1G

- memory-swap: 0

$ sudo docker run -it --name test-redis -d -p 6399:6399 --memory="1g" --memory-swappiness 0 --restart=always redis /bin/bash
bf5560cc4fc5975594016f028ca07ec435202cb276ca2f7f2f95f4f13346d5c1
$ cat /sys/fs/cgroup/memory/docker/bf5560cc4fc5975594016f028ca07ec435202cb276ca2f7f2f95f4f13346d5c1/memory.limit_in_bytes 
1073741824

$ cat /sys/fs/cgroup/memory/docker/bf5560cc4fc5975594016f028ca07ec435202cb276ca2f7f2f95f4f13346d5c1/memory.swappiness 
0

이번에는 

- memory : 100M

- memory-swap: 10

$ sudo docker run -it --name test-redis -d -p 6399:6399 --memory="200M" --memory-swappiness 10 --restart=always redis /bin/bash
2035a84b0f9540774fa05c0e8c920c398fe9ddeefd1e54389dceb36fd9e90424
$ cat /sys/fs/cgroup/memory/docker/2035a84b0f9540774fa05c0e8c920c398fe9ddeefd1e54389dceb36fd9e90424/memory.limit_in_bytes 
209715200
$ cat /sys/fs/cgroup/memory/docker/2035a84b0f9540774fa05c0e8c920c398fe9ddeefd1e54389dceb36fd9e90424/memory.swappiness 
10

 

docker -> (docker.sock) -> containerd -> (shim) -> runc을 통해 cgroup을 만들고 값을 넣어주어서 메모리 제한이 적용되는 것을 확인했다.

 

 

ps aux | grep docker 명령으로 확인해보면

- docker

- docker-containerd

- docker-containred-shim

- docker-runc

 

4개의 프로세스가 돌아가는 것을 확인할 수 있다.


참고

# 커널 swap 지원 불가

WARNING: Your kernel does not support swap limit capabilities, memory limited without swap.

# /etc/default/grub
...
...GRUB_CMDLINE_LINUX_DEFAULT="cgroup_enable=memory swapaccount=1"
# update-grub 
# reboot

 

 

 


출처

- https://tech.ssut.me/what-even-is-a-container/

- https://betterprogramming.pub/docker-for-front-end-developers-c758a44e622f

- https://tech.kakao.com/2020/06/29/cgroup-driver/

- https://brunch.co.kr/@jehovah/35

- https://docs.docker.com/config/containers/resource_constraints/#--memory-swap-details

'클라우드 컴퓨팅 > 쿠버네티스' 카테고리의 다른 글

CKA(Certified Kubernetes Administrator) 시험 비중  (0) 2021.09.11
Kubernetes TroubleShooting  (0) 2021.09.11
docker 기본  (0) 2021.09.05
Kubernetes Pod Scheduling  (0) 2021.08.13
Kubernetes Workloads  (0) 2021.08.08

+ Recent posts