CoreOS で Docker をはじめる


Docker 便利そうな事にようやく気付いたので、CoreOS で始めるメモ。Mac OS X で作業してます。`diskutil` や `pbcopy` なんかは環境に合わせて読み変えて下さい。


# 物理サーバーへ CoreOS をインストール

## インストールディスクの作成

1. [CoreOS の web サイト](https://coreos.com/)より [ISO ファイル](https://coreos.com/docs/running-coreos/platforms/iso/)をダウンロード
2. USB メモリへ ISO ファイルのディスクイメージをコピー:

```sh
diskutil unmountDisk /dev/diskN  # USB メモリをアンマウント
sudo dd if=./coreos_production_iso_image.iso of=/dev/diskN bs=1m  # dd で ISO を USB メモリにコピー
```

* [Installing to Disk](https://coreos.com/docs/running-coreos/bare-metal/installing-to-disk/)


## インストール

### cloud-config の作成

インストーラーの設定をカスタマイズする `cloud-config.yml` を作成する。CentOS における `ks.cfg` みたいなものか。

SSH 鍵 `coreos_key` ペアを作成:

```
ssh-keygen -t rsa -C anony@foo.bar -f ./coreos_key
```

公開鍵 `coreos_key.pub` の文字列をコピーして `cloud-config.yml` に使用:

```
cat ./coreos_key.pub | pbcopy
```

`cloud-config.yml` を作成:

```
touch ./cloud-config.yml
```

```yaml:cloud-config.yml
#cloud-config

coreos:
  units:
    - name: etcd.service
      command: start
    - name: fleet.service
      command: start
    - name: docker.service
      command: start

    # タイムゾーンを JST に設定
    - name: timezone.service
      command: start
      content: |
        [Unit]
        Description=timezone
        [Service]
        Type=oneshot
        RemainAfterExit=yes
        ExecStart=/usr/bin/ln -sf ../usr/share/zoneinfo/Japan /etc/localtime

    # 固定 IP の設定追加
    - name: 10-static.network
      runtime: false
      content: |
        [Match]
        Name=eno1
        [Network]
        Address=192.168.0.98/24
        Gateway=192.168.0.1
        DNS=192.168.0.1

    # NFS 自動マウントの設定追加
    - name: media-myvolume.mount
      command: start
      content: |
        [Mount]
        What=192.168.0.88:/var
        Where=/media/myvolume
        Type=nfs
        Options=nolock

  update:
    - reboot-strategy: best-effort

ssh_authorized_keys:
    # 管理ユーザー core の公開鍵。coreos_key.pub の文字列を使用
    - ssh-rsa AAAAB3NzaC[...] anony@foo.bar

hostname: coreos  # ホスト名を適当に指定

manage_etc_hosts: localhost  # ローカルの名前解決用
```

* [Customize with Cloud-Config](https://coreos.com/docs/cluster-management/setup/cloudinit-cloud-config/)
* [Network Configuration](https://coreos.com/docs/cluster-management/setup/network-config-with-networkd/)
* [Mounting Storage](https://coreos.com/docs/cluster-management/setup/mounting-storage/)


### USB メモリから起動

CoreOS の USB メモリから PC を起動する。自動で `core` ユーザーとしてログインする:

```
localhost login: core (automatic login)
CoreOS (stable)
Update Strategy: No Reboots
core@localhost ~ $
```

サーバーの IP アドレスを確認する:

```
ip add show
```

```
eno1: flags=4163<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500 
	inet 192.168.0.98 netmask 255.255.255.0 broadcast 192.168.1.255
```

一時的に `core` ユーザーにパスワードを設定する:

```
sudo passwd core
```

設定したパスワードを使用して、作成した `cloud-config.yml` を作業 PC からサーバーに転送する:

```
scp ./cloud-config.yml core@192.168.0.98:~
```


### 実際にインストール

下記コマンドをサーバーで実行してインストールする:

```
sudo coreos-install -d /dev/sda -C stable -c ~/cloud-config.yml
```

* `/dev/sda` は環境に合わせて読み変える。

インストールが完了したら再起動する:

```
sudo reboot
```

## 接続確認

作業 PC から SSH 鍵 `coreos_key` を用いて CoreOS サーバーへ SSH 接続できるか確認する:

```
ssh -i ./coreos_key core@192.168.0.98
```

ちなみに `cloud-config.yml` の設定は `/var/lib/coreos-install/user_data` を更新する事で変更が可能。設定の更新が必要な場合はこちらから。

接続できたら完了。以降は SSH 経由のこのサーバー上で Docker を実行する。


# Vagrant で CoreOS を実行

ちょっと寄り道で、物理サーバーではなく Vagrant で CoreOS を起動する方法も書いておく。

CoreOS が公式に `Vagrantfile` を公開しているので、これを利用する。

CoreOS の GitHub リポジトリから `Vagrantfile` 一式をクローンする:

```
git clone https://github.com/coreos/coreos-vagrant.git
cd ./coreos-vagrant
```

あとは普通に Vagrant の手順です:

```
vagrant up
vagrant ssh
```

* [Running CoreOS on Vagrant](https://coreos.com/docs/running-coreos/platforms/vagrant/)


# Docker で "Hello, world."

ここから Docker の話。CoreOS に SSH ログインした状態で作業する。

まずは簡単に Docker の動作確認で、既存のコンテナから `echo` コマンドを叩いて "Hello, world." と表示してみる。


## イメージの取得

まず [Docker Hub](https://registry.hub.docker.com/) という公式のイメージ共有クラウドサービスから CentOS のイメージを取得する。尚、予め Docker Hub でユーザー登録をしてアカウントを取得しておく。

Docker Hub へログインする:

```
docker login
```

CentOS のイメージを検索する:

```
docker search centos
```

```
NAME                                DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
centos                              The official build of CentOS.                   764       [OK]       
ansible/centos7-ansible             Ansible on Centos7                              27                   [OK]
tutum/centos                        Centos image with SSH access. For the root...   12                   [OK]
blalor/centos                       Bare-bones base CentOS 6.5 image                8                    [OK]
[...]
```

検索結果の一番上 `centos` が公式なイメージなので、こちらの最新版 (`centos:latest`) を取得する:

```
docker pull centos:latest
```


## コンテナの生成

Docker Hub 等から取得してきたホスト上のイメージ一覧は下記のコマンドで確認できる:

```
docker images
```

```
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
centos              7                   8efe422e6104        2 weeks ago         224 MB
centos              centos7             8efe422e6104        2 weeks ago         224 MB
centos              latest              8efe422e6104        2 weeks ago         224 MB
```

* `centos` のイメージが 3 つ表示されているが、これは `IMAGE ID` が示す通り同一のイメージ。タグの違いから 3 つ表示されている。

上記 `centos:latest` イメージ上で `echo` コマンドを実行してみる:

```
docker run centos:latest echo 'Hello, world.'
```

```
Hello, world.
```

上記の通り `Hello, world.` と標準出力が返されるはず。

これは `centos:latest` イメージを元に、`echo 'Hello, world.'` を実行するコンテナを生成した事になる。

尚、コンテナは実行が終了しても削除されない。下記のコマンドを実行すると終了したコンテナが残っているのが分かる:

```
core@coreos ~ $ docker ps -a
CONTAINER ID        IMAGE                        COMMAND                CREATED             STATUS                       PORTS                                          NAMES
bab4118699d3        centos:7                     "echo 'Hello, world.   7 seconds ago       Exited (0) 6 seconds ago                                                    trusting_elion
```

これは明示的に削除するまで、いつまでも残る。削除する場合は以下のようにする:

```
core@coreos ~ $ docker rm bab4118699d3
bab4118699d3
```


### インタラクティブに実行

* [Foreground - Run Reference - Docker Documentation](http://docs.docker.com/reference/run/#foreground)

```
core@coreos ~ $ docker run -i -t --rm=true centos:latest sh
sh-4.2# echo 'Hello, world.'
Hello, world.
sh-4.2# exit
exit
core@coreos ~ $ 
```

* `-i` (`--interactive=true`) 標準出力受け付けの維持
* `-t` (`--tty=true`) 疑似 TTY の割り当て
* `--rm=true` 終了時にコンテナを自動削除


### バックグラウンドで実行

* [Detached (-d) - Run Reference - Docker Documentation](http://docs.docker.com/reference/run/#detached-d)

```
core@coreos ~ $ docker run -d centos:latest sh -c "while true; do echo \"Hello, world.\"; sleep 1; done"
ae101d4eb9eaa8cc43a6789fa8752825cb43df8e6e9a11093d44f5ebe61bb96c
```

* `-d` (`--detach=true`) コンテナをバックグランドで実行し、コンテナ ID を返す

`docker ps` でバックグラウンドプロセス一覧を取得する:

```
core@coreos ~ $ docker ps
CONTAINER ID        IMAGE                        COMMAND                CREATED              STATUS              PORTS                    NAMES
ae101d4eb9ea        centos:7                     "\"sh -c 'while true   About a minute ago   Up About a minute                            hungry_shockley
```

`docker logs` にプロセスの `NAME` を指定して出力のログを取得する:

```
core@coreos ~ $ docker logs hungry_shockley
Hello, world.
Hello, world.
Hello, world.
```

`docker stop` にプロセスの `NAME` を指定して終了させる:

```
core@coreos ~ $ docker stop hungry_shockley
hungry_shockley
```

`docker rm` に `CONTAINER ID` を指定してコンテナを削除する:

```
core@coreos ~ $ docker rm ae101d4eb9ea
ae101d4eb9ea
```


# Dockerfile の利用

既存の `centos` イメージ上で様々なプロセスを立ち上げてみて、それをカスタムイメージとして保存してみます。


## CentOS コンテナで Apache と SSH を立ち上げてみる

まずはインタラクティブに `centos` イメージ上に自分の環境を構築してみます。

[`centos:centos7` は `systemctl` 回りの挙動が難しかった](http://qiita.com/yunano/items/9637ee21a71eba197345)ので `centos:centos6` で旧来通り `service` コマンドを使用する事にします。

`centos:centos6` コンテナをインタラクティブに起動します:

```
docker run -i -t --rm=true centos:centos6 sh
```

インタラクティブシェルに入ると `root` ユーザーとしてログインしている。サンドボックス化されているので、権限など気にせずガンガン `root` で作業する事にする。

下記手順で Apache と SSH 環境をセットアップします:

```sh
yum update -y
yum install -y httpd openssh-server

sed -ri "s/^#PermitRootLogin yes/PermitRootLogin yes/" /etc/ssh/sshd_config
sed -ri "s/^PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config
sed -ri "s/^ChallengeResponseAuthentication yes/ChallengeResponseAuthentication no/" /etc/ssh/sshd_config
sed -ri "s/^UsePAM yes/UsePAM no/" /etc/ssh/sshd_config

mkdir -m 700 /root/.ssh

cat - > /root/.ssh/authorized_keys <<__KEY__
ssh-rsa AAAAB3NzaC[...] anony@foo.bar
__KEY__

chmod 600 /root/.ssh/authorized_keys
chown root:root /root/.ssh/authorized_keys

chkconfig httpd on
chkconfig sshd on

service httpd start
service sshd start
```

と、ここまでセットアップしてサーバーは立ち上がったが、サンドボックス化されているので、これらのサーバーを外部に公開する術が分からない...

...と、これは `docker run` する時のパラメータを追加する事で解決する:

```
docker run -i -t --rm=true --expose=22,80 -p 10022:22 -p 10080:80 centos:centos6 sh
```

* `--expose=PORT` コンテナ内で外部公開を許可するポートを指定する。
* `-p PUBLISHED:EXPOSED` コンテナ内部で外部公開を許可されたポート `EXPOSED` とサーバー上で公開するのに使用する割り当てポート `PUBLISHED` の対で指定する。 

これで サーバーが立ち上がっている事を確認できる。

* [EXPOSE (incoming ports) - Run Reference - Docker Documentation](http://docs.docker.com/reference/run/#expose-incoming-ports)


## イメージ化する

さて、外部公開の方法だが、これはインタラクティブにコンテナを生成するのではなく、`Dockerfile` というファイルにコンテナで実行するコマンドを列挙して、バックグランド化するのがベスト。


### Dockerfile の作成


前述のインタラクティブシェルで実行した内容を `Dockerfile` で表現すると、下記の 2 つのファイルになる。

```authorized_keys:authorized_keys
ssh-rsa AAAAB3NzaC[...] anony@foo.bar
```

```Dockerfile:Dockerfile
FROM centos:centos6

MAINTAINER Yu I. <yu.inao@facebook.com>

RUN yum update -y
RUN yum install -y httpd openssh-server

RUN sed -ri "s/^#PermitRootLogin yes/PermitRootLogin yes/" /etc/ssh/sshd_config
RUN sed -ri "s/^PasswordAuthentication yes/PasswordAuthentication no/" /etc/ssh/sshd_config
RUN sed -ri "s/^ChallengeResponseAuthentication yes/ChallengeResponseAuthentication no/" /etc/ssh/sshd_config
RUN sed -ri "s/^UsePAM yes/UsePAM no/" /etc/ssh/sshd_config

RUN mkdir -m 700 /root/.ssh
ADD ./authorized_keys /root/.ssh/authorized_keys
RUN chmod 600 /root/.ssh/authorized_keys && chown root:root /root/.ssh/authorized_keys

RUN chkconfig httpd on
RUN chkconfig sshd on

RUN service httpd start
RUN service sshd start

EXPOSE 22 80

CMD [ "/sbin/init" ]
```

これら 2 つのファイルを任意のディレクトリ (ここでは `myimage`) に格納する:

```
./myimage/
├── Dockerfile
└── authorized_keys
```

* [Dockerfile - Docker Documentation](https://docs.docker.com/reference/builder/#entrypoint)


### ビルドしてイメージ化

`Dockerfile` を書いたらビルドして前述の CentOS 等と同じくイメージ化する。

ビルドは下記のコマンドで:

```
docker build -t myimage ./myimage/
```

* `-t` (`--tag=""`) イメージを識別するためのリポジトリ名とタグ名を指定

完了したら下記のコマンドでホスト上のイメージに登録されているのを確認:

```
docker images
```


### イメージを実行

あとは `centos` イメージと同様、`docker run -d` でバックグランド実行してやれば良い。

ただしコンテナのポートを適切に公開するパラメータを付けて実行する:

```
docker run -d -p 10022:22 -p 10080:80 myimage
```

* `-p` (`--publish=[]`) ホストの指定ポートでコンテナ内部の指定ポートを公開

これで HTTP サーバーであれば下記のアドレスでアクセス可能になる:

```
http://192.168.0.98:10080/
```

つかれた。