DockerCompose Version2への移行でハマった点


DockerComposeの設定ファイルのフォーマットをVersion1からVersion2に変更した際に、いくつかはまったところがあったので残しておきます。間違ってる点や、もっといい方法があれば教えてもらえると助かります。

### システム構成

今回移行したシステムは、Railsアプリ2つとリバースプロキシ1つを持つ構成になっています。Railsアプリはどちらも同じイメージをベースとしており、設定だけが少し異なっています。実際はもうちょっとコンテナは多いですが、あまり関係ないので省略してます。

![DockerContainerRelation.png](https://qiita-image-store.s3.amazonaws.com/0/140617/de57f4ed-60b0-7370-981b-1749e86dedbc.png "DockerContainerRelation.png")

また、移行前後でDocker Engine, Docker Composeのバージョンアップも行っています。

||移行前|移行後|
|:--|:--|:--|
|Docker Engine|1.10|1.12|
|Docker Compose|1.6|1.8|

### 移行前の設定

移行前の各アプリケーションのdocker-compose.ymlは以下のようになってます。imageは公式のものではなく独自に作っているものなので名前は適当に変えてます。

リバースプロキシとRailsアプリは通信できるようにしていますが、Docker Composeのexternal_linksは使ってません。これを使うと、リバースプロキシ配下のアプリを停止した時に、リバースプロキシまで停止してしまい、他のアプリへの接続ができなくなってしまったためです。1年くらい前に構築した時の情報なので、今は違うかもしれません。。。

* リバースプロキシ(一部抜粋)
 
```docker-compose.yml
nginx:
  image: hoge/nginx:latest
  ports:
    - "80:80"
```

* Railsアプリ1(一部抜粋)

```docker-compose.yml
redis:
  image: hoge/redis:latest
rails:
  image: hoge/rails:latest
  links:
    - redis:redis
```

* Railsアプリ2(一部抜粋)

```docker-compose.yml
redis:
  image: hoge/redis:latest
rails:
  image: hoge/rails:latest
  links:
    - redis:redis
```

### 移行後の設定(失敗1回目)

まずは、単純に書式を変更してみました。

* リバースプロキシ(一部抜粋)

```docker-compose.yml
version: '2'
services:
  nginx:
    image: hoge/nginx:latest
    ports:
      - "80:80"
```

* Railsアプリ1(一部抜粋)

```docker-compose.yml
version: '2'
services:
  redis:
    image: hoge/rails:latest
  rails:
    image: hoge/rails:latest
    depends_on:
      - redis
```

* Railsアプリ2(一部抜粋)

```docker-compose.yml
version: '2'
services:
  redis:
    image: hoge/redis:latest
  rails:
    image: hoge/rails:latest
    depends_on:
      - redis
```

単純に書式を変更しただけだと、リバースプロキシアプリからRailsアプリのコンテナに接続できなくなりました。Version1の時はこの設定で全てのコンテナが同じサブネットに属していたので接続できましたが、上記の設定だとそれぞれのアプリが別のネットワークに属するようになったことが原因です。そこで、全てのアプリが同じネットワークに属するように専用のネットワークを設定しました。

### 移行後の設定(失敗2回目)

* リバースプロキシ(一部抜粋)

```docker-compose.yml
version: '2'
services:
  nginx:
    image: hoge/nginx:latest
    ports:
      - "80:80"
    networks:
      - proxy_network
networks:
  proxy_network:
    external: true
```

* Railsアプリ1(一部抜粋)

```docker-compose.yml
version: '2'
services:
  redis:
    image: hoge/redis:latest
    networks:
      - proxy_network
  rails:
    image: hoge/rails:latest
    depends_on:
      - redis
    networks:
      - proxy_network
networks:
  proxy_network:
    external: true
```

* Railsアプリ2(一部抜粋)

```docker-compose.yml
version: '2'
services:
  redis:
    image: hoge/redis:latest
    networks:
      - proxy_network
  rails:
    image: hoge/rails:latest
    depends_on:
      - redis
    networks:
      - proxy_network
networks:
  proxy_network:
    external: true
```

アプリケーション共通のネットワークとして`proxy_network`を定義しました。ネットワークの作成は次のコマンドを実行しました。

```bash:command
$ docker network create --driver bridge proxy_network
```

共通のネットワークに属することで、リバースプロキシから各アプリケーションに接続できるようになりました。

#### 新しい問題の発生

これで解決と思いましたが、今度はどちらか一方のRailsアプリがredisに接続ができなくなる現象が発生しました。Rails1アプリのredisコンテナのIPアドレスが`172.21.0.2`となっているのに、Rails1アプリのrailsコンテナからredisにpingを打つと`172.21.0.5`に向けて打っていました。この172.21.0.5は何かを調べると`Rails2アプリのredisコンテナ`でした。Railsアプリではredisコンテナに接続するための接続先ホスト名を、DockerComposeで設定したサービス名`redis`としていましたが、すべてのアプリケーションを共通ネットワークに属するようにしたことで、サービス名`redis`がRails1,Rails2の2つのアプリに含まれることになり、サービス名が重複したことで、コンテナ内蔵DNSが異なるIPアドレスを返すようになっていたことが原因のようです。この現象の調査では、Railsアプリを一つずつ動かすと正常に接続ができたため、原因を見つけるまでに結構時間がかかってしまいました。

そこで、この問題を解決するために2つの方法を考えました。

* サービス名が重複しないように変更する
* 外部アプリケーションからアクセス不要なコンテナは共通ネットワークではない別のネットワークに属するようにする

アプリケーションが増えるたびに新しいネットワークを追加するのは構成が複雑になるかな、と思いましたので今回は1つ目の方法で対応することにしました。今考えると、ネットワークを分けた方が素直で楽だったかもしれません。

### 移行後の設定(失敗3回目)

```docker-compose.yml
version: '2'
services:
  nginx:
    image: hoge/nginx:latest
    ports:
      - "80:80"
    networks:
      - proxy_network
networks:
  proxy_network:
    external: true
```

* Railsアプリ1(一部抜粋)

```docker-compose.yml
version: '2'
services:
  rails1_redis:
    image: hoge/redis:latest
    networks:
      - proxy_network
  rails1:
    image: hoge/rails:latest
    depends_on:
      - rails1_redis
    networks:
      - proxy_network
networks:
  proxy_network:
    external: true
```

* Railsアプリ2(一部抜粋)

```docker-compose.yml
version: '2'
services:
  rails2_redis:
    image: hoge/redis:latest
    networks:
      - proxy_network
  rails2:
    image: hoge/rails:latest
    depends_on:
      - rails2_redis
    networks:
      - proxy_network
networks:
  proxy_network:
    external: true
```

これでrails1コンテナからrails1_redisコンテナ、rails2コンテナからrails2_redisコンテナのIPアドレスを正しく認識できるようになりました。


#### 問題発生、再び

今度こそ解決だ、と思ったのですが今度はRailsアプリケーションでrails1_redisにアクセスしようとすると`InvalidURIError`が発生するようになりました。どうやらホスト名に`_`が含まれることが原因のようです。そこで、サービス名の`_`を`-`に変更しました。DockerComposeとは直接関係ない問題ですが、サービス名の変更によるトラブルなので、一緒に書いておきます。

### 移行後の設定(最終形)

```docker-compose.yml
version: '2'
services:
  nginx:
    image: hoge/nginx:latest
    ports:
      - "80:80"
    networks:
      - proxy_network
networks:
  proxy_network:
    external: true
```

* Railsアプリ1(一部抜粋)
 
```docker-compose.yml
version: '2'
services:
  rails1-redis:
    image: hoge/redis:latest
    networks:
      - proxy_network
  rails1:
    image: hoge/rails:latest
    depends_on:
      - rails1-redis
    networks:
      - proxy_network
networks:
  proxy_network:
    external: true
```

* Railsアプリ2(一部抜粋)

```docker-compose.yml
version: '2'
services:
  rails2-redis:
    image: hoge/redis:latest
    networks:
      - proxy_network
  rails2:
    image: hoge/rails:latest
    depends_on:
      - rails2-redis
    networks:
      - proxy_network
networks:
  proxy_network:
    external: true
```

これでやっと全てが正常に動くようになりました。

DockerCompose便利なんですけど、更新スピードが早くてついていくのが大変ですね。こまめにチェックしておかないといけないですね。