Docker Guide

Docker logo

우측의 목차 tocbot을 이용하면 편리합니다!

1. 도커를 왜 쓸까? 뭐길래?

들어가며

서버 스터디를 하면서 ‘클라우드에 도커를 올려라’라는 말을 듣고, 듣긴 들은 것 같은데 대체 도커가 뭔지를 몰랐다. 단어 하나만 듣고 서버 NHN TOAST 클라우드에 여자저차 올리긴 했지만 완벽한 개념을 숙지하지 못하고 마구잡이로 올린 탓에 의미가 없었다.

다들 도커를 접하는 많은 이유가 있겠지만, 도커, 이미지, 컨테이너 등 기본적인 개념을 알고 ‘왜’ 쓰는지 알아야 할 것 같아서 정리해보았다.

도커의 등장 전,

기존에 서버를 관리하는 것은 매우 어려웠고 복잡한 영역이어서, 새 서버를 세팅하려면 그때마다 config등의 구축환경을 다시 세팅하고, 그 후에도 리눅스 버전이나 환경의 변화가 생기면 충돌이 일어나기 쉽상이었다.

한 서버에 다수의 프로그램을 설치하면 라이브러리, 포트 충돌을 고려한 설치가 굉장히 어려웠고, 마이크로서비스 아키텍쳐, DevOps 등의 등장으로 서버 관리는 더 어려워졌다.

도커의 등장,

이후 도커의 등장으로 서버 관리의 방식은 완전히 바뀌었다.

도커는 컨테이너 기반의 오픈소스 가상화 플랫폼이다. 컨테이너 안에는 다양한 프로그램, 실행 환경을 ‘컨테이너’라는 개념으로 추상화하고 클라우드, PC 등 어디서든 실행할 수 있다. 구글에서는 모든 서비스들이 20억개의 컨테이너로 동작한다고 한다.

VM vs Docker

컨테이너는 격리된 공간에서 프로세스가 동작하는 기술이다. 기존의 가상화 방식은 OS를 가상화하는 것이었다. VMware 등의 가상머신은 Host OS 위에 Guest OS 전체를 가상화한다. 이 방식은 사용법도 간단하고 여러가지 운영체제를 가상화할 수 있지만 무겁고 느리기 때문에 운영환경의 사용에는 적합하지 않았다.

d11

이후 CPU의 가상화 기술을 이용한 반가상화 방식이 등장한다. Guest OS가 필요하지만 전체 OS를 가상화하는 방식이 아니어서 성능은 향상되었다. 이 기술은 OpenStack, AWS 등의 클라우드 서비스의 가상 컴퓨터 기술의 기반이 되었다.

전가상화, 반가상화 모두 성능 문제가 있었다. 이를 개선한 프로세스 격리 방식이 등장한다. 리눅스에서 리눅스 컨테이너라고 하며 단순하게 프로세스를 격리시키는 방식이기 때문에 가볍고 빠르게 동작한다. CPU나 메모리 등의 자원을 프로세스가 필요한 만큼만 할당하고 더 필요하면 그때마다 추가적으로 할당하기 때문에 성능적인 손실이 거의 없었다.

하나의 서버에 다수의 컨테이너를 실행하면 서로 영향을 주지 않고 독립적으로 실행된다. 실행 중인 컨테이너에 접속하며 명령어 입력, 패키지 설치, 사용자 추가 등 모든 작업을 할 수 있다. 컨테이너를 만드는 시간은 길어야 2초로 가상머신보다 훨씬 빠르다.

이 전에도 프로세스 격리 방식의 가상화 기술(LXC, Jail 등)이 있었지만 성공하지는 못했다. 도커는 LXC를 기반으로 시작해서 이 후 자체적인 libcontainer 기술을 사용했고 runC 기술에 합쳐졌다. 도커가 성공한 이유는 존재하는 좋은 기술들을 잘 포장해서 오픈 소스로 배포했기 때문이 아닐까.

Image, Container, …

