[Docker] 컨테이너의 기초와 운영

2023. 4. 16. 20:20Developers 공간 [Basic]/Backend

728x90
반응형

로컬에서 환경 셋팅을 할때 라이브러리 셋팅 혹은 배포할 때 Provisioning 및 Orchestration을 위해서 다양한 분야에서 docker를 활용합니다.

 

Docker는 "Container"라는 그릇에 필요한 구성 파일, 라이브러리 및 dependency들을 하나의 실행 유닛으로 패키징하여, 환경에 구애받지 않고 실행, 배포 및 확장할 수 있도록 돕는 오픈 소스 소프트웨어 Platform입니다.

 

최근에는 AWS, Google Cloud 등 다양한 클라우드 기반의 서비스에서 VM대신 Container를 활용해 서비스하기도 하고, 로컬에서 작업할 때도 느려터진 VM보다는 Container기반의 작업환경을 구축하는 분들이 많은 것 같습니다.

 

이번 챕터에서는 Docker를 살펴보고 다양한 개념에 대해 정리하고자 합니다.

<구성>
1. 로컬에서 작업하기 
    a. Virtualization, Container, Image
    b. Local에서 환경 구축하기
    c. 추가적인 Docker의 기능
2. 효율적인 이미지 관리
    a. 필자의 환경 설정 루틴
    b. 이미지 build 하기
    c. docker-compose
3. 효율적인 Instance 관리
    a. swarm 모드와 docker stack
    b. node 확장하기 

글효과 분류1 : 코드

글효과 분류2 : 폴더/파일

글효과 분류3 : 용어설명

글효과 분류4 : 글 내 참조


1. 로컬에서 작업하기

앞서 언급한 바와 같이, Docker를 활용하는 분야는 굉장히 다양합니다. 이번 챕터에서는 먼저 Docker의 개념에 대해 살펴보면서 단순히 로컬에서 다양한 라이브러리 및 dependency들을 관리하기 위해 어떻게 활용할 수 있는지를 살펴보고자 합니다.


a. Virtualization, Container, Image

서버 가상화(Virtualization)이란 물리적인 서버 환경의 활용 저하 및 공간 제약 등의 단점을 보완하기 위한 방법으로, 아래와 같이 다양한 Virtualization 방법이 있습니다. 서버 가상화 이외에도 보안에 유리한 VDI(Virtual Desktop Virtualization, 데스크톱 가상화)와 배포에 유리한 Application Vitualization이 있습니다.

먼저 Type1 Virtualization은 하드웨어 바로 위에서 Hypervisor가 다수의 VM을 관장하는 형태이며, Type2 Virtualization은 하드웨어 위에 Host OS가 있고, 그 위에 Hypervisor가 응용프로그램과 같이 VM을 관장하는 형태입니다. 종류는 아래와 같습니다.

** Hypervisor : VMM(Virtual Machine Monitor)라고도 불리며, 물리적인 서버(CPU, Memory, Storage, Network, Graphic)와 가상서버 사이를 중재하며 통신하는 소프트웨어 입니다. 즉, 가상서버 OS가 보낼 다양한 종류의 명령어들을 내 하드웨어에 맞게 번역해 전달하며, 다양한 자원 운용을 하는 역할입니다.

  • Type1 : Citrix Xen, VMWare ESXi, Microsoft Hyper-V, KVM
  • Type2 : Oracle VirtualBox, VMWare Workstation, VirtualPC, 

Type1은 또 아래와 같이 가상화하는 방법에 대해 전가상화(Full Virtualization)반가상화(Para-Virtualization)으로 나뉩니다. 전가상화는 하드웨어에 명령하기 위한 모든 역할을 Hypervisor를 통해 진행하지만, 반가상화는 Guest OS를 수정해 Hypervisor에게 조금 더 단순하게 명령어를 전달할 수 있습니다. 종류는 아래와 같으며, 이외에도 OS-level 가상화라는 같은 OS의 가상서버를 올리는 방법도 있지만, 다루지 않겠습니다.

  • 전가상화 : VMWare ESX, VMWare VMWare, Microsoft Hyper-V
  • 반가상화 : Citrix Xen, 

위와 같은 VM(Virtual Machine) 기반 방법들은 Guest OS가 필요하므로, 자원 운용이 어렵고 오버헤드가 큰 단점이 있습니다. 따라서 Hypervisor 기반이 아닌 hostOS 자체를 활용하는 Container Virtualization이 등장하게 됩니다. Container란 OS없이 Host의 자원을 그대로 사용하는 Application으로, 서버 운영을 위한 라이브러리만 "이미지"에 담아 설치하므로 경량화되어 빠르고, 직관적입니다. 즉, Image는 하나의 Object 선언이고, Container는 하나의 Instance라고 생각하시면 됩니다.

 

이런 Container 기반의 가상화를 지원하게 해주는 다양한 기능은 다음과 같습니다. 먼저, Docker는 Overlay Network를 통해 물리적으로 연결하지 않고 Container끼리 통신이 가능하게 하며, 이미지를 형성할 때 Union File System을 통해 레이어 방식으로 여러개의 파일 시스템(Read-only Image Layer + Writable Container Layer)을 하나의 파일 시스템 형태로 적재할 수 있습니다. 이렇게 실행된 Container Instance들을 쉽게 Clustering 하고 Orchestration할 수 있습니다.

** Orchestration : 여러개의 서버와 여러개의 서비스를 편리 하게 관리해주는 작업으로, Clustering, Scheduling, Service Discovery, Logging, Monitoring 같은 작업을 통칭합니다.

** Clustering : 여러개의 서버를 하나의 서버처럼 사용하는 방식입니다.

** Service Discovery : Service가 어떤 서버에서 돌아가고 있는지를 관리하는 방식입니다.

 

[다양한 Virtualization]

 

