Docker로 서버 프로그램의 완벽한 실행환경 만들기
Docker란 무엇이고 왜 사용해야 할까?
서버 프로그램은 기능이 늘어나고 유지보수를 하면 할 수록 환경 구축에 시간이 필요로 하게 되며, 동일한 서버를 재구축하기 위해 똑같은 절차로 반복을 해야 할 경우가 생긴다. 예로들어 테스트 서버에서 설정한 내용들을 실 개발 서버에 반영할 경우에도 같은 환경을 만들어 주어야 하고 업데이트 배포를 할 때마다 기존 개발 서버와 다른점은 없는지, 개발 환경과 달라 정상동작하지 않게 될지 늘 조마조마 하게 된다.
Container
이런 문제를 해결할 좋은 방법이 바로 Docker 의 컨테이너 개념이다. 간단히 말해서 프로그램이 실행될 가상 환경이다. 기존에는 프로그램이 동작하기 위해 종속성 해결, 프로그램 설치 및 실행을 하여 프로세스가 동작하도록 환경을 만들어 왔다면, 이젠 그 환경을 가상 환경에 가두어 두는 것이다. 가상환경이라 느리진 않을까 생각할 수 있다. 기존의 가상화는 각 가상 머신마다 Guest OS 를 설치하였지만, Docker 는 Guest OS 를 설치하지 않고 프로세스만 격리 하는 방식으로 Host OS 와 자원을 공유하는 방식이라 성능이 좋다.
Image
또한 배포도 상당히 간단한데 Docker 의 컨테이너를 구성하는 설정값들을 모아 만든 것이 바로 이미지라는 개념이다. 예를 들어 nginx 를 Ubuntu 에서 설치하기 까지의 과정과 포트 정보들을 모은 것이라고 생각하면 된다. 근데 이런 이미지도 컨테이너를 수정할 때마다 매번 새로 만들거나 개발하다보면 용량이 늘어나기 마련인데 이를 해결하기 위해 레이어 개념을 사용한다. Ubuntu OS 에서 Nginx 를 설치하고 Nodejs 앱이 동작하기 까지의 과정을 보면 다음과 같다.
1 | 2 | 3 |
---|---|---|
Node.js | ||
Nginx | Nginx | |
Ubuntu | Ubuntu | Ubuntu |
이미지에 기존 내용에 추가된 레이어만 기억하게 된다. 그럼 Node.js 버전만 변경했을땐 어떻게 될까?
1 | 2 |
---|---|
Node.js(new version) | |
Nginx | Nginx |
Ubuntu | Ubuntu |
기존의 Node.js 버전만 바뀌고 나머지는 같으므로 Node.js 레이어만 바뀌고 새 버전의 이미지가 생성된다.
이 이미지를 실행하면 컨테이너가 생성되는 것이다.
직접 도커 이미지를 만들어 보자
도커 이미지를 만들기 위해 Dockerfile
에 스크립트를 작성한 예시이다.
# 1. Ubuntu 18.04 설치
FROM ubuntu:18.04
MAINTAINER --- # email address
RUN apt-get -y update
RUN apt-get -y upgrade
# 2. Node.js 설치
RUN apt-get install curl -y
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -
RUN apt-get install -y nodejs
# 3. 설치된 Node.js 버전 확인
RUN node --version
# 4. PM2 설치
RUN npm i pm2@4.2.3 -g
RUN pm2 -version
# 5. 작업 도구 설치
RUN apt-get install -y htop tmux vim
# 6. express-ip 종속성 https://github.com/danielstjules/geoip-ultralight
COPY ./geoip-country.dat /data/geoip-country.dat
COPY ./geoip-country6.dat /data/geoip-country6.dat
# 7. 빌드된 Node.js 서비스 복사
COPY ./dist /root/node-app
COPY ./package.json /package.json
COPY ./ecosystem.config.js /root/node-app
WORKDIR /root/node-app
# 8. Node.js 서비스 시작
EXPOSE 63966
# CMD pm2 start bundle.js
CMD ["pm2-runtime", "ecosystem.config.js"]
본 과정은 Ubuntu 18.04 에서 Node.js 10 으로 작성된 서버 프로그램을 PM2 를 이용해 호스팅하기까지의 과정이 작성되어 있다.
- Run
컨테이너 내부에서 실행할 명령어
- COPY
호스트에서 컨테이너에 전달할 파일 혹은 폴더
- CMD
컨테이너 실행할 때 실행할 명령어 정의
- WORKDIR
작업할 디렉토리 설정
- EXPOSE
컨테이너 실행시 오픈할 포트
- FROM
기반이 될 이미지
간혹 COPY를 하거나 이미지를 생성하기 위해 BUILD 하는 과정에서 필요 없는 파일까지 모두 BUILD 되어 이미지 용량을 키우게 되는데 이를 위해 .gitignore 같이 .dockerignore 파일이 존재한다.
이미지 생성을 위한 BUILD 명령어는 다음과 같다
docker build -t <TAG NAME> <DIR>
생성된 이미지를 컨테이너로 실행하는 명령어는 다음과 같다.
docker run -d -p <SRC PORT>:<DST PORT> --name <CONTAINER NAME> <IMAGE NAME>
-d 백그라운드에서 실행
-p 포트 포워딩
-name 컨테이너 이름 지정
이미지를 BUILD 하고 실행까진 했지만 아직까진 절차가 간단해서 새 버전을 생성할 때마다 명령어를 쳐도 가능은 하다. 하지만 이미지가 늘어나고 서로 네트워크를 연결시키는 등의 설정이 추가되면 명령어로는 버티기 힘들어지는 순간이 있는데 그나마 길게 작성해본 실행 명령어이다.
docker run -d --restart always -v D:/Shared/mariadb:/var/lib/mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root --name mariadb_local mariadb
docker run --name myadmin -d --link mariadb_local:db -p 8080:80 phpmyadmin/phpmyadmin
docker run -d --link mariadb_local:db -e DATABASE_HOST=localhost --restart always -v D:/Shared/node/log:/root/node-app/log -v D:/Shared/node/resource:/root/node-app/resource -p 63966:63966 --name node_app node-app
보기도 힘들어 졌을 뿐만 아니라 설명하기도 힘들어졌다.
이를 위해 Docker composer 를 사용하면 된다.
Docker 의 작곡가라는 것과 알맞게 배포를 어떻게 해야할지에 대한 일련의 내용을 yaml
형식으로 작성한 파일이다. 그럼 방금 위에서 보았던
배포시 입력한 명령어를 docker-compose.yaml 에 정리해 보자
version: '3.7'
networks:
decepticon:
driver: bridge
services:
mariadb:
networks:
- decepticon
image: mariadb:10.4
container_name: mariadb_local
restart: always
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=root
volumes:
- D:/Shared/mariadb:/var/lib/mysql
phpmyadmin:
restart: always
networks:
- decepticon
image: phpmyadmin/phpmyadmin:5.0.1
ports:
- "8080:80"
links:
- "mariadb:db"
environment:
- MYSQL_ROOT_PASSWORD=root
- PMA_HOST=mariadb
- MYSQL_USER=root
- MYSQL_PASSWORD=root
app:
networks:
- decepticon
image: node-app
links:
- "mariadb"
environment:
- DATABASE_HOST=mariadb
restart: always
volumes:
- D:/Shared/node/log:/root/node-app/log
- D:/Shared/node/resource:/root/node-app/resource
ports:
- "63966:63966"
container_name: node_app
각 app, phpmyadmin 이란 컨테이너는 mariadb 컨테이너란 DB에 연결되어야 하므로 network 방식 중 bridge
방식을 사용하여 묶어놓았다.
각 컨테이너가 동작하기 위한 환경변수와 마운팅할 볼륨을 지정하였다.
그럼 실제 실행은 어떻게 할까?
docker-compose up -d
기존 명령어보다 매우 쉽게 컨테이너를 실행시킴으로서 간단한 배포가 가능해진다.