도커에서 가장 중요한 개념은 컨테이너, 이미지이다. 이미지는 컨테이너 실행에 필요한 파일, 설정값을 모두 포함하고 있는 것이고 변하지 않는다. 이미지를 실행한 것이 컨테이너이고 여기서 추가, 변화되는 값은 컨테이너에 저장된다. 같은 이미지로 다수의 컨테이너를 생성할 수 있고 컨테이너의 변화가 생겨도 이미지에는 영향을 주지 않는다.

예를 들어 CentOS 이미지는 CentOS를 실행하기 위한 모든 파일, 설정값을 가지고 있고, Tensorflow 이미지 안에는 Tensorflow, Python, Jupyter 등 딥 러닝에 필요한 환경이 모두 들어가 있다. 이미지는 컨테이너를 실행하기 위한 모든 정보를 가지고 있기 때문에 개발 환경 등의 구축을 위해서 이것저것 설치하고 설정할 필요가 없다. 새로운 서버가 추가되면 만들어 놓은 이미지를 다운받고 컨테이너만 생성하면 된다.

도커 이미지는 Dockerhub에서 업로드, 다운로드 할 수도 있다. 현재 공개된 이미지는 50만개가 넘고 누구나 쉽게 명령어를 이용하여 이미지를 만들고 배포하고(push) 다운로드(pull)받을 수 있다.

Layer

도커 이미지는 컨테이너 실행에 대한 모든 정보를 담고 있기 때문에 용량이 매우 크다. 따라서 기존의 이미지에 파일 하나를 추가했다고 수백메가가 넘어가는 이미지를 다시 다운로드한다면 매우 비효율적일 수 밖에 없다.

도커는 레이어; Layer라는 개념을 사용한다. 유니온 파일 시스템을 이용하여 다수의 레이어를 하나의 파일시스템으로 사용할 수 있게 해준다. 이미지는 여러 개의 읽기 전용 레이어(Read-only Layer)로 구성되고 파일에 변화(추가, 수정)가 생기면 새로운 레이어가 생성된다.

예를 들어 CentOS 이미지가 C1 + C2 레이어의 집합이라면 이 이미지에서 nginx가 구동되는 환경의 이미지 레이어는 C1 + C2 + nginx 가 된다. 이런 식으로 기존의 이미지 레이어 위에 레이어가 추가되는 방식으로 최소한의 용량을 사용할 수 있게 된다. 여러대의 서버에 배포하는 것을 감안하면 엄청나게 영리한 설계이다.

도입을 마치며

도커를 기반으로 한 오픈소스 프로젝트는 수십만개가 넘어가고 굉장히 활발히 진행되고 있다. 훌륭한 생테계를 기반으로 클라우드 컨테이너 세계에서 de facto(사실상 표준)가 되었다. 이번에는 도커에 대한 전반적인 역사와 개념을 매우 얕게 정리해보았는데 다음부터는 본격적으로 도커의 설치, 컨테이너 관리와 활용 등을 정리해보도록 하겠다.

2. 도커 설치하기

도커는 소스를 컴파일해서 설치하는 방법, 자동 설치 스크립트를 사용하는 방법, 리눅스 배포판의 패키지로 설치하는 방법이 있다. 소스를 컴파일해서 설치하는 방법은 복잡하니 직접 찾아보자.

1. 자동 설치 스크립트

리눅스 버전을 자동으로 인식하여 도커 패키지를 설치해주는 스크립트이다.

1
sudo wget -qO- https://get.docker.com/ | sh

이 스크립트로 도커를 설치하면 hello-world 이미지도 같이 설치되는데, 사용하지 않으므로 삭제하도록 하자.

1
2
sudo docker rm 'sudo docker ps -aq'
sudo docker rmi hello-world

2. 패키지 직접 설치

  • Ubuntu
    1
    2
    3
    sudo apt-get update
    sudo apt-get nistall docker.io
    sudo ln -sf /usr/bin/docker.io /usr/local/bin/docker

/usr/bin/docker.io 실행파일을 /usr/local/bin/docker 로 링크해서 사용

  • CentOS7
    1
    2
    3
    sudo yum install docker
    sudo service docker start #Docker 서비스 실행
    sudo chkconfig docker on #부팅시 자동 실행
  • Mac, Windows의 경우는 도커 공식홈에서 받고 설치하면 되므로 다른 글을 참조하자.
