Menu

문서정보

목차

이 문서는 도커레퍼런스가 아니다. 기본적인 이해와 사용이 가능하도록 하는데 목적이 있다.

도커(Docker)

도커는 오픈소스기반의 컨테이너 소프트웨어다. 도커 컨테이너는 애플리케이션을 실행하기 위해서 필요한 코드, 런타임, 시스템 툴, 시스템 라이브러리 등을 모두 포함한채로 배포 할 수 있다. 덕분에 컨테이너는 어떤 환경에서도 동일하게 작동한다.

도커는 리눅스 운영체제에서 실행되는 운영체제 레벨의 가상화 소프트웨어다. 따라서 리눅스에서 실행 되는 소프트웨들만 컨테이너로 만들 수 있다. KVM 처럼 다른 운영체제를 실행 할 수 없다.

문서의 내용은 우분투 리눅스 15.10을 기준으로 한다. VirtualBox로 가상 머신을 만들어서 테스트 한다.

도커 컨테이너 기초

도커 컨테이너의 구성

프로세스가 프로그램 이미지에서 시작되듯이, 도커 컨테이너는 도커 이미지(Docker base image)로 부터 만들어진다. 아래 그림을 보자.

 컨테이너 구성에서 실행

워드프레스 애플리케이션 실행을 위한 도커 이미지가 있다. 이 이미지로 부터, 서비스를 위한 여러 개의 컨테이너들을 만들 수 있다. 컨테이너는 도커 이미지의 데이터를 복사하지 않는다. 수정되거나 추가되는 데이터가 있으면 변경된 정보를 diff 파일 형태로 저장을 한다. 따라서
  1. 하나의 이미지로 부터 여러개의 컨테이너를 실행 할 수 다.
  2. 컨테이너 실행을 위해서 데이터를 복사할 필요가 없다.
  3. 변경 혹은 추가되는 데이터는 diff 파일 형태로 저장된다. 이를 위해서 AUFS와 같은 유니온 파일 시스템(Union file system) 혹은 ZFS와 같은 COW파일 시스템을 이용한다.
  4. 변경된 정보만 저장하기 때문에 디스크 공간을 효율적으로 사용 할 수 있다.
변경된 정보를 저장한다는 것은 원본 데이터 + diff 데이터과정이 필요하다는 것을 의미 한다. 데이터가 많아지면 느려질 수 밖에 없는 구조다. 이 문제를 해결 하기 위해서 데이터 만을 저장하는 볼륨을 따로 구성하거나 ZFS나 Btrfs와 같은 COw 파일 시스템을 이용하기도 한다. 볼륨 소개 부분에서 간단히 살펴보도록 하겠다.

아래 그림은 도커의 애플리케이션구성을 묘사하고 있다.

 도커 애플리케이션 구성

  1. 유저는 docker 명령 혹은 REST API를 이용해서 Docker Engine에 컨테이너 명령을 전송한다.
  2. Docker Engine : 유저의 요청을 받아서 containerd로 전달한다. 일종의 API 서버의 역할을 한다.
  3. containerd : 컨테이너 데몬이다. 실제 컨테이너를 실행하고 삭제하는 등의 일을 한다.
  4. runc : OCI - The Open Container Initiative는 리눅스 기반의 산업 표준 컨테이너를의해 만들어진 컨소시엄이다. AWS, CISCO, CoreOS, Docker, Facebook, Google등이 참여하고 있다. runc는 OCI의 첫번째 runtime 구현물이다.
도커를 실행하고 나면 두 개의 프로세스를 확인 할 수 있다.
# ps -ef | grep docker
root       585     1  0 13:24 ?        00:00:00 /usr/bin/docker daemon -H fd://
root       638   585  0 13:24 ?        00:00:00 docker-containerd -l /var/run/docker/libcontainerd/docker-containerd.sock --runtime docker-runc --start-timeout 2m
docker가 Docker Engine의 역할을, docker-containerd가 containerd의 역할을 한다. docker-containerd의 실행 옵션을 보면 runtime으로 docker-runc를 설정한 걸 확인 할 수 있다. 서비스에 따라서 다양한 runtime을 사용 할 수 있다는 의미다.

