Docker複数コンテナから成るサービスの取り扱い

**更新：2016/09/23 23:30**

当初「EFKスタックで始めるDocker Compose on CoreOS」というタイトルにしていましたが、EFKに全然触れていないことに気付いたのでタイトル変更。

Dockerをどのようにサービスとして扱っていくか、という全体的なライフサイクル、運用的な部分を知りたくて、EFKスタックをDocker化して動かしていくにはどうするかということを学んでみました。要するにやりたいのは、 **複数コンテナから成り立つサービスを、いかに永続化して動かすか。**

前提
====

* ホストOSはCoreOSとする（元々さくらクラウドで借りていたので）。従ってローリングアップデートによるOS再起動への対応が必要。
* ただしCoreOS1台のみの構成のため、フェイルオーバーは考慮しない。
* 勉強のために各Dockerイメージは公式の配布イメージ等を使わず、自分でDockerfileを書く。
* DockerイメージのベースOSはAlpine linuxを使う。

複数コンテナの協働
====

いわゆるウェブサーバーとDBサーバーを別でコンテナ立てて一緒に動かすような需要。今回の場合はElasticsearch、Kibana、fluentdを別コンテナで立てることにした。前知識だと、この分野はDocker Composeを使えばいいのかな？と思っていたのだけど、CoreOSの場合はfleetがあることを知る。

## fleet

CoreOSは複数のホストOSでクラスターを組み、その上でコンテナを動かすが、どのコンテナを動かすのかというスケジューリングを担うのがfleet。Systemdライクなつくりになっていて、Unitファイルを使って定義を記述し、それに沿ってCoreOSが必要なコンテナを上げる。

複数コンテナが必要な場合はそのようにUnitファイルを書いてしまえばいいので、fleetでもDocker Compose的なことはできる気がした。むしろCoreOSだとDocker Composeがデフォルトで導入されていないので、スケジューリングにはfleetを使うのが前提になっているのかもしれない？ また複数コンテナのネットワーキングも、[flannel](https://coreos.com/flannel/docs/latest/flannel-config.html)というのを使うとできるらしい。

## Docker Compose

一方のDocker Composeはyamlファイルを使って、どのコンテナが必要か、各コンテナにポートやボリュームなどのオプションはどのように与えるかという部分を定義していく。

```yaml:docker-compose.yml
version: '2'

services:
  elasticsearch:
    build: ./elasticsearch
    volumes:
      - /opt/esdata:/run/elasticsearch-2.3/data
    ports:
      - "9200:9200"
      - "9300:9300"

  kibana:
    build: ./kibana
    ports:
      - "5601:5601"

  fluentd:
    build: ./fluentd
    ports:
      - "24224:24224"
```

個人的にはこのやり方がしっくりきたいので、Docker Composeを今回は使っている。というのも、UnitファイルはどちらかといえばOS側の定義という印象が強いし、fleetはDockerとは明確に別サービスになる。一方composeのyamlファイルであれば、そのDockerサービスのレポジトリの中に含めてしまえるので、Dockerサービスをパッケージングしているという意識が強くなる気がした。従って今回のファイル構成は以下のようになっている。

```
.
├── docker-compose.yaml
├── compose-efk.env
├── elasticsearch
│   ├── Dockerfile
│   └── elasticsearch.yml
├── fluentd
│   ├── Dockerfile
│   └── fluent.conf
├── kibana
│   └── Dockerfile
└── README.md
```

ディレクトリのトップに`docker-compose.yml`を配置し、各コンテナの定義はフォルダを分けて、必要なDockerfileや設定定義ファイルを入れておく。サービス全体の構成は`docker-compose.yml`を見ればわかり、コンテナの設定はフォルダでまとめることができるので見通しがよくなる。

[chroju/docker_elasticsearch: elasticsearch with alpine linux](https://github.com/chroju/docker_elasticsearch)

Docker Composeは`docker-compose`コマンドにより操作する。だいたいは`docker`コマンドと似た感覚で扱うことができる。まとめられた複数コンテナによるサービスは「プロジェクト」とされ、docker-compose.ymlの所在するフォルダ名がプロジェクト名（変更したい場合は`-p`で指定が可能）となり、`up`や`stop`といったプロジェクトに対する操作は、基本的にカレントディレクトリ上のdocker-compose.ymlで記載されたプロジェクトへの操作になる。別のファイルを操作する場合は`-f`でファイルを指定する。`docker-compose`コマンドはまた別でまとめたい。

```bash
# コンテナを作成して起動
$ docker-compose up
# 起動中のプロジェクトを確認
$ docker-compose ps
# プロジェクトを停止
$ docker-compose stop
```

Docker Composeの永続化
====

CoreOSはローリングアップデートで自動再起動がかかるので、サービスを永続化するために何かしらの仕掛けが必要になる。Docker Composeはあくまで、複数コンテナを協働させるためのサービスなので、スケジューリングには対応していない。

## fleet

これについてもfleetがCoreOSだとデフォルトで使われる。Unitファイルを定義してサービス化したDockerコンテナをfleetに登録しておくと、CoreOSが落ちた場合はクラスタ内の別のCoreOS上でコンテナを継続稼動させてくれる。なのでUnitファイルにDocker Composeのコマンドを書いて、fleetにサービスとして登録する。

```unit:efk.service
[Unit]
    Description=efk
    After=docker.service

[Service]
    ExecStart=docker-compose -f /opt/efk/docker-compose.yml -p efk -d up
    ExecStop=docker-compose -p efk down
```

ただ、fleetには`systemctl enable`に相当するコマンドはないため、単体ホストにおける自動起動については、`cloud-config.yml`上で定義する必要がある。

```yaml:cloud-config.yml
coreos:
  units:
    - name: "fleet.service"
      command: "start"
    - name: "efk.service"
      command: "start"
```

## Docker Swarm

自分は使ったことがないのでなんともだが、Docker側が用意しているスケジューリングツールとしてはDocker Swarmが存在する。これもCompose同様にCoreOSには初期導入されていない。というのも、CoreOSは独自でetcdによるクラスタリングを行っているからだ。Swarmとetcdを並行導入して扱うというのが果たして行儀が良いのか、なんとも判断しかねるので今回はやめておいた。


DockerとCoreOSのエコシステムの相違
====

DockerにおけるSwarm、Composeと、CoreOSにおけるetcd、fleetがそれぞれ対応関係にあって、どちらを選んで実装するかが悩ましいように思えた。そもそもCoreOSは独自のコンテナ実装であるrktを開発しているので、Dockerのエコシステムとは別のものを進めたいのかとも思えるが、一方で両社はコンテナ仕様の統一も進めていて、よくわからない。

参考：[Docker対抗のコンテナ型仮想化、CoreOSの「rkt 1.0」正式リリース。Dockerイメージも実行可能 － Publickey](http://www.publickey1.jp/blog/16/dockercoreosrkt_10docker.html)

現状コンテナを扱う上ではDockerがデファクト・スタンダードだとは思うが、エコシステムのベストプラクティスはどこから学べばいいのかというのが、まだ腑に落ちていない。