1
sudo docker version

도커 버전 명령어를 통해 설치가 잘 되었는지 확인해보자.

이로써 Docker 설치가 끝났다!

3. 이미지 받아서 실행하기 (ft. 컨테이너 명령어, sudo 입력 안하기)

tip. sudo 입력하지 않기

docker를 쓰다보면 sudo를 항상 붙여줘야 하는데 이는 docker 명령을 root 권한으로 실행해야 하기 때문에 일반 계정에서는 sudo가 항상 붙는 것이다. 매번 입력하기 귀찮고 빠뜨릴 때도 많다. sudo를 입력하지 않는 방법은 2가지이다.

  1. 처음부터 root 계정으로 로그인하거나 root 계정으로 전환
1
sudo su #root 계정으로 전환
  1. 현재 계정을 docker 그룹에 포함
1
2
sudo usermod -aG docker ${USER}
sudo service docker restart

도커 이미지 다운로드 받아서 실행하기

도커는 이미지를 만들거나 받아서 호스트에 저장한 후 컨테이너로 run해서 프로세스로 만든다. 무언가 실행하려면 이미지가 필요한 셈이다.

이미지를 직접 만드는 방법도 있고, docker hub를 이용해서 공식 이미지나 다른 사용자들이 만들어놓은 이미지를 다운로드받는 방법도 있다.

우선 Docker Hub에서 이미지를 받는 방법이다. 우분투 리눅스 이미지를 받아보겠다.

1
docker pull ubuntu:latest

docker pull <이미지 이름>:<태그> 형식이다. latest를 설정하면 최신 버전을 받는다. latest 대신 16.04, 18.04를 입력해서 태그를 지정해 줄 수도 있다.
이미지 이름에서 username/imagename:tagname 형식을 사용하면 공식 이미지가 아닌 사용자 이미지를 받는다.

1
2
docker images
docker run -it --name hello ubuntu /bin/bash

이제 docker images 명령어를 통해 이미지를 출력하여 도커 이미지가 잘 받아졌는지 확인하고 ubuntu 이미지를 컨테이너로 실행해보자.

docker run <옵션> <이미지 이름> <실행할 파일> 형식이다.
위의 코드는 ubuntu 이미지를 컨테이너로 생성 후 이미지 안의 /bin/bash를 실행한다. 이미지 이름 대신 이미지 ID를 사용해도 좋다.

-it 옵션을 사용하면 실행된 Bash 쉘에 입출력을 할 수 있다.
--name 옵션을 사용하면 컨테이너 이름을 지정해줄 수 있고, 지정하지 않으면 자동으로 이름을 부여한다.

이제 Host OS와 완전 격리된 공간이 생성되었다. cd,ls 명령으로 확인해보면 완전 분리된 공간임을 알 수 있다. exit를 입력하면 Bash 쉘에서 빠져나온다. (도커 -> HostOS)

저기서 만든 컨테이너에서 exit를 입력하면 /bin/bash파일을 직접 실행했기 때문에 컨테이너가 정지상태로 바뀐다.

여기서 컨테이너의 상태가 나왔는데, 컨테이너의 상태나 시작, 종료 등은 어떻게 해야할까?

컨테이너 관련 명령어

1
2
3
4
5
6
7
8
docker ps -a #모든 컨테이너 출력(정지 컨테이너 포함)
docker ps #실행 중인 컨테이너만 출력
docker start hello #hello 이름의 컨테이너 시작
docker restart hello #hello 이름의 컨테이너 재시작(재부팅)
docker attach hello #컨테이너에 접속(bash 쉘 접속)
docker stop hello #hello 이름의 컨테이너 종료
docker rm hello #hello 이름의 컨테이너 삭제
docker rm -f hello #hello 이름의 컨테이너 강제삭제

여기서 컨테이너를 삭제하는 명령어는 rm이고, 이미지를 삭제하는 명령어는 rmi를 쓰면 된다.

우분투 공식 이미지를 받아서 컨테이너화 시키고 그 컨테이너를 시작, 종료하는 명령어도 알게 되었다. 다음에는 나만의 Docker 이미지를 생성해보도록 하겠다.