도커 설치

Get Started with Docker Engine사이트에서 설치 할 수 있다. 자세한 내용은 사이트의 문서를 참고하자. curl로 설치했다.
# curl -fsSL https://get.docker.com/ | sh

도커 버전을 확인해보자. (2016년 6월 5일)현재 도커 최신 버전은 1.11.2다.
# docker version
Client:
 Version:      1.11.2
 API version:  1.23
 Go version:   go1.5.4
 Git commit:   b9f10c9
 Built:        Wed Jun  1 21:54:25 2016
 OS/Arch:      linux/amd64

Server:
 Version:      1.11.2
 API version:  1.23
 Go version:   go1.5.4
 Git commit:   b9f10c9
 Built:        Wed Jun  1 21:54:25 2016
 OS/Arch:      linux/amd64

info명령으로 도커의 상세 정보를 확인해 보자.
# docker info
Containers: 1
 Running: 0
 Paused: 0
 Stopped: 1
Images: 1
Server Version: 1.11.2
Storage Driver: aufs
 Root Dir: /var/lib/docker/aufs
 Backing Filesystem: extfs
 Dirs: 7
 Dirperm1 Supported: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins: 
 Volume: local
 Network: bridge null host
Kernel Version: 4.2.0-16-generic
Operating System: Ubuntu 15.10
OSType: linux
Architecture: x86_64
CPUs: 1
Total Memory: 992.9 MiB
Name: ubuntu
ID: VTAZ:6TEW:VV2G:QF37:TFGW:XCD6:IR3N:PMYR:6LKJ:2KLI:CID7:I4TO
Docker Root Dir: /var/lib/docker
Debug mode (client): false
Debug mode (server): false
Registry: https://index.docker.io/v1/
WARNING: No swap limit support

컨테이너 실행에서 삭제까지

이미지 설치
컨테이너는 도커 이미지로 부터 실행이 된다. 따라서 컨테이너를 실행할 도커 이미지를 로컬 호스트에 설치해야 한다. docker search명령으로 원하는 이미지를 찾을 수 있다. ubuntu 리눅스 이미지를 찾아보자.
# docker search ubuntu
NAME                              DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
ubuntu                            Ubuntu is a Debian-based Linux operating s...   4041      [OK]       
ubuntu-upstart                    Upstart is an event-based replacement for ...   63        [OK]       
rastasheep/ubuntu-sshd            Dockerized SSH service, built on top of of...   28                   [OK]
torusware/speedus-ubuntu          Always updated official Ubuntu docker imag...   26                   [OK]
우분투 리눅스 기반의 많은 이미지들을 찾을 수 있다. 이미지 저장소에는 일반 개발자들이 만든 수많은 이미지들이 올라간다. Mysql만 해도 25개가 넘는 이미지가 검색된다. 이들 중 믿을만한 이미지를 선택해야 하는데, STARTS와 OFFICIAL로 선택 할 수 있다. 그냥 STARTS가 높은 이미지를 선택하면 된다. --stars 옵션으로 최소 starts 갯수를 설정할 수 있다.
# docker search --stars=30 mysql
NAME                 DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
mysql                MySQL is a widely used, open-source relati...   2454      [OK]       
mysql/mysql-server   Optimized MySQL Server Docker images. Crea...   156                  [OK]
centurylink/mysql    Image containing mysql. Optimized to be li...   45                   [OK]
sameersbn/mysql                                                      35                   [OK]

원하는 이미지를 찾았다면, pull 명령으로 로컬 호스트에 설치한다. ubuntu 이미지를 설치해보자.
# docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
5ba4f30e5bea: Pull complete 
9d7d19c9dc56: Pull complete 
ac6ad7efd0f9: Pull complete 
e7491a747824: Pull complete 
a3ed95caeb02: Pull complete 
Digest: sha256:46fb5d001b88ad904c5c732b086b596b92cfb4a4840a3abd0e35dbb6870585e4
Status: Downloaded newer image for ubuntu:latest

