Dockerを使ってWeb環境を作ってみる


# はじめに

事の発端は日本語ドメインを使ってみたい、と思ってしまったからというところなんですが、せっかくなのでDockerを使ってWebシステムを作って、知見を貯めていこうということが目的です。

# 構成

## 要件
- 2つのDockerホストがあり、1つはサービスホスト、1つは運用ホストとして利用する。
- サービス用ホストにはbind, nginxが稼働するコンテナが動作している。
- 運用ホストではfluentdが動作し、ログを集約し、ローカルのファイルとして保存する。

## 概要構成図

![abstract.jpg](https://qiita-image-store.s3.amazonaws.com/0/99704/b9ea98d8-cb92-ab09-9520-faf39bf7c11f.jpeg "abstract.jpg")


# 設定

## サーバ
### 初期設定

- conohaのVPS(512MBのもの)を2台契約し、その上にUbuntu16.04を導入。
- /etc/hosts, /etc/hostname, /etc/network/interfacesあたりを適当に設定。
- もちろんapt-getでパッケージの更新とか、

## Docker基本方針
### インストール

- 下記を参考にインストールを行います。
- https://docs.docker.com/engine/installation/linux/ubuntu/#os-requirements

### ネットワーク

- Dockerはコンテナ間のリンクやホスト間でL2接続など色々あるが、特にそういうことは考えずに図にあるIPレベルでの通信を行います。
- 詳細まで書くと次のようなイメージになると思います。

![network.jpg](https://qiita-image-store.s3.amazonaws.com/0/99704/d8ff4205-0b6f-ce2d-9843-344b93508b78.jpeg "network.jpg")

- docker0配下のIPはデフォルトで172.17.0.0/24から自動で払い出されますが、NATされ、実IPで通信します。今回の場合はグローバルIPで通信します。
- グローバルIPで通信するため、ufwを利用して余計な通信は落とすように制御します。

### ロギング

- コンテナ内のアプリケーションから直接fluentdに吐かせてもいいものの、dockerのfluentd driverを利用することで、コンテナが標準入力に出力したデータを指定したfluentdに送ることが出来ます。
- コンテナ起動時にオプションとして、下記を渡します。
- tagはNameでもいいのですが、コンテナ再起動時、本来は別のコンテナとして扱うべきだと思うのでIDという扱いにしました。

|オプション名|値|説明|
|:---|:--:|:--|
|--log-driver|fluentd|ログドライバーとして、fluentdを指定|
|--log-opt|fluentd-address=XX.XX.XX.YY:24224|fluentdサーバを指定|
|--log-opt|tag=docker.{{.FullID}}|ログのtagを指定|

### タイムゾーン
- Dockerコンテナは標準でUTCになっているので、JSTに直します。
- 方法は複数あるみたいですが、ここでは下記オプションをコンテナ起動時に引き渡すことで実現します。

|オプション名|値|説明|
|:---|:--:|:--|
|-v|/etc/localtime:/etc/localtime:ro|dockerホストのtimezoneファイルを、コンテナ内にマウントさせます。|


## fluentd
### 基本

- 下記コンテナイメージを使います。

|項目名|説明|
|:---|:---|
|利用するコンテナイメージ|https://github.com/fluent/fluentd-docker-image|


### fluent.conf

- confは以下のように記載し保存しておきます。
- 保存先は$HOME/fluentdとかが良いと思いますが、他のコンテナとまぜこぜになってわかりにくくならなければどこでも良いと思います。

```fluentd
<source>
  type forward
  port 24224
  bind 0.0.0.0
</source>
<match docker.**>
  type file
  path /fluentd/logs/all.log
  time_slice_format %Y%m%d
  time_format %Y%m%dT%H%M%S%z
</match>
```

- ちゃんと通信ができているか確認したい場合はファイルに書き出すよりstdoutに書き出すようにして確認したほうがわかりやすいかもしれません。
- その場合は以下のようなconfを代わりに投入してください。

```fluentd
<source>
  type forward
  port 24224
  bind 0.0.0.0
</source>
<match docker.**>
  type stdout
</match>
```

そのうえで

```bash
$ docker logs fluentd-container-id
```

### コンテナの起動

- まずはじめにログを蓄えるためのfluentdコンテナを起動するためのパラメータを考えます。
- 重要なことはログはコンテナ内ではなく、ローカルボリューム側に保存するということです。

|オプション名|値|説明|
|:---|:--:|:--|
|-p|24224:24224|ホスト側の24224番ポートに対して、コンテナの24224番ポートをマッピングします。|
|-v|$(pwd)/log:/fluentd/logs|コンテナ内でログは/fluentd/logsに吐き出されますが、カレントディレクトリ直下のlogディレクトリをマウントする設定とします。※|
|-v|$(pwd)/fluent.conf:/fluentd/etc/fluent.conf|カレントディレクトリ直下にあるconfファイルをデフォルトのパスにあるconfファイルにマウントします。|
|--name|fluentd|コンテナ名をfluentdとします。|

- 起動コマンド

```bash
$ docker run -d -p 24224:24224 -v $(pwd)/log:/fluentd/logs -v $(pwd)/fluent.conf:/fluentd/etc/fluent.conf -v /etc/localtime:/etc/localtime:ro --name fluentd fluent/fluentd
```

- 私の環境の場合、ホスト側のlogディレクトリはパーミッションが777でないとコンテナ起動時にエラーが出て正しく動作しませんでした。
- 正確な原因、解決方法は今のところ不明ですが、もしis not writable的なエラーが出た場合は試してみてください。

### ufwの設定

- internetからフルオープンだと問題があると思うので下記で絞っておきます。

```bash
$ ufw allow from XX.XX.XX.XX to any port 24224
```

- もちろん先に、SSHを開ける設定など入れてからenableした方がいいと思います。

## bindの設定
### 基本
- 下記コンテナイメージを使います。

|項目名|説明|
|:---|:---|
|利用するコンテナイメージ|https://github.com/sameersbn/docker-bind|

### コンテナの起動
- このコンテナイメージは設定変更用のmuninがついてくるので、特に設定ファイルなどは用意しなくても大丈夫ですが、コンテナ再起動時などに問題が起こらないように設定ファイル類はホスト側のファイルシステムに保存されるようにします。

|オプション名|値|説明|
|:---|:--:|:--|
|-p|53:53/tcp|ホスト側の53番ポートに対して、コンテナの53番ポートをマッピングします。(TCP)|
|-p|53:53/udp|ホスト側の53番ポートに対して、コンテナの53番ポートをマッピングします。(UDP)|
|-v|$(pwd)/bind:/data|コンテナ内のデータは/data内に保存されます。その領域にホスト側の./bindをマウントすることで、コンフィグを始めとしたデータ類をホスト内に保存します。|
|--name|bind|コンテナ名はbindとします。|

- 起動コマンド

```bash
docker run --name bind -d -v /etc/localtime:/etc/localtime:ro -p 53:53/tcp -p 53:53/udp -v /opt/docker/data/bind:/data --log-driver=fluentd --log-opt=fluentd-address=XX.XX.XX.YY:24224 --log-opt=tag=docker.{{.FullID}}   sameersbn/bind:9.9.5-20170129
```

### 起動後の設定
- コンテナ起動後、munin経由で設定変更を行います。
- muninはデフォルトで10000ポートでlistenしていますが、パスワード認証だけは過去の色々なトラウマからsshトンネリングでアクセスすることとします。
- まずは、bindコンテナのローカルIPを調べます。

```bash
$ docker inspect -f "{{ .NetworkSettings.Networks.bridge.IPAddress}}" bind-container-id
```

- 今回の場合172.17.0.x:10000

```bash
$ ssh -i 公開鍵のパス username@XX.XX.XX.XX -L 8080:172.17.0.x:10000
```

- こうすることで、https://localhost:10000/でmuninにアクセスできます。
- muninのユーザはroot, パスワードはpasswordなのでログインして設定を行ってください。
- ここでmuninの設定やbindの設定値自体は割愛します。

## nginxの設定
### 基本
- 下記コンテナイメージを使います。

|項目名|説明|
|:---|:---|
|利用するコンテナイメージ|https://hub.docker.com/_/nginx/|

### コンテナの起動
- 今回は難しいことはせず、とりあえず起動してアクセスできることを確認します。

|オプション名|値|説明|
|:---|:--:|:--|
|-p|80:80|ホスト側の80番ポートに対して、コンテナの80番ポートをマッピングします。|
|-v|$(pwd)/nginx:/usr/share/nginx/html:ro|コンテンツは$(pwd)/nginxに保存し、その領域をコンテナの公開領域にリードオンリーでマウントします。|
|--name|nginx|コンテナ名はnginxとします。|

- 起動コマンド

```bash
$ docker run --name nginx -v /etc/localtime:/etc/localtime:ro -p 80:80 -v $(pwd)/nginx:/usr/share/nginx/html:ro --log-driver=fluentd --log-opt=fluentd-address=XX.XX.XX.YY:24224 --log-opt=tag=docker.{{.FullID}} -d nginx
```

# まとめ
- これでWebサーバが最低限動く程度の構成はできたかと思います。
- ただ、パッと思いつくところでHTTPS対応であったり、性能監視であったりはまだ未着手なので、そのあたりもうまいやり方がないかは近々調査してみようと思います。

- Dockerってあまりピンときていないところがじつはあったのですが、言ってしまえばアプリケーションをデプロイする、という発想をするとしっくり来るということを先輩に教わりスッキリしました。
- Docker = VMという発想が強いと中々扱いにくいものに見えますが、アプリケーションという単位で考えると結構扱いやすくなる気がしました。

# 次の目標
- 済 [HTTPS対応](http://qiita.com/marufeuille/items/ee7829247631be576ef9)
- 性能監視
- ホスト間のL2接続