Docker Compose - Compose の拡張サービス

# Docker Compose - Compose の拡張サービス

原文：Extending services in Compose
https://docs.docker.com/compose/extends/

Docker Compose の `extends`（拡張）キーワードによって、別々のファイルにある共通の設定を共有できるようにしたり、まったく別のプロジェクトでも使えるようになります。拡張サービス（Extending service）は、一般的に定義されたサービスを複数のサーバで再利用するときに便利です。１つの場所でサービスの定義をしておけば、`extends` を使うことで、どこからでも参照できます。

あるいは、複数の環境に、少しだけサービスが異なる（あるいは、いくつかの設定を変えている）同じアプリケーションをデプロイすることもできます。更に、コピー・ペーストを使わずに設定できます。

### extends 設定の理解

サービスを `docker-compose.yml` で定義するとき、他のサービスの拡張を次のように宣言できます：

```
web:
  extends:
    file: common-services.yml
    service: webapp
```

この命令によって、Compose は `webapp` サービスの定義にあたって `common-services.yml` ファイルの設定を再利用します。`common-services.yml` は次のようなものと仮定します：

```
webapp:
  build: .
  ports:
    - "8000:8000"
  volumes:
    - "/data"
```

この例では、`docker-compose.yml` に書いたものを使って構築したものと同じ結果になります。同じというのは、`web` の下で定義された `build`、`ports`、`volumes` の設定です。

ローカルにある `docker-compose.yml` 設定ファイルの定義（あるいは再定義）を更に進めてみましょう。

```
web:
  extends:
    file: common-services.yml
    service: webapp
  environment:
    - DEBUG=1
  cpu_shares: 5
```

また、`web` サービスに別のサービスをリンクする記述もできます：

```
web:
  extends:
    file: common-services.yml
    service: webapp
  environment:
    - DEBUG=1
  cpu_shares: 5
  links:
    - db
db:
  image: postgres
```

`extends` の使い方の詳細は、リファレンスをご参照ください。


### 使用例

この例はクイックスタートガイドのアプリケーションを再利用します（Compose に慣れていなければ、まずはクイックスタート・ガイドに取り組むことを推奨します）。例では、Compose をローカルの開発に使い、プロダクション環境へのデプロイを仮定していました。

ローカルとプロダクションの環境は似ていますが、いくつかの違いがあります。開発環境では、アプリケーション・コードを変更できるよう、ボリュームをマウントしていました。一方のプロダクションでは、外からみるコードは不変（イミュータブル）です。これは間違って変更しないように裏付けるものです。開発環境ではローカルの Redis コンテナを使いますが、プロダクションでは他のチームが管理する `redis-production.example.com` というサービスを使います。

この例の `extends` を設定するには、次のことが必要です：

1. Docker イメージのウェブアプリケーションを `Dockerfile` で定義し、Compose サービスは `common.yml` で定義します。

2. 開発環境を通常の Compose ファイル `docker-compose.yml` で定義します。

* `extends` を使って webサービスを取得（pull）
* コードを読み込めるボリュームを設定
* アプリケーションがローカルで使うための追加 Redis サービスを作成


3.プロダクション環境を３つめの Compose ファイル `production.yml` で定義します。

* `extends` を使って web サービスを取得（pull）
* web サービスが外部のプロダクション Redis サービスに接続できるよう定義

#### web アプリケーションの定義

web アプリケーションの定義は次のようにします：

1. ファイル `app.py` を作成します。

このファイルには、Flask を HTTP に使い、Redis カウンタを増やすという、簡単な Python アプリケーションを記述します。

```
from flask import Flask
from redis import Redis
import os


app = Flask(__name__)
redis = Redis(host=os.environ['REDIS_HOST'], port=6379)


@app.route('/')
def hello():
   redis.incr('hits')
   return 'Hello World! I have been seen %s times.\n' % redis.get('hits')


if __name__ == "__main__":
   app.run(host="0.0.0.0", debug=True)
```

このコードの環境変数 `REDIS_HOST` は、Redis の接続先を定義します。


2.`requirements.txt` ファイルに Python の依存関係を定義します：

```
flask
redis
```

3.アプリケーションが入っているイメージを構築する `Dockerfile` を作成します：

```
FROM python:2.7
ADD . /code
WORKDIR /code
RUN pip install -r requirements.txt
CMD python app.py
```

4.Compose の設定をする `common.yml` ファイルを作成します：

この設定は、どのようにアプリケーションを実行するか定義します。

```
web:
  build: .
  ports:
    - "5000:5000"
```

典型的なのは、この設定を `docker-compose.yml` に落とし込む方法ですが、複数のファイルで `extends` を使って読み込むためには、ファイルを分ける必要があります。

#### 開発環境の定義

1. ファイル `docker-compose.yml` ファイルを作成します。

`extends` オプションは、先の章で作成した `common.yml` から `web` サービスを取得します。