이런 컨테이너라는 개념으로 처음 등장한 것이 Linux에서의 Cgroups(Control Groups)와 Namespace를 활용하는 LXC(Linux Container)입니다. 이외에도 FreeBSB의 Jail, Solaris의 Solaris Zones, Google의 LMCTFY라는 기술이 있습니다. 이 중에 LXC를 기반으로 2013년 시작한 것이 Docker입니다. 이후에는 자체적인 libcontainer기술을 사용하고, 또 이후에는 runC기술에 합쳐졌습니다. 

** libcontainer(native) : 기존의 LXC에 이어서 특정 OS외 다양한 리눅스들을 지원가능하도록 Docker에서 개발한 라이브러리입니다.

** runC : OCI Runtime 표준을 준수하는 컨테이너를 생성 & 실행하기 위한 Low-level CLI Tool 입니다. 실제로 컨테이너를 생성하고 종료하는 역할을 합니다.

** OCI(Open Container Initiative) : 컨테이너 기술의 발전으로, Container Engine의 대표격인 Docker 이외에도 RKT나 HyperContainer등 다양한 컨테이너 엔진이 등장했습니다. 이에 대한 표준 Interface가 OCI입니다.

 

아래 그림은 위에서 언급한 runC를 포함해 Docker CLI까지의 Stack입니다.

  • Docker-CLI : docker engine 혹은 dockerd라는 docker engine을 거쳐 containerd로 가기 위한 User Interface 입니다.
  • containerd : Docker에서 개발한 Container Runtime으로, 컨테이너를 탑재하고 운영체제 kernel과 함께 작동하는 Docker Engine의 구성요소 입니다. 비슷한 Container Runtime으로 Kubernetes에서 만든 CRI-O가 있습니다

[기본적인 Docker를 동작하기 위한 Stack + Kubernetes]

** CRI(Container Runtime Interface) : containers 집합인 Pods 를 다루기 위한 플러그인 인터페이스 입니다. Kubernetes에서 소개할 내용으로 kubelet을 통해 container를 생성하고 삭제하는 방식으로 동작합니다.

** cri-containerd : 위의가 CRI가 containerd와 동작하기 위한 중간 Daemon입니다.


b. Local에서 환경 구축하기

먼저 Docker를 설치해보려고 합니다. 아래와 같은 명령어로 간단하게 docker를 설치할 수 있습니다.