4. 나만의 이미지 생성하기(ft. Dockerfile)

이전에 Docker Hub에서 만들어놓은 이미지를 다운로드 받아서 구동하는 것을 다뤘는데, 이번에는 Dockerfile을 직접 짜서 나만의 이미지를 만들어보려고 한다.

들어가기 전에,

사실 이 부분에 대해서 이해가 많이 어려웠다. Container에서 필요한 프로그램들을 다운로드 받고 commit 명령어를 사용해서 이미지를 만들어도 될텐데, 왜 굳이 Dockerfile을 bash 명령어를 써가며 이미지를 빌드해야 되는지 몰랐다.

그렇게 찾아본 결과 commit 명령어를 사용해서 이미지를 생성하는 방법은 실무에서 거의 쓰이지 않는다고 한다. 패키지 업데이트도 힘들고, 버전 관리도 까다로워서 Dockerfile을 파일화해서 관리하는 것이 버전 관리나 패키지 업데이트 등의 면에서도 우월하다는 것이다.

이제 Dockerfile을 만들어보자.

Dockerfile은 Docker 이미지 설정 파일이다. 이 파일에 설정된 내용대로 이미지를 생성한다. 먼저 example 디렉토리를 생성한 뒤 해당 디렉토리로 이동한다. 모든 빌드와 관련된 파일들이 한 폴더(example)에서 이루어진다.

1
2
mkdir example
cd example

이번에 다루는 이미지 빌드는 모든 과정을 Dockerfile과 쉘 스크립트 파일로 통합하는 방법이다.
그러면 build-run.sh 파일을 만들고 인자를 전달해서 실행하면 이미지를 만들고 컨테이너로 run까지 one-click으로 할 수 있게 된다.

Dockerfile 관련 명령어

Dockerfile을 만들기 전에 필요한 명령어들을 몇 개 알아보자.

FROM

어떤 이미지를 기반으로 할지 설정한다. Docker 이미지는 기존에 만들어진 이미지를 기반으로 생성한다. 만약 완전히 새로운 이미지를 생성하고 싶으면(“맨 땅에서 시작한다”) FROM scratch를 쓰면 된다.

MAINTAINER

메인테이너(제작자) 정보이다. Author(저자)라고 생각하면 된다.

RUN

쉘 스크립트/명령을 실행한다.
이미지 생성 중에는 사용자 입력을 받을 수 없다. 그래서 apt-get install 명령어를 사용할 경우 -y 옵션을 붙여야 한다.

VOLUME

호스트와 공유할 디렉터리 목록이다. docker run 명령어에서 -v 옵션으로 설정할 수도 있다.

ADD

빌드시에 주어진 컨텍스트에서 첫번째 인자로 주어진 파일, 폴더를 두번째 인자로 주어진 컨테이너 경로에 추가한다.

CMD

컨테이너가 시작되었을 때 실행할 실행 파일/쉘 스크립트이다.

EXPOSE

호스트와 연결할 포트 번호이다. 외부와 통신을 가능하게 노출시켜 준다. 기본적으로 컨테이너 실행시 docker run -p 옵션을 주게 되면 암묵적으로 EXPOSE가 된다.

이 명령어들을 바탕으로 ubuntu 18.04 기반의 xrdp, vim 등의 필수 패키지들이 설치된 나만의 우분투 이미지 Dockerfile을 만들어 보았다.

Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
FROM ubuntu:18.04
#set root password
RUN echo "root:ubuntu" | chpasswd
# install packages
RUN apt-get update \
&& apt-get install --yes --force-yes --no-install-recommends \
sudo \
software-properties-common \
xorg \
xserver-xorg \
xfce4 \
gnome-themes-standard \
gtk2-engines-pixbuf \
file-roller \
evince \
gpicview \
leafpad \
xfce4-whiskermenu-plugin \
ttf-ubuntu-font-family \
dbus-x11 \
vnc4server \
vim \
xfce4-terminal \
xrdp \
xorgxrdp
# add the user and designate sudo authority
RUN adduser ubuntu
RUN echo "ubuntu:ubuntu" | chpasswd
RUN echo "ubuntu ALL=(ALL:ALL) ALL" >> /etc/sudoers
#set the port number of xrdp
RUN sed -i 's/3389/port_number/' /etc/xrdp/xrdp.ini
#install xubuntu-desktop
RUN apt-get install --yes --force-yes --no-install-recommends \
xubuntu-desktop \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# insert entrypoint.sh and set ENTRYPOINT
ADD entrypoint.sh /entrypoint.sh
ENTRYPOINT /entrypoint.sh

