docker-compose + nginx-proxy でウェブアプリ楽々検証

この記事は [Docker Advent Calendar 2017](https://qiita.com/advent-calendar/2017/docker) の12日目です。

昨日は [vankobe さん](https://qiita.com/vankobe/)の「[golangでdockerをはじめる ~ goのwebサーバーをdockerでたててみた ~](https://qiita.com/vankobe/items/f4c09e8e4b580651b568)」でした。

# はじめに

巷には便利なオープンソースのアプリケーションが溢れている。試してみたいものが山ほどあるが、その度に仮想マシンを用意したりデータベースの設定をしたりと、何かと手間がかかる。

そこで今回は Docker + docker-compose + nginx-proxy を利用して、ウェブアプリを気軽に動かしてみよう。

## docker-compose とは

複数の Docker コンテナをよしなに起動してくれるツール。

https://docs.docker.com/compose/

## nginx-proxy とは

同じ環境下で動いている Docker コンテナ上の Web サーバを見つけて、自動的にリバースプロキシを設定してくれるコンテナ。

https://github.com/jwilder/nginx-proxy

# 用意するもの

* Docker を動かすための適当なサーバ (VM で OK)

# 下準備

まず Docker が動く環境を用意する。既にある場合は読み飛ばしてください。

## CoreOS のインストール

VMware ESXi 上に、仮想マシンとして CoreOS をインストールする。

まずは公式サイトから iso イメージを落としてくる。今回は Stable の `coreos_production_iso_image.iso` を使う。

https://coreos.com/os/docs/latest/booting-with-iso.html

iso イメージを仮想マシンにマウントし、起動する。

![coreos01.png](https://qiita-image-store.s3.amazonaws.com/0/104866/8bce3f56-4704-32c4-919b-17c2d2bbcba3.png)

この状態では、まだインストールされていない。とりあえず ssh で入れるように、`core` ユーザにパスワードを設定し、ネットワークの設定をする (NIC の名前や IP アドレスは適宜読み替え)。

```
$ sudo passwd core
(適当なパスワード)
$ sudo ifconfig ens32 192.168.0.10/24
$ sudo route add default gw 192.168.0.1
$ sudo sh -c 'echo "nameserver 8.8.8.8" > /etc/resolv.conf'
```

この VM に到達できるネットワーク環境から、ssh でログインする。

ログインできたら、ホームディレクトリ直下に、インストール情報を設定するファイル `cloud-config.yaml` を作成する。

```yaml:~/cloud-config.yaml
#cloud-config

hostname: coreos

write_files:
  - path: /etc/systemd/network/10-static.network
    permissions: 0644
    content: |
      [Match]
      Name=ens32
      [Network]
      Address=192.168.0.10/24
      Gateway=192.168.0.1
      DNS=8.8.8.8

ssh_authorized_keys:
  - ssh-rsa AAAAB3N...
```

* 1行目の `#cloud-config` は必須
* `hostname` は適当に設定
* `ssh-rsa AAAAB3N...` の箇所には、ssh でログインする元のマシンの公開鍵を記述

そしてディスクに CoreOS をインストールする (今回は `/dev/sda`)。

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

インストールが終わったら OS を再起動する。

```
$ sudo reboot
```

※ 1回再起動しただけだと、なぜか IP アドレスを DHCP から取ってきてしまったので、2回再起動した。

## docker-compose のインストール

CoreOS が入ったので、次に docker-compose をインストールする。

公式の GitHub レポジトリから適当なバージョンを選ぶ。今回は `1.17.1` を使う。

https://github.com/docker/compose/releases/

```
$ sudo mkdir -p /opt/bin
$ curl -L "https://github.com/docker/compose/releases/download/1.17.1/docker-compose-Linux-x86_64" \
    | sudo tee /opt/bin/docker-compose > /dev/null
$ sudo chmod +x /opt/bin/docker-compose
```

入ったかどうか確かめる。

```
$ docker-compose
Define and run multi-container applications with Docker.

Usage:
  docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]
  docker-compose -h|--help

Options:
  -f, --file FILE             Specify an alternate compose file (default: docker-compose.yml)
  -p, --project-name NAME     Specify an alternate project name (default: directory name)
  --verbose                   Show more output
  --no-ansi                   Do not print ANSI control characters
  -v, --version               Print version and exit
  -H, --host HOST             Daemon socket to connect to

  --tls                       Use TLS; implied by --tlsverify
  --tlscacert CA_PATH         Trust certs signed only by this CA
  --tlscert CLIENT_CERT_PATH  Path to TLS certificate file
  --tlskey TLS_KEY_PATH       Path to TLS key file
  --tlsverify                 Use TLS and verify the remote
  --skip-hostname-check       Don't check the daemon's hostname against the name specified
                              in the client certificate (for example if your docker host
                              is an IP address)
  --project-directory PATH    Specify an alternate working directory
                              (default: the path of the Compose file)

Commands:
  build              Build or rebuild services
  bundle             Generate a Docker bundle from the Compose file
  config             Validate and view the Compose file
  create             Create services
  down               Stop and remove containers, networks, images, and volumes
  events             Receive real time events from containers
  exec               Execute a command in a running container
  help               Get help on a command
  images             List images
  kill               Kill containers
  logs               View output from containers
  pause              Pause services
  port               Print the public port for a port binding
  ps                 List containers
  pull               Pull service images
  push               Push service images
  restart            Restart services
  rm                 Remove stopped containers
  run                Run a one-off command
  scale              Set number of containers for a service
  start              Start services
  stop               Stop services
  top                Display the running processes
  unpause            Unpause services
  up                 Create and start containers
  version            Show the Docker-Compose version information
```

```
$ docker-compose version
docker-compose version 1.17.1, build 6d101fb
docker-py version: 2.5.1
CPython version: 2.7.13
OpenSSL version: OpenSSL 1.0.1t  3 May 2016
```

うまく動いてそうだ。

## nginx-proxy コンテナのインストール

docker-compose が使えるようになったので、次に nginx-proxy コンテナをインストールする。

```
$ mkdir -p ~/docker-compose.d/nginx-proxy    # 適当なディレクトリを掘る
$ cd ~/docker-compose.d/nginx-proxy
```

`docker-compose.yml` ファイルを用意する。

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

services:
  nginx-proxy:
    image: jwilder/nginx-proxy:latest
    container_name: nginx-proxy
    privileged: true
    ports:
      - 80:80
    volumes:
      - ./data/htpasswd:/etc/nginx/htpasswd
      - ./data/conf.d:/etc/nginx/conf.d
      - /etc/nginx/vhost.d
      - /usr/share/nginx/html
      - /var/run/docker.sock:/tmp/docker.sock:ro
    network_mode: "bridge"
    restart: always
```

ポイントは `network_mode: "bridge"` を付けること (これを書かないと nginx-proxy が他のコンテナを見つけられなかった)。

docker-compose で nginx-proxy コンテナを起動する。

```
$  docker-compose up -d
```

以上で docker-compose + nginx-proxy が使えるようになった。

# 使ってみる

## コンテナの起動

docker-compose + nginx-proxy を使う準備が整ったので、適当なウェブアプリを起動してみよう。今回は Zabbix にする。

公式の Dockerfile を参考にしつつ、docker-compose.yml を書く。

https://hub.docker.com/u/zabbix/

出来上がったものがこちらです。

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

services:
  mysql:
    image: mysql:5.7
    command: mysqld --character-set-server=utf8 --collation-server=utf8_unicode_ci
    volumes:
      - ./data/mysql:/var/lib/mysql
    expose:
      - "3306"
    environment:
      MYSQL_ROOT_PASSWORD: docker
      MYSQL_DATABASE: zabbix
      MYSQL_USER: zabbix
      MYSQL_PASSWORD: zabbix
    network_mode: "bridge"

  zabbix-server:
    image: zabbix/zabbix-server-mysql:alpine-3.2-latest
    volumes:
      - ./data/zabbix:/var/lib/zabbix
    environment:
      DB_SERVER_HOST: mysql
      MYSQL_USER: zabbix
      MYSQL_PASSWORD: zabbix
    expose:
      - "10051"
    depends_on:
      - mysql
    links:
      - mysql
    network_mode: "bridge"

  zabbix-web:
    image: zabbix/zabbix-web-nginx-mysql:alpine-3.2-latest
    environment:
      VIRTUAL_HOST: zabbix.example.com
      VIRTUAL_PORT: 80
      DB_SERVER_HOST: mysql
      MYSQL_USER: zabbix
      MYSQL_PASSWORD: zabbix
      ZBX_SERVER_HOST: zabbix-server
      TZ: Asia/Tokyo
    expose:
      - "80"
    depends_on:
      - mysql
      - zabbix-server
    links:
      - mysql
      - zabbix-server
    network_mode: "bridge"

  zabbix-agent:
    image: zabbix/zabbix-agent:alpine-latest
    environment:
      ZBX_SERVER_HOST: zabbix-server
    expose:
      - "10050"
    depends_on:
      - zabbix-server
    links:
      - zabbix-server
    privileged: true
    network_mode: "bridge"
```

ポイントは以下の通り。

* ウェブサーバのコンテナの環境変数 `VIRTUAL_HOST` に、リバースプロキシに設定したいホスト名を書く。
* 同じく `VIRTUAL_PORT` には、コンテナによって EXPOSE されるウェブサーバのポート番号を書く (今回は 80)。
* すべてのコンテナに `network_mode: "bridge"` を書く。

なお `VIRTUAL_HOST` に書くホスト名は、あらかじめ Docker のホストの IP アドレスに DNS で A レコードを設定しておく。ワイルドカードで書くと楽だと思う。

そして各コンテナを起動する。

```
$ docker-compose up -d
```

起動したか確認する。

```
$ docker-compose ps
         Name                       Command               State         Ports
------------------------------------------------------------------------------------
zabbix_mysql_1           docker-entrypoint.sh mysql ...   Up      3306/tcp
zabbix_zabbix-agent_1    /bin/bash /run_zabbix_comp ...   Up      10050/tcp
zabbix_zabbix-server_1   /bin/bash /run_zabbix_comp ...   Up      10051/tcp, 162/udp
zabbix_zabbix-web_1      /bin/bash /run_zabbix_comp ...   Up      443/tcp, 80/tcp
```

## アクセス

起動した `zabbix-web` コンテナに、nginx-proxy 経由でアクセスしてみる。

あらかじめ設定しておいたホスト名 (上記の例なら `zabbix.example.com`) に Web ブラウザでアクセスする。

```
http://zabbix.example.com/
```

![zabbix01.png](https://qiita-image-store.s3.amazonaws.com/0/104866/d238f9c5-114d-ff51-0e2a-8eb19ca8f69e.png)

動いてる！

# おわりに

docker-compose + nginx-proxy を使って、ウェブアプリを手軽に動かすことができた。Dockerfile が用意されているアプリケーションなら、同じ要領で簡単に検証することができるだろう。

なお、CoreOS を入れたホストにグローバル IP アドレスが付いているなら、`letsencrypt-nginx` というコンテナを併せて使うことで、Let's Encrypt の証明書もインストールすることができる。詳細はまた今後。