Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

용어

진행하기 전에 용어는 정리해야 겠다.
  • VM : Hypervisor 위에 올라가는 Virtual machine.
  • Dock Image : 줄여서 이미지. Dock의 최소 관리 단위. 이 이미지로 부터 컨테이너가 실행된다.
  • Dock 컨테이너 : 줄여서 컨테이너. 이미지로 부터 만들어진 실행중인 도커 프로세스
  • Docker : 위의 이미지와 컨테이너를 관리하는 소프트웨어
  • Ubuntu 17.04 : 문서의 내용은 Ubuntu 17.04를 기준으로 테스트했다.
  • 호스트 운영체제 : docker 이미지와 컨테이너를 관리하는 운영체제
  • AWS : Amazon Web Service.

Docker에 대해서

Docker는 애플리케이션 영역에서 작동하는 가상화 플랫폼이다. 개발자와 시스템관리자는 docker를 이용해서 빠르고 쉽게 그들이 개발한 소프트웨어들을 격리된 운영체제 환경에서 테스트하고 배포할 수 있다.

물론 VM(가상머신)이라고 부르는 도구들을 이용해서 이런 일들을 할 수 있다. AWS와 같은 서비스를 이용한다면, (소프트웨어까지 모두 포함한)운영체제의 스냅샷을 떠서 배포할 수 있다. vagrant와 같은 가상화 기반 도구를 이용하면, 통합된 개발환경을 만들 수 있다.

실제 docker의 기본 개념의 기존의 가상화와 크게 다르지 않다. 단 접근방식에 있어 차이가 있다. 기존의 가상화라고 하면, 하드웨어와 그위에 올라가는 운영체제까지 한번에 띄우는 운영체제 가상화를 의미한다. 이런 방식의 가상화는 (기본적로 하드웨어+소프트웨어 조합이라서)이해하기가 쉽다는 장점과 (독립된 운영체제이기 때문에)격리가 매우 쉽다는 장점이 있다.

반면 단점도 있는데,

VM 방식의 경우 APM(Apache+PHP+Mysql)으로 구성된 간단한 웹서버를 배포하려면, 운영체제까지 전부 배포해야 한다. 3개의 소프트웨어와 약간의 하드디스크, 메모리 공간이 필요할 뿐인데, 운영체제를 모두 포함한 거대한 이미지를 배포해야 한다는 것은 상당한 낭비다. 운영체제를 실행하는 과정을 거쳐야 하기 때문에, 서비스 가동까지 꽤나 많은 시간이 걸리는 것도 단점이다.

도커는 하드웨어를 추상화하지 않는다. 운영체제의 자원을 모두 공유하며, 사실상 프로세스로 간주된다. 따라서 CPU, 메모리, 디스크 공간을 효율적으로 사용 할 수 있다. 운영체제를 가동할 필요가 없기 때문에, 매우 빨리 실행할 수 있다는 장점도 있다. 실제 실행해보면 일반 프로세스 실행하는 것과 별 차이가 없다.

그러면서도 가상화의 장점들인 네트워크, 디스크, 유저 격리를 할 수 있다. 사용자 입장에서는 컨테이너와 VM의 차이를 (거의)느낄 수 없다. 이런 도커의 특징은 특히 "애플리케이션의 서비스"에 아주 적합하다. Paas 혹은 Saas 애플리케이션의 개발과 배포에 유용하게 사용 할 수 있다.

컨테이너 vs VMs

Docker는 리눅스 컨테이너(Container) 시스템인 libcontainer 를 이용해서 하나의 운영체제위에서 격리된 컴퓨팅 환경을 운영할 수 있도록 도와주는 애플리케이션 레벨 가상화 소프트웨어다.

Docker는 AUFS라는 파일 시스템을 사용하는데, 읽기 전용 부분과 쓰기 부분이 따로 있고 이들을 병합할 수 있다. 해서 읽기 전용으로는 공통으로 배포되는 정보를 저장해서 다른 개발자들과 공유할 수 있다. 예를들어 Ruby on Rails 환경을 만들었다면, ROR은 읽기 전용 공간에 저장해서 다른 개발자들에게 배포한 후, 쓰기 공간을 이용해서 프로그래밍하는 식의 개발이 가능하다.

VM과 컨테이너 방식을 비교한 그림이다.

컨테이너 방식이 훨씬 효율적이라는 걸 예상할 수 있다. VM 보다 더 가볍고, 더 빠르고, 더 작은 공간만을 필요로 한다. 기본적으로 프로세스 단위로 작동하기 때문에, 수백개이상의 컨테이너를 별 어려움 없이 돌릴 수 있다. Full virtualized 방식의 가상화 시스템에서 VM을 실행하려면, 수분의 시간이 걸린다. 컨테이너 방식에서는 (운영체제가 올라오는게 아니기 때문에)수 초면 된다.