여기서

1
2
RUN sed -i 's/3389/port_number/' /etc/xrdp/xrdp.ini
cs

sed 명령어는 파일에 있는 특정 단어를 찾아서 다른 단어로 바꾸는 역할을 한다.

1
ENTRYPOINT /entrypoint.sh

ENTRYPOINT 명령어는 컨테이너가 처음 실행될 때(run/start 시) 한번 수행되는 스크립트를 지정한다. 따라서 이미지를 빌드하고 컨테이너로 run하면 entrypoint.sh 쉘 스크립트가 실행된다.

entrypoint.sh

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
# create a dbus system daemon
service dbus start
# create the sock dir properly
/bin/sh /usr/share/xrdp/socksetup
# run xrdp and xrdp-sesman in the foreground so the logs show in docker
xrdp-sesman -ns &
xrdp -ns &
# run shell for interface
/bin/bash

dbus, xrdp 서비스를 위한 entrypoint 설정이다.

이제 이미지 빌드에 필요한 Dockerfile, entrypoint.sh를 만들었으니 docker run에 필요한 최종 파일인 build-run.sh 파일을 만들어보자.

build-run.sh

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
#edit the port number in Dockerfile
sed -i 's/port_number/'$1'/' ./Dockerfile
#start building image from Dockerfile
docker build -t $2 .
#run container from built image
docker container run -d -it --name $3 -p $1:$1 $2
docker container start $3
#return Dockerfile into first state
sed -i 's/'$1'/port_number/' ./Dockerfile

$1, $2, $3build-run.sh의 인자 번호이다.

1
./build-run.sh [포트번호] [이미지 이름] [컨테이너 이름]

위 명령어를 실행하게 되면 해당 포트 번호, 이미지 이름, 컨테이너 이름으로 이미지 빌드, 실행(run)까지 완료하게 된다.

이미지 빌드에는 시간이 다소 걸린다. 잘 참고 기다리면 나만의 이미지가 생성된다. 이 이미지를 도커 허브에 올려보고 다른 os 기반에서 다운로드 받아서 컨테이너에 올려보자.

5. 도커 살펴보기 (이미지, 컨테이너 관련 명령어)

지금까지 기본적인 명령어와 이미지 생성 방법을 알아보았다. 이제 이미지, 컨테이너 정보를 조회하는 방법, 컨테이너에서 파일 꺼내기, 변경된 파일을 확인하기, 변경 사항을 이미지로 저장하는 방법을 알아보자.

history

1
docker history hello:0.1

docker history <이미지 이름>:<태그> 형식이다. 이미지 이름 대신 ID를 사용할 수도 있다.

cp

1
docker cp hello:/etc/nginx/nginx.conf ./

docker cp <컨테이너 이름>:<경로> <호스트 경로> 형식이다. 호스트 경로에 컨테이너 내부 파일 nginx.conf 파일이 복사된다.

commit

1
docker commit -a "zini <zinirun.blogspot.com>" -m "add hello.txt" hello hello:0.2

docker commit <옵션> <컨테이너 이름> <이미지 이름>:<태그> 형식이다. 컨테이너의 변경 사항을 이미지 파일로 생성한다. -a 옵션은 커밋한 사용자 정보, -m 옵션은 로그 메시지를 설정한다. 그렇게 hello 컨테이너를 hello:0.2 이미지로 생성하게 된다.

diff

1
docker diff hello

docker diff <컨테이너 이름> 형식이다. 컨테이너 ID를 사용해도 된다. 출력하게 되면

