はじめてのDocker（マルチコンテナ環境編）

この記事は[iRidge Advent Calendar 2018](https://qiita.com/advent-calendar/2018/iridge)の23日目の記事です。
そして[はじめてのDocker（基本編）](https://qiita.com/Kamihara/items/6fbb2ea8ca2f511875fd)の続きです。
この記事も長いです。

## マルチコンテナ環境

サービス毎にコンテナを分割して保つのがよい。
各層を異なるコンテナで分割することにより、それぞれに最も適切なインスタンスタイプで構成することが出来る。

`SF Food Trucks`というアプリをDocker化してみる。以下のような構成になっている。

* バックエンドはPython(Flask)
* 検索にはElasticsearch

まずは、リポジトリをクローンする。

```zsh
$ git clone https://github.com/prakhar1989/FoodTrucks
$ cd FoodTrucks
$ tree -L 2
.
├── Dockerfile
├── README.md
├── aws-compose.yml
├── docker-compose.yml
├── flask-app
│   ├── app.py
│   ├── package-lock.json
│   ├── package.json
│   ├── requirements.txt
│   ├── static
│   ├── templates
│   └── webpack.config.js
├── setup-aws-ecs.sh
├── setup-docker.sh
├── shot.png
└── utils
    ├── generate_geojson.py
    └── trucks.geojson
```

* `flask-app`フォルダにはPythonアプリケーションが含まれる。
* `utils`フォルダーには、Elasticsearchにデータをロードするためのユーティリティがいくつか含まれている。
* YAMLファイルとDockerfileも含まれている。

このアプリをどうやってDocker化するか考えてみよう。
アプリケーションはFlaskバックエンドサーバとElasticsearchからなるので、自然に分割するなら以下の2つのコンテナとなる。

* Flaskのプロセスが稼働するコンテナ
* ESのプロセスが稼働するコンテナ

ボトルネックになっている場所によってコンテナの追加をすることで、スケール出来る。

前のセクションでFlaskコンテナは作成済み。
Elasticsearch用に、DockerHubを探してみると、公式にサポートされたESのイメージがあることが分かる。

```zsh
$ docker search elasticsearch
NAME                                  DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
elasticsearch                         Elasticsearch is a powerful open source sear…   3229                [OK]
kibana                                Kibana gives shape to any kind of data — str…   1316                [OK]
nshou/elasticsearch-kibana            Elasticsearch-6.3.1 Kibana-6.3.1                90                                      [OK]
itzg/elasticsearch                    Provides an easily configurable Elasticsearc…   67                                      [OK]
mobz/elasticsearch-head               elasticsearch-head front-end and standalone …   40
kubernetes/fluentd-elasticsearch      An image that ingests Docker container log f…   25
lmenezes/elasticsearch-kopf           elasticsearch kopf                              17                                      [OK]
tutum/elasticsearch                   Elasticsearch image - listens in port 9200.     16                                      [OK]
bitnami/elasticsearch                 Bitnami Docker Image for Elasticsearch          12                                      [OK]
monsantoco/elasticsearch              ElasticSearch Docker image                      11                                      [OK]
taskrabbit/elasticsearch-dump         Import and export tools for elasticsearch       10                                      [OK]
mesoscloud/elasticsearch              [UNMAINTAINED] Elasticsearch                    9                                       [OK]
justwatch/elasticsearch_exporter      Elasticsearch stats exporter for Prometheus     8
blacktop/elasticsearch                Alpine Linux based Elasticsearch Docker Image   5                                       [OK]
centerforopenscience/elasticsearch    Elasticsearch                                   4                                       [OK]
frodenas/elasticsearch                A Docker Image for Elasticsearch                3                                       [OK]
barchart/elasticsearch-aws            Elasticsearch AWS node                          2
forkdelta/fluentd-elasticsearch       fluent/fluentd with fluent-plugin-elasticsea…   1                                       [OK]
thingswise/elasticsearch              Elasticsearch + etcd2 peer discovery            1                                       [OK]
jetstack/elasticsearch-pet            An elasticsearch image for kubernetes PetSets   1                                       [OK]
phenompeople/elasticsearch            Elasticsearch is a powerful open source sear…   1                                       [OK]
driveclutch/infra-elasticsearch-aws   Elasticsearch Docker for use in AWS             0                                       [OK]
backplane/elasticsearch-curator       Elasticsearch Curator (https://github.com/el…   0
wreulicke/elasticsearch               elasticsearch                                   0                                       [OK]
18fgsa/elasticsearch                  Built from https://github.com/docker-library…   0
```

補足：Elasticは自社の製品を自身のレジストリでメンテナンスしている。Elasticsearchを使うなら、そのレジストリのイメージを使用することが推奨されている。

まずはイメージをpullしよう。

```zsh
$ docker pull docker.elastic.co/elasticsearch/elasticsearch:6.3.2
6.3.2: Pulling from elasticsearch/elasticsearch
7dc0dca2b151: Pull complete
72d60ff53590: Pull complete
ca55c9f7cc1f: Pull complete
822d6592a660: Pull complete
22eceb1ece84: Pull complete
30e73cf19e42: Pull complete
f05e800ca884: Pull complete
3e6ee2f75301: Pull complete
Digest: sha256:8f06aecf7227dbc67ee62d8d05db680f8a29d0296ecd74c60d21f1fe665e04b0
Status: Downloaded newer image for docker.elastic.co/elasticsearch/elasticsearch:6.3.2
```

特定のポートでElasticsearchクラスターがシングルノードで実行するよう環境変数を設定し、開発モードで実行する。

```zsh
$ docker run -d --name es -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.3.2
a1835fc92536dbb87cd2af9f9539030617fe1a6aa7776258030553fbac656968
```

一度コンテナが起動すると、`docker container logs`にコンテナ名かIDを指定してログを参照できる。
正常に起動されていれば以下のようにログが記録されている。

```zsh
$ docker container logs es
OpenJDK 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
[2018-11-21T09:56:30,714][INFO ][o.e.n.Node               ] [] initializing ...
[2018-11-21T09:56:30,767][INFO ][o.e.e.NodeEnvironment    ] [FrD0Sbv] using [1] data paths, mounts [[/ (overlay)]], net usable_space [51.7gb], net total_space [58.4gb], types [overlay]
[2018-11-21T09:56:30,768][INFO ][o.e.e.NodeEnvironment    ] [FrD0Sbv] heap size [1007.3mb], compressed ordinary object pointers [true]
[2018-11-21T09:56:30,770][INFO ][o.e.n.Node               ] [FrD0Sbv] node name derived from node ID [FrD0SbvgRlyfivGpJdc9xw]; set [node.name] to override
[2018-11-21T09:56:30,770][INFO ][o.e.n.Node               ] [FrD0Sbv] version[6.3.2], pid[1], build[default/tar/053779d/2018-07-20T05:20:23.451332Z], OS[Linux/4.9.125-linuxkit/amd64], JVM["Oracle Corporation"/OpenJDK 64-Bit Server VM/10.0.2/10.0.2+13]
[2018-11-21T09:56:30,770][INFO ][o.e.n.Node               ] [FrD0Sbv] JVM arguments [-Xms1g, -Xmx1g, -XX:+UseConcMarkSweepGC, -XX:CMSInitiatingOccupancyFraction=75, -XX:+UseCMSInitiatingOccupancyOnly, -XX:+AlwaysPreTouch, -Xss1m, -Djava.awt.headless=true, -Dfile.encoding=UTF-8, -Djna.nosys=true, -XX:-OmitStackTraceInFastThrow, -Dio.netty.noUnsafe=true, -Dio.netty.noKeySetOptimization=true, -Dio.netty.recycler.maxCapacityPerThread=0, -Dlog4j.shutdownHookEnabled=false, -Dlog4j2.disable.jmx=true, -Djava.io.tmpdir=/tmp/elasticsearch.CyesL65N, -XX:+HeapDumpOnOutOfMemoryError, -XX:HeapDumpPath=data, -XX:ErrorFile=logs/hs_err_pid%p.log, -Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m, -Djava.locale.providers=COMPAT, -XX:UseAVX=2, -Des.cgroups.hierarchy.override=/, -Des.path.home=/usr/share/elasticsearch, -Des.path.conf=/usr/share/elasticsearch/config, -Des.distribution.flavor=default, -Des.distribution.type=tar]
[2018-11-21T09:56:32,671][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [aggs-matrix-stats]
[2018-11-21T09:56:32,672][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [analysis-common]
[2018-11-21T09:56:32,672][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [ingest-common]
[2018-11-21T09:56:32,672][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [lang-expression]
[2018-11-21T09:56:32,672][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [lang-mustache]
[2018-11-21T09:56:32,673][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [lang-painless]
[2018-11-21T09:56:32,673][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [mapper-extras]
[2018-11-21T09:56:32,673][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [parent-join]
[2018-11-21T09:56:32,673][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [percolator]
[2018-11-21T09:56:32,673][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [rank-eval]
[2018-11-21T09:56:32,674][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [reindex]
[2018-11-21T09:56:32,674][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [repository-url]
[2018-11-21T09:56:32,674][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [transport-netty4]
[2018-11-21T09:56:32,674][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [tribe]
[2018-11-21T09:56:32,675][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [x-pack-core]
[2018-11-21T09:56:32,675][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [x-pack-deprecation]
[2018-11-21T09:56:32,675][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [x-pack-graph]
[2018-11-21T09:56:32,676][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [x-pack-logstash]
[2018-11-21T09:56:32,676][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [x-pack-ml]
[2018-11-21T09:56:32,676][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [x-pack-monitoring]
[2018-11-21T09:56:32,676][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [x-pack-rollup]
[2018-11-21T09:56:32,676][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [x-pack-security]
[2018-11-21T09:56:32,676][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [x-pack-sql]
[2018-11-21T09:56:32,676][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [x-pack-upgrade]
[2018-11-21T09:56:32,677][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded module [x-pack-watcher]
[2018-11-21T09:56:32,677][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded plugin [ingest-geoip]
[2018-11-21T09:56:32,677][INFO ][o.e.p.PluginsService     ] [FrD0Sbv] loaded plugin [ingest-user-agent]
[2018-11-21T09:56:35,748][INFO ][o.e.x.s.a.s.FileRolesStore] [FrD0Sbv] parsed [0] roles from file [/usr/share/elasticsearch/config/roles.yml]
[2018-11-21T09:56:36,728][INFO ][o.e.x.m.j.p.l.CppLogMessageHandler] [controller/78] [Main.cc@109] controller (64 bit): Version 6.3.2 (Build 903094f295d249) Copyright (c) 2018 Elasticsearch BV
[2018-11-21T09:56:37,649][INFO ][o.e.d.DiscoveryModule    ] [FrD0Sbv] using discovery type [single-node]
[2018-11-21T09:56:38,565][INFO ][o.e.n.Node               ] [FrD0Sbv] initialized
[2018-11-21T09:56:38,566][INFO ][o.e.n.Node               ] [FrD0Sbv] starting ...
[2018-11-21T09:56:38,712][INFO ][o.e.t.TransportService   ] [FrD0Sbv] publish_address {172.17.0.2:9300}, bound_addresses {0.0.0.0:9300}
[2018-11-21T09:56:38,795][INFO ][o.e.x.s.t.n.SecurityNetty4HttpServerTransport] [FrD0Sbv] publish_address {172.17.0.2:9200}, bound_addresses {0.0.0.0:9200}
[2018-11-21T09:56:38,795][INFO ][o.e.n.Node               ] [FrD0Sbv] started
[2018-11-21T09:56:38,858][WARN ][o.e.x.s.a.s.m.NativeRoleMappingStore] [FrD0Sbv] Failed to clear cache for realms [[]]
[2018-11-21T09:56:38,934][INFO ][o.e.g.GatewayService     ] [FrD0Sbv] recovered [0] indices into cluster_state
[2018-11-21T09:56:39,155][INFO ][o.e.c.m.MetaDataIndexTemplateService] [FrD0Sbv] adding template [.triggered_watches] for index patterns [.triggered_watches*]
[2018-11-21T09:56:39,234][INFO ][o.e.c.m.MetaDataIndexTemplateService] [FrD0Sbv] adding template [.watch-history-7] for index patterns [.watcher-history-7*]
[2018-11-21T09:56:39,258][INFO ][o.e.c.m.MetaDataIndexTemplateService] [FrD0Sbv] adding template [.watches] for index patterns [.watches*]
[2018-11-21T09:56:39,319][INFO ][o.e.c.m.MetaDataIndexTemplateService] [FrD0Sbv] adding template [.monitoring-logstash] for index patterns [.monitoring-logstash-6-*]
[2018-11-21T09:56:39,373][INFO ][o.e.c.m.MetaDataIndexTemplateService] [FrD0Sbv] adding template [.monitoring-es] for index patterns [.monitoring-es-6-*]
[2018-11-21T09:56:39,408][INFO ][o.e.c.m.MetaDataIndexTemplateService] [FrD0Sbv] adding template [.monitoring-alerts] for index patterns [.monitoring-alerts-6]
[2018-11-21T09:56:39,441][INFO ][o.e.c.m.MetaDataIndexTemplateService] [FrD0Sbv] adding template [.monitoring-beats] for index patterns [.monitoring-beats-6-*]
[2018-11-21T09:56:39,459][INFO ][o.e.c.m.MetaDataIndexTemplateService] [FrD0Sbv] adding template [.monitoring-kibana] for index patterns [.monitoring-kibana-6-*]
[2018-11-21T09:56:39,515][INFO ][o.e.l.LicenseService     ] [FrD0Sbv] license [fbfad846-62ae-40cf-a904-f5d79162b23a] mode [basic] - valid
```

ESコンテナにcurlでリクエストを送信できるか試してみると、以下のようなレスポンスが確認できる。

```zsh
$ curl localhost:9200
{
  "name" : "FrD0Sbv",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "yptGnKU_TKu8NyYsYJlIog",
  "version" : {
    "number" : "6.3.2",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "053779d",
    "build_date" : "2018-07-20T05:20:23.451332Z",
    "build_snapshot" : false,
    "lucene_version" : "7.3.1",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}
```

Flaskコンテナも稼働させるための`Dockerfile`を作成する。
直前のセクションでは、`python:3-onbuild`イメージを使用していたが今回は、`pip`経由での依存関係のインストールはやめて、プロダクション用に圧縮されたJavaScriptを同時に生成したい。
このためには、Nodejsが必要必要なので、`ubuntu`ベースイメージから初めて、`Dockerfile`をスクラッチで作成する。

flaskアプリケーション用のDockerファイルは以下のようになる。

```Dockerfile
# start from base
FROM ubuntu:latest

# install system-wide deps for python and node
RUN apt-get -yqq update
RUN apt-get -yqq install python-pip python-dev curl gnupg
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash
RUN apt-get install -yq nodejs

# copy our application code
ADD flask-app /opt/flask-app
WORKDIR /opt/flask-app

# fetch app specific deps
RUN npm install
RUN npm run build
RUN pip install -r requirements.txt

# expose port
EXPOSE 5000

# start app
CMD [ "python", "./app.py" ]
```

このDockerfileの内容には以下が含まれている。

* UbuntuLTS baseイメージと`apt-get`を使用して、PythonとNodeをインストールしている。
    * `yqq`フラグは、全てのプロンプトに`Yes`を回答するために使われる。
* `ADD`コマンドで、アプリケーションをコンテナ内の新しいボリューム`/opt/flask-app`にコピーしている。
    * これをワーキングディレクトリにも設定しているので、以降のコマンドはこの場所を起点にして実行される。

イメージをビルドし、コンテナを起動する。

```zsh
$ docker build -t kamihara/foodtrucks-web .
```

初回実行時には、Dockerクライアントがubuntuイメージをダウンロードし、全てのコマンドを実行してイメージを準備する。
何らかのアプリケーションコード修正後に`docker build`を再実行すると、ほぼ瞬時に変更される。アプリケーションを実行してみよう。

```zsh
$ docker run -P --rm kamihara/foodtrucks-web
Unable to connect to ES. Retrying in 5 secs...
Unable to connect to ES. Retrying in 5 secs...
Unable to connect to ES. Retrying in 5 secs...
Out of retries. Bailing out...
```

Elasticsearchに接続できなかったため、実行に失敗してしまった。
よって、コンテナ間の通信について検討する必要が出てきた。

### ネットワーク

`docker container ls`を実行してみると、

```
$ docker container ls
CONTAINER ID        IMAGE                                                 COMMAND                  CREATED             STATUS              PORTS                                            NAMES
a1835fc92536        docker.elastic.co/elasticsearch/elasticsearch:6.3.2   "/usr/local/bin/dock…"   4 days ago          Up 4 seconds        0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp   es
```

ESコンテナは`0.0.0.0:9200`ポートで直接アクセス出来る状態で起動していることが分かる。
flaskアプリケーションにこのURLへの接続を指示できれば、ESと疎通できるはず。

この作業を行うには、FlaskコンテナにESコンテナが0.0.0.0（デフォルトではポート9200）で動作していることを伝える必要がある。
しかしながら、`0.0.0.0`はローカルからESコンテナにアクセスするIPなので、正しくない。
他のコンテナがアクセス可能なIPアドレスは何かを確認する必要がある。

Dockerをインストールしたときには3つのネットワークが自動的に作成される。

```zsh
$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
aedaa2f3e76b        bridge              bridge              local
7997ab619ebb        host                host                local
36ae0014eda5        none                null                local
```

bridgeネットワークはコンテナがデフォルトで実行されるネットワークである。
以下のコマンドでbridgeネットワークを調べてみると

```zsh
$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "aedaa2f3e76b41011b952d7e74cec825226653dda513c9791e7aa4b3c49d2e57",
        "Created": "2018-11-26T01:01:26.02792362Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "a1835fc92536dbb87cd2af9f9539030617fe1a6aa7776258030553fbac656968": {
                "Name": "es",
                "EndpointID": "07172ead66cabd2c58694533158926debcf06506119bcc6f08c2a10cc43e5c05",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]
```

コンテナ`a1835fc92536`が`Containers`の下に記載されている。
また、このコンテナのIPアドレスに`172.17.0.2/16`が割当済みで有ることもわかる。
flaskコンテナを起動してこのIPアドレスへのアクセスを試してみるために、`bash`プロセスを使用して、コンテナをインタラクティブモードで開始する。

```
$ docker run -it --rm kamihara/foodtrucks-web bash
root@0b04aac899e4:/opt/flask-app# curl 172.17.0.2:9200
{
  "name" : "FrD0Sbv",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "yptGnKU_TKu8NyYsYJlIog",
  "version" : {
    "number" : "6.3.2",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "053779d",
    "build_date" : "2018-07-20T05:20:23.451332Z",
    "build_snapshot" : false,
    "lucene_version" : "7.3.1",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}
```

curlコマンを実行してみると、`172.17.0.2：9200`でESと通信できていることが分かる。

コンテナ同士が相互に通信するとき、このアプローチではまだ2つの問題が残っている。

1. Flaskコンテナにホスト名`es`が`172.17.0.2`で有ること、またはIP変更時にそのIPをどうやって伝えればよいか。
1. bridgeネットワークは全てのコンテナでデフォルトとなっているので、この方式はセキュアでない。

この問題は、`docker network`コマンドを使用することでネットワークを隔離したまま、独自のネットワークを定義することで解消出来る。

```
$ docker network create foodtrucks-net
e5a8dfdb776b0f7b389e1bcc443ae06e7c8e444f1ca126964e6793352f72b913
$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
aedaa2f3e76b        bridge              bridge              local
e5a8dfdb776b        foodtrucks-net      bridge              local
7997ab619ebb        host                host                local
36ae0014eda5        none                null                local
```

`network create`コマンドは新しいbridgeネットワークを作成する。
Dockerブリッジドライバはホストマシンへ自動的にルールをインストールし、異なるブリッジに属するコンテナはお互いに直接通信は出来ないようにする。
作成可能な他の種類のネットワークもあり、それは[公式ドキュメント](https://docs.docker.com/network/)を参照すると良い。

ネットワークが出来たので、`--net`を使用することでコンテナをこのネットワーク内で起動出来る。
その前に、bridgeネットワークで稼働しているESコンテナを停止する。

```bash
$ docker container stop es
$ docker ps -a
CONTAINER ID        IMAGE                                                 COMMAND                  CREATED             STATUS                       PORTS               NAMES
a1835fc92536        docker.elastic.co/elasticsearch/elasticsearch:6.3.2   "/usr/local/bin/dock…"   5 days ago          Exited (143) 2 minutes ago                       es
f19d5084c39c        kamihara/catnip                                       "python ./app.py"        6 days ago          Exited (0) 6 days ago                            upbeat_khayyam
2c3fba23a98f        prakhar1989/static-site                               "./wrapper.sh"           7 days ago          Exited (255) 7 days ago      80/tcp, 443/tcp     static
721f9bed9efb        prakhar1989/static-site                               "./wrapper.sh"           7 days ago          Exited (0) 7 days ago                            static-site
# 削除
$ docker rm a1835fc92536
a1835fc92536
$ docker ps -a
CONTAINER ID        IMAGE                     COMMAND             CREATED             STATUS                    PORTS               NAMES
f19d5084c39c        kamihara/catnip           "python ./app.py"   6 days ago          Exited (0) 6 days ago                         upbeat_khayyam
2c3fba23a98f        prakhar1989/static-site   "./wrapper.sh"      7 days ago          Exited (255) 7 days ago   80/tcp, 443/tcp     static
721f9bed9efb        prakhar1989/static-site   "./wrapper.sh"      7 days ago          Exited (0) 7 days ago                         static-site
$ docker run -d --name es --net foodtrucks-net -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.3.2
821fb24410a7e0c5460c6ec2c1ab7ba344e17d79755fb6f697d35d2ad42a716d
$ docker network inspect foodtrucks-net
[
    {
        "Name": "foodtrucks-net",
        "Id": "e5a8dfdb776b0f7b389e1bcc443ae06e7c8e444f1ca126964e6793352f72b913",
        "Created": "2018-11-26T08:38:36.2868316Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.19.0.0/16",
                    "Gateway": "172.19.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "821fb24410a7e0c5460c6ec2c1ab7ba344e17d79755fb6f697d35d2ad42a716d": {
                "Name": "es",
                "EndpointID": "389154fc667279b6a09e87adaa483807fc217057598b26e0ae20890a4d57af1e",
                "MacAddress": "02:42:ac:13:00:02",
                "IPv4Address": "172.19.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]
```

この通り、`es`コンテナは今`foodtrucks-net`ブリッジネットワーク内で稼働している。flaskアプリを`foodtrucks-net`ネットワーク内で起動してみよう。

```zsh
$ docker run -it --rm --net foodtrucks-net kamihara/foodtrucks-web bash
root@a11f8d551252:/opt/flask-app# curl es:9200
{
  "name" : "WROeoeP",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "JxO9dDfUQdiAzDUqRrcvww",
  "version" : {
    "number" : "6.3.2",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "053779d",
    "build_date" : "2018-07-20T05:20:23.451332Z",
    "build_snapshot" : false,
    "lucene_version" : "7.3.1",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}
root@a11f8d551252:/opt/flask-app# ls
app.py  node_modules  package-lock.json  package.json  requirements.txt  static  templates  webpack.config.js
root@a11f8d551252:/opt/flask-app# python app.py
Index not found...
Loading data in elasticsearch ...
Total trucks loaded:  623
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
^Croot@a11f8d551252:/opt/flask-app# exit
```

ユーザ定義ネットワーク上で、コンテナはIPアドレスで通信できるだけでなくコンテナ名を名前解決することが出来る。
この機能は自動サービス検出と呼ばれる。Flaskのコンテナを以下のように起動して

```zsh
$ docker run -d --net foodtrucks-net -p 5000:5000 --name foodtrucks-web kamihara/foodtrucks-web
01b484cf3400f835165061182b1d3ef5fbc88777c98827fddcaebccf96232597
$ docker container ls
CONTAINER ID        IMAGE                                                 COMMAND                  CREATED             STATUS              PORTS                                            NAMES
01b484cf3400        kamihara/foodtrucks-web                               "python ./app.py"        19 seconds ago      Up 17 seconds       0.0.0.0:5000->5000/tcp                           foodtrucks-web
821fb24410a7        docker.elastic.co/elasticsearch/elasticsearch:6.3.2   "/usr/local/bin/dock…"   38 minutes ago      Up 38 minutes       0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp   es
$ curl -I 0.0.0.0:5000
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 3697
Server: Werkzeug/0.11.2 Python/2.7.15rc1
Date: Mon, 26 Nov 2018 10:39:36 GMT
```

http://0.0.0.0:5000 にアクセスすれば、アプリケーションが動いていることがわかる。

実質的には以下たった4つのコマンドをタイプしただけで、コンテナ間の通信ができるようになっている。

```bash
#!/bin/bash

# Flaskコンテナのビルド
docker build -t kamihara/foodtrucks-web .

# ブリッジネットワークの作成
docker network create foodtrucks-net

# Elasticsearchコンテナの起動
docker run -d --name es --net foodtrucks-net -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.3.2

# Flaskアプリケーションコンテナの起動
docker run -d --net foodtrucks-net -p 5000:5000 --name foodtrucks-web kamihara/foodtrucks-web
```

### Docker Compose

ここまで、Dockerクライアントの理解に全ての時間を費やしてきた。
Dockerのエコシステムには良いオープンソースツールがある。いくつか紹介すると、

1. Docker Machine - Dockerホストを端末上、クラウドプロバイダ、独自のデータセンタ内に作成する
2. Docker Compose - マルチコンテナのDockerアプリケーションを定義、実行するためのツール
3. Docker Swarm - Dockerのネイティブクラスタリングソリューション
4. Kubernetes - コンテナ化されたアプリケーションのデプロイ、スケール、管理を自動化するオープンソースのシステム

ここではDockerComposeに注目する。

DockerComposeはマルチコンテナのDockerアプリケーションを容易に定義、実行するために使われる。
`docker-compose.yml`というコンフィグファイルを提供し、それが依存するアプリケーションとサービスのスイートを一コマンドで呼び出すことを可能にしている。

SF-Foodtrucks用の`docker-compose.yml`を作成してみる。

WindowsかMacの環境を使っているなら、DockerComposeはDockerToolboxに入っているので既にインストールされている。
Linuxユーザは簡単に導入できる。Pythonで書かれているので、`pip install docker-compose`でよい。

docker-composeがインストール出来ているか以下のコマンドで確認出来る。

```
$ docker-compose --version
docker-compose version 1.23.1, build b02f1306
```

`docker-compose.yml`の構文はとても単純で、以下の通りに書ける。

```docker-compose.yml
version: "3"
services:
  es:
    image: docker.elastic.co/elasticsearch/elasticsearch:6.3.2
    container_name: es
    environment:
      - discovery.type=single-node
    ports:
      - 9200:9200
    volumes:
      - esdata1:/usr/share/elasticsearch/data
  web:
    image: kamihara/foodtrucks-web
    command: python app.py
    depends_on:
      - es
    ports:
      - 5000:5000
    volumes:
      - ./flask-app:/opt/flask-app
volumes:
    esdata1:
      driver: local
```

以下にYAMLファイルの内容を整理しておく。

* 親レベルにはサービスの名前（`es`と`web`）を定義している。
* Dockerを実行する必要があるそれぞれのサービスに、`image`用のパラメータを追加することが出来る。
    * `es`用には、Elasticレジストリで使用可能な`elasticsearch`イメージを参照している。
    * Flaskアプリケーション用には、このセクションの初めで作成したイメージを参照している。
* `command`や`port`のような他のパラメータを経由して、コンテナに対してさらなる情報を与えている。
* `volumes`パラメータは`web`コンテナ内のコードが存在するマウントポイントを特定している。
    * ログにアクセスしたいときなどに有益。
* `es`コンテナ用にボリュームを追加しているが、これは再起動時にも読み込まれたデータを保持するためである。
* `depends_on`を指定することで、dockerに`es`コンテナを`web`コンテナより前に起動するよう伝えている。
    * より詳細は[docker compose docs](https://docs.docker.com/compose/compose-file/#depends_on)を参照のこと。

`docker-compose`の挙動を確認しよう。開始する前に、ポートを空けておかなければならない。もしFlaskとESのコンテナが実行中なら、停止しよう。

```
$ docker stop $(docker ps -q)
```

`docker-compose`を実行しよう。food trucksのディレクトリに移動して`docker-compose up`を実行すると

```zsh
$ docker-compose up
...
es     | [2018-11-27T07:01:34,420][INFO ][o.e.c.m.MetaDataMappingService] [ghfQE9H] [sfdata/DeajJs8mSZeEoEcUxIQu4w] update_mapping [truck]
es     | [2018-11-27T07:01:34,676][INFO ][o.e.c.m.MetaDataMappingService] [ghfQE9H] [sfdata/DeajJs8mSZeEoEcUxIQu4w] update_mapping [truck]
es     | [2018-11-27T07:01:34,750][INFO ][o.e.c.m.MetaDataMappingService] [ghfQE9H] [sfdata/DeajJs8mSZeEoEcUxIQu4w] update_mapping [truck]
web_1_3d94c46b1b02 |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
```

URLにアクセスすれば、アプリケーションが動いているのがわかる。
僅かなコンフィグで2つのコンテナが協調して稼働した。改めてデタッチモードで起動し直す。

```zsh
^CGracefully stopping... (press Ctrl+C again to force)
Stopping foodtrucks_web_1_3d94c46b1b02 ... done
Stopping es                            ... done
$ docker-compose up -d
Starting es ... done
Starting foodtrucks_web_1_3d94c46b1b02 ... done
$ docker-compose ps
            Name                           Command               State                Ports
---------------------------------------------------------------------------------------------------------
es                              /usr/local/bin/docker-entr ...   Up      0.0.0.0:9200->9200/tcp, 9300/tcp
foodtrucks_web_1_3d94c46b1b02   python app.py                    Up      0.0.0.0:5000->5000/tcp
```

名前はComposeによって自動的に作成された。ネットワークも自動的に構築されたか検証してみる。

まずサービスを停止しよう。一コマンドでいつでも停止できる。
データボリュームは維持され、`docker-compose up`を使用して同じデータでクラスタを再起動できるが、クラスタとデータボリュームを削除したいときには`docker-compose down -v`を使う。

```zsh
$ docker-compose down -v
Stopping foodtrucks_web_1_3d94c46b1b02 ... done
Stopping es                            ... done
Removing foodtrucks_web_1_3d94c46b1b02 ... done
Removing es                            ... done
Removing network foodtrucks_default
Removing volume foodtrucks_esdata1
```

そして、以前作成した`foodtrucks`ネットワークも削除する。

```zsh
$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
c0b3cf366693        bridge              bridge              local
e5a8dfdb776b        foodtrucks-net      bridge              local
7997ab619ebb        host                host                local
36ae0014eda5        none                null                local
$ docker network rm foodtrucks-net
foodtrucks-net
$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
c0b3cf366693        bridge              bridge              local
7997ab619ebb        host                host                local
36ae0014eda5        none                null                local
```

きれいな状態になったので、再実行してみる。

```zsh
$ docker-compose up -d
Creating network "foodtrucks_default" with the default driver
Creating volume "foodtrucks_esdata1" with local driver
Creating es ... done
Creating foodtrucks_web_1_7d8317b58b44 ... done
$ docker container ls
CONTAINER ID        IMAGE                                                 COMMAND                  CREATED              STATUS              PORTS                              NAMES
828ac02b53a6        kamihara/foodtrucks-web                               "python app.py"          About a minute ago   Up About a minute   0.0.0.0:5000->5000/tcp             foodtrucks_web_1_c7ef0af5b1cd
d1d55c85d5c1        docker.elastic.co/elasticsearch/elasticsearch:6.3.2   "/usr/local/bin/dock…"   About a minute ago   Up About a minute   0.0.0.0:9200->9200/tcp, 9300/tcp   es
```

ネットワークが作成されたか、`docker network ls`で見てみる。

```zsh
$ docker network ls
NETWORK ID          NAME                 DRIVER              SCOPE
c0b3cf366693        bridge               bridge              local
f761c310405b        foodtrucks_default   bridge              local
7997ab619ebb        host                 host                local
36ae0014eda5        none                 null                local
$ docker ps
CONTAINER ID        IMAGE                                                 COMMAND                  CREATED             STATUS              PORTS                              NAMES
828ac02b53a6        kamihara/foodtrucks-web                               "python app.py"          9 minutes ago       Up 9 minutes        0.0.0.0:5000->5000/tcp             foodtrucks_web_1_c7ef0af5b1cd
d1d55c85d5c1        docker.elastic.co/elasticsearch/elasticsearch:6.3.2   "/usr/local/bin/dock…"   9 minutes ago       Up 9 minutes        0.0.0.0:9200->9200/tcp, 9300/tcp   es
$ docker network inspect foodtrucks_default
[
    {
        "Name": "foodtrucks_default",
        "Id": "f761c310405b588a63c193b9b867fbfd87a7d722c0f2080b0746971b208fb934",
        "Created": "2018-11-27T07:23:55.176879249Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.21.0.0/16",
                    "Gateway": "172.21.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": true,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "828ac02b53a6322ba266ab3abe503a13a4d192cad57790b417122521a0e6f27e": {
                "Name": "foodtrucks_web_1_c7ef0af5b1cd",
                "EndpointID": "dd358bc29da9782e9c02f7a3f0a205a08457e4b477535ac8e7ff780ab96dea4d",
                "MacAddress": "02:42:ac:15:00:03",
                "IPv4Address": "172.21.0.3/16",
                "IPv6Address": ""
            },
            "d1d55c85d5c1e25c0561175fa8cae96b52d5062560d9cf185f2ee7b0a000264d": {
                "Name": "es",
                "EndpointID": "f9a633426dae173965540317ebb41052ae09e05d874b3612d853c3d415a3a583",
                "MacAddress": "02:42:ac:15:00:02",
                "IPv4Address": "172.21.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "default",
            "com.docker.compose.project": "foodtrucks",
            "com.docker.compose.version": "1.23.1"
        }
    }
]
```

Composeが`foodtrucks_default`という新しいネットワークを作成し、そのネットワーク内の両サービスを接続して通信可能にしている。
それぞれのコンテナがデフォルトネットワークに属し、お互いに到達可能で、お互いをコンテナ名と同一のホスト名で検出可能である。

### 開発のワークフロー

先程起動したFoodtrucksアプリをどのように変更できるかを見てみよう。

**注：この辺りは元サイトで想定していた挙動と異なる挙動をした部分があります。その点についてもメモを補記してそのまま載せています。**

まず`/hello`にリクエストが来たときに`Hello World!`メッセージを表示する変更を加えることにする。
現状は404を返す。

```
$ curl -I 0.0.0.0:5000/hello
HTTP/1.0 404 NOT FOUND
Content-Type: text/html
Content-Length: 233
Server: Werkzeug/0.11.2 Python/2.7.15rc1
Date: Tue, 27 Nov 2018 07:57:34 GMT
```

Flask内では、@app.route構文でルーティングが定義され、このアプリケーションでは`/`、`/debug`、`/search`の3つが定義されていることがわかる。
`/`はmainアプリケーション、`debug`はデバッグ情報の返却、`search`はElasticsearchのクエリをアプリが使用するために使われる。

```zsh
$ curl -I 0.0.0.0:5000/debug
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 104
Server: Werkzeug/0.11.2 Python/2.7.15rc1
Date: Tue, 27 Nov 2018 08:33:15 GMT
$ curl 0.0.0.0:5000/debug
{
  "msg": "yellow open sfdata fV2RucMhRim1g5g7r0r8XQ 5 1 623 0 1.1mb 1.1mb\n",
  "status": "success"
}
```

`flask-app/app.py`をエディターで開いて以下の通り変更する。

```flask-app/app.py
# add a new hello route
@app.route('/hello')
def hello():
    return "hello world!"
```

もう一度リクエストを発行してみると…

```zsh
$ curl -I 0.0.0.0:5000/hello
HTTP/1.0 404 NOT FOUND
Content-Type: text/html
Content-Length: 233
Server: Werkzeug/0.11.2 Python/2.7.15rc1
Date: Tue, 27 Nov 2018 08:40:23 GMT
```

これはうまく動作しなかった。`app.py`を変更したが、変更したファイルは自分の端末にある。
一方、Dockerは`kamihara/foodtrucks-web`イメージを元にしたコンテナで実行されているので、この変更を検知していない。
このことを検証するために、以下を試してみる。

```zsh
$ docker-compose run web bash
Starting es ... done
root@44768b369d96:/opt/flask-app# ls
app.py  package-lock.json  package.json  requirements.txt  static  templates  webpack.config.js
root@44768b369d96:/opt/flask-app# grep hello app.py
# add a new hello route
@app.route('/hello')
def hello():
    return "hello world!"
root@44768b369d96:/opt/flask-app#
# ホントはgrepしても引っかからないはず…チュートリアルと異なる挙動になった。
```

ここで`docker-compose run`コマンドを実行することで、変更がコンテナ内の`app.py`内に存在しないことを検証した。
これは`docker run`に似ているが、サービス（この場合は`web`）の追加引数が必要である。
`bash`を実行し、Dockerfileに記載している通り、`/opt/flask-app`ディレクトリで開いている。
grepコマンドから、変更がファイルにないことがわかる（※はずだが、上記のとおりgrepで引っかかっている…）。

docker-composeに対して、イメージを使用しない代わりに、ローカルのファイルを使用することを伝える必要がある。
また、デバッグモードを`true`にして、Flaskが`app.py`の変更時にサーバをリロードするようにしておく。
`docker-compose.yml`の`web`の部分を以下のように変更すればよい。

```docker-compose.yml
version: "3"
services:
  es:
    image: docker.elastic.co/elasticsearch/elasticsearch:6.3.2
    container_name: es
    environment:
      - discovery.type=single-node
    ports:
      - 9200:9200
    volumes:
      - esdata1:/usr/share/elasticsearch/data
  web:
    build: . # ここを修正
    command: python app.py
    environment:
      - DEBUG=True  # ここも修正
    depends_on:
      - es
    ports:
      - "5000:5000"
    volumes:
      - ./flask-app:/opt/flask-app
volumes:
    esdata1:
      driver: local
```

変更したら、コンテナを停止して起動しよう。

```zsh
$ docker-compose down -v
Stopping foodtrucks_web_1_c7ef0af5b1cd ... done
Stopping es                            ... done
Removing foodtrucks_web_run_1_89de83521f04 ... done
Removing foodtrucks_web_1_c7ef0af5b1cd     ... done
Removing es                                ... done
Removing network foodtrucks_default
Removing volume foodtrucks_esdata1
$ docker-compose up -d
Creating es ... done
Creating foodtrucks_web_1_88824b529262 ... done
```

最後のステップとして、`app.py`に新しいルートを加えた変更をしよう。curlを試してみると正しいレスポンスが返ってくる。

```
$ curl 0.0.0.0:5000/hello
hello world!
```

### AWS Elastic Container Service

今ではGCP、AWS、Azureやその他にコンテナをデプロイ出来る。
ここではElastic Container Service (ECS) を見ていく。

幸運にも、ECSはDockerComposeファイルに対応できるCLIツールをもち、自動的にECS上にクラスタをプロビジョニング出来る。
すでに`docker-compose.yml`があるので、AWSでの起動はそれほど大変にはならない。

まずはCLIのインストールをしないといけない。インストールが完了していることを以下で確認できる。

```zsh
# 今回はbrewでインストールした
$ ecs-cli --version
ecs-cli version 1.12.0 (*UNKNOWN)
```

最初のステップはインスタンスにログインするときに使うキーペアを手に入れることだ。
EC2コンソールで新しいキーペアを作成する。キーペアをダウンロードして安全な場所に格納する。リージョン名にも注意。
※今回は自社のテスト用のアカウントで作業

以下のように`configure`コマンドにクラスタを設置したいリージョン名とクラスタ名を与えてCLIを設定する。
これはリージョン名はキーペアを作成したリージョン名と一致していること。

```zsh
$ ecs-cli configure --region ap-northeast-1 --cluster foodtrucks
INFO[0000] Saved ECS CLI cluster configuration default.
# これで~/.ecs/configが作成されている
```

次のステップで、CLIがCloudFormationテンプレートの作成を可能にする。

```zsh
$ ecs-cli up --keypair kamihara_ecs --capability-iam --size 2 --instance-type t2.micro
INFO[0000] Using recommended Amazon Linux 2 AMI with ECS Agent 1.22.0 and Docker version 18.06.1-ce
INFO[0000] Created cluster                               cluster=foodtrucks region=ap-northeast-1
INFO[0001] Waiting for your cluster resources to be created...
INFO[0001] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0062] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0122] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
VPC created: vpc-08f713d0c6bf2df12
Security Group created: sg-0d8353d0c09bf5255
Subnet created: subnet-01a99e2c22ac71ba5
Subnet created: subnet-07df0a1a5fd91cf07
Cluster creation succeeded.
```

* キーペアの名称（この場合`kamihara_ecs`）、インスタンス数（`--size`）、コンテナを実行したいインスタンスタイプを与えている。
* `--capability-iam`はCLIにこのコマンドがIAMリソースを作成する可能性があることを伝えている。

最後に`docker-compose.yml`ファイルを少し修正する。
オリジナルを修正する代わりにコピーを作成して、`aws-compose.yml`とする。このファイルの中身は以下の通り。

```aws-compose.yml
es:
  image: elasticsearch
  cpu_shares: 100
  mem_limit: 262144000
web:
  image: kamihara/foodtrucks-web
  cpu_shares: 100
  mem_limit: 262144000
  ports:
    - "80:5000"
  links:
    - es
```

`docker-compose.yml`からの変更箇所は以下。

* `mem_limit`と`cpu_shares`を各コンテナに与えている。
    * `t2.micro`インスタンスで実行されるので、メモリを250MBアロケートする。

次のステップに行く前に、イメージをDockerHubへ公開する必要がある。

```zsh
$ docker push kamihara/foodtrucks-web
The push refers to repository [docker.io/kamihara/foodtrucks-web]
738059566fc3: Pushed
f298a4233adb: Pushed
c92b6e1d8cbf: Pushed
878fb0c95398: Pushed
cfd0f20925b8: Pushed
ab97b8516899: Pushed
4353f244ebae: Pushed
db946201b3c0: Pushed
268a067217b5: Mounted from library/ubuntu
c01d74f99de4: Mounted from library/ubuntu
ccd4d61916aa: Mounted from library/ubuntu
8f2b771487e9: Mounted from library/ubuntu
f49017d4d5ce: Mounted from library/ubuntu
latest: digest: sha256:8b4c9d3d63bb7e292b8210638edae577b943a769c1b3f45959f60f13a55ba284 size: 3048
```

アプリケーションをECSにデプロイしてみる。

```zsh
$ ecs-cli compose --file aws-compose.yml up
WARN[0000] Skipping unsupported YAML option for service...  option name=networks service name=es
WARN[0000] Skipping unsupported YAML option for service...  option name=networks service name=web
INFO[0000] Using ECS task definition                     TaskDefinition="FoodTrucks:2"
INFO[0000] Starting container...                         container=6b8a711a-2ab5-4465-a7bc-180a18613f3f/es
INFO[0000] Starting container...                         container=6b8a711a-2ab5-4465-a7bc-180a18613f3f/web
INFO[0000] Describe ECS container status                 container=6b8a711a-2ab5-4465-a7bc-180a18613f3f/es desiredStatus=RUNNING lastStatus=PENDING taskDefinition="FoodTrucks:2"
INFO[0000] Describe ECS container status                 container=6b8a711a-2ab5-4465-a7bc-180a18613f3f/web desiredStatus=RUNNING lastStatus=PENDING taskDefinition="FoodTrucks:2"
INFO[0012] Describe ECS container status                 container=6b8a711a-2ab5-4465-a7bc-180a18613f3f/es desiredStatus=RUNNING lastStatus=PENDING taskDefinition="FoodTrucks:2"
INFO[0012] Describe ECS container status                 container=6b8a711a-2ab5-4465-a7bc-180a18613f3f/web desiredStatus=RUNNING lastStatus=PENDING taskDefinition="FoodTrucks:2"
INFO[0024] Describe ECS container status                 container=6b8a711a-2ab5-4465-a7bc-180a18613f3f/es desiredStatus=RUNNING lastStatus=PENDING taskDefinition="FoodTrucks:2"
INFO[0024] Describe ECS container status                 container=6b8a711a-2ab5-4465-a7bc-180a18613f3f/web desiredStatus=RUNNING lastStatus=PENDING taskDefinition="FoodTrucks:2"
INFO[0036] Started container...                          container=6b8a711a-2ab5-4465-a7bc-180a18613f3f/es desiredStatus=RUNNING lastStatus=RUNNING taskDefinition="FoodTrucks:2"
INFO[0036] Started container...                          container=6b8a711a-2ab5-4465-a7bc-180a18613f3f/web desiredStatus=RUNNING lastStatus=RUNNING taskDefinition="FoodTrucks:2"
```

`--file`指定で`aws-compose.yml`を参照させている。
もし、全てうまくいっていれば、`desiredStatus=RUNNING lastStatus=RUNNING`をが最終行に表示されているはずだ。

アプリケーションが起動したが、アクセス方法を確認する必要がある、

```
$ ecs-cli ps
Name                                      State    Ports                      TaskDefinition  Health
6b8a711a-2ab5-4465-a7bc-180a18613f3f/es   RUNNING                             FoodTrucks:2    UNKNOWN
6b8a711a-2ab5-4465-a7bc-180a18613f3f/web  RUNNING  13.115.33.62:80->5000/tcp  FoodTrucks:2    UNKNOWN
```

ブラウザで http://13.115.33.62/ を開くとアプリケーションが起動していることがわかる。
ECSコンソール上の表示を確認すると、foodtrucksと名付けられたECSクラスタが1タスク2コンテナインスタンスで実行されている。

##　おわりに

複数コンテナにより構成されるアプリケーションのデプロイについて、ネットワークの構築やdocker compose、最終的にECSへのデプロイまでをひとしきり追っかけました。
[昨日の記事](https://qiita.com/Kamihara/items/6fbb2ea8ca2f511875fd)も含めかなりボリュームのあるチュートリアルですが、全くの初心者から始めて業務に活用できるようにするためには十分な内容という印象です。
今回掲載した記事はあくまでも元サイトを進めていった中でのメモですので、ぜひ https://docker-curriculum.com/ をご覧いただければと思います。