images 명령으로 로컬 호스트에 설치된 이미지 목록을 읽을 수 있다.
# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              latest              2fa927b5cdd3        9 days ago          122 MB
컨테이너 실행
run명령으로 컨테이너를 실행할 수 있다. 앞서 설치한 ubuntu 이미지로 부터 컨테이너를 만들어보자.
# docker run --name mytest -i -t ubuntu /bin/bash
root@306e25250c8a:/# 
이 명령을 실행하고 나면, ubuntu 컨테이너에 있는 /bin/bash가 실행된다.

docker ps 를 이용해서 실행 중인 컨테이너 목록을 읽을 수 있다.
# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
306e25250c8a        ubuntu              "/bin/bash"         8 minutes ago       Up 6 seconds                            mytest
컨테이너 start
mytest 컨테이너에서 exit를 해보자. 컨테이너의 실행 명령인 /bin/bash가 종료되면서, 컨테이너도 함께 종료될 것이다. docker ps로 확인해 보면 컨테이너가 보이지 않을 것이다.
# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

docker ps는 실행중인 컨테이너만 보여준다. -a 옵션을 이용해서 모든 컨테이너 목록을 확인 할 수 있다.
# docker ps -a
CONTAINER ID      IMAGE         COMMAND             CREATED             STATUS                      PORTS               NAMES
306e25250c8a      ubuntu        "/bin/bash"         13 minutes ago      Exited (0) 26 seconds ago                       mytest

stop상태의 컨테이너는 start 명령으로 실행 할 수 있다.
# docker start mytest 
mytest
start 명령 다음에, 컨테이너 아이디 혹은 컨테이너 이름을 명시하면 된다. start 명령을 내리면 run 명령시에 등록했던 COMMAND가 실행된다.

attach 명령을 이용해서 실행 중인 컨테이너에 Attach 할 수 있다.
# docker attach mytest
root@306e25250c8a:/# 

exec 명령을 이용해서 컨테이너 명령을 실행 할 수 있다.
# docker exec mytest ls -al /root
total 20
drwx------  2 root root 4096 Jun  5 16:43 .
drwxr-xr-x 34 root root 4096 Jun  5 17:19 ..
-rw-------  1 root root   18 Jun  5 16:50 .bash_history
-rw-r--r--  1 root root 3106 Oct 22  2015 .bashrc
-rw-r--r--  1 root root  148 Aug 17  2015 .profile

컨테이너 stop
사용하지 않는 컨테이너는 stop으로 종료한다.
# docker stop 7087533572a9 
7087533572a9 

stop 된 컨테이너는 ps -a 명령으로 확인 할 수 있다.
# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
7087533572a9        ubuntu              "/bin/bash"         41 hours ago        Exited (0) 25 minutes ago                       mysql
96ae38b69ac0        ubuntu              "/bin/bash"         7 weeks ago         Exited (0) 42 hours ago                         test01
Exited 상태인 것을 확인 할 있다. stop 상태의 컨테이너는 start 명령으로 실행 할 수 있다.

컨테이너 삭제
rm 명령으로 컨테이너를 삭제 할 수 있다.
# docker rm  7087533572a9 

컨테이너 정보 확인