1
2
3
4
5
6
7
8
9
10
11
12
13
ubuntu@zini-01:/home/zini/doc_ex$ docker diff hello
C /run
A /run/xrdp
A /run/xrdp/sockdir
A /run/dbus
A /run/dbus/pid
A /run/dbus/system_bus_socket
C /tmp
A /tmp/.X11-unix
C /var
C /var/log
C /var/log/xrdp.log
C /var/log/xrdp-sesman.log

변경된 파일 목록을 출력하는데 A는 추가된 파일, C는 변경된 파일, D는 삭제된 파일이다.

inspect

1
docker inspect hello

docker inspect <이미지 or 컨테이너 이름> 형식이다. ID를 사용해도 된다. 이미지나 컨테이너의 세부 정보를 출력한다.

6. DockerHub 사용하기

이번에는 Docker를 좀 더 다양하게 활용하는 방법, Docker Hub 사용법을 알아보겠다.

Docker Login

우선 Docker Hub에 로그인 후 회원가입하자.

1
docker login

명령어를 입력하여 docker 계정으로 로그인하자.

로컬에 이미지 저장하기

Docker 레지스트리 서버도 Docker Hub에 이미지로 제공된다. 먼저 레지스트리 이미지를 받는다.

1
docker pull registry:latest

registry:latest 이미지를 컨테이너로 실행한다.

1
2
3
docker run -d -p 5000:5000 hello-registry \
-v /tmp/registry:/tmp/registry \
registry:latest

이 명령어를 실행하면 이미지 파일이 호스트의 /tmp/registry 디렉터리에 저장된다.

push 명령으로 이미지 올리기

1
2
docker tag hello:0.1 zini/hello:0.1
docker push zini/hello:0.1

docker tag <이미지 이름>:<태그> <Docker 사용자명/레지스트리 URL>/<이미지 이름>:<태그> 형식이다.

docker hub에 업로드할 때는 이미지 이름의 형식을 맞춰줘야 한다. 도커허브의 아이디가 zini라면 zini/이미지이름:태그 형식으로 tag 명령어를 이용하여 이미지 이름을 바꿔주고 push하면 업로드 된다.

pull 명령으로 이미지 받기

1
docker pull zini/hello:0.1

이전에서도 우분투 공식 이미지를 받을 때

1
docker pull ubuntu:latest

명령어를 사용했을 것이다. 우분투는 공식 이미지이기 때문에 URL/ID 필요없이 <이미지이름>:<태그> 형식을 사용하면 된다. 방금 push로 올린 이미지를 pull로 받아보자.

1
2
3
ubuntu@zini-01:/home/zini/doc_ex$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
zini/hello 0.1 e100ad1021f7 22 hours ago 1.17GB

docker images 명령어로 확인하면 이미지가 받아진 것을 확인할 수 있다.

이제 이 이미지를 삭제해보자.

1
docker rmi zini/hello:0.1

rmi 명령어를 사용하면 이미지를 삭제할 수 있다. 강제로 삭제할 경우 -f 옵션을 추가하면 된다.

7. 도커 컨테이너 연결하기 (nginX, mongoDB)

도커로 이미지를 생성할 때 필요한 프로그램, 예를 들면 웹 서버, DB 등의 프로그램들을 모두 설치할 수도 있지만 보통은 프로그램별로 이미지를 생성 후 컨테이너화해서 연결한다.

이번에는 nginx 웹 서버와 MongoDB의 이미지를 생성 후 연결해보도록 하겠다.

우선 MongoDB 이미지를 생성한다.

1
docker run --name db -d mongo

필자의 경우 80 포트는 이미 우분투 host os에서 nginx 서버로 돌아가고 있기 때문에 8888 포트를 열어주어서 도커의 80 포트와 연결하였다.

1
docker run --name web -d -p 8888:80 --link db:db nginx

-p 8888:80 에 주목하자. -p 옵션은 호스트의 포트와 도커의 가상 포트를 연결한다. 형식은 -p <Host Port>:<Docker Port>이다. 필자는 이 부분을 모르고 8888:8888로 연결했다가 같이 스터디하는 친구에게 혼났다. 도커에 대한 개념을 확실하게 알고 했어야 했는데..

