실용주의 FE Dev
Search…
⌃K

Docker

도커란 무엇인가

하나의 서버를 여러개의 프로그램을 설치하고 포트를 설정하고 서로 다른 서버를 연결하는 작업은 고급 개발자들의 섬세한 작업이 필요한 영역이다. 하지만 Docker의 등장으로 서버관리 방식이 완전히 바뀌게 되었다.

도커란

도커는 컨테이너 기반의 오픈소스 가상화 플랫폼이다. 서버에서 이야기하는 컨테이너는 다양한 프로그램, 실행환경을 컨테이너로 추상화하고 동일한 인터페이스를 제공하여 프로그램의 배포 및 관리를 단순하게 해주는 것이다. 백엔드 프로그램, 데이터베이스 서버, 메시지 큐 등 어떤 프로그램도 컨테이너로 추상화 할 수 있다.

컨테이너

격리된 공간에서 프로세스가 동작하는 기술이다. 가상화 기술의 하나지만 기존 방식과는 차이가 있다.
기존의 가상화 방식은 주로 OS 가상화를 하였다. VMWare나 VirtualBox같은 가상머신은 호스트 OS위에 게스트 OS 전체를 가상화하여 사용하는 방식이다. 이 방식은 여러가지 OS를 가상화 할 수 있고 비교적 사용법이 간단하지만 무겁고 느려서 운영환경에선 사용할 수 없었다.
이러한 상황을 개선하기 위해 CPU의 가상화 기술을 이용한 KVM(Kernal-based Virtual Machine)과 반가상화(Paravitualization) 방식의 Xen이 등장했다. 이러한 방식은 게스트 OS가 필요하긴 하지만 전체 OS를 가상화하는 방식이 아니였기 때문에 호스트형 가상화 방식에 비해 성능이 향상되었다. 이러한 기술들은 OpenStack이나 AWS, Rackspace같은 클라우드 서비스에서 가상 컴퓨팅 기술의 기반이 되었다.
전가상화든 반가상화든 추가적인 OS를 설치하여 가상화하는 방법은 어쨋든 성능문제가 있었고 이를 개선하기 위해 프로세스를 격리하는 방식이 등장한다.
리눅스에서는 이 방식을 리눅스 컨테이너라고 하고 단순히 프로세스를 격리시키기 때문에 가볍고 빠르게 동작한다. CPU나 메모리는 딱 프로세스가 필요한 만큼만 추가로 사용하고 성능적으로도 거의 손실이 없다.
하나의 서버에 여러개의 컨테이너를 실행하면 서로 영향을 미치지 않고 독립적으로 실행되어 마치 가벼운 Virtual Machine을 사용하는 느낌을 준다. 실행중인 컨테이너에 접속하여 명령어를 입력할 수 있고 apt-get이나 yum으로 패키지를 설치할 수 있으며 사용자도 추가하고 여러개의 프로세스를 백그라운드로 실행할 수도 있다. CPU나 메모리 사용량을 제한할 수 있고 호스트의 특정 포트와 연결하거나 호스트의 특정 디렉토리를 내부 디렉토리인 것처럼 사용할 수 있다.

이미지

도커에서 가장 중요한 개념은 컨테이너와 함께 이미지라는 개념이다.
이미지는 컨테이너 실행에 필요한 파일과 설정값등을 포함하고 있다. 상태값을 가지지 않고 변하지 않는 다. 컨테이너는 이미지를 실행한 상태라고 볼 수 있고 추가되거나 변하는 값은 컨테이너에 저장된다. 같은 이미지에서 여러개의 컨테이너를 생성할 수 있고 컨테이너의 상태가 바뀌거나 컨테이너가 삭제되더라도 이미지는 변하지 않고 그대로 남아있다.
ubuntu 이미지는 ubuntu를 실행하기 위한 모든 파일을 가지고 있고 MySQL이미지는 debian을 기반으로 MySQL을 실행하는 데 필요한 파일과 실행 명령어, 포트 정보등을 가지고 있다. 좀 더 복잡한 예로 Gitlab 이미지는 centOS를 기반으로 ruby, go, database, redis, gitlab source, nginx 등을 가지고 있다.
이미지는 컨테이너를 실행하기 위한 모든 정보를 가지고 있기 때문에 더 이상 의존성 파일을 컴파일하고 이것저것 설치할 필요가 없다. 새로운 서버가 추가되면 미리 만들어 놓은 이미지를 다운받고 컨테이너를 생성만 하면 된다. 한 서버에 여러개의 컨테이너를 실행할 수 있고, 수십, 수백, 수천대의 서버도 문제없다.
도커 이미지는 Docker hub에 등록하거나 Docker Registry 저장소를 직접 만들어 관리할 수 있다.