Dock 이미지 관리

한장의 그림으로 Dock 이미지 관리를 설명할 수 있다.

  1. Docker Public Registery : 공개 Dock 이미지 저장소다. 다양한 종류의 애플리케이션을 탑재한 이미지들이 등록돼 있다.
  2. Pull Image : 개발자는 공개 Dock 이미지 저장소로 부터, 원하는 이미지를 찾은(search)다음 개발 PC로 pull해서 개발한다.
  3. Image commit : 개발하면서 변경된 부분이 있다면, commit을 한다.
  4. Docker Private Registery : 개발이 끝난 이미지는 private dock 이미지 저장소에 Push 한다.
  5. 마지막으로 서비스 시스템에서 이미지를 pull해서 실행하면 배포가끝난다.
Git을 이용한 소프트웨어 개발/배포 과정과 매우 유사하다.

Docker 개발 Tutorial

환경

Ubuntu Linux 14.04가 설치된 개인 노트북에서 테스트를 진행했다.

프로젝트 시나리오

Docker 개발을 위한 나름대로의 간단한 시나리오를 만들었다. 이 시나리오에 따라서 Docker 테스트를 진행한다.
  • Apache+PHP5 기반의 웹서비스를 개발하기로 했다.
  • 서비스의 이름은 "HelloWorld"로 Hello world를 출력하는 하는 php 파일을 하나 가지고 있다.
  • 웹 서비스를 위해 필요한 것들만 배포하고 싶다. 해서 기본적인 기능만 담고 있는 작은 우분투 이미지를 pull해서 개발한다.
  • Apache, php5, Mysql을 설치하고 Commit한다. 이 이미지의 이름은 "my_app"이다.
  • my_app 이미지를 Docker private registery에 등록 한다.
  • 이제 개발자들은 my_app을 pull해서, 애플리케이션을 개발할 수 있다.
  • HelloWorld API 개발을 끝냈다면, 서비스 서버에서 pull한다.
이 경우 Apache+PHP5 까지를 이미지로 떠서 배포를 하고, HelloWorld 애플리케이션은 "git"으로 관리하는 방법을 사용할 수 있겠으나, docker 사용성을 테스트하는게 목적이니 그냥 HelloWorld까지 이미지로 만든다음 pull해서 서비스 하기로 했다.

설치

docker 오피셜 사이트에서 설치할 수 있다. docker-ee(Enterprise Edition)docker-ce(Community Edition) 두개의 버전이 있다. 나는 CE 버전을 선택했다.

요즘 우분투 리눅스는 도커를 기본으로 배포하기는 한데, 좀 오래된 버전인 경우가 많다. 지금 설치돼 있는 도커를 삭제하고 새로 깔기로 했다.
$ sudo apt-get remove docker docker-engine docker.io

docker 설치를 위해서 몇 개 커널 패키지를 다운로드 한다.
$ sudo apt-get update
$ sudo apt-get install \
    linux-image-extra-$(uname -r) \
    linux-image-extra-virtual

HTTPS 기반으로 레포지토리를 이용하기 위한 설정을 한다.
$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common

Docker 오피셜 GPG Key를 추가한다.
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo apt-key fingerprint 0EBFCD88
pub   rsa4096 2017-02-22 [SCEA]
      9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88
uid           [ unknown] Docker Release (CE deb) <docker@docker.com>
sub   rsa4096 2017-02-22 [S]

도커 레포지토리를 추가 한다.
$ sudo add-apt-repository \
   "deb [arch=armhf] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

docker-ce 버전을 설치하자.
$ apt-get install docker-ce

도커 버전을 확인해보자.
$ docker info
Containers: 8
 Running: 0
 Paused: 0
 Stopped: 8
Images: 121
Server Version: 17.06.0-ce
....

컨테이너 실행

프로젝트에 사용할 이미지를 선택하기 위해서 Docker public Registery에 등록된 공개 이미지를 검색했다. 우분투 기반운영체제로 찾을 거라서 ubuntu를 키워드로 검색
# docker search ubuntu
NAME                             DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
ubuntu                           Ubuntu is a Debian-based Linux operating s...   1713      [OK]       
ansible/ubuntu14.04-ansible      Ubuntu 14.04 LTS with ansible                   51                   [OK]
torusware/speedus-ubuntu         Always updated official Ubuntu docker imag...   25                   [OK]
ubuntu-upstart                   Upstart is an event-based replacement for ...   25        [OK]       
tutum/ubuntu                     Ubuntu image with SSH access. For the root...   22                   [OK]
sequenceiq/hadoop-ubuntu         An easy way to try Hadoop on Ubuntu             17                   [OK]
dorowu/ubuntu-desktop-lxde-vnc   Ubuntu with openssh-server and NoVNC on po...   12                   [OK]
....
  1. NAME : 이미지의 이름
  2. DESCRIPTION : 이미지에 대한 설명
  3. STARTS : 인기도.
  4. OFFICIAL : docker.com 에서 직접 지원하는 이미지.
  5. AUTOMATED : Dockerfile를 이용 Docker Hub에서 자동으로 빌드한 이미지다. 일반적으로 Official 이미지를 기본 이미지로 해서 만들어진다.