이 명령어를 실행하면 nginx 최신 이미지를 받고 앞서 실행한 db 컨테이너 mongoDB와 연결하여 web 이라는 이름의 nginx 서버를 컨테이너로 실행한다.

일반적으로 IP를 통해서 브라우저로 접속할 경우 포트를 입력하지 않으면 80 포트로 자동 접속한다. nginx 서버를 그렇게 연결하고 싶다면 -p 80:80 옵션으로 실행하면 된다.

d71

브라우저로 <IP>:<호스트 연결 포트>로 접속하면 nginx 기본 페이지가 출력됨을 확인할 수 있다!

nginx 컨테이너는 mongoDB 컨테이너와 연결되어 있어서 web 컨테이너 안에서 db:27017 주소로 db 컨테이너의 mongoDB에 접속할 수 있다.

8. Git, Docker로 앱 배포하기

서버를 운영하고 애플리케이션을 배포하는 방법은 서비스 환경, 구축하는 사람에 따라 다양한 방법이 나올 수 있다. 이번에는 분산형 버전 관리 시스템 Git과 Docker를 이용한 애플리케이션 배포 방법을 알아보겠다.

참고 도서: 가장 빨리 만나는 Docker (이재홍)

서버 한 대에 애플리케이션 배포하기

대략적인 순서는 다음과 같다.

  1. 개발자 PC에서 애플리케이션 개발
  2. git push 명령으로 소스를 서버에 업로드
  3. 서버에서 저장소에 git push 명령이 발생하며 git hook을 실행
  4. git hook에서 도커 이미지를 생성, 이미지를 컨테이너로 실행

이제 본격적으로 배포하는 과정을 알아보자.

  1. 개발자 PC에서 Git 설치, 저장소 생성
    개발자 PC에 Git을 설치한다.

우분투, CentOS에서 install git 명령어를 사용해도 상관없다. 필자는 윈도우의 Git Bash로 실습하겠다.

Git Bash를 실행하고 명령어로 Git 저장소를 생성하고, 저장소 디렉토리로 이동한다.

1
2
git init exampleapp
cd exampleapp

git config 명령으로 이메일, 이름을 설정한다.

1
2
git config --global user.email zini@example.com
git config --global user.name "zini"
  1. 개발자 PC에서 Node.js로 웹 서버 작성 및 도커파일(Dockerfile) 작성
    개발자 PC에서 간단히 Node.js로 웹서버를 작성한다. 그 내용을 app.js로 저장한다.

app.js

1
2
3
4
5
6
7
8
var express = require('express');
var app = express();

app.get(['/', '/index.html'], function (req, res) {
res.send('Hello Docker');
});

app.listen(80);

npm 패키지 사용을 위해 다음과 같이 작성하고 package.json로 저장한다.

package.json

1
2
3
4
5
6
7
8
{
"name": "exampleapp",
"description": "Hello Docker",
"version": "0.0.1",
"dependencies": {
"express": "4.4.x"
}
}

이제 Dockerfile을 작성하자. 서버에서 도커 이미지를 생성할 수 있도록 개발자 PC에서 Dockerfile을 작성한다. 다음 내용을 Dockerfile로 저장한다.

Dockerfile

1
2
3
4
5
6
7
8
FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y nodejs npm
ADD app.js /var/www/app.js
ADD package.json /var/www/package.json
WORKDIR /var/www
RUN npm install
CMD nodejs app.js

모든 파일(js, json, Dockefile)은 git 디렉토리의 exampleapp에 위치시킨다.

git add, git commit 명령으로 개발자 PC의 exampleapp 저장소에 파일을 커밋한다.

1
2
3
4
git add app.js package.json
git commit -m "add source"
git add Dockerfile
git commit -m "add Dockerfile"
  1. 개발자 PC에서 SSH 키 생성
    개발자 PC에서 ssh-keygen 명령을 실행하여 SSH키를 생성한다.
1
ssh-keygen