```
web:
  extends:
    file: common.yml
    service: web
  volumes:
    - .:/code
  links:
    - redis
  environment:
    - REDIS_HOST=redis
redis:
  image: redis
```

`web` サービスに新しく追加定義しているのは：

* 外にある `common.yml` から `web` の基本設定を取得する。
* 基本設定 `common.yml` の設定に `volumes` と `links` を追加する。
* 環境変数 `REDIS_HOST` に redis コンテナのリンク先を設定する。この環境では Docker Hub から `radis` イメージを取得して使う。

2.`docker-compose up` の実行。

Compose は web と redis コンテナの作成、リンク、起動を一緒に行います。アプリケーション・コードのある場所は、web コンテナの中にマウントします。

3.`app.py` のメッセージでコードがマウントされているか確認します。表示が `Hello world!` から `Hello from Compose!` になります。

変更を確認するには、ブラウザを再読み込みするのを忘れないでください！

#### プロダクション環境の定義

ほとんど完了していますが、次はプロダクション環境を定義します：

1. ファイル `production.yml` を作成します。

`docker-compose.yml` と同じように、`extends` オプションは `common.yml` の `web` サービスを取得します。

```
web:
  extends:
    file: common.yml
    service: web
  environment:
    - REDIS_HOST=redis-production.example.com
```

2.`docker-compose -f production.yml up` を実行します。

Compose は web コンテナ***だけ*** 作成し、`REDIS_HOST` 環境変数で設定された Redis へ接続します。この変数が指し示すのはプロダクションの Rails インスタンスです。

メモ：このウェブ・アプリケーションをブラウザで表示しようとしてもエラーになります。これは `redis-production.example.com` という Redis server が実在しないからです。

以上で、`extends` キーを使い、他の設定ファイルからサービスを同時に読み込めます。アプリケーションの開発時は、`common.yml` に web サービスが必要な変更を行えます。Compose は `docker-compose` 実行時、開発・プロダクション両方の環境で適切なものを選びます。これでもうコピー＆ペーストしたり、手動で各々の環境を同期する必要はなくなります。

### リファレンス

あらゆるサービスで、`extends` を他の設定キーと共に使えます。辞書に期待するように、 `file` と `service` という２つのキーを常に必要とします。

`file` キーはどのファイルを読みにいくか指定します。絶対パスか相対パスで指定します。相対パスは、現在のファイルの場所からの相対パスです。

`service` キーは `web` や `database` のように、サービスを拡張する名前を指定します。

自分自身を他のものへ拡張することもできます。拡張の制限はありません。Compose は循環参照をサポートしないため、それらがあると `docker-compose` は処理を中断します。

### 設定の追加と上書き

Compose はローカルにオリジナルのサービス設定をコピーしますが、`links` と `volumes_from` を除外します。これは現在のファイルを読み込むとき、サービス間の依存状況を明確にするためです。また、参照しているファイルが壊れて結果を返さなくても、これらをローカルで定義することもできます。

オリジナルのサービスとローカルのサービスで同じオプション設定が定義された場合は、ローカル値を "上書き" するか、オリジナル・サービスの定義を"拡張"(extend)します。この動作は他の設定オプションとは異なるものです。

`image`、`command`、`mem_limit` のような１つしか値を持たないオプションは、新しい値が古い値を上書きします。**これはデフォルトの振る舞いであり、例外全てを以下に記述します**。

```
# オリジナルのサービス
command: python app.py

# ローカルのサービス
command: python otherapp.py

# 結果
command: python otherapp.py
```

`build` と `image` の場合、もしオリジナルのサービスが定義されていれば、Compose は他の設定を破棄します。

```
# オリジナルのサービス
build: .

# ローカルのサービス
image: redis

# 結果
image: redis

# オリジナルのサービス
image: redis

# ローカルのサービス
build: .

# 結果
build: .
```

`ports`、`expose`、`external_links`、`dns`、`dns_search` は**複数のオプション**を持つので、Compose はそれぞれの値を連結します。

```
# オリジナルのサービス
export:
  - "3000"

# ローカルのサービス
expose:
  - "4000"
  - "5000"

# 結果
expose:
  - "3000"
  - "4000"
  - "5000"
```

`environment` と `lables` の場合、Compose はローカルで定義された値を優先してマージ（統合）します。

```
# オリジナルのサービス
environment:
  - FOO=original
  - BAR=original

# ローカルのサービス
environment:
  - BAR=local
  - BAZ=local

# 結果
environment:
  - FOO=original
  - BAR=local
  - BAZ=local
```

最後に `volume` と `devices` も、Compose はローカルで定義された値を優先してマージ（統合）します。

```
# オリジナルのサービス
volumes:
  - /original-dir/foo:/foo
  - /original-dir/bar:/bar

# ローカルのサービス
volumes:
  - /local-dir/bar:/bar
  - /local-dir/baz/:baz

# 結果
volumes:
  - /original-dir/foo:/foo
  - /local-dir/bar:/bar
  - /local-dir/baz/:baz
```