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

 

예를 들면, 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

도커(docker)는 컨테이너를 실행하기 위한 실행 환경(컨테이너 런타임) 및 툴킷이다. 쿠버네티스는 도커 이외의 컨테이너 런타임도 지원하도록 개발되어있다. 

 

쿠버네티스 사용에 앞서 도커 이미지를 빌드하고, 이미지 보관/배포 서버인 도커 레지스트리로 푸시하는 방법을 알아 두어야 한다. 또한, 반드시 알아두면 좋은 지식을 간단히 알아볼 것이다.

 

도커 컨테이너

도커 컨테이너는 도커 이미지를 기반으로 실행되는 프로세스다. 도커 이미지만 있다면 환경의 영향을 받지 않고 다양한 환경에서 컨테이너를 기동시킬 수 있기 때문에 이식성이 높다. 

도커 컨테이너는 가상 머신에 비해 '가볍다', '시작과 중지가 빠르다' 등과 같은 장점이 있다.

그 이유는 가상 머신은 하이퍼바이저를 이용하여 게스트 OS를 동작시키지만, 도커 컨테이너는 호스트 머신의 커널을 이용하면서 네임스페이스 분리와 cgroups를 이용한 제어를 통해 독립적인 OS와 같은 환경을 만들 수 있기 때문이다. 

 

https://www.docker.com/blog/containers-replacing-virtual-machines/

 

도커 컨테이너 설계

도커 컨테이너를 생성할 때 주로 주의해야할 점은 아래와 같다.

- 1 컨테이너당 1 프로세스

여러 프로세스를 기동하도록 만들면 주변 에코시스템과 맞지 않거나 관리가 힘들어진다.

- 변경 불가능한 인프라 이미지로 생성

컨테이너 이미지안에 애플리케이션 실행 바이너리나 관련 리소스를 가능한 한 포함시켜 컨테이너 이미지를 변경불가능한 상태로 만든다.

- 경량의 도커 이미지로 생성

경량 배포판으로는 알파인 리눅스(Alpine Linux)나 Distroless가 있다.

- 실행 계정은 root 이외의 사용자로 한다.

 

도커 파일 작성법

# Alpine 3.11 버전 golang 1.14.1 이미지를 기반 이미지로 사용
From golang:1.14.1-alpine3.11

# 빌드할 머신에 있는 main.go 파일을 컨테이너에 복사
COPY ./main.go ./

# 빌드 시 컨테이너 내부에서 명령어 실행
RUN go build -o ./go-app ./main.go

# 실행 계정을 nobody로 지정
USER nobody

# 컨테이너가 기동할 때 실행할 명령어 정의
ENTRYPOINT ["./go-app"]

도커 파일에서 사용할 수 있는 명령

명령 요약
FROM 기반 이미지를 지정
MAINTAINER 컨테이너 이미지 관리자 정보를 기입(현재는 비추천, 아래 LABEL 명령을 사용함)
LABEL 컨테이너 이미지의 메타데이터를 키:밸류 형식으로 지정(예: LABEL maintainer="XX<xxx@xxx.com>")
USER 명령어 실행 계정 지정
WORKDIR 명령어를 실행할 작업 디렉터리 지정(디렉터리가 없을 경우 생성)
EXPOSE 컨테이너 실행 시 Listen할 포트 지정
COPY 로컬에 있는 파일을 컨테이너로 복사
ADD 로컬에 있는 tar.gz 파일의 압축을 풀고 파일을 컨테이너로 복사
RUN 컨테이너 안에서 명령을 실행
ENTRYPOINT 컨테이너 기동 시에 실행할 명령어
CMD 컨테이너 기동 시에 실행할 명령어 인수

 

ENTRYPOINT와 CMD의 관계

ENTRYPOINT CMD 실행되는 명령어
"/bin/ls" "-a" /bin/ls -a
지정안함 /bin/ls /bin/ls
"/bin/sh", "-c" "ls -a" /bin/sh -c "ls -a"

ex) docker run ubuntu-sleeper 10 -> 10초간 sleep

FROM ubuntu
ENTRYPOINT ["sleep"]


ex) docker run --entrypoint sleep ubuntu-sleeper 10

--entrypoint : it will override entrypoint

FROM ubuntu
ENTRYPOINT ["sleep"]
CMD["5"] #파라미터가 없을 때 default 값을 지정하고자 할 경우, 파라미터 전달시 overrided 됨.

도커 실행시  Environment 전달

docker run -e APP_COLOR=pink simple-webapp-color

 

도커 이미지 빌드 및 확인

docker image build -t sample-image:0.1 (-f DockerFile) .

docker images

 