아래 STEP4와 STEP5는 nvidia-docker를 설치하는 방법인데, docker 내부에서 GPU와 CUDA셋팅을 유용하게 활용하기 위해 필자는 nvidia-docker라는 wrapper를 설치하여 활용합니다. (https://github.com/NVIDIA/nvidia-docker)

# STEP1. Install Docker
curl -fsSL https://get.docker.com/ | sudo sh

# STEP2. Give Authority to user
sudo usermod -aG docker $USER # 현재 접속중인 사용자에게 권한주기

# STEP3. Check installed docker
docker version

# STEP4. install nvidia-docker
sudo apt-get install -y nvidia-docker2

# STEP5. restart docker
sudo systemctl restart docker

 

Docker를 설치하고 나서 Container를 만드는 코드는 아래와 같습니다. 이미지는 Container Image가 등록되어 있는 Repository인 Dockerhub(https://hub.docker.com/)에서 사용했습니다.

  • new_docker.sh : docker run 명령어를 활용해 
    1. --privileged : 컨테이너 안에서 Host의 리눅스 커널 기능(Capability)을 사용해 자원에 접근할 수 있습니다.
    2. --cap-add=SYS_ADMIN : --privileged와 함께 syscall을 사용하기 위한 명령어입니다.
    3. --security-opt seccomp=tk_default.json : 프로파일링을 위해 --prvileged 혹은 --cap-add 명령어 대신 seccomp security profile 기능을 키는 방법 입니다.
      • Secure computing mode (seccomp) :Linux kernel에서 사용할 수 있는 feature로, application's access를 제한하기 위해 사용됩니다.
      • STEP1. 파일을 다운로드 : https://github.com/moby/moby/blob/master/profiles/seccomp/default.json
      • STEP2. syscalls 아래 다음 행을 추가
        {
           "name": "perf_event_open",
           "action": "SCMP_ACT_ALLOW",
           "args": []
        },
    4. -d  : detatched/daemon 모드로, 컨테이너를 세션에서 실행하는 것이 아니고, background로 실행하기 위해 사용합니다.
    5. --rm  : 프로세스가 종료되면 컨테이너가 제거되도록 합니다. (저는 사용하지 않습니다)
    6. -p 0000:0000  : 포트 포워딩을 위해 사용합니다. 아래는 예시입니다.
      • 6006 : Tenseorboard 
      • 8888 : Jupyter
      • 8080 : Tomcat 등 HTTP를 활용한 서비스, Netron 등
      • 22 : ssh 
    7. -e AAA=ABCD
      --env="AAA=ABCD" : 환경 변수를 셋팅하기 위해 사용합니다.
      • DISPLAY, XAUTHORITY, XDG_RUNTIME_DIR, QT_X11_NO_MITSHM : GUI를 위한 환경변수 입니다.
    8. --net=host : 컨테이너의 네트워크를 host로 설정합니다. (뒤에서 설명)
    9. --workdir : 컨테이너의 Working Directory를 지정해줍니다.
    10. --ipc=host :  공유메모리 세그먼트로 프로세스간 통신을 가속화하는 방법에 대한 설정입니다. default로 private혹은 sharable로 되어있지만 host로 셋팅해주면 호스트 시스템의 IPC Namespace를 활용합니다.
    11. --shm-size=16g : 공유메모리의 크기를 설정해주기 위한 설정입니다. default로는 4MB이지만, 부족하면 프로세스 실행시의 공유메모리가 부족하므로, 내 메모리에 맞게 지정해줍니다.
    12. -v /A/B:/A'/B':rw : host의 volume을 공유하기 위한 명령어입니다. 
      • XFILE/XSOCK/XAUTH : X11을 위한 공유 파일들입니다. 안에서 
    13. --name : 만들어질 컨테이너의 이름입니다.
    14. -it : -i는 표준입출력(stdin)을 활성화하는 것이고 -t는 가상 tty(pesudo tty) 모드를 통해 쉘에 명령어를 작성할 수 있도록 하겠다는 의미입니다. 이것이 있어야 bash를 활용해 접속할 수 있습니다.
  • docker cp /FROM tk_ENV1:/TO : Host의 위치에서 Container내부로 정보를 복사하기 위한 명령어 입니다.
  • xhost +local:root : 아래와 같이 실행 혹은 ~/.xinitrc에 추가해주면 Display권한을 줄 수 있습니다.
#!/bin/bash
USER=user
CONTAINER_NAME=tk_ENV1

XSOCK=/tmp/.X11-unix
XAUTH=/tmp/.docker.xauth
XFILE=/run/user/1000

TK_XAUTH=$XAUTHORITY
TK_DATAPATH=/home/user/Desktop/DATA
TK_DOWNLOAD=/home/user/Downloads
TK_USB=/media/user
TK_WORKSPACE=/home/user/Desktop/CONTAINER_WORKSPACE
DOCKER_HOME=/root

echo "TK XAUTH is " $TK_XAUTH
echo "TK DATAPATH is " $TK_DATAPATH

nvidia-docker run --privileged=true \
    --cap-add=SYS_ADMIN \
    --security-opt seccomp=tk_default.json \
    -d \
    -p 6006:6006 \
    -p 8888:8888 \
    -p 8080:8080 \
    -p 1000:22 \
    -p 45555:1001 \
    -e DISPLAY=$DISPLAY \
    -e XAUTHORITY=$TK_AUTH \
    -e XDG_RUNTIME_DIR=$XFILE \
    --env="QT_X11_NO_MITSHM=1"\
    --net=host \
    --workdir=$DOCKER_HOME \
    --ipc=host \
    --shm-size=16g \
    -v $XFILE:$XFILE:rw \
    -v $XSOCK:$XSOCK:rw \
    -v $TK_XAUTH:$XAUTH:rw \
    -v $TK_DATAPATH:$DOCKER_HOME/DATASET:ro \
    -v $TK_DOWNLOAD:$DOCKER_HOME/DOWNLOAD:ro \
    -v $TK_USB:$DOCKER_HOME/usb:rw \
    -v $TK_WORKSPACE:$DOCKER_HOME/workspace:rw \
    --name $CONTAINER_NAME \
    -it nvidia/cuda:10.2-cudnn8-devel-ubuntu18.04 bash

docker cp ~/.vimrc $CONTAINER_NAME:$DOCKER_HOME
docker cp ~/.vim $CONTAINER_NAME:$DOCKER_HOME
docker cp ~/.tmux.conf $CONTAINER_NAME:$DOCKER_HOME
docker cp ./environment.txt $CONTAINER_NAME:$DOCKER_HOME
sudo xhost +local:root
더보기

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

<필자가 사용하는 ~/.vimrc>

.vimrc파일은 vim을 작업하기 위한 셋팅파일입니다.

set hlsearch
set autoindent
set cindent
set nu "line number
set ts=4 "tab size
set shiftwidth=4 "indent width
set paste
set showmatch
if has("autocmd")
    au BufReadPost *
    \ if line("'\"") > 0 && line("'\"") <= line("$") |
    \ exe "norm g`\"" |
    \ endif
endif

if has("syntax")
syntax on
endif

colorscheme jellybeans

set background=dark
set t_Co=256
set mouse=a "wheel able
set clipboard=unnamed
set smartindent
set tabstop=4
set expandtab
set encoding=utf-8
set fileencoding=utf-8
set fileencodings=utf-8,euc-kr,cp949

** .vim 파일은 아래와 같이 받을 수 있습니다.

# https://github.com/nanotech/jellybeans.vim
mkdir -p ~/.vim/colors
cd ~/.vim/colors
curl -O https://raw.githubusercontent.com/nanotech/jellybeans.vim/master/colors/jellybeans.vim

<필자가 사용한 ~/.tmux.conf>

.tmux.conf는 tmux를 활용하기 위한 셋팅 파일입니다.

# Linux only
set -g mouse on

bind P paste-buffer
bind -T copy-mode-vi v send-keys -X begin-selection
bind -T copy-mode-vi y send-keys -X copy-selection
bind -T copy-mode-vi y send-keys -X copy-pipe-and-cancel 'xclip -sel clip -i'
bind -T copy-mode-vi r send-keys -X rectangle-selection

#map copy mode to ctrl+alt+c
bind-key -n 'C-M-c' copy-mode

#map paste buffer to ctrl+alt+v
bind-key -n 'C-M-v' paste-buffer

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

  • restart_docker.sh : docker restart 명령어를 통해 컨테이너를 재실행하고, docker exec 명령어를 활용해 해당 컨테이너에 명령어를 날려줍니다. 아래 명령어는 bash로 접근하기 위한 명령어입니다.
    1. -e
      • COLUMNS, LINES : 컨테이너에 bash로 접근시 글씨가 덮어씌워지는 현상을 없애기 위해 현재 실행할때의 환경을 넘겨주는 것입니다.
      • TERM : 현재 터미널의 종류에 대한 정보를 넘겨주기 위함입니다.
      • LC_ALL: 컨테이너의 한글 환경을 셋팅해주기 위해서 입니다.
    2. -u : user를 지정해줄 수 있는 옵션입니다.
sudo xhost +local:root

docker restart tk_ENV1

# Start with root user
docker exec -ti -e COLUMNS=$COLUMNS -e LINES=$LINES -e TERM=$TERM -e LC_ALL=C.UTF-8 tk_ENV1 bash

# Start with tkay user (if possible)
docker exec -ti -e COLUMNS=$COLUMNS -e LINES=$LINES -e TERM=$TERM -e LC_ALL=C.UTF-8 -u tkay tk_ENV1 bash
  • start.docker.sh : docker exec 명령어를 활용해 해당 컨테이너에 명령어를 날려줍니다. 아래 명령어는 bash로 접근하기 위한 명령어입니다.
sudo xhost +local:root

docker exec -ti -e COLUMNS=$COLUMNS -e LINES=$LINES -e TERM=$TERM -e LC_ALL=C.UTF-8 tk_ENV1 bash

c. 추가적인 Docker의 기능
  • 컨테이너 다루기 : 실행 중인 컨테이너를 다루기 위한 명령어는 아래와 같습니다
# Rename Container
docker rename A B

# See all containers
docker ps 

# See all containers including not working containers
docker ps -a

# Stop container
docker stop A

# Remove Docker container which is stopped
docker rm A

# See logs of container
docker logs <container명>

# See all images
docker images

# Remove Docker image
docker rmi A
  • Docker 간 통신 : Docker에서 활용할 수 있는 네트워크의 종류는 아래와 같습니다. 이런 네트워크를 Container에 연결 및 생성하는 명령어도 아래를 참조합니다. 컨테이너의 네트워크 모드는 Host 모드와 Bridge 모드로 나뉘는데, 둘 중에 한가지만 가능합니다. (즉, Bridge모드일 때 host 네트워크에 연결이 되지 않습니다.)
    • bridge : Host에서 ifconfig 명령어를 통해 확인할 수 있는 docker0네트워크입니다  같은 bridge에 있는 Container끼리는 통신이 가능하며, docker run 시 설정하지 않으면 default입니다.
    • host : host의 네트워크 환경 그대로 사용하고 싶을 때 사용합니다. 주로 컨테이너가 1개만 존재할 때 사용합니다. "--net=host" 옵션을 활용합니다.
    • none : "--net=none" 옵션을 활용합니다.
    • container : 다른 특정 네트워크의 네트워크를 사용하고 싶을 때 사용하는 옵션입니다. "--net=<컨테이너ID>" 옵션을 활용합니다.
    • overlay : 분산 네트워크에서 사용시에 host가 여러개이면 사용하는 옵션입니다. 모든 machine에서 swarm mode가 활성화되어있어야 합니다.
# See Docker Network list
docker network ls 

# Create Docker Network called "tmp_network"
docker network create –driver=bridge tmp_network

# Check the state of Container including network
docker inspect CONTAINER

# Check the state of Network
docker network inspect tmp_network

# Connect Docker Network to existing Container
docker network connect tmp_network CONTAINER

# Connect Docker Network to new Container
docker run –itd –-name=NEWCONTAINER –net=tmp_network

# Disconnect Docker Network to Container
docker network disconnect tmp_network CONTAINER

# Delete Docker Network
docker network rm tmp_network
  • 컴퓨터가 켜지면 자동으로 docker가 시작되도록 하는 방법1 : docker run 명령어를 실행할 때 -restart always 옵션을 활용합니다. 
docker run -restart always -p 9300:9300 docker.elastic.co/elasticsearch/elasticsearch:6.4.3
  • 컴퓨터가 켜지면 자동으로 docker가 시작되도록 하는 방법2 : /etc/systemd/system/[설정한 서비스].service에 아래 내용을 포함시켜 준 뒤, 아래의 명령어를 실행합니다.
[Unit]
Wants=docker.service
After=docker.service

[Service]
RemainAfterExit=yes
ExecStart=/usr/bin/docker restart [실행 원하는 컨테이너]
ExecStop=/usr/bin/docker stop [실행 원하는 컨테이너]

[Install]
WantedBy=multi-user.target

 

# Start service
systemctl start [설정한 서비스]
systemctl enable [설정한 서비스]

# Check service
ll /etc/systemd/system/multi-user.target.wants/[설정한 서비스]
systemctl list-unit-files | grep [설정한 서비스]

2. 효율적인 이미지 관리

위에서 컨테이너를 만들어낼 때는 앞서 언급한 바와 같이 Dockerhub(https://hub.docker.com/)에 등록된 이미지를 활용해 만들어냈습니다. 하지만 배포를 하거나 내가 원하는 작업환경을 미리 만들어 놓기 위해서, 이미지를 원하는 형태로 찍어둘 필요가 있습니다.

이에 대해서 한번 살펴보고자 합니다.


a. 필자의 환경 설정 루틴

배포를 위해서나 특정 Application을 위해서는 이미지를 만들어 배포하는 것이 당연히 유리합니다. 하지만 필자는 다양한 오픈소스를 접할 기회가 많고, 특정한 오픈소스에 맞게 라이브러리 및 Dependency들을 맞춰주기 위해 환경을 다양하게 바꾸는 상황이 많기 때문에, Custom 이미지를 만들어 놓기 보다는 아래와 같이 environment.txt를 만들어두고 매번 컨테이너의 환경셋팅을 직접해주곤 합니다.

 

사실 어떻게 보면 이부분은 docker와는 무관한 이야기일 수 있습니다. 하지만 환경셋팅을 할 때 어떤 것을 셋팅하는지를 먼저 설명드리기 위해 필자의 루틴을 언급하는 정도라고 이해해 주시면 될 것 같습니다.

 

먼저, 필자는 아래와 같이 Library에 대한 스펙을 먼저 정하는 Top-Down 방식을 활용하기 때문에 Cuda버전을 아래와 같이 정하고 이에 맞는 라이브러리들을 List-up해 적어둡니다. 이렇게 하면 컨테이너를 만들어두고 나서, 어떤 환경에서 작업중인지를 쉽게 알 수 있습니다. 디테일 하게 어떻게 환경셋팅을 하는지에 대해서는 링크(https://tkayyoo.tistory.com/17)에 자세히 설명해두었습니다.

cuda : 10.2
cudnn : 8.1.0.77

python3 : python3.6.9 (/usr/lib/python3.6)
                tensorflow-gpu=2.3.1 [tensorflow 2.1.0<=version is not supported for cuda10.2]
                tensorboard=2.4.0
                opencv-python=4.4.0.46
                matplotlib=3.3.3
                easydict=1.9
                packaging=20.4
                scipy=1.5.4
                onnx=1.8.0
                torch=
                scikit-image
pip3(conda) : 20.2.4

그다음 아래와 같이 필요한 라이브러리와 패키지들을 설치합니다.

# apt-get
apt-get update -y && apt-get install -y vim git feh tmux tzdata python3 python3-pip libsm6 libxrender-dev libgl1-mesa-glx vim-gui-common libgtk2.0-dev cmake libgtk-3-dev libtbb2 libgtk2.0-dev pkg-config libprotobuf-dev protobuf-compiler gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-tools libxine2-dev qt5-default libnss3 libjson-c-dev libjsoncpp-dev build-essential tree wget curl
sudo apt-get install -y openjdk-11-jdk

# python package
pip3 install --upgrade pip
pip3 install tensorflow-gpu==2.3.1 tensorboard_plugin_profile opencv-python matplotlib easydict packaging tensorflow_addons scipy onnx tf2onnx scikit-image --use-feature=2020-resolver
pip3 install torch==1.9.1 torchvision==0.10.1 torchaudio==0.9.1 tensorboard 
pip3 install opencv-python matplotlib easydict packaging scipy onnx scikit-image pyyaml requests tqdm

# downloaded packages with wheel file
pip3 install /root/DOWNLOAD/TensorRT-7.0.0.11/python/tensorrt-7.0.0.11-cp36-none-linux_x86_64.whl
pip3 install /root/DOWNLOAD/TensorRT-7.0.0.11/uff/uff-0.6.5-py2.py3-none-any.whl
pip3 install /root/DOWNLOAD/TensorRT-7.0.0.11/graphsurgeon/graphsurgeon-0.4.1-py2.py3-none-any.whl

# downloaded packages with copy
cp ~/DOWNLOAD/TensorRT-7.0.0.11/bin/trtexec /usr/bin/
cp ~/DOWNLOAD/TensorRT-7.0.0.11/targets/x86_64-linux-gnu/lib/lib* /usr/local/cuda-10.2/targets/x86_64-linux/lib/

 

.bashrc 파일이나 /etc/environment 파일에 환경변수들을 추가해줍니다.

>>.bashrc
alias MOVE='cd /home/user/Desktop/WORKSPACE/path'

>>.bashrc : CUDA-10.2
export LD_LIBRARY_PATH=/usr/local/cuda-10.2/lib64:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/local/cuda-10.2/extras/CUPTI/lib64:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/local/cuda-10.2/targets/x86_64-linux/lib:$LD_LIBRARY_PATH
export PATH=/usr/local/cuda-10.2/bin:$PATH

>>.bashrc : TensorRT -----------must CUDA 10.2, 11.0, 11.1
export LD_LIBRARY_PATH=/root/DOWNLOAD/TensorRT-7.0.0.11:$LD_LIBRARY_PATH

>> /etc/environment
$JAVA_HOME="/usr/lib/jvm/java-11-openjdk-arm64"

CUDA 설치 확인을 아래와 같은 명령어로 하기도 합니다.

/sbin/ldconfig -N -v $(sed 's/:/ /g' <<< $LD_LIBRARY_PATH) | grep libcupti

b. 이미지 build 하기

위와 같이 다양한 환경셋팅이 아닌 공통의 이미지를 통해 배포하기 위해서는 이미지를 만들어 놓는 것이 좋습니다. 그럼 위와 같이 다양한 패키지 설치 및 환경변수 셋팅들을 이미지 빌드 시기에 하는 방법에 대해 알아보고자 합니다.

 

이를 위해서는 Dockerfile이라는 파일을 작성할 필요가 있습니다. 아래는 필자가 작성했던 Dockerfile 예시입니다.

  • Dockerfile
    • FROM : Base이미지를 지정할 때 사용합니다.
    • MAINTAINER : 해당 이미지의 생성,유지,보수 등 관리하는 사람을 명시하기 위해 사용합니다.
    • USER : Dockerfile에서 useradd로 생성된 user들을 지정해줄 때 사용합니다.
    • ENV : 컨테이너 안의 환경변수 셋팅
    • ARG : Dockerfile 셋팅 중에의 임시 환경변수 셋팅
    • WORKDIR : 명령이 실행되는 디렉토리를 지정
    • EXPOSE : 지정된 네트워크 포트에서 수신대기중인 것을 알려주기 위해 사용합니다. (실제로 포트포워딩을 하는 것은 아닙니다)
    • RUN : bash를 활용해 패키지 설치 등에 사용되는데, image layer 자체를 만들어 냅니다.
    • ENTRYPOINT : RUN과 다르게 이미 생성 후에 컨테이너에서 실행되므로, 어떤 명령어를 실행에 주로 사용됩니다. 
    • CMD : RUN과 다르게 이미 생성 후에 컨테이너에서 실행되므로, 어떤 명령어를 실행에 주로 사용됩니다. ENTRYPOINT와 다르게 docker run으로 실행시에 변수를 전달해 기존 변수를 변경할 수 있습니다.
    • ADD : <Url>에서 <Container 경로> 로 다운로드하는 명령어
    • COPY : <Host 경로> 에서 <Container 경로> 로 복사 하는 명령어
FROM nvidia/cuda:11.4.2-cudnn8-devel-ubuntu20.04
MAINTAINER mymail@mymail.com

ENV TZ="Asia/Seoul"
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN rm -rf /etc/apt/trusted.gpg.d/*
ARG DEBIAN_FRONTEND=noninteractive

# STEP1. Install Packages
RUN apt-get update -y && apt-get install -y tzdata
RUN apt-get update -y && apt-get install -y vim git feh tmux python3 python3-pip libsm6 libxrender-dev libgl1-mesa-glx vim-gui-common libgtk2.0-dev cmake libgtk-3-dev libtbb2 libgtk2.0-dev pkg-config libprotobuf-dev protobuf-compiler gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-tools libxine2-dev qt5-default libnss3 libjson-c-dev libjsoncpp-dev net-tools nautilus libboost-all-dev libssl-dev libnvidia-gl-460 build-essential tree wget curl unzip openjdk-11-jdk
RUN apt-get -y install -y openjdk-11-jdk

RUN pip3 install --upgrade pip
RUN pip3 install tensorflow-gpu==2.3.1 tensorboard_plugin_profile opencv-python matplotlib easydict packaging tensorflow_addons scipy onnx tf2onnx scikit-image --use-feature=2020-resolver
RUN pip3 install torch==1.9.1 torchvision==0.10.1 torchaudio==0.9.1 tensorboard
RUN pip3 install opencv-python matplotlib easydict packaging scipy pyyaml requests tqdm onnx==1.8.1 cython onnxruntime submitit pathlib tensorboardX scikit-image pyyaml numba plyfile trimesh pyqt5 vtk
RUN pip3 install mayavi
RUN pip3 install terminaltables shapely joblib scikit-learn torchinfo torchsummary
RUN pip3 install --upgrade --force-reinstall setuptools==58.0
RUN pip3 install -U 'git+https://github.com/cocodataset/cocoapi.git#subdirectory=PythonAPI'

# STEP2. Environmental Variables Setting
#  >> Custom Setting
ENV MOVE='cd /home/user/Desktop/WORKSPACE/path'
RUN echo "alias MOVE=$MOVE" >> ~/.bashrc

#  >> .bashrc : CUDA-11.1
ENV LD_LIBRARY_PATH=/usr/local/cuda-11.1/lib64:$LD_LIBRARY_PATH
ENV LD_LIBRARY_PATH=/usr/local/cuda-11.1/extras/CUPTI/lib64:$LD_LIBRARY_PATH
ENV LD_LIBRARY_PATH=/usr/local/cuda-11.1/compat:$LD_LIBRARY_PATH
ENV LD_LIBRARY_PATH=/usr/local/cuda-11.1/targets/x86_64-linux/lib:$LD_LIBRARY_PATH
ENV PATH=/usr/local/cuda-11.1/bin:$PATH
RUN echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> ~/.bashrc

# >> JAVA
RUN echo "\$JAVA_HOME = /usr/lib/jvm/java-11-openjdk-arm64" >> /etc/environment

# >> PYTHONPATH
ENV PYTHONPATH=/usr/local/lib/python3.8/site-packages:/usr/local/lib/python3.8/dist-packages:$PYTHONPATH
RUN echo "export PYTHONPATH=$PYTHONPATH" >> ~/.bashrc

# STEP3. Copy Custom Project Folders
COPY ./MYFOLDER/ /tmp/TOFOLDER/
WORKDIR /tmp/TOFOLDER
RUN start.sh

EXPOSE 8080
COPY ./project_1/build/libs/demo-0.0.1-SNAPSHOT.jar .
CMD java -jar demo-0.0.1-SNAPSHOT.jar

WORKDIR /

그 다음 Dockerfile이 위치한 곳에서 아래와 같은 명령어를 통해 build하고, Repository에 push할 수도 있습니다.

#!/bin/sh

# Build Docker Image
nvidia-docker build -t my/repo:tag1.0.0 .
docker build -f Dockerfile -t my/repo2 .

# Rename Image
docker tag my/repo:tag1.0.0 my/repo:tag1.0.1
docker tag my/repo2 my/repo2:tag1.0.0

# Push Images to Repository
docker login
docker push my/repo:tag1.0.0
docker push my/repo2

c. docker-compose

여기까지 내가 원하는 이미지를 Build해서 관리하는 것에 대해 살펴보았습니다. 이번엔 yaml 파일에 기반해 이미지를 build하고 바로 배포하는 docker-compose에 대해 설명해보고자 합니다.

먼저 설치하는 방법은 아래 명령어와 같습니다. 버전은 링크(https://github.com/docker/compose/releases)를 참조해서 아래 명령어에서 수정해 실행할 수 있습니다.

sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

다음으로 docker-compose를 실행하는 코드를 설명해보도록 하겠습니다. 먼저, 아래와 같이 프로젝트를 구성했을 때를 예로 들겠습니다.

  • Project
    ├── docker-compose.yml
    └── FOLDER
          ├── Dockerfile
          └── third_party

Dockerfile에 대해서는 앞에서 설명했으므로, docker-compose.yml을 어떻게 적는지 예시를 아래 보이겠습니다. bash를 활용해 접속하고 싶을 때, stdin_opentty 옵션을 꼭넣어주어야 합니다.

version : " 3.0.0"
services :
    nodeapp:
        build: "./FOLDER"
        container_name : "mycontainer"
        working_dir : "/usr/src/app"
        networks:
          - default
        ports :
          - "1000:22"
        volumes :
          - "/home/user/Desktop/WORKSPACE:/usr/src/app/workspace"
        stdin_open : true
        tty: true

이제 docker-compose.yml파일이 있는 위치에서 service를 실행하는 명령어를 간단히 몇가지 보이겠습니다.

# build and start with docker-compose.yaml in daemon mode
docker-compose up -d
docker-compose start

# stop containers 
docker-compose down
docker-compose stop

# list up containers
docker-compose ps

# check logs
docker-compose logs

3. 효율적인 Instance 관리

이제 이미지도 컨테이너도 다양한 방법으로 만들 수 있을 것 같습니다. 그럼 이제 이렇게 만들어진 컨테이너들이 효율적으로 자원을 관리하면서 돌아갈 수 있는 환경을 만들어주고자 합니다.

 

앞서 Container의 장점중에 Orchestration에 대해 설명한 바가 있습니다. 이를 위한 Orchestration Tool은 docker등 Linux container화 된 Application들을 자동 배포 확장하는 오픈소스 플랫폼이며, 아래와 같이 여러가지가 있습니다.

  • (CoreOS) fleet
  • (Apache) Mesos
  • (Google + Cloud Native Computing Foundation) Kubernetes
  • (AWS) EC2 Container Service(ECS)
  • (HashiCorp) Nomad
  • (Rancher) Cattle
  • (Redhat) Cockpit
  • (Cloud Foundry) Diego
  • (Docker) Swarm

이중에 제가 알고 있는 부분은 ECS, Kubernetes, Swarm 정도인데, AWS에서는 ECS를 활용하는 것이 당연하고, 적은 수의 Cluster에서는 Docker Swarm, 큰 규모의 클러스터에서는 Kubernetes를 활용한다고 합니다.

 

이중에 Docker Swarm을 알아보겠습니다.


a. swarm 모드와 docker stack

swarm mode란 Docker의 1.12버전부터 포함되어 편리하게 Server Orchestration을 할 수 있는 기능입니다. 또한 swarm mode가 켜진다는 것은 클러스터에 가입되는 것을 의미합니다. 

 

Docker Swarm에 대해 설명하자면 다음의 용어들을 먼저 알아야합니다.

  • 노드(node) : 보통 하나의 서버에 하나의 Dockerd만 실행하므로, 서버의 단위라고 볼 수 있습니다. 노드 중에는 전체 상태를 관리하고 명령어를 실행하는 Manager Node와, 매니저 노드의 명령을 맡는 Worker Node가 있습니다.
  • 스택(Stack) : deploy하는 서비스의 집합입니다.
  • 서비스(Service) : 기본적인 배포 단위로, 하나의 이미지를 기반으로 여러개의 컨테이너를 만들 수 있습니다.
  • 태스크(Task) : 컨테이너 배포 단위로, 하나의 서비스는 여러개의 task를 가질 수 있습니다.

swarm mode를 초기화하고, deploy하고, update하고, 조회하는 코드는 아래와 같습니다.

# Set up swarm mode
docker swarm init
docker swarm init --advertise-addr eno1

# deploy stack with .yml file
docker stack deploy -c FOLDER/docker-stack.yml <Stack명>

# force to update service
docker service update --force <Stack명>_<서비스명>

# scale up service to 3
docker service scale <서비스명>=3

# list services
docker service ls
docker stack ps <Stack명>

# Remove Service
docker stack rm <Stack명>

# Set down swarm mode
docker swarm leave

docker swarm init함수의 경우, 아래와 같은 에러가 날 수 있습니다. 이는 이더넷이 2개 이상 잡혀서 advertise할 이더넷을 하나를 지정해야 하는 에러 이므로, 위와 같이 --advertise-addr로 지정해줍니다.

Error response from daemon : could not choose an IP address to advertise since this system has multiple addresses on different interfaces

swarm mode로 전환하고 나면 아래와 같이 token이 나옵니다. 이는 worker node에서 방금 실행한 master node를 등록하기 위함이므로, 기억해두었다가 다른 서버에서 실행할 수 있습니다.

To add a worker to this swarm, run the following command:
    docker swarm join --token SWMTKN-1-000000 123.456.789.102:8888

위에서 deploy하기 위한 yml파일(위에서는 docker-stack.yml)은 위에서 docker-compose.yml과 조금은 다릅니다. 예로 아래에 예시 내용을 보여드립니다.

version : " 3.0"
services :
    nodeapp:
        image : "myimage"
        networks:
          - micro-service
        ports :
          - "1000:22"
        volumes :
          - "/home/user/Desktop/WORKSPACE:/usr/src/app/workspace"
        deploy:
            mode: replicated
            replicas: 1
networks:
    micro-service:
        driver: overlay

이렇게 실행된 서비스를 docker service ls로 확인하면 아래와 같은 결과가 나옵니다.

ID       NAME                MODE        REPLICAS    IMAGE                  PORTS
il7elw  stack_nodeapp  replicated    0/1                myimage:latest      *:1000->22/tcp
더보기

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

< 실제 Service에 활용하기 위한 예시>

아래는 실제 서버를 구성하면서 테스트했던 docker-stack.yml 에 대해 보이고자 합니다. 아래 그림은 로직을 도식화한 그림이니 참조해서 아래 파일을 보시기 바랍니다.

[아래 docker-stack.yml에 대한 도식화 그림]

아래 docker-stack.yml 파일에 구현된 서비스는 이와 같습니다.

version: '3.5'

services:
    webpage:
        image: my-webpage:latest
       ports:
            - 80:80
        deploy:
            mode: replicated
            replicas: 1
        networks:
            - micro-service
    service-api:
        image: my-service-api:latest
        ports:
            - 5000:5000
        deploy:
            mode: replicated
            replicas: 1
        networks:
            - micro-service
    mongo:
        image: mongo:4.4
        ports:
            - 27017:27017
        volumes:
            - /PATH/mongodb:/data/db
        environment:
            - MONGO_INITDB_ROOT_USERNAME=admin
            - MONGO_INITDB_ROOT_PASSWORD=password
            - MONGO_INITDB_DATABASE=myID
        deploy:
            mode: replicated
            replicas: 1
        networks:
            - micro-service
    jenkins:
        image: jenkins/jenkins:lts
        user: root
        ports:
            - 8080:8080
            - 50000:50000
        volumes:
            - type: bind
              source: /PATH/jenkins
              target: /var/jenkins_home
            - type: bind
              source: /var/run/docker.sock
              target: /var/run/docker.sock
            - type: bind
              source: /usr/bin/docker
              target: /usr/bin/docker
        deploy:
            mode: replicated
            replicas: 1
        networks:
            - micro-service
    nexus:
        image: sonatype/nexus3:latest
        user: root
        ports:
            - 8081:8081
        volumes:
            - type: bind
              source: /PATH/nexus
              target: /nexus-data
        environment:
            TZ: "Asia/Seoul"
        networks:
            - micro-service
networks:
    micro-service:
        driver: overlay

RestAPI, DB, CI/CD에 대해서는 나중에 다른글을 통해 자세히 설명하겠습니다. 위에서 보시는 바와 같이 이중 CI/CD의 경우 Jenkins, RestAPI의 경우 FastAPI를 활용해 구현했는데, 이유는 아래 간단히 그림과 표로 대체하겠습니다.

[https://katalon.com/resources-center/blog/ci-cd-tools]
Framework Django Flask FastAPI Express
Language Python Python Python Javascript
Initial Release 2005.07 2010.04 2018.12 2010.11
Ecosystem High High Low High
Performance Medium Medium High High
Size High Medium Law Medium
ASGI Supported(3.0+) Supported(2.0+) Supported Supported
Type Declaration Supported Supported Supported Not Supported
OpenAPI Docs Not Supported Supported Supported Not Supported

 **ASGI (Asynchronous Server Gateway Interface) : 궁금하시면 아래 더보기를 클릭하세요

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

더보기

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

<ASGI의 개념과 왜 FaskAPI를 써야하는지>

WebServer란 HTTP Request에 맞게 정적으로 Response해주는 기능입니다. (nginx, apache 등)

[https://jellybeanz.medium.com/cgi-wsgi-asgi-%EB%9E%80-cgi-wsgi-asgi-bc0ba75fa5cd]

WAS(WebApplicationServer)란 WebServer와 WebContainer(CGI)를 합친 기능으로, HTTP Request에 맞게 동적으로 Response해주는 기능입니다.
** 동적 Response : Response에 따라 데이터를 가공해서 다른 웹페이지를 Response해주는 것

보통 자원 효율 및 보안을 위해, 단순한 정적 처리를 WebServer, 다양한 로직처리는 WAS로 처리하게 해줍니다.

[https://jellybeanz.medium.com/cgi-wsgi-asgi-%EB%9E%80-cgi-wsgi-asgi-bc0ba75fa5cd]

 그럼 이때 CGI(Common Gateway Interface)는 가장 오래된 WebServer Interface로, 단순히 Request Response해주는 기존 Webserver의 역할을 동적으로 기능하게 하기 위한 Gateway입니다.

[https://jellybeanz.medium.com/cgi-wsgi-asgi-%EB%9E%80-cgi-wsgi-asgi-bc0ba75fa5cd]

2003년, Python 표준인 WSGI(Web Server Gateway Interface, 위스키)가 등장하는데,  Request를 python application으로 보내기 위한 Interface입니다. 따라서 Flask, django 같은 웹 프레임워크를 파이썬에서 실행할 수 있게 됩니다.

[https://jellybeanz.medium.com/cgi-wsgi-asgi-%EB%9E%80-cgi-wsgi-asgi-bc0ba75fa5cd]

ASGI(Asynchronous Server Gateway Interface)는 WSGI와 비슷한 구조를 가지지만 모든 요청을 Asynchronous하게 처리되도록 개선되었습니다. 즉, 기존에 WSGI보다 대용량 트래픽 처리를 유연하게 하기 위해, 여러작업을 비동기적으로 처리하는 인터페이스이며, 예로 Uvicorn이 있습니다.

FastAPI가 ASGI기반의 프레임워크기 때문에 사용하고자 했습니다.

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


b. node 확장하기

단일 노드의 클러스터에서는 위와 같이 셋팅하면 이제 서비스할 수 있을 것 같습니다. 하지만 클러스터에 노드가 여러개인 경우 어떻게 확장할 수 있을지 알아보기 위해 간단한 명령어를 아래 소개하겠습니다.

# list manager node token
docker swarm join-token manager
 
# list worker node token
docker swarm join-token worker

# list all nodes
docker node ls

# connect node
docker swarm join --token <manager/worker token> HOST:PORT

docker node ls라는 명령어를 통해서 node정보를 보면 아래와 같습니다. 이는 반드시 master node에서 실행되어야 합니다.

ID         HOSTNAME    STATUS    AVAILABILITY   MANAGER_STATUS    ENGINE_VERSION
zi4m7   user                  Ready       Active                Leader                           20.10.5

이렇게 docker swarm에 대해 보였지만, 이외에도 kubernetes와 같이 다양한 툴을 활용해 컨테이너들을 관리할 수 있습니다.

 

추가적으로 IaC(Infrastructure as Code)라는 개념이있는데, kubernetes와 같은 간편한 orchestration tool로도 클라우드 인프라를 구축하기 복잡할 때나 과정을 단순화하기 위해서, 코드를 통해 관리하고 Provisioning 하기 위한 도구입니다.

 

즉, 코드를 통해 어떠한 환경에서도 동일한 인프라를 구축하기 위한 추상화도구 이며, 가장 큰 예로 앤서블(ansible)테라폼(Terraform)이 있습니다. 이에 대해서는 다음에 알아보도록 하겠습니다.

[인프라 구축에 필요한 고려사항]


https://subicura.com/2017/01/19/docker-guide-for-beginners-1.html

https://subicura.com/2017/02/25/container-orchestration-with-docker-swarm.html

https://azure.microsoft.com/ko-kr/resources/cloud-computing-dictionary/what-is-a-container/#overview

https://youtu.be/MDsjINTL7Ek

https://hsjs.tistory.com/64

https://jellybeanz.medium.com/cgi-wsgi-asgi-%EB%9E%80-cgi-wsgi-asgi-bc0ba75fa5cd

https://help.iwinv.kr/manual/read.html?idx=572 

 

728x90
반응형