레이어 저장방식

도커 이미지는 컨테이너를 실행하기 위한 모든 정보를 가지고 있기 때문에 보통 용량이 수백메가에 이른다. 처음 이미지를 다운받을 땐 크게 부담이 안되지만 기존 이미지에 파알 하나 추가했다고 수백메가를 다시 다운받는다면 매우 비효율적일 수 밖에 없다.
도커는 이런 문제를 해결하기 위해 레이어라는 개념을 사용하고 유니온 파일 시스템을 이용하여 여러개의 레이어를 하나의 파일시스템으로 사용할 수 있게 해준다. 이미지는 여러개의 읽기 전용 레이어로 구성되고 파일이 추가되어나 수정되면 새로운 레이어가 생성된다.
ubuntu 이미지가 A + B + C의 집합이라면, ubuntu 이미지를 베이스로 만든 nginx 이미지는 A + B + C + nginx가 된다. webapp 이미지를 nginx 이미지 기반으로 만들었다면 예상대로 A + B + C + nginx + source 레이어로 구성된다. webapp 소스를 수정하면 A, B, C, nginx 레이어를 제외한 새로운 source(v2) 레이어만 다운받으면 되기 때문에 굉장히 효율적으로 이미지를 관리할 수 있습니다.
컨테이너를 생성할 때도 레이어 방식을 사용한다. 기존의 이미지 레이어 위에 읽기/쓰기 레이어를 추가한다. 이미지 레이어를 그대로 사용하면서 컨테이너가 실행중에 생성하는 파일이나 변경된 내용은 읽기/쓰기 레이어에 저장되므로 여러개의 컨테이너를 생성해도 최소한의 용량만 사용한다.
가상화의 특성상 이미지 용량이 크고 여러대의 서버에 배포하는 것을 감안하면 단순하지만 엄청나게 영리한 설계이다.

이미지 경로

이미지는 url 방식으로 관리하며 태그를 붙일 수 있다. ubuntu 14.04 이미지는 docker.io/library/ubuntu:14.04 또는 docker.io/library/ubuntu:trusty이고 docker.io/library는 생략가능하여 ubuntu:14.04로 사용할 수 있다. 이러한 방식은 이해하기 쉽고 편리하게 사용할 수 있으며 태그 기능을 잘 이용하면 테스트롤백도 쉽게 할 수 있다.

도커 이미지 만들기

Dockerfile

DSL(Domain-Specific Language): 특정한 도멘일을 적용하는데 특화된 컴퓨터 언어
도커는 이미지를 만들기 위해 Dockerfile이라는 파일에 자체 DSL(Domain-specific language) 언어를 이용하여 이미지 생성 과정을 적습니다. 간단하지만 유용한 아이디어인데, 서버에 어떤 프로그램을 설치하려고 이것저것 의존성 패키지를 설치하고 설정파일을 만들었던 과정을 Dockerfile로 관리하면 된다. 이 파일은 소스와 함께 버전 관리되고 원한다면 누구나 이미지 생성과정을 보고 수정할 수 있다.
# vertx/vertx3 debian version
FROM subicura/vertx3:3.3.1
ADD build/distributions/app-3.3.1.tar /
ADD config.template.json /app-3.3.1/bin/config.json
ADD docker/script/start.sh /usr/local/bin/
RUN ln -s /usr/local/bin/start.sh /start.sh
EXPOSE 8080
EXPOSE 7000
CMD ["start.sh"]

도커 설치 및 실행

컨테이너 업데이트

  1. 1.
    새 버전의 이미지를 다운
    • docker pull
  2. 2.
    기존 컨테이너 삭제
    • docker stop
    • docker rm
  3. 3.
    새 버전의 이미지 실행
    • docker run
컨테이너를 삭제하면 컨테이너에서 생성된 파일이 사라진다. 컨테이너 삭제시 유지해야하는 데이터는 반드시 컨테이너 외부 스토리지에 저장해야 한다.

컨테이너 실행하기

도커를 실행하는 명령어는 다음과 같다.
docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]
-p 옵션을 사용하면 호스트의 1234포트를 컨테이너의 6379포트로 연결하였고, localhost:1234로 접속하면 redis를 사용가능하게 되는 것이다.
docker run -p 1234:6379 redis

이미지 만들고 배포하기

도커는 이미지를 만들기 위해 Dockerfile이라는 이미지 빌드용 DSL(Domain Specific Language)파일을 사용한다.
Ruby 웹 어플리케이션을 ubuntu에 배포하는 과정 예제
  1. 1.
    ubuntu 설치
  2. 2.
    ruby 설치
  3. 3.
    소스 복사
  4. 4.
    Gen 패키지 설치
  5. 5.
    Sinatra 서버 실행