inspect 명령을 이용해서 컨테이너의 상세 정보를 확인 할 수 있다. 내용이 많아서 일부만 간추렸다.
root@ubuntu:~# docker inspect mytest 
[
    {
        "Id": "306e25250c8a5e5c90085b5090ff0c24c7a90bc6debe9065c640edd8240bb020",
        "Created": "2016-06-05T16:42:37.295762438Z",
        "Path": "/bin/bash",
        "Args": [],
        "State": {
            "Status": "running",
        },
        "Image": "sha256:2fa927b5cdd31cdec0027ff4f45ef4343795c7a2d19a9af4f32425132a222330",
        "HostConfig": {
        "Mounts": [],
        "Config": {
            "Hostname": "306e25250c8a",
            "Domainname": ""
        },
        "NetworkSettings": {
            "Bridge": "",
            "EndpointID": "f5b9de3159fc00d3d776ef731d22c8984d0e48dc591b3a89ea463ab97fd0bae0",
            "Gateway": "172.17.0.1",
            "GlobalIPv6Address": "",
            "IPAddress": "172.17.0.2",
            "IPPrefixLen": 16,
            "IPv6Gateway": "",
            "MacAddress": "02:42:ac:11:00:02",
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "b785ed69fabf2ffcfd7df1ec8046d675cba5d2b818eb0b89a5461ef6a78bc0d6",
                    "EndpointID": "f5b9de3159fc00d3d776ef731d22c8984d0e48dc591b3a89ea463ab97fd0bae0",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:02"
                }
            }
        }
    }
]
컨테이너의 아이디, 네트워크, 볼륨등 자세한 정보를 확인 할 있다.

컨테이너 네트워크 구성

컨테이너를 서비스하기 위해서는 네트워크가 구성되야 한다. 도커는 리눅스 브릿지를 이용해서 호스트 레벨 네트워크를 구성한다. ifconfig를 이용해서 도커 네트워크를 확인 할 수 있다.
# ifconfig
docker0   Link encap:Ethernet  HWaddr 02:42:85:32:e2:0a  
          inet addr:172.17.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
          UP BROADCAST MULTICAST  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)
컨테이너 네트워크의 구성은 다음과 같다.

 컨테이너 네트워크 구성