명령어 실행시 나오는 문장에서는 모두 Enter로 넘어간다.
이제 /home/(사용자 계정)/.ssh 디렉토리에 id_rsa.pub, id_rsa.pub 파일이 생성되었다.

  1. 서버에 Git 설치 및 저장소 생성
    이제 서버를 설정하자. Docker가 리눅스 전용이므로 리눅스 서버에서 작업하자.
    필자는 우분투를 사용하겠다. 서버에 Git을 설치하는 것부터 시작하자.
1
sudo apt-get install git

현재 리눅스 계정의 홈 디렉토리(/home/<사용자 계정>)에 exampleapp 저장소를 생성한다. 그리고 개발자 PC에서 push한 소스를 받을 수 있도록 receive.denycurrentbranchignore로 설정한다.

1
2
git init exampleapp
git config receive.denycurrentbranch ignore
  1. 서버에 Docker 설치 및 SSH 키 설정
    이제 서버에 Docker를 설치한다. 설치되어 있다면 넘어가자.
1
2
sudo apt-get install docker.io
sudo ln -sf /usr/bin/docker.io /usr/local/bin/docker

서버의 /home/<서버 사용자 계정> 디렉토리에 .ssh 디렉토리를 생성 후 권한을 설정한다.

1
2
mkdir .ssh
chmod 700 .ssh

필자는 .ssh 폴더가 왜인지 모르게 이미 존재해서 따로 설정하지 않았다.

.ssh 디렉토리에 authorized_keys 파일을 생성한다(vi 명령어로). 개발자 PC에서 생성한 id_rsa.pub 파일의 내용을 (메모장으로 열어) 복사해서 authorized_keys 파일에 붙여넣는다.

ssh-rsa ~ 형식의 내용일 것이다. authorized_keys 파일도 권한을 설정한다.

1
chmod 600 authorized_keys

이제 개발자 PC에서 비밀번호 없이 git push 명령을 사용할 수 있다.

  1. 서버에 Git Hook 설정
    개발자 PC에서 git push 명령으로 소스를 올리면 Docker 이미지, 컨테이너를 생성하도록 Git Hook을 설정한다.

다음 내용을 /home/<서버 사용자 계정>/exampleapp/.git/hooks 디렉토리에 post-receive로 저장한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

APP_NAME=exampleapp
APP_DIR=$HOME/$APP_NAME
REVISION=$(expr substr $(git rev-parse --verify HEAD) 1 7)

GIT_WORK_TREE=$APP_DIR git checkout -f

cd $APP_DIR
docker build --tag $APP_NAME:$REVISION .
docker stop $APP_NAME
docker rm $APP_NAME
docker run -d --name $APP_NAME -p 80:80 $APP_NAME:$REVISION

이제 post-receive 파일에 실행권한을 부여한다.

1
chmod +x post-receive

Git Hook 설정이 끝났다.

  1. 개발자 PC에서 소스 Push
    이제 개발자 PC로 돌아와서 exampleapp 저장소 디렉토리로 이동한 뒤 git remote add 명령어로 origin 주소를 설정한다.
1
git remote add origin 서버사용자계정@IP주소/도메인:exampleapp

git push 명령으로 소스를 서버에 업로드한다.

1
git push origin master

명령 출력 결과에서 Docker 이미지와 컨테이너가 생성되는 모습을 볼 수 있다. git push 명령어가 완전히 끝나면 웹 브라우저를 실행하여 IP 주소를 입력해보자. Hello Docker가 표시된다. 소스를 수정한 뒤 서버에 Push 하면 새 Docker 컨테이너가 생성된다.

마치며

기본적인 도커의 설치, 사용부터 배포까지 알아보았다. 반년 전에 공부했던 포스팅 내용을 한번에 정리했는데 이게 나은 것 같다. 도커는 정말 무궁무진하다. 여러가지 토이 플젝이던, 큰 플젝이던 거의 90%의 웹 서비스에는 도커를 사용했다. 한번 쓸 줄 알면 다음 쓰는 건 쉽다. 한번씩 자동화 스크립트를 만들어서 배포해보면 (물론 기업에서 사용하는 스크립트와는 비교불가겠지만) 경이로움을 금치 못한다. 도커는 충분히 공부할 가치가 있는 플랫폼이라고 생각한다.