쉘 스크립트 예제
# 1. ubuntu 설치 (패키지 업데이트)
apt-get update
# 2. ruby 설치
apt-get install ruby
gem install bundler
# 3. 소스 복사
mkdir -p /usr/src/app
scp Gemfile app.rb [email protected]:/usr/src/app # From host
# 4. Gem 패키지 설치
bundle install
# 5. Sinatra 서버 실행
bundle exec ruby app.rb
Dockerfile로 과정을 옮긴 예제
# 1. ubuntu 설치 (패키지 업데이트 + 만든사람 표시)
FROM ubuntu:16.04
RUN apt-get -y update
# 2. ruby 설치
RUN apt-get -y install ruby
RUN gem install bundler
# 3. 소스 복사
COPY . /usr/src/app
# 4. Gem 패키지 설치 (실행 디렉토리 설정)
WORKDIR /usr/src/app
RUN bundle install
# 5. Sinatra 서버 실행 (Listen 포트 정의)
EXPOSE 4567
CMD bundle exec ruby app.rb -o 0.0.0.0
Docker build
이미지를 빌드하는 명령어는 다음과 같다.
docker build [OPTIONS] PATH | URL
생성할 이미지 이름을 지정하기 위해서는 -t(--tag) 옵션을 사용하면 빌드 가능하다. Dockerfile을 만든 디렉토리로 이동하여 다음 명령어를 입력한다.
docker build -t app .
이미지가 잘 생성됬는 지 확인하려면 docker images 명령어로 확인 가능하다.

이미지 저장소

빌드한 도커 이미지는 서버에 직접 파일을 복사하지 않는 다. 도커 레지스트리는 이미지 저장소를 사용한다. 도커 명령어를 이용하여 이미지를 레지스트리에 푸시하고 다른 서버에서 풀 받아 사용하는 구조이다.
도커 레지스트리는 오픈소스로 무료로 설치할 수 있고 설치형이 싫다면 도커(Docker Inc.)에서 서비스 중인 도커 허브를 사용할 수 있다.

Docker Hub

도커 허브는 도커에서 제공하는 기본 이미지 저장소로 ubuntu, centos, debian등의 베이스 이미지와 ruby, golang, java, python 등의 공식 이미지가 저장되어 있다.

이미지 태그

도커 이미지 이름은 다음과 같은 형태로 구성된다.
[Registry URL]/[사용자 ID]/[이미지명]:[tag]
Registry URL은 기본적으로 도커 허브를 바라보고 있고 사용자 ID를 지정하지 않으면 기본값인 library를 사용한다. 따라서 ubuntu = library/ubuntu = docker.io/library/ubuntu는 모두 동일한 표현이다.

보안

도커 레지스트리는 일반적인 HTTP 프로토콜을 사용하여 이미지를 전송한다. 따라서 SSL(HTTPS)을 사용하지 않으면 이미지 내용이 유출될 수 있다. 이런 보안 이슈 때문에 도커는 기본적으로 로컬 서버를 제외하곤 HTTP 사용을 금지하고 있다.

배포하기

기존 배포 방식

기존에 애플리케이션을 배포하는 방식은 언어, 프레임워크, 웹서버, 리눅스 배포판 등 개발자의 취향에 따라 각각 다른 방식을 사용했다. 새로운 서버를 셋팅하고 한 번에 배포를 성공한다는 것 굉장히 힘든 일이었고 의존성 라이브러리가 제대로 설치되었는지 검증하기도 매우 어려웠다.
ftp, ant, gradle, capistrano 등 다양한 배포툴이 저마다의 장점을 가지고 등장하였고 배포하는 방식을 하나로 정의한다는 건 거의 불가능했다.

컨테이너 배포 방식

컨테이너를 사용하면 어떤 언어, 어떤 프레임워크를 쓰든 상관없이 배포 과정이 단순해 진다. 단순히 이미지를 다운받고 컨테이너를 실행하면 된다.

컨테이너 업데이트

도커를 사용하면 업데이트하는 방식도 배포와 큰 차이가 없다. 최신 이미지를 기반으로 새 컨테이너를 만들고 이전 컨테이너를 중지(삭제)하면 된다. 최신 소스를 어떻게 복사할지 서버 프로세스는 어떻게 재시작할지 고민할 필요가 없다.
컨테이너를 중지하지 않고 교체하는 방법은 없기 때문에 서비스에 영향이 없으려면 로드 벨러서를 사용해야 한다.