멀티 스테이지 빌드

기동용 컨테이너에 불필요한 소프트웨어를 설치하지 않는 것은 보안 측면에서도 좋을 뿐만아니라 이미지 사이즈를 줄일 수 있다. 이를 위해 멀티 스테이즈 빌드를 활용하자.

# Stage 1 컨테이너(애플리케이션 컴파일)
FROM golang:1.14.1-alpine3.11 as builder
COPY ./main.go ./
RUN go build -o /go-app ./main.go

# Stage 2 컨테이너(컴파일한 바이너리를 포함한 실행용 컨테이너 생성)
FROM alpine:3.11
# Stage 1에서 컴파일한 결과물 복사
COPY --from=builder /go-app .
ENTRYPOINT ["./go-app"]
# Stage 1 컨테이너(애플리케이션 컴파일)
FROM golang:1.14.1-alpine3.11 as builder
COPY ./main.go ./
RUN CGO_ENABLED=0 go build -o /go-app ./main.go

# Stage 2 컨테이너(컴파일한 바이너리를 포함한 실행용 컨테이너 생성)
FROM scratch
# Stage 1에서 컴파일한 결과물 복사
COPY --from=builder /go-app .
ENTRYPOINT ["./go-app"]

예제는 2단계 구성이지만 3단계 이상의 구성도 가능하다!

 

alpine 이미지 대신 scratch 이미지를 기반으로 사용하면 이미지 사이즈를 더 줄일 수 있는 것을 확인할 수 있다.

(base) jds@jdsui-MacBookPro chapter01 % docker images

REPOSITORY                           TAG                                                     IMAGE ID       CREATED          SIZE

smaple-image                         0.3                                                     a96b26cc1aa5   16 seconds ago   7.41MB

smaple-image                         0.2                                                     72e55cd3def7   46 seconds ago   13.1MB

 

도커 허브에 이미지 푸시

docker login

option 1) docker image build -t DOCKERHUB_USER/sample-image:0.x .

option 2)docker image tag sample-image:0.1 DOCKERHUB_USER/sample-image:0.x

docker image push DOCKERHUB_USER/sample-image:0.x

docker logout

 

컨테이너 기동

docker container run -d -p 1234:8080 sample-image:0.1

(base) jds@jdsui-MacBookPro chapter01 % docker container run -d -p 1234:8080 smaple-image:0.2
7edb8b08e3c4f6385ec814b9157063bbd0fea3da8baed6b61e8c7ee85afee769

(base) jds@jdsui-MacBookPro chapter01 % curl http://localhost:1234
Hello, Kubernetes% 

 


Docker Storage

Docker Storage drivers

도커 엔진은 OS에 따라서 적절한 storage driver를 선택한다.

- AUFS, ZFS, BTRFS, Device Mapper, Overlay, Overlay2

 

 

Docker volume drivers

- local, Azure File Storage, Convoy, AWS EBS, ...

$ docker run -it \
      --name mysql
      --volume-driver rexray/ebs
      --mount src=ebs-vol,target=/var/lib/mysql
      mysql

 

 

도커 파일 시스템

/var/lib/docker# ls
builder   containers  network   plugins   swarm  trust
buildkit  image       overlay2  runtimes  tmp    volumes

 

Docker Layer

- Image Layers

$ docker build -t test/my-app:0.0.1

- Container Layers

$ docker run test/my-app

출처 : https://docs.docker.com/storage/storagedriver/

Docker Volumes & Usage

- volume mount : 볼륨을 생성하지 않은 상태에서 도커 run 명령을 사용해서 볼륨지정을 했다면 자동생성된다.

$ docker volume create data_volume
$ docker run -v data_volume:/var/lib/mysql mysql

- bind mount : any location on the docker host

$ docker run -v ./current-dir:/var/lib/mysql mysql

출처 : https://docs.docker.com/storage/tmpfs/

참고) Container Interface

- CRI(Container Runtime Interface)

- CNI(Container Network Interface)

- CSI(Container Storage Interface)

 

 

다음 포스팅에서 도커 namespace와 cgroup에 대해서 살펴보도록 하자.
https://espossible.tistory.com/19

 


출처

- https://www.docker.com/blog/containers-replacing-virtual-machines/

- [책]쿠버네티스완벽가이드 마사야 아오야마 지음, 박상욱 옮김(길벗)

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

Kubernetes TroubleShooting  (0) 2021.09.11
docker 리소스 제한  (0) 2021.09.05
Kubernetes Pod Scheduling  (0) 2021.08.13
Kubernetes Workloads  (0) 2021.08.08
Kubernetes JSONPATH 사용법  (0) 2021.08.08

+ Recent posts