나는 ubuntu 이미지를 다운로드 하기로 했다. pull로 이미지를 다운로드 할 수 있다.
# docker.io pull ubuntu
Pulling repository ubuntu
c5881f11ded9: Download complete 
463ff6be4238: Download complete 
195eb90b5349: Download complete 
3db9c44f4520: Download complete 
58faa899733f: Download complete 

이미지를 실행해서 컨테이너를 만든 후 운영체제 정보를 확인했다.
# docker.io run -i -t ubuntu /bin/bash
WARNING: Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : [8.8.8.8 8.8.4.4]
root@1e29e208fffe:/# cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04 LTS"

컨테이너 정보를 확인해보자.
# docker.io ps
CONTAINER ID    IMAGE           COMMAND       CREATED          STATUS          PORTS     NAMES
1e29e208fffe    ubuntu:14.04    /bin/bash     35 seconds ago   Up 35 seconds             angry_albattani

컨테이너의 네트워크 정보를 확인했다.
root@1e29e208fffe:/# ifconfig
eth0      Link encap:Ethernet  HWaddr 2e:8c:7a:2a:b5:a2  
          inet addr:172.17.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::2c8c:7aff:fe2a:b5a2/64 Scope:Link
          UP BROADCAST RUNNING  MTU:1500  Metric:1
          RX packets:8 errors:0 dropped:0 overruns:0 frame:0
          TX packets:10 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:648 (648.0 B)  TX bytes:828 (828.0 B)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
172.17.0.2이라는 IP가 할당됐다.

컨테이너의 네트워크는 어떻게 관리되는 되는 건지 궁금해서 호스트 운영체제에서 확인해봤다.
# ifconfig 
docker0   Link encap:Ethernet  HWaddr be:bd:3e:84:50:83  
          inet addr:172.17.42.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::8494:5cff:fed3:640e/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:10 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:688 (688.0 B)  TX bytes:648 (648.0 B)
# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.11.1    0.0.0.0         UG    0      0        0 wlan0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0

docker를 위한 브릿지 인터페이스를 확인할 수 있다.
# brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.bebd3e845083       no              vethbf4c

현재 설치한 우분투 이미지는 최소한의 패키지만 가지고 있다. 예컨데 ssh 같은 것도 설치되있지 않다. 하여, ssh를 설치하기로 했다.
root@1e29e208fffe:/# apt-get install ssh

1e29e208fffe는 현재 ssh를 설치했다. 이 컨테이너를 두고두고 써먹기 위해서 my_img로 commit(저장)한다.
# docker.io commit 1e29e208fffe my_img
1f19ac8d36e3c1666331f0260cba13b41cc20145e86e92759e9578ffe3af1cab

이미지가 저장됐는지 확인해보자. 이미지의 이름은 my_app, 아이디 1f19ac8d36e3로 저장된걸 확인할 수 있다..
# docker.io images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
my_app              latest              1f19ac8d36e3        14 seconds ago      323.1 MB
ubuntu              utopic              58faa899733f        6 days ago          196 MB
...

내가 만든 이미지 my_app를 실행했다. 컨테이너 아이디가 e47eb44c6c99인 컨터이너가 실행됐다.
# docker.io run -i -t 1f19ac8d36e3 /bin/bash
WARNING: Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : [8.8.8.8 8.8.4.4]
root@e47eb44c6c99:/#

다음으로 넘어가기 전에 내가 한일을 간단히 정리해 보자.
  1. Public docker registry에서 ubuntu image를 pull 했다.
  2. pull한 이미지로 부터 컨테이너를 만들었다.
  3. 깡통 컨테이너다. 적어도 ssh는 설치해야지..
  4. 변경사항(ssh 설치)를 적용한 새로운 이미지를 commit 했다.
이제 Apache와 PHP를 설치한 HelloWorld 프로젝트를 위한 이미지를 만들면 된다.

Apache, PHP5 설치

my_app 이미지로 컨테이너를 띄운다음 apache와 php를 설치한다.
root@e47eb44c6c99:/# apt-get install apache2
root@e47eb44c6c99:/# apt-get install php5 

Hello world를 서비스하기 위해서 index.php 파일 추가.
# cat /var/www/html/index.php
<?php
  echo "Hello World.";
