Docker ネットワークで遊ぶ
ソフトウェアエンジニアって物理現象を相手にするの嫌いですよね(偏見)。LAN ケーブル買ってきて断線とかしてたら最悪。ってことで今日は Docker network で仮想的なネットワークを作って遊んでみる。秋葉原で何も買ってこなくてよいので楽チン。
Docker network の機能を使うと、起動した Docker コンテナが他のコンテナと通信できたりする。もちろん外部との通信もできる。今回は Docker network のうち host ネットワークと bridge ネットワークを使って遊んでみたメモ。ホストマシンは ubuntu 20.04。
簡単なサンプルサーバーを作る
Docker を起動する準備として、まずは nodejs の express フレームワークを使ってシンプルな Web アプリを作ってみる。 3150 番ポートにアクセスされると Hello World!
と返すだけのサーバアプリである。以下の通りに index.js を実装した。
const express = require('express') const app = express() const port = 3150 app.get('/', (req, res) => { res.send('Hello World!\n') }) app.listen(port, () => { console.log(`Example app listening at http://localhost:${port}`) })
この Web アプリの依存関係をインストールするための package.json は以下。
{ "name": "exp_server", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "express": "^4.17.1" } }
index.js と package.json を同じディレクトリに置いて npm install を実行すれば自動的に依存ライブラリがインストールされる。とはいっても、今回はそのインストールを Docker イメージのビルド時にやってしまうため、ただ2つのファイルを手元に作っておけばよい。
今回用いるコンテナイメージを作成するための Dockerfile は以下の通り。
FROM ubuntu:18.04 RUN apt update \ && apt install -y sudo curl \ && curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - \ && sudo apt-get install -y nodejs WORKDIR /root COPY index.js /root COPY package.json /root RUN npm install CMD node index.js
ubuntu:18.04 をベースとして 、nodejs や npm を NodeSource からダウンロードしてきて、コンテナ内にインストールする。Docker Hub の node イメージ を使ってもよいのかもしれないが、ubuntu からコマンド叩くほうが個人的には何やってるか分かりやすいので今回はこの形を採用した。
index.js と package.json を手元に置いて、以下のコマンドを実行することにより exp_ubuntu
という名前のコンテナイメージをビルドする。
$ docker build -t exp_ubuntu .
しばらく待ったらコンテナイメージのビルドが完了する。以下のコマンドで確認できる。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE exp_ubuntu latest c8f01578eecc About a minute ago 270MB
これで Docker コンテナを起動する準備が完了した。
host ネットワーク
まずは host ネットワークから作ってみる。以下のページを参考にした。
Use host networking | Docker Documentation
host ネットワークは Docker を動かしているホストマシンと同一のネットワークである。host ネットワーク上でコンテナを起動することは、Docker コンテナをホストマシン上の1つのアプリとして実行するような感じになる。
以下のコマンドにより、先ほど作成した Web アプリのコンテナイメージを host ネットワーク上で起動する。server1
というコンテナ名を付けている。--network host
というオプションによって、接続するネットワークとして host を指定しているのがポイント。
$ docker run -d --network host --name server1 exp_ubuntu $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4975bdef4adb exp_ubuntu "/bin/sh -c 'node in…" 8 seconds ago Up 6 seconds server1
ブラウザから http://localhost:3150/
にアクセスすると Hello World!
というメッセージが表示される。3150 番のポートでリクエストを受け付けていることが確認できた。
ちなみに、これはホストマシン上で以下のコマンドを起動したときの挙動と全く変わらない。
$ node index.js
host ネットワーク上のコンテナとして Web アプリを起動する利点は、環境構築をコンテナという隔離されたところでできる点だけでなく、性能の最適化を行うときに便利だったりするらしい。
起動中のコンテナの中身を覗いてみる。lsof
コマンドによって 3150 番のポートで LISTEN されていることが確認できる。
$ docker exec -it server1 bash ~# apt install -y lsof ~# lsof -i COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME node 7 root 18u IPv6 706707 0t0 TCP *:3150 (LISTEN) ~# exit
docker network inspect
コマンドで host ネットワークの状況を確認できる。
$ docker network inspect host [ { "Name": "host", ... "Containers": { "4975bdef4adbdf2b520d9b0d35838e46dc9cdc6ab4e31496830ae7625c93e55e": { "Name": "server1", "EndpointID": "75088badf4fd62ed7edf0f1265a0177214ddcfa9e172f961b9ff143f7f8feb2d", "MacAddress": "", "IPv4Address": "", "IPv6Address": "" } }, "Options": {}, "Labels": {} } ]
Web アプリを起動している server1
コンテナがネットワーク上に登録されていることが確認できた。
最後に server1
を停止してコンテナを削除する。
$ docker container stop server1 $ docker container rm server1
bridge ネットワーク
つぎに bridge ネットワーク上でコンテナを起動してみる。bridge ネットワークはもっともオーソドックスな Docker ネットワークであり、同じネットワーク内のコンテナ同士の通信を実現する。ホストマシンとは異なるネットワークであるのがポイントである。以下を参考にした。
Use bridge networks | Docker Documentation
bridge ネットワーク上で Web アプリを起動してみる。せっかくなので2つのコンテナを起動してみる。それぞれ server1
, server2
という名前を付けている。--network bridge
というオプションによって bridge ネットワーク上でコンテナを起動することを指定している。ただし bridge ネットワークはデフォルトのネットワークなので、このオプションは無くてもよい。
$ docker run -d --network bridge -p2001:3150 --name server1 exp_ubuntu $ docker run -d --network bridge -p2002:3150 --name server2 exp_ubuntu
-p2001:3150
というオプションによって、ホストマシン側に公開するポートを指定している。具体的には、ホストマシン側の 2001 番ポートが bridge ネットワーク側の 3150 番ポートに対応する。実際、http://localhost:2001/
や http://localhost:2002/
にアクセスすると Web アプリにアクセスできる。
bridge ネットワークを inspect してみる。
$ docker network inspect bridge [ { "Name": "bridge", ... "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.17.0.0/16" } ] }, ... "Containers": { "328253b90470aa98c8419aec36c36610b113e6c2393ce5fd3ff37c261efb366d": { "Name": "server1", "EndpointID": "0d26a79bd7969059afc12765a4e207887c6ffccfda7d2bdf93162ae3df7e4d76", "MacAddress": "02:42:ac:11:00:02", "IPv4Address": "172.17.0.2/16", "IPv6Address": "" }, "99b67c70283e1f1111430bb2c6c126e967a422522196ed636356b4be2c7e9642": { "Name": "server2", "EndpointID": "a7167ff0b5562125fb14a07872d4fb96eb9c0ca4a2b017a68892ff1ba864a945", "MacAddress": "02:42:ac:11:00:03", "IPv4Address": "172.17.0.3/16", "IPv6Address": "" } }, ... } ]
server1
や server2
はそれぞれ、bridge ネットワーク内の IP アドレス 172.17.0.2
と 172.17.0.3
が割り当てられていることが分かる。
ポートの対応関係は docker ps
コマンドで確認できる。PORTS
列にまさにポートの対応関係が示されている。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 5385113b3b6d exp_ubuntu "/bin/sh -c 'node in…" 7 minutes ago Up 7 minutes 0.0.0.0:2002->3150/tcp server2 5717a0502c50 exp_ubuntu "/bin/sh -c 'node in…" 7 minutes ago Up 7 minutes 0.0.0.0:2001->3150/tcp server1
ちなみに、ホストマシン上で ip コマンドを叩くと docker0
という仮想ブリッジが存在することが確認できる。ホストマシンと bridge ネットワーク上のコンテナはこのブリッジ経由で通信される。
$ ip a show docker0 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:36:1e:b2:60 brd ff:ff:ff:ff:ff:ff inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0 valid_lft forever preferred_lft forever inet6 fe80::42:36ff:fe1e:b260/64 scope link valid_lft forever preferred_lft forever
所感
手軽に Docker コンテナ同士をネットワークで接続できるのでいろいろ実験してみたくなる。自分でネットワーク構成考えてみて、各ネットワークにコンテナを配置する遊びとかできそう。簡易的なロードバランサーとか実装してみたくなった。