도커의 네트워크는 network inspect 명령으로 확인 할 수 있다.
# docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "b785ed69fabf2ffcfd7df1ec8046d675cba5d2b818eb0b89a5461ef6a78bc0d6",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16"
                }
            ]
        },
        "Internal": false,
        "Containers": {
            "306e25250c8a5e5c90085b5090ff0c24c7a90bc6debe9065c640edd8240bb020": {
                "Name": "mytest",
                "EndpointID": "f5b9de3159fc00d3d776ef731d22c8984d0e48dc591b3a89ea463ab97fd0bae0",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]
컨테이너가 네트워크에 연결되는 과정을 살펴보자. 도커는 컨테이너를 만들 때 veth(Virtual Ethernet device)를 만든다. Veth는 쌍(pair)로 만들어지는데, 하나는 컨테이너에 다른 하나는 브릿지에 연결이 된다. Veth는 양방향 파이프와 같아서 한쪽에서 데이터를 전송하면 다른 한쪽에서 데이터를 수신할 수 있다. 이렇게 해서 브릿지로 전송된 패킷은 리눅스의 라우팅 테이블룰에 따라서 물리적인 네트워크 카드(eth0)으로 경로가 설정되고 바깥 네트워크로 나가게 된다.

컨테이너는 도커네트워크 영역에서 사설 IP를 할당받는다. 컨테이너의 IP를 확인해보자.
# docker inspect -f {{.NetworkSettings.IPAddress}} mytest 
172.17.0.2
사설 IP인 만큼 인터넷 통신이 불가능 한데, 도커는 IP 마스커레이드(Masquerade)를 이용해서 외부 네트워크와 통신을 가능하게 만든다. 마스커레이드는 Source IPDestination IP를 변경해서 인터넷과 통신이 가능하도록 해주는 네트워크 기술이다.

docker 서비스를 실행하고 나서 iptable 정보를 확인해 보자.
# iptables-save 
# Generated by iptables-save v1.4.21 on Tue Jun 14 23:44:59 2016
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [6:377]
:POSTROUTING ACCEPT [6:377]
:DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
#### 생략 ......
11줄에 마스커레이딩 설정을 볼 수 있다. "소스 IP가 172.17.0.0/16 인 패킷은 도커내부로 향하는 것을 제외하고 모두 마스커레이딩을 하라"는 내용을 담고 있다.
포트포워딩
웹 서비스를 위해서 컨테이너에 NginX를 설치했다고 가정해 보자. NginX는 80번 포트로 서비스를 한다. 외부에서 이 컨테이너의 80번 포트에 접근을 하려면 -p 옵션을 이용해서 포트포워딩 설정을 해야 한다.

컨테이너 볼륨

컨테이너는 유니온 파일 시스템을 사용한다. 유니온 파일 시스템은 읽기쓰기를 분리하는 것으로 하나의 파일 시스템을 볼 수 있도록 구현을 하고 있다. 컨테이너는 원본 이미지를 읽기 전용으로 마운트(mount)해서 데이터를 읽고, 쓰기 전용 파일시스템에 변경된 정보를 저장한다.

디스크 공간을 효율적으로 사용 할 수 있는 매우 훌륭한 방법이지만 변경되는 데이터가 많아질 수록 읽기/쓰기 성능이 느려지는 문제가 있다. 일단 유니온 파일 시스템 자체가 (ext4 같은)네이티브 파일 시스템 위에 올라가기 때문에 그 자체로 성능 희생이 따를 수 밖에 없는 구조다.

도커는 데이터 볼륨으로 이 문제를 해결 하고 있다. 외부 파일 시스템을 저장소로 쓰는 개념이다. 아래 그림을 보자.

 데이터 볼륨

컨테이너를 실행 할 때, 외부(호스트 운영체제)에 있는 파일 시스템을 마운트해서 사용 할 수 있다. 도커에서는 -v 옵션으로 데이터 볼륨을 이용 할 수 있다. mysql 컨테이너를 만든다고 가정해보자. mysql 컨테이너는 읽기/쓰기 성능이 중요하다. mysql의 data 디렉토리를 데이터 볼륨으로 구성하면 네이티브와 다름 없는 읽기/쓰기 성능을 얻을 수 있다.
# mkdir /volumes/mysql
# docker run -it --name mysql -v /volumes/mysql:/mnt/mysql ubuntu /bin/bash 
root@7087533572a9:/# 
로컬 호스트에 /volumes/mysql 디렉토리를 만들고, 이 디렉토리를 mysql 컨테이너의 데이터 볼륨으로 제공 했다. 컨테이너에서 마운트 정보를 확인해 보자.
root@7087533572a9:/# mount | grep mysql
/dev/dm-0 on /mnt/mysql type ext4 (rw,relatime,errors=remount-ro,data=ordered)

root@7087533572a9:/# df -h
Filesystem      Size  Used Avail Use% Mounted on
none            6.5G  1.5G  4.7G  24% /
tmpfs           497M     0  497M   0% /dev
tmpfs           497M     0  497M   0% /sys/fs/cgroup
/dev/dm-0       6.5G  1.5G  4.7G  24% /mnt/mysql
LVM으로 직접 마운트 해서 사용하고 있다. df를 실행해보면 호스트 파일 시스템을 그대로 사용하고 있는 것도 확인 할 수 있다. 이제 mysql의 데이터 디렉토리를 /mnt/mysql로 설정해서 실행하면 된다. 도커 기반으로 애플리케이션을 배포한다면, 주요 데이터들은 데이터 볼륨을 이용하도록 구성하면 된다.

docker inspect로 자세한 데이터 볼륨 정보를 확인 할 수 있다.
# docker inspect mysql
        "Mounts": [
            {
                "Source": "/volumes",
                "Destination": "/mnt/mysql",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],
디렉토리가 아닌 파일 단위로도 마운트를 할 수 있다. /etc/hosts, /etc/resolv.conf와 같은 설정파일을 컨테이너와 공유 하고 싶을 때 사용 할 수 있다.
# docker run -it --name mysql -v /etc/resolv.conf:/etc/resolv.conf:ro ubuntu /bin/bash 

성능 외에 네트워크 파일 시스템을 사용 할 수 있다는 것도 큰 장점이다. 컨테이너 기반의 가상화 인프라를 만들었다고 가정해보자. 이 인프라는 여러 개의 컴퓨터 노드들로 구성이 될 것이다. 컨테이너들은 이들 노드들을 자유롭게 이동 할 수 있어야 한다.

 네트워크 파일 시스템을 이용한 컨테이너 이동

그림을 보자. 노란색 컨테이너가 Host OS 1 에서 실행 중이다. 그런데, 컨테이너가 Host OS 2로 이전하게 됐다. 호스트에 문제가 생겨서 혹은 자원이 부족 등의 이유로 컨테이너는 호스트들 사이를 떠나닌다. 이때 데이터 볼륨이 원격 스토리지에 있다면, 컨테이너는 자유롭게 떠다닐 수 있을 것이다.

원격 스토리지 구성과 컨테이너 볼륨은 XX장에서 자세히 다룬다.

컨테이너 라이프 사이클

컨테이너의 실행에서 삭제까지의 라이프 사이클이다.

 컨테이너의 라이프 사이클

  1. 도커 이미지로 부터 시작한다.
  2. 도커 이미지로 부터 컨테이너를 만든다. create 명령으로 만들 수 있다. run 명령은 create후 start까지 수행한다.
  3. 컨테이너를 실행(start) 한다.
  4. 컨테이너를 중지(stop) 한다.
  5. 더 이상 필요 없는 컨테이느는 삭제 한다.

도커 이미지 관리

도커 이미지 저장소

도커 이미지는 저장소에 저장이 된다. 개발자는 저장소에 있는 이미지를 pull해서 컨테이너를 만들고, 컨테이너로 부터 새로운 이미지를 만들어서 다시 저장소에 저장해서 다른 개발자들과 공유 할 수 있다. 이 도커 저장소를 Docker registry server라고 부른다.

특별히 레지스트리 서버를 설정하지 않을 경우에는 https://index.docker.io/v1을 레지스트리 서버로 사용한다. 레지스트리 서버 정보는 docker info로 확인 할 수 있다.
# docker info
Containers: 1
 Running: 0
 Paused: 0
 Stopped: 1
Images: 2
Server Version: 1.11.0
// .... 생략
Registry: https://index.docker.io/v1/
WARNING: No swap limit support
이 저장소는 인터넷에 공개된 저장소다. 회사나 개인 프로젝트 관리를 위해서 프라이빗 레지스트리 서버(private registry server)를 구성 할 수도 있다. private registry server는 XXX장에서 자세히 다루도록 하겠다.

commit

도커 이미지는 읽기 전용으로 열리고, 수정된 데이터는 diff형태로 쌓인다. 이것을 배포 하려면 다시 이미지 형태로 만들어야 할 것이다. commit명령을 내리면 원본이미지의 내용이 업데이트 된다.

mysql을 이미지를 만든다고 가정해보자. 개발자는 ubuntu 이미지로 부터 컨테이너를 만들고, apt-get을 이용해서 mysql 패키지를 설치한다.
# docker run -it --name mysql ubuntu /bin/bash
root@620d293b5399:/# apt-get install mysql-server
이제 호스트 운영체제에서 commit을 하면 된다.
# docker commit -a "yundream <yundream@gmail.com>" -m "mysql server install" mysql mysql:0.1
sha256:32eb9202363840a8354b3e1b48da1036a8580850d7218eca39cd220b6c6b809b
-a는 author, -m은 커밋 메시지를 설정하기 위해서 사용한다.

도커 이미지 목록을 확인해 보자.
# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mysql               0.1                 32eb92023638        30 seconds ago      340.1 MB
ubuntu              latest              b72889fa879c        9 weeks ago         188 MB

export & import

현재 이 도커 이미지는 로컬 호스트에 저장돼 있다. 다른 유저와 공유하려면 레지스트리 서버에 이미지를 등록하거나 export해야 한다. 아직 프라이빗 레지스트리 서버를 가지고 있지 않으므로 export로 공유하는 방법을 알아보자.

앞서 개발한 mysql:0.1이미지를 export 해보자. export할 컨테이너를 실행한 다음 export 명령을 실행하면 된다.
# docker run -it --name mysql mysql:0.1 /bin/bash
# docker export mysql > mysql-0.1.tar

이 파일을 공유 받은 유저는 import명령을 이용해서 이미지로 복원 할 수 있다.
# cat mysql-0.1.tar | docker import - mysql/local
sha256:4de49ed5e0af470f642007b2ed8b1ec0ed6cae2245310fd624ce06f831411bc1

# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mysql/local         latest              4de49ed5e0af        19 seconds ago      314.9 MB
....

Dockerfile

도커는 Dockerfile에 기록된 지침을 읽어서 자동으로 이미지를 만들수 있다. 일종의 Makefile이라고 보면 되겠다. 앞서 commit나 export & import로 이미지를 만드는 방법을 살펴봤는데, 실제 환경에서는 Dockerfile을 이용해서 이미지 형상을 관리한다. Dockerfile에 사용 하는 instructions을 간단히 살펴보고 예제 도커 파일을 만들어 보겠다.

FROM
이미지를 만들 base 이미지를 설정한다. Dockerfile의 첫 줄은 보통 FROM 으로 시작한다. 아래 예제는 ubuntu이미지를 기반으로 새로운 이미지를 만들라는 의미다.
FROM ubuntu

MAINTAINER
Dockerfile의 관리자가 누구인지를 알려주기 위해서 사용한다. 실행되는 지시어는 아니다.
MAINTAINER yundream

RUN
베이스 이미지에서 실행 할 명령이다. 예를들어 ubunut 기반에서 APM(Apache+PHP5+Mysql)스택 이미지를 만들고 싶다면, 아래와 같이 설정하면 된다.
FROM ubuntu
RUN apt-get update
RUN apt-get install -y apache2 
RUN apt-get install -y php5
RUN apt-get install -y mysql-server

CMD
RUN은 이미지 빌드시간에 호출이 된다. 반면 CMD는 컨테이너가 start 할 때 실행된다.
CMD ["echo", "hello world"]
이 컨테이너는 start 될 때 echo "hello world"가 실행된다. 만약 docker run시 실행 명령을 설정할 경우 이 명령이 새로운 CMD로 설정되며, 이전 CMD는 무시된다.

ENTRYPOINT
컨테이너는 프로세스다. 따라서 이미지로 부터 컨테이너를 start 하려면 어떤 프로그램을 실행 해야 한다. ENTRYPOINT는 컨테이너를 start 할 때, 실행하는 프로그램이다. 도커의 기본 ENTRYPOINT는 /bin/sh -c다. 유저가 docker run ubuntu /bin/bash를 실행하면 컨테이너는 /bin/sh -c /bin/bash를 실행한다.

즉 엄밀하게 따지면 CMD는 프로그램을 실행하는게 아니고, ENTRYPOINT의 매개변수를 설정하는 옵션이라고 할 수 있다.

webserver라는 웹 애플리케이션 서버를 도커 이미지로 만든다고 가정해 보자. webserver는 컨테이너 start와 함께 실행을 해야 한다. 크게 두가지 방법이 있을 수 있다.
CMD /opt/joinc/webserver
이렇게 하면 /bin/sh -c /opt/joinc/webserver가 실행된다. 이미지는 반드시 /bin/sh 를 포함하고 있어야 한다.

ENTRYPOINT ["/opt/joinc/webserver"]
이렇게 하면 /opt/joinc/webserver가 직접 실행된다. 유저가 docker run ubuntu "- help"를 하면 - help가 webserver의 매개변수로 전달된다.

ENTRYPOINT와 CMD를 조합하면, 컨테이너에 간단하게 매개변수나 파일을 전달 할 수 있다. 파일의 경우에는 전달할 파일을 포함한 디렉토리를 데이터 볼륨으로 만들고, 이 경로를 매개변수로 설정하면 된다. 예를 들어서 /tmp/webserver.conf를 전달하기를 원한다면 아래와 같이 하면 된다.
# docker run -it -v /tmp/webserver.conf:/opt/joinc/conf/webserver.conf:ro myimage -c /opt/joinc/conf/webserver.conf
ENV
ENV는 환경 변수(Environment variable)를 선언하기 위해서 사용한다. 이 변수는 Dockerfile 내에서 사용 할 수 있다.
FROM busybox
ENV foo /bar
WORKDIR ${foo}     # WORKDIR /bar
ADD . $foo         # ADD . /bar
COPY \$foo /quux   # COPY $foo /quux

COPY
호스트에 있는 파일이나 디렉토리를 이미지로 복사할 수 있다.
COPY README.md /opt/joinc/doc
ADD
COPY와 달리 URL을 지원한다. 원격에 있는 파일을 이미지에 복사할 수 있다. 호스크에 있는 파일도 복사 할 수 있기 때문에 COPY 대신 사용 할 수도 있다.
ADD http://www.example.com/downloads/sample.tar.gz /usr/src

VOLUME
컨테이너에 호스트 볼륨을 제공하기 위해서 사용한다.
VOLUME /data

WORKDIR
작업디렉토리를 설정한다. RUN, CMD, ENTRYPOINT, COPY, ADD 등의 지시어는 이 디렉토리에서 실행된다.
WORKDIR /opt
WORKDIR join
RUN pwd
/opt/joinc가 출력될 것이다.

예제

Hello World를 출력하는 웹 서버를 개발하고 도커이미지 만들고 컨테이너로 서비스한다

웹 애플리케이션

유저가 페이지를 요청하면 "Hello world"를 반환하는 간단한 애플리케이션을 만들었다. 프로그램의 이름은 helloworld.sh 다.
#!/bin/sh
while true ; do  printf "HTTP/1.1 200 OK\n\n Hello world" | nc -l -p 80  ; done

Base 이미지 선택

ubuntu 대신에 busybox를 선택했다. busybox는 리눅스의 기본적인 명령의 하나의 실행파일로 제공하는 프로그램이다. 원래는 한장의 플로피 디스크로 구성 가능한 복구 디스크나 인스톨 디스크를 만드는게 목적이었다. 이 것을 도커 이미지 형태로 만들어서 제공을 하고 있다. 단지 하나의 실행파일만 포함하고 있기 때문에, 크기가 매우 작다. busybox 이미지를 pull 한 다음 크기를 비교해 보았다.
# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              latest              b72889fa879c        9 weeks ago         188 MB
busybox             latest              47bcc53f74dc        3 months ago        1.113 MB
이미지의 크기가 1MB 정도다. 작고 가벼운 컨테이너를 만들기 위해서 널리 사용하고 있다.

도커 이미지 만들기

Dockerfile을 이용해서 도커 이미지를 만들기로 했다. 홈디렉토리 밑에 workspace 디렉토리를 만든 다음, helloworld.sh 프로그램을 복사했다. 그리고 아래와 같은 Dockerfile 을 만들었다.
FROM busybox
RUN mkdir -p /opt/joinc
COPY helloworld.sh /opt/joinc
CMD /opt/joinc/helloworld.sh
docker build 명령으로 이미지를 만든다.
# docker build -t helloworld/joinc .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM busybox
 ---> 47bcc53f74dc
Step 2 : RUN mkdir -p /opt/joinc
 ---> Using cache
 ---> 40da7c7b579a
Step 3 : COPY helloworld.sh /opt/joinc
 ---> 6755ff1b09b4
.... 생략
-t 옵션으로 이미지 이름을 설정했다. 이미지가 만들어졌는지 확인해보자.
# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
helloworld/joinc    latest              ed0352f268e4        5 minutes ago       1.113 MB
mysql/local         latest              4de49ed5e0af        22 hours ago        314.9 MB
컨테이너를 실행했다.
# docker run -d helloworld/joinc
6d6a0c784c93f82752959b59c66dd51fa29e3af9716741554f581beefd4ee650
curl로 테스트
# curl 172.17.0.2 
Hello world