?>
이걸로 HelloWorld 프로젝트용 이미지를 만들었다.

현재 컨테이너를 이미지로 commit 하기

commit 명령을 이용해서 현재 컨테이너의 상태를 저장할 수 있다.
# docker.io commit 315657d3cd9f my_app
f6899ddd85761637834727d5512548976920449a4f5d59a0a5d8e42d0e37d30b

Image 목록을 확인해보면, my_app 이라는 이름으로 현재 컨테이너가 저장된걸 확인할 수 있을 거다.
# docker.io images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
my_app              latest              f6899ddd8576        45 seconds ago      316.2 MB

새로 만든 이미지로 컨테이너를 만들어보자.
# docker.io run -i -t my_app /bin/bash
WARNING: Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : [8.8.8.8 8.8.4.4]

굳.. 잘 되네.. apache와 php까지 떠 있다. 이제 개발자들이 지금 만든 이미지를 공유해서 사용할 수 있도록 Private Registery에 등록하면 되겠다.

Private Registery를 이용한 docker 이미지 배포

Docker Registery에 등록하기

Register 서버를 만들기 위한 삽질을 또 어떻게 해야 하나 고민을 했는데, 다행히 public docker registery에 registy 이미지가 있었다.
# docker.io pull registry:latest

설치된 이미지를 확인했다.
# docker.io images | grep registry
registry            latest              6e526128fd5b        2 weeks ago         553.1 MB

registry이미지를 이용해서 컨테이너를 실행한다.
# docker.io run --name private-registry -d -p 5000:5000 registry
WARNING: Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : [8.8.8.8 8.8.4.4]
  1. --name : 컨테이너의 이름은 private-registry다.
  2. -d : 데몬모드로 실행했다.
  3. -p : 포트포워딩. localhost:5000을 컨테이너:5000으로 포트포워딩 했다.
컨테이너 정보를 확인
# docker.io ps 
CONTAINER ID  IMAGE             COMMAND                CREATED         STATUS         PORTS                   NAMES
f3a4296938db  registry:latest   /bin/sh -c exec dock   3 seconds ago   Up 3 seconds   0.0.0.0:5000->5000/tcp  private-registry

private-registry가 떳는지 curl로 확인했다.
~# curl -v localhost:5000
* Rebuilt URL to: localhost:5000/
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 5000 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:5000
> Accept: */*
> 
< HTTP/1.1 200 OK
* Server gunicorn/18.0 is not blacklisted
< Server: gunicorn/18.0
< Date: Mon, 07 Jul 2014 15:24:31 GMT
< Connection: keep-alive
< Expires: -1
< Content-Type: application/json
< Pragma: no-cache
< Cache-Control: no-cache
< Content-Length: 39
< X-Docker-Registry-Version: 0.7.3
< X-Docker-Registry-Config: dev
< 
* Connection #0 to host localhost left intact

my_app 이미지를 push해보자. Push하기전에 이미지에 이름을 붙여줘야 한다. 이미지 이름은 my_app_demo로 설정했다.
# docker.io tag my_app localhost:5000/my_app_demo
# docker.io images | grep my_app_demo
localhost:5000/my_app_demo   latest              f6899ddd8576        About an hour ago   316.2 MB

Private registery에 이미지를 push 한다.
# docker.io push localhost:5000/my_app_demo
The push refers to a repository [localhost:5000/my_app_demo] (len: 1)
Sending image list
Pushing repository localhost:5000/my_app_demo (1 tags)
511136ea3c5a: Image successfully pushed 
d7ac5e4f1812: Image successfully pushed 
2f4b4d6a4a06: Image successfully pushed 
83ff768040a0: Image successfully pushed 
6c37f792ddac: Image successfully pushed 
e54ca5efa2e9: Image successfully pushed 
f6899ddd8576: Image successfully pushed 
Pushing tag for rev [f6899ddd8576] on {http://localhost:5000/v1/repositories/my_app_demo/tags/latest}

이미지가 제대로 등록됐는지 확인해 보자.
# curl http://localhost:5000/v1/repositories/my_app_demo/tags/latest
"f6899ddd85761637834727d5512548976920449a4f5d59a0a5d8e42d0e37d30b"

Registery에 있는 이미지에서 컨테이너 만들기

이제 Private registery에 있는 이미지로 컨테이너를 만들면 된다.
# docker.io run -i -t localhost:5000/my_app_demo /bin/bash
WARNING: Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : [8.8.8.8 8.8.4.4]
root@309eff26360d:/#

앞으로 할일

우선순위 별로
  1. Docker cheat sheet 작성
  2. Docker 네트워크 환경 구축
  3. Chef와 docker를 이용한 배포 자동화
  4. S3에 Private registery 구축하기

참고