Macで初めてDockerを使う際に覚えておくと良いこと

## はじめに
[DockerをMacに入れた。Docker設定メモ](http://qiita.com/syou007/items/2bb4c8cd33ecbef1214e)を書いて、Dockerに関しての知識が随分深まった。

はじめは`docker-compose`を使ってサイトを構築してみる記事を書こうと思ったのだが、あまりにも簡単にできてしまったので`docker-compose`も含めた今まで得た知識をメモとして残しておきます。

この記事は僕のように全く`Docker`を使ったことがない人向けの記事です。
これから`Docker`で遊びたい方の役に立てればと思います。

[前回](http://qiita.com/syou007/items/2bb4c8cd33ecbef1214e)の記事の書き直しになっている部分もあります。

## Dockerとは？
`Docker`の位置付けは`Vagrant`などと同じ仮想OSに近いポジションにいると思います。
しかし、大きく違う点として`Docker`はイメージベースで駆動するため構築や(DockerHubを使用した)コピーが簡単にできるところにあります。

`Vagrant`とは違いプロセス内で実行されるため起動と終了があっという間に行えます。
（`Vagrant`はプロセス単位で建てるので、複数起動するとメモリを大量に消費する。）

この利点を使うと、人が作ったイメージ（例えばRedmineなど）をもらってそのまま起動（コンテナ作成）すればあっという間にサイトなどの構築ができます。

僕もまだ使い始めなのでうまく活用できてませんが、この技術をうまく使うと自社内のツールなどを簡単に構築できるのではないでしょうか？

### イメージの受け渡しについて
ただ、作成したイメージの受け渡しをするためには`DockerHub`を使用する必要があるため企業で使用するためにはプライベートリポジトリ（有料）を使う必要がありそうです。
→export/importを使えば可能でした。後述します。

金額は調べてませんが、十分使う価値があるのではないでしょうか？

僕は環境構築が苦手なので、誰かがイメージで環境を作ってくれるととても助かりますw

## Dockerのインストール
Dockerを使うにあたって以下をはじめに入れておく必要がある。

* VirtualBox
* HomeBrew

Dockerを使うためには以下のコマンドを実行する。

```console
$ brew install docker docker-machine
```

人が作ったイメージを使うのに便利なので`docker-compose`も入れておく。

```console
$ brew install docker-compose
```

インストールができたか確認する。

```console
$ docker -v
Docker version 1.8.1, build d12ea79h
$ docker-machine -v
docker-machine version 0.4.1 (HEAD)
```

## VMのインストールと削除
DockerはVM上で動くので`docker-machine`を使ってVMをインストールします

```console
$ docker-machine create --driver virtualbox dev
```

`dev`の部分はVMの名前なので何でも良いです。
他のVMの名前と重ならないものを指定してください。

VM起動後に`Docker`コマンドが操作するために、VMの設定を`Docker`へ伝えます。

```console
$ docker-machine env dev
```

このコマンドをみると、設定される項目が表示されます。
これをそのまま起動コマンドに組み込みます。

```console
$ vi ~/.bash_profile 
# 一番最後に設定
eval "$(docker-machine env dev)"
# 反映
$ source ~/.bash_profile 
```

これで`Docker`が使えるようになります。

また、VMを削除する場合は以下のコマンドで削除できます。

```console
$ docker-machine rm dev
```

## docker-machineで使うコマンド
`docker-machine`はVMを立てた後に（僕は）使うことはほとんどありません。
一応記載してますが、まず覚えなくても良いでしょう。

### IPアドレスの確認
VMのIPを確認できます。

```console
$ docker-machine ip dev
192.168.99.100
```

SSHでイメージに入りたいときなどに使うので、あまり使いませんが覚えおておくといいと思います。

### VMの確認
`docker-machine`が管理しているVMを見れます。

```console
$ docker-machine ls
NAME   ACTIVE   DRIVER       STATE     URL                         SWARM
dev    *        virtualbox   Running   tcp://192.168.99.100:2376   
```

### VMへログイン
どうしてもVMの中に入りたくなったら使う・・・？

```console
$ docker-machine ssh dev
```

### VMの起動/終了コマンド
ローカルPCで動かしている場合はメモリの問題で、`Docker`の状態をそのままにしながら落としたい時があると思います。
その場合は以下のように行います。

```console
# 終了
$ docker-machine stop dev
# 起動
$ docker-machine start dev
# IPアドレスが変わる場合は必要に応じて起動後に一度実行する。
$ source ./bash_profile
```

## Dockerで使うコマンド
主に使うのは`Docker`コマンドになります。
僕が使っているコマンドを紹介します。

### イメージ検索
何はともあれイメージを見つけなければ`Docker`を使えません。

```console
$ docker search ubuntu
```

このコマンド以外にも`DockerHub`の公式ページでも探せます。
検索したページの右側に`Docker Pull Command`が書かれてますので、そのコマンドを叩けばイメージを取得できます。

[DockerHub](https://hub.docker.com/)

### イメージ取得
コンテナ起動するためのイメージを取得します。

```console
$ docker pull ubuntu:latest
```

OFFICIAL REPOSITORYとして[こんなイメージ](https://hub.docker.com/_/hello-world/)も存在してますw

### コンテナ作成（起動）
取得したイメージを起動すると`コンテナ`と呼ばれる単位で起動します。
`Docker`はコマンドベースで起動されるので、起動時にコマンドを渡す必要がある。

また、起動イメージを`pull`してない場合は自動的に取得しに行ってくれる。

例えば以下のコマンドは取得したイメージを起動して`echo "Hello World"`コマンドを実行させる場合は以下のコマンドを実行する。

```console
$ docker run ubuntu:latest echo "Hello World"
Hello World
```

なお一度起動するとコンテナが残ってしまうので何度も上記のコマンドを叩かない方が良い。
（確認方法と削除方法は後述します。）

コンソールに入る場合は以下のコマンドを実行します。

```console
$ docker run -it ubuntu:latest /bin/bash
```

この方法で起動すると、取得したイメージを起動するしてコンソールを出力することができる。
`exit`を入力するとコンソールが終了してコンテナも終了します。

#### 主なオプション

* -d：バックグラウンドで実行する（後述）。既定では、コンテナーをフォアグラウンドで実行するため、Webサーバーやアプリケーションサーバーなど常時実行するコンテナーで指定
* -i：コンテナーの標準入力を開く。/bin/bashなどでコンテナーを操作する際に指定
* -t：tty（端末デバイス）を確保する。/bin/bashなどでコンテナーを操作する際に指定
* -p ｛ホストのポート番号｝:｛コンテナーのポート番号｝：Dockerサーバーのホストとポートマッピングを構成

[ついに1.0がリリース！ Dockerのインストールと主なコマンドの使い方 (2/3)](http://www.atmarkit.co.jp/ait/articles/1406/10/news031_2.html)より引用。

#### コンテナ？
僕が`Docker`を学んでいて疑問だった点は`イメージ`や`コンテナ`の概念だった。
`Docker`でいう`イメージ`とはAWSの`AMIs`とほとんど同じで、仮想OSのスナップショットとほとんど同じだ。
その`イメージ`を実体化（インスタンス化）したものが`コンテナ`と呼ばれる。

`コンテナ`は実施した命令が終わると終了してしまうが、`コンテナ`自身は残るため再実行させたりすることができる。

`コンテナ`は`イメージ`の上で成り立っているため、`イメージ`を削除するためには`コンテナ`を削除する必要がある。

この概念を抑えると同じイメージから幾つものコンテナを作成させれるので`Docker`の良さがわかってくる。（とにかく起動が早い！）

なお、`コンテナ`という名前の通りで起動時に与えれるコマンドは一つだけです。
後述する`sshd`で`chef`を実行するような使い方は基本的に推奨されていないようです。

### イメージ確認
現在保有しているイメージを確認するためには以下のコマンドで行える。

```console
$ docker images
REPOSITORY                      TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu                          latest              89d5d8e8bafb        13 hours ago        187.9 MB
```

### コンテナ確認
イメージ同様でコンテナを確認する場合は以下のコマンドで行える。

```console
$ docker ps
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS              PORTS                         NAMES
9534b3aa773e        ubuntu:latest             "/bin/bash"              3 seconds ago       Up 3 seconds                                      ubuntu1
```

ここまでの記事を実施した人はまだコンテナが起動していないはずなので、上記のコマンドを実施しても何もでてこないはずだ。
すでに終了したコンテナも確認するためには`-a`を付けて実施する。

```console
$ docker ps -a
CONTAINER ID        IMAGE                       COMMAND                  CREATED              STATUS                      PORTS                         NAMES
9534b3aa773e        ubuntu:latest               "/bin/bash"              About a minute ago   Exited (0) 3 seconds ago                                  ubuntu1
```

`STATUS`が`Up`になっているコンテナは起動中で`Exited`になっているコンテナは終了している。
`NAMES`は指定しないと勝手につけられるので、実施する場合に`name`を付けてあげると管理がしやすい。

#### ex)
以下のように`--name`を付けて起動させると`NAMES`が指定される。
また、同じ名前をつけることはできないため誤って同じコンテナを増やしてしまうこともなくなるのでできるだけ名前を付けてあげよう。

```console
$ docker run -itd --name ubuntu1 ubuntu:latest /bin/bash
```

この例ではコンテナをバックグランドで起動させているので、`docker ps`で表示されるようになる。
以下の説明では注釈がない限り`ubuntu1`は`コンテナ名`を指す。

### コンテナに入る。
バックグランドで起動しているコンテナに入る場合は以下のコマンドで入れる。

```console
$ docker attach ubuntu1
```

初めのコマンドは見えないので、その場合は以下のコマンドで入れば見れる。
(@hidekuroさんに教えてもらいました。)

```console
$ docker logs CONTAINER_NAME && docker attach CONTAINER_NAME
```

一度入り込んで抜けるとコンテナは止まってる。
再度起動するためには`docker start`を使用する。（後述）

### コンテナの停止
起動しているコンテナを停止させる場合は以下のコマンドで行う。

```console
$ docker stop ubuntu1
```

### コンテナ起動
コンテナを再起動させる場合は以下のコマンドで行う。

```console
$ docker start ubuntu1
```

このあと`attach`する場合もあるが、コンテナの命令が常駐系の命令だった場合はこれだけでサービスが立ち上がる。

#### ex)
例えば以下のようなコマンドで立ち上げられたコンテナは、起動すればそのコンテナにSSHで接続することができる。

```console
$ docker run -d -p 22 --name sshd test-sshd /usr/sbin/sshd -D
```

### コンテナの削除
コンテナを削除するためにはコンテナを停止してから行う必要がある。

```console
$ docker rm ubuntu1
```

### ファイルの送受信
コンテナへファイルを送ったりもらう場合は以下のように行える。

```console
$ docker cp test ubunt1:test/
$ docker cp ubunt1:test/test test
```

SCPとほとんど同じように使えます。

### イメージの保存
作成したコンテナをイメージとして保存する場合は以下のように行う。

```console
$ docker start ubuntu1
$ docker attach ubuntu1
root@93d9d73dff28:/# apt-get install -y nginx
root@93d9d73dff28:/# dpkg -l nginx
root@93d9d73dff28:/# exit
```

立ち上げたコンテナに対して変更を加える。（例ではnginxをインストールしている。）
変更内容をイメージに保存します。

```console
$ docker commit ubuntu1 ubuntu/nginx
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu/nginx        latest              a3bbb2669586        7 seconds ago       206.1 MB
ubuntu              latest              e9ae3c220b23        3 weeks ago         187.9 MB
```

新しいイメージができてるので、今後はこのイメージからコンテナを作成することができる。

#### Nginxを起動する場合
おまけだが、上記のイメージを使ってnginxコンテナを80番ポートフォワードで起動する場合は以下のように行う。

```console
$ docker run -d -p 80:80 --name nginx1 ubuntu/nginx /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
$ curl localhost:80
```

これで`nginx`が実行できるが、、、Macでは実際は80番ポートは使えないので[こちら](http://qiita.com/kochizufan/items/c6c1b12748a4e08fde3f)を参照して適当なポート番号を割り当てて実行するとnginxへアクセスできる。

### イメージの削除
イメージの削除はそのイメージから作成されたコンテナを全て削除しないと実施できない。

```console
$ docker rmi ubuntu
もしくは
$ docker rmi [IMAGE ID]
```

### コンテナのexport
コンテナをファイルに保存する場合は以下のコマンドでできます。
イメージからではなくコンテナから`コンテナ名`を指定してファイルに落とします。

```console
$ docker export ubuntu > backupfile
```

### ファイルからイメージへimport
`export`したファイルからイメージにする場合は以下のコマンドでできます。

```console
$ cat backupfile | docker import - backup/ubuntu:1
```

### イメージのsave
イメージをファイルに落とす場合は`save`コマンドを使います。

```console
$ docker save ubuntu/nginx > backupfile
or
$ docker save ubuntu/nginx | gzip > backupfile
```

ファイルサイズが大きいので`gzip`をする事をお勧めします。

### イメージのload
ファイルイメージからイメージを取得する場合は`load`コマンドを使います。
イメージ名やタグなどは保存したときと同じなので、自分でテストをしたい場合はイメージを削除してから行ってください。

```console
$ docker load < backupfile
```

ですが、これを使えばファイルベースでイメージのやり取りができます。

## Dockerfile
イメージを作成する際に`pull`で取得しても良いのだが、`Dockerfile`に命令を記載して`pull`+`レシピ`を入れてイメージを作成することができる。

これを使うと`chef`のようにある程度準備が整った環境を用意することができる。
先ほどの`nginx`を例にすると以下のようになる。

```console
$ mkdir nginx
$ cd nginx
$ vi Dokerfile
```

Dockerfileの中身は以下のようになる。

```Dockerfile
FROM ubuntu
RUN apt-get install -y nginx
ADD index.html /usr/share/nginx/html/
```

この例では`index.html`を配置しているので、同ディレクトリに`index.html`を用意する。

```console
$ vi index.html
<h1>test</h1>
```

出来上がったDockerfileを使ってイメージを作る場合は以下のコマンドで行える。

```console
$ docker build -t ubuntu/nginx:10 .
```

`ubuntu/nginx`という名前でTAGを`10`で作成する。
最後の.は現在のディレクトリを指している。

あとは以下のように起動すればnginxが起動する。

```console
$ docker run -d -p 80:80 --name nginx1 ubuntu/nginx:10 /usr/sbin/nginx -g
```

VM上で他のポート番号と重なる場合は以下のようにポート番号を変えて実行すると良い。

```console
$ docker run -d -p 18888:80 --name nginx1 ubuntu/nginx:10 /usr/sbin/nginx -g
```

### ENTRYPOINTを使用する。
`Dokerfile`を以下のように記載すると起動コマンドを省略することができる。

```Dokerfile
FROM ubuntu
RUN apt-get install -y nginx
ADD index.html /usr/share/nginx/html/
ENTRYPOINT /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
```

コンテナ起動まで行う。

```console
$ docker build -t ubuntu/nginx:1.1 .
$ docker run -d --name nginx2 -p 62733:80 ubuntu/nginx:1.1
```

## docker-compose
[Dockerで即実行できる、社内・自宅向けオープンソースWebアプリ](http://qiita.com/y_hokkey/items/406b5a8c4bc15354d069)などのようなレシピを実行するためには`docker-compose`を使用する。

冒頭でインストールは終わっていると思うので、実際に使って見る。
[Dockerで即実行できる、社内・自宅向けオープンソースWebアプリ](http://qiita.com/y_hokkey/items/406b5a8c4bc15354d069)に記載されているRedmineを入れてみる。

```console
$ mkdir redmine
$ cd redmine/
$ vi docker-compose.yml
```

`docker-compose.yml`に上記サイトに記載されている内容を記載する。

```yaml
data00mysql:
  image: busybox:buildroot-2014.02
  volumes:
    - /data/mysql:/var/lib/mysql
data00redmine:
  image: busybox:buildroot-2014.02
  volumes:
    # 添付ファイル保存先
    - /data/redmine/files:/home/redmine/data/files
    # プラグイン
    - /data/redmine/plugins:/home/redmine/data/plugins
    # テーマ
    - /data/redmine/themes:/home/redmine/data/themes
mysql:
  image: mysql:5.6.24
  environment:
    MYSQL_ROOT_PASSWORD: password
  volumes_from:
    - data00mysql
  ports:
    - "3306:3306"
redmine:
  image: sameersbn/redmine:2.6.3
  volumes_from:
    - data00redmine
    # - data00gitbucket
  links:
    - mysql:mysql
  ports:
    - "80:80"
  environment:
    # REDMINE_HTTPS: true
    # REDMINE_RELATIVE_URL_ROOT: /redmine
    DB_USER: root
    DB_NAME: redmine
    DB_PASS: password
    # SMTP_USER: google@gmail.com
    # SMTP_PASS: password
```

イメージを作成する。

```console
$ docker-compose up -d
$ docker ps
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS              PORTS                         NAMES
c0f9bff7513b        sameersbn/redmine:2.6.3   "/app/init app:start"    8 minutes ago       Up 8 minutes        0.0.0.0:80->80/tcp, 443/tcp   redmine_redmine_1
```

時間がかかるが上記のようになったと思う。
VMのポートフォワーディングを設定(僕は18888で登録した)して`http://localhost:18888/`へアクセスするとRedmineが立ち上がっている事が確認できる。

Githubを見ると`admin/admin`で入れるようなので、実際に入ってみると`mysql`などとも連携されている事がわかる。

すごい・・・・


## おまけ
ここから先はおまけです。
`Chef`を使って`Docker`の環境を作れるのかの実験をしてみます。
`Docker`に`Chef`を流し込むメリットがあるかどうかは置いておいて、、、やってみます。

[EC2にDockerを入れてSSH接続する方法](http://qiita.com/s-kiriki/items/267678b5e5d801f9b1bf)

こちらのサイトの通りにSSH接続できる状態のDockerイメージを作成します。
`Chef`を流し込むまでは、上記のサイトの通りです。

### Dockerfileの作成
サイトの通りの`Docker`ファイルを作成します。

```Dockerfile
FROM centos

# Install
RUN yum update -y
RUN yum install -y sudo
RUN yum install -y passwd
RUN yum install -y openssh-server
RUN yum install -y openssh-clients

RUN /usr/bin/ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key -C '' -N ''
RUN /usr/bin/ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -C '' -N ''
RUN sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config
RUN sed -ri 's/#UsePAM no/UsePAM no/g' /etc/ssh/sshd_config

RUN echo 'root:root' |chpasswd
RUN sed -ri 's/#PermitRootLogin yes/PermitRootLogin yes/g' /etc/ssh/sshd_config

EXPOSE 22
```

### イメージの作成
イメージを作成して起動します。
コンテナの名前は付けておいたほうが便利なので、`sshd`という名前を付けておきます。

```console
$ docker build -t test-sshd .
$ docker run -d -p 22 --name sshd test-sshd /usr/sbin/sshd -D
```

起動したコンテナを確認します。

```console
$ docker ps
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS              PORTS                         NAMES
d77c46ce2216        test-sshd                 "/usr/sbin/sshd -D"      5 seconds ago       Up 5 seconds        0.0.0.0:32768->22/tcp         sshd
```

`PORTS`の部分である`0.0.0.0:32768->22/tcp`が重要です。
必要に応じて[こちら](http://qiita.com/kochizufan/items/c6c1b12748a4e08fde3f)を変更してください。

### SSH接続を行う。
`Docker`が動いているVMのIPアドレスをチェックします。

```console
$ docker-machine ip dev
192.168.99.100
```

この二つの情報を使ってSSH接続を行います。

```console
$ ssh root@192.168.99.100 -p 32768
```

パスワードは`root`です。
入れたことを確認できたので、ここから`chef`を流し込みます。

### Chefの準備
SSH接続の情報を記載します。

```console:~/.ssh/config
Host test-sshd
  HostName 192.168.99.100
  Port 32768
  User root
```

念のため以下のコマンドで入れることを試します。

```console
$ ssh test-sshd
```

* [これからはじめるChefのチュートリアル　基本編](http://qiita.com/syou007/items/70006424d66e3f41152d)
* [これからはじめるChefのチュートリアル　応用編](http://qiita.com/syou007/items/e753f1f84c7b6b45f4ea)

ここで作成した`chef`を使用します。

```json:nodes/test-sshd.json
{
  "run_list": [
    "ruby"
  ]
}
```

と定義します。
（僕の作ったレシピは`centos`へ`nginx`をインストールする時に失敗するので、`ruby`のインストールのみを行います。）

### Chefの実施
それでは`chef`を流し込んでみましょう！

```console
$ bundle exec knife solo prepare test-sshd
```

何度かパスワードを聞かれますので、上記の通り`root`を入力してください。

```console
$ bundle exec knife solo cook test-sshd
```

を実施すると`chef`で作成したレシピが流し込まれます。

```console
$ ssh test-sshd
[root@d77c46ce2216 ~]# ruby -v
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
```

うまく入りましたね！

### SSH接続に関して
このように`Docker`を`sshd`で起動することによって`Vagrant`のように使えるようになります。
しかし、このように使うなら`Vagrant`の方が煩わしさがないので利点があるかどうかは微妙なところです。

結局イメージをコピーすれば複数台のサーバーを建てれるので、あえて`Docker`に`Chef`を流し込む必要はないかもしれません。

とはいえ、`Vagrant`と同じことができるということは仮想OSとして使えるということになりますので覚えておく価値はありそうです。
（本番環境と同じイメージを作りたいときなどは便利だと思います。）

## あとは・・・
イメージの共有をするためには`DockerHub`を使う必要があるが、今回は長くなったのでまたの機会にやってみようと思う。
また、ローカルPCのディレクトリをマウントさせてコンテナを起動する事もできるみたいだけど、これも今度やってみようと思う。

```console
$ mkdir /host_data
$ echo "test123" > /host_data/file
$ docker run -v /host_data:/container_data centos /bin/bash -c "cat /container_data/file"
test123
```

[[Docker] ホストのディレクトリをコンテナにマウントする](http://www.84kure.com/blog/2015/05/01/docker-%E3%83%9B%E3%82%B9%E3%83%88%E3%81%AE%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA%E3%82%92%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%81%AB%E3%83%9E%E3%82%A6%E3%83%B3%E3%83%88%E3%81%99/)より抜粋。

## 最後に
いろいろ記事を書いたおかげでDockerに関してある程度理解が出来てきた。
ただ、残念ながら今の状況ではDockerを使う機会がないのでこのメモでしばらくDockerとは離れてしまう。

せっかく覚えたので、機会があればローカルに便利機能をいろいろ入れたいところだが・・・

これも覚えておいた方が良いなどの情報があったら是非教えていただけると嬉しいです！

なお、このページが参考になったので載せておきます。

[Docker チートシート](https://www.qoosky.net/references/206/)

## 筆者について
筆者は[TownSoft](http://townsoft.jp)の屋号を掲げているフリーランスです。
お仕事お受けいたしますので、お気軽にご連絡ください。