この記事はiRidge Advent Calendar 2018の23日目の記事です。
そしてはじめてのDocker(基本編)の続きです。
この記事も長いです。
マルチコンテナ環境
サービス毎にコンテナを分割して保つのがよい。
各層を異なるコンテナで分割することにより、それぞれに最も適切なインスタンスタイプで構成することが出来る。
SF Food Trucks
というアプリをDocker化してみる。以下のような構成になっている。
- バックエンドはPython(Flask)
- 検索にはElasticsearch
まずは、リポジトリをクローンする。
$ 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のイメージがあることが分かる。
$ 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しよう。
$ 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クラスターがシングルノードで実行するよう環境変数を設定し、開発モードで実行する。
$ 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を指定してログを参照できる。
正常に起動されていれば以下のようにログが記録されている。
$ 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でリクエストを送信できるか試してみると、以下のようなレスポンスが確認できる。
$ 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ファイルは以下のようになる。
# 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
にコピーしている。- これをワーキングディレクトリにも設定しているので、以降のコマンドはこの場所を起点にして実行される。
イメージをビルドし、コンテナを起動する。
$ docker build -t kamihara/foodtrucks-web .
初回実行時には、Dockerクライアントがubuntuイメージをダウンロードし、全てのコマンドを実行してイメージを準備する。
何らかのアプリケーションコード修正後にdocker build
を再実行すると、ほぼ瞬時に変更される。アプリケーションを実行してみよう。
$ 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つのネットワークが自動的に作成される。
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
aedaa2f3e76b bridge bridge local
7997ab619ebb host host local
36ae0014eda5 none null local
bridgeネットワークはコンテナがデフォルトで実行されるネットワークである。
以下のコマンドでbridgeネットワークを調べてみると
$ 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つの問題が残っている。
- Flaskコンテナにホスト名
es
が172.17.0.2
で有ること、またはIP変更時にそのIPをどうやって伝えればよいか。 - 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ブリッジドライバはホストマシンへ自動的にルールをインストールし、異なるブリッジに属するコンテナはお互いに直接通信は出来ないようにする。
作成可能な他の種類のネットワークもあり、それは公式ドキュメントを参照すると良い。
ネットワークが出来たので、--net
を使用することでコンテナをこのネットワーク内で起動出来る。
その前に、bridgeネットワークで稼働しているESコンテナを停止する。
$ 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
ネットワーク内で起動してみよう。
$ 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のコンテナを以下のように起動して
$ 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つのコマンドをタイプしただけで、コンテナ間の通信ができるようになっている。
#!/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のエコシステムには良いオープンソースツールがある。いくつか紹介すると、
- Docker Machine - Dockerホストを端末上、クラウドプロバイダ、独自のデータセンタ内に作成する
- Docker Compose - マルチコンテナのDockerアプリケーションを定義、実行するためのツール
- Docker Swarm - Dockerのネイティブクラスタリングソリューション
- 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
の構文はとても単純で、以下の通りに書ける。
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を参照のこと。
docker-compose
の挙動を確認しよう。開始する前に、ポートを空けておかなければならない。もしFlaskとESのコンテナが実行中なら、停止しよう。
$ docker stop $(docker ps -q)
docker-compose
を実行しよう。food trucksのディレクトリに移動してdocker-compose up
を実行すると
$ 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つのコンテナが協調して稼働した。改めてデタッチモードで起動し直す。
^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
を使う。
$ 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
ネットワークも削除する。
$ 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
きれいな状態になったので、再実行してみる。
$ 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
で見てみる。
$ 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のクエリをアプリが使用するために使われる。
$ 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
をエディターで開いて以下の通り変更する。
# add a new hello route
@app.route('/hello')
def hello():
return "hello world!"
もう一度リクエストを発行してみると…
$ 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
イメージを元にしたコンテナで実行されているので、この変更を検知していない。
このことを検証するために、以下を試してみる。
$ 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
の部分を以下のように変更すればよい。
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
変更したら、コンテナを停止して起動しよう。
$ 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のインストールをしないといけない。インストールが完了していることを以下で確認できる。
# 今回はbrewでインストールした
$ ecs-cli --version
ecs-cli version 1.12.0 (*UNKNOWN)
最初のステップはインスタンスにログインするときに使うキーペアを手に入れることだ。
EC2コンソールで新しいキーペアを作成する。キーペアをダウンロードして安全な場所に格納する。リージョン名にも注意。
※今回は自社のテスト用のアカウントで作業
以下のようにconfigure
コマンドにクラスタを設置したいリージョン名とクラスタ名を与えてCLIを設定する。
これはリージョン名はキーペアを作成したリージョン名と一致していること。
$ ecs-cli configure --region ap-northeast-1 --cluster foodtrucks
INFO[0000] Saved ECS CLI cluster configuration default.
# これで~/.ecs/configが作成されている
次のステップで、CLIがCloudFormationテンプレートの作成を可能にする。
$ 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
とする。このファイルの中身は以下の通り。
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へ公開する必要がある。
$ 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にデプロイしてみる。
$ 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://docker-curriculum.com/ をご覧いただければと思います。