dockerで簡単なNode.jsスクリプトを実行する
お題
dockerでNode.jsスクリプトを起動してみる.
nodeはホスト端末でやる場合,初期セッティングとかバージョン管理とか面倒くさい(らしい)
簡単なスクリプトであれば,docker上で動かしてみることでテストが楽になるかも.
作業環境
Docker 18.09.3 on Ubuntu 18.04 (virtualbox on macOS)
ubuntuにdockerをインストールする手順はこちら:
koimedenshi.hatenablog.com
やること
これまでの記事同様,dockerhubのドキュメントを参考に手を動かしてみる.
nodeのhow to useドキュメントは長くてdockerhubのページに収まらない(dockerhubには文字数制限があるらしい)ので,以下のgithubを見るように書いてあった.
とりあえずこれをやってみる.
手順
上記のドキュメントには3通りの方法が書いてあった.
- Dockerfileで起動
- docker-composeで起動
- コマンド1行で起動
順番にやってみる.
1. Dockerfileで起動
新規のディレクトリで新規Dockerfileを作成
touch Dockerfile
Dockerfileに以下の内容を書き込む
# specify the node base image with your desired version node:<version> FROM node:8 # replace this with your application's default port EXPOSE 8888
docker buildでimageを作成する
$ docker build -t my-nodejs-app .
作成したimageでコンテナを起動する⇨コンテナ内のnode.js対話環境に接続され,nodeコマンドをインタラクティブに実行できる.
$ docker run -it --rm --name my-running-app my-nodejs-app > # 対話環境に接続 > console.log("hello world"); hello world undefined > > .help # ヘルプ .break Sometimes you get stuck, this gets you out .clear Alias for .break .editor Enter editor mode .exit Exit the repl .help Print this help message .load Load JS from a file into the REPL session .save Save all evaluated commands in this REPL session to a file > > .exit # 終了
-rmオプションをつけて起動したので,対話シェルの終了とともにコンテナが削除される
2. docker-composeで起動
例示されているdocker-compose.yamlは以下の通り
version: "2" services: node: image: "node:8" user: "node" working_dir: /home/node/app environment: - NODE_ENV=production volumes: - ./:/home/node/app expose: - "8081" command: "npm start"
これだけではNode.jsスクリプトを実行できない.
このコンテナは起動後にnpm start
コマンドを実行するが,このコマンドを実行するには同じディレクトリ内にpackage.json
という設定ファイルが設置されている必要がある.
https://nodejs.org/en/docs/guides/nodejs-docker-webapp/ を参考に作成したpackage.jsonがこちら.
{ "name": "docker_web_app", "version": "1.0.0", "description": "Node.js on Docker", "author": "XXXX XXXX", "main": "myfirst.js", "scripts": { "start": "node myfirst.js" } }
これでコンテナ内でnode myfirst.js
が実行されるようになる.
次にmyfirst.js
を用意する.
https://www.w3schools.com/nodejs/nodejs_get_started.asp から引用したjsがこちら.
var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/html'}); res.end('Hello World!'); }).listen(8080);
最終的に以下の3つのファイルを同一ディレクトリに用意することになる.
$ tree . . ├── docker-compose.yaml ├── myfirst.js └── package.json
このディレクトリに移動して以下のコマンドを実行する
docker-compose up -d
これでコンテナが起動しhttpサーバが待ち受け状態になるが,ポート8081でexposeしているので,自分自身またはリンクしたコンテナからしかアクセスできない.
- 自分自身からアクセスするには以下のようにすればよい
docker exec -it <node.jsコンテナ名> sh #/ curl localhost:8080
別のコンテナからリンクさせる場合は,同一ネットワークでコンテナ同士を接続する(長くなるので省略)
expose: - "8081"
これを
ports: - "8081:8080"
こうする.
この例は(チュートリアルとしては)お手軽感が薄い.
3. コマンド1行で起動
単一のNode.jsスクリプトをお手軽に実行したい場合は,コマンドオプションで指定するのが楽.
例示されているコマンドは以下
docker run -it --rm --name my-running-script -v "$PWD":/usr/src/app -w /usr/src/app node:8 node your-daemon-or-script.js
-vオプションでカレントディレクトリのファイルを/usr/src/appにコピーしているので,コマンド実行時のカレントディレクトリにNode.jsスクリプトを置いておけばよい.
例えば以下のようなスクリプトを用意する(前述のmyfirst.jsと同一)
var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/html'}); res.end('Hello World!'); }).listen(8080);
このように実行したいNode.jsスクリプトがhttpサーバのような場合,-pオプションでポートを解放しないとホストからアクセスできない.
例えば-p 8081:8080
オプションをつけることで,localhost:8081からアクセスできるようにする.
最終的なコマンドは以下の通り
docker run -it --rm -p 8081:8080 --name my-running-script -v "$PWD":/usr/src/app -w /usr/src/app node:8 node your-daemon-or-script.js
コマンド実行後http://localhost:8081にアクセスすればHello World!
となるはず.
おわりに
dockerでNode.jsスクリプトを実行する方法を,https://github.com/nodejs/docker-node/blob/master/README.md#how-to-use-this-imageを参考にやってみた.
Node.jsのdockerコンテナ化に関しては色々ドキュメントがあるので,参考にしていきたい.
swarm tutorialをdindでやってみる 完結編
やること
前回記事の続き
以下のように,6台のdindコンテナによるswarm nodeが構築されるところまでやった
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 71jzsyefebwvmiz5va29cnhn4 * manager1 Ready Active Leader 18.09.3 0e3bl408q8lufo5xczwny5l9m manager2 Ready Active Reachable 18.09.3 icd4ixgyqvsc0ebrdf8w5esxy manager3 Ready Active Reachable 18.09.3 woo1rgys2gsgpffjtyrec7gqe worker1 Ready Active 18.09.3 kv6dnud4pyfk0269xlqauwngd worker2 Ready Active 18.09.3 i9ygvq1nvcqqpdyatpygl39ax worker3 Ready Active 18.09.3
以下,docker swarm tutorialの後半部分をこのdind版swarmでもやってみる.
手順
manager1ノードでweb
というserviceを立ち上げる.
(dockerにおけるserviceという概念に関してはこのへんとかに書いてある)
docker exec -it manager1 docker service create -p 80:80 --name web nginx:latest
ブラウザからmanager1ホストのIPへアクセスすると,立ち上がったサービスを確認できる manager1のIPはhostnameコマンドなどで確認できる
$ hostname -i 172.22.0.2
なお,manager1以外のホストのIPへアクセスしても同様に表示される.
これはswarmのRouting Meshという機能らしい
チュートリアルにはない手順だが,docker service psコマンドでサービスの状態を見てみる
$ docker exec -it manager1 docker service ps web ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS kw541zci4kni web.1 nginx:latest manager2 Running Running 20 minutes ago /
serviceの起動コマンドはmanager1で実行したが,実際のサービスはmanager2ノードで立ち上がっているっぽい.
80番ポートへのアクセスは,このノードへルーティングされているようだ.
次にサービスをスケーリングする.
webというサービスを合計15個になるよう複製する
この操作で端末の動作が重たくなるので注意.(メモリ1GBのVMでも動いたけど,だいぶもっさりした)
docker exec -it manager1 docker service scale web=15
docker service lsコマンドでレプリカが作成されたことを確認
$ docker exec -it manager1 docker service ls ID NAME MODE REPLICAS IMAGE PORTS 4x6ycl1ftacd web replicated 15/15 nginx:latest *:80->80/tcp
docker service psコマンドでwebサービスを確認すると,15個のレプリカが6台のノードに割り振られていることがわかる
$ docker exec -it manager1 docker service ps web ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS kw541zci4kni web.1 nginx:latest manager2 Running Running 28 minutes ago 3l7bfnzp1y09 web.2 nginx:latest manager2 Running Running 4 minutes ago s20rm41zo6uw web.3 nginx:latest manager1 Running Running 2 minutes ago a99tv7ha01et web.4 nginx:latest manager3 Running Running 2 minutes ago qb38lx8ov9ge web.5 nginx:latest worker3 Running Running 2 minutes ago vlihahkxs9pv web.6 nginx:latest worker1 Running Running 2 minutes ago 7yzl5f31a9rb web.7 nginx:latest worker2 Running Running 2 minutes ago 4we17vy5xtmg web.8 nginx:latest worker2 Running Running 2 minutes ago yy5kn8mv8hvz web.9 nginx:latest worker1 Running Running 2 minutes ago aej45h59wln3 web.10 nginx:latest worker3 Running Running 2 minutes ago dygbxzxrbuwc web.11 nginx:latest manager1 Running Running 2 minutes ago zbhsfnn1p5ux web.12 nginx:latest manager1 Running Running 2 minutes ago zlud7r6h9y1e web.13 nginx:latest manager3 Running Running 2 minutes ago q2xz28vesyy9 web.14 nginx:latest manager2 Running Running 4 minutes ago vf5f395d6ydp web.15 nginx:latest worker3 Running Running 2 minutes ago
docker node update --availability drain
コマンドで,指定したノードからサービスを放出できる
以下のコマンドにより,worker1ノードのサービスをdrainする.
docker exec -it manager1 docker node update --availability drain worker1
ふたたびサービスの状態を確認
$ docker exec -it manager1 docker service ps web ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS kw541zci4kni web.1 nginx:latest manager2 Running Running 32 minutes ago 3l7bfnzp1y09 web.2 nginx:latest manager2 Running Running 8 minutes ago s20rm41zo6uw web.3 nginx:latest manager1 Running Running 6 minutes ago a99tv7ha01et web.4 nginx:latest manager3 Running Running 6 minutes ago qb38lx8ov9ge web.5 nginx:latest worker3 Running Running 6 minutes ago 199q9r5nicnz web.6 nginx:latest manager3 Running Starting 4 seconds ago vlihahkxs9pv \_ web.6 nginx:latest worker1 Shutdown Shutdown 4 seconds ago 7yzl5f31a9rb web.7 nginx:latest worker2 Running Running 6 minutes ago 4we17vy5xtmg web.8 nginx:latest worker2 Running Running 6 minutes ago vnqvozak8s7m web.9 nginx:latest worker2 Running Starting 3 seconds ago yy5kn8mv8hvz \_ web.9 nginx:latest worker1 Shutdown Shutdown 4 seconds ago aej45h59wln3 web.10 nginx:latest worker3 Running Running 6 minutes ago dygbxzxrbuwc web.11 nginx:latest manager1 Running Running 6 minutes ago zbhsfnn1p5ux web.12 nginx:latest manager1 Running Running 6 minutes ago zlud7r6h9y1e web.13 nginx:latest manager3 Running Running 6 minutes ago q2xz28vesyy9 web.14 nginx:latest manager2 Running Running 8 minutes ago vf5f395d6ydp web.15 nginx:latest worker3 Running Running 6 minutes ago
worker1にいたweb.6とweb.9がシャットダウンされ,代わりにmanager3およびworker2ノードで実行されている
worker1の状態は,docker node lsコマンドでも確認できる
$ docker exec -it manager1 docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 71jzsyefebwvmiz5va29cnhn4 * manager1 Ready Active Leader 18.09.3 0e3bl408q8lufo5xczwny5l9m manager2 Ready Active Reachable 18.09.3 icd4ixgyqvsc0ebrdf8w5esxy manager3 Ready Active Reachable 18.09.3 woo1rgys2gsgpffjtyrec7gqe worker1 Ready Drain 18.09.3 kv6dnud4pyfk0269xlqauwngd worker2 Ready Active 18.09.3 i9ygvq1nvcqqpdyatpygl39ax worker3 Ready Active 18.09.3
AVAILABILITYがDrainとなっている
次にサービスをスケールダウンする.
以下のコマンドでレプリカの数を15から10まで減らす
docker service scale web=10
サービスの状態確認結果がこちら
$ docker exec -it manager1 docker service ps web ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS kw541zci4kni web.1 nginx:latest manager2 Running Running 41 minutes ago 3l7bfnzp1y09 web.2 nginx:latest manager2 Running Running 17 minutes ago s20rm41zo6uw web.3 nginx:latest manager1 Running Running 15 minutes ago a99tv7ha01et web.4 nginx:latest manager3 Running Running 15 minutes ago qb38lx8ov9ge web.5 nginx:latest worker3 Running Running 15 minutes ago 199q9r5nicnz web.6 nginx:latest manager3 Running Running 8 minutes ago vlihahkxs9pv \_ web.6 nginx:latest worker1 Shutdown Shutdown 8 minutes ago 7yzl5f31a9rb web.7 nginx:latest worker2 Running Running 15 minutes ago 4we17vy5xtmg web.8 nginx:latest worker2 Running Running 15 minutes ago yy5kn8mv8hvz web.9 nginx:latest worker1 Shutdown Shutdown 8 minutes ago aej45h59wln3 web.10 nginx:latest worker3 Running Running 15 minutes ago dygbxzxrbuwc web.11 nginx:latest manager1 Running Running 15 minutes ago
少しわかりにくいが,worker1のweb.9がシャットダウンされているので,確かにレプリカ数は10となっている
次にさきほど放出したworker1を再びアクティブに戻す
docker exec -it manager1 docker node update --availability active worker1
ノードの状態を確認する
$ docker exec -it manager1 docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 71jzsyefebwvmiz5va29cnhn4 * manager1 Ready Active Leader 18.09.3 0e3bl408q8lufo5xczwny5l9m manager2 Ready Active Reachable 18.09.3 icd4ixgyqvsc0ebrdf8w5esxy manager3 Ready Active Reachable 18.09.3 woo1rgys2gsgpffjtyrec7gqe worker1 Ready Active 18.09.3 kv6dnud4pyfk0269xlqauwngd worker2 Ready Active 18.09.3 i9ygvq1nvcqqpdyatpygl39ax worker3 Ready Active 18.09.3
worker1の状態がDrain -> Activeと変化した
サービスの状態を確認する
$ docker exec -it manager1 docker service ps web ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS kw541zci4kni web.1 nginx:latest manager2 Running Running about an hour ago 3l7bfnzp1y09 web.2 nginx:latest manager2 Running Running 25 minutes ago s20rm41zo6uw web.3 nginx:latest manager1 Running Running 23 minutes ago a99tv7ha01et web.4 nginx:latest manager3 Running Running 23 minutes ago qb38lx8ov9ge web.5 nginx:latest worker3 Running Running 23 minutes ago 199q9r5nicnz web.6 nginx:latest manager3 Running Running 17 minutes ago vlihahkxs9pv \_ web.6 nginx:latest worker1 Shutdown Shutdown 17 minutes ago 7yzl5f31a9rb web.7 nginx:latest worker2 Running Running 23 minutes ago 4we17vy5xtmg web.8 nginx:latest worker2 Running Running 23 minutes ago yy5kn8mv8hvz web.9 nginx:latest worker1 Shutdown Shutdown 17 minutes ago aej45h59wln3 web.10 nginx:latest worker3 Running Running 23 minutes ago dygbxzxrbuwc web.11 nginx:latest manager1 Running Running 23 minutes ago
worker1ノードをクラスタに復帰させたが,中のサービスは停止しており,レプリカ数に変化はない
次にmanager1ノードをクラスタから退出させる.
docker exec -it manager1 docker swarm leave --force
クラスタからリーダーを退出させた.これによりswarmは,次のリーダーを自動的に立ててくれる
manager2でdocker node ls
コマンドを実行し,ノードの状態を確認する
$ docker exec -it manager2 docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 71jzsyefebwvmiz5va29cnhn4 manager1 Down Active Unreachable 18.09.3 0e3bl408q8lufo5xczwny5l9m * manager2 Ready Active Leader 18.09.3 icd4ixgyqvsc0ebrdf8w5esxy manager3 Ready Active Reachable 18.09.3 woo1rgys2gsgpffjtyrec7gqe worker1 Ready Active 18.09.3 kv6dnud4pyfk0269xlqauwngd worker2 Ready Active 18.09.3 i9ygvq1nvcqqpdyatpygl39ax worker3 Ready Active 18.09.3
manager1の状態がDownとなっており,MANAGER STATUSもUnreachableとなっている.
manager2が代わりのリーダーとなっている.
最後にサービスを削除する
サービスの削除は以下のコマンドで行う
docker exec -it manager2 docker service rm web
各ノードに分散していたサービスを一括削除できる.
このチュートリアルはこれで終了となる.
不要となったコンテンツを削除する場合は以下のコマンドを実行する
docker stop manager1 manager2 manager3 worker1 worker2 worker3 docker rm manager1 manager2 manager3 worker1 worker2 worker3 docker network rm dind-net
おわりに
docker swarm tutorialを試してみた.
この分野に全く詳しくないので,便利だなーくらいの感想しかない.
きっとk8sはもっと便利なのだろう.
なお今回のチュートリアルではdindコンテナを使ってみた.
ほとんど同じような挙動をしているので,雰囲気をつかむくらいであればdindでもできそうではある.
VMを立てるのと比べてリソースを減らせたかというとよくわからない.スケールした時に動作がもっさりしたりする.
試してはいないが,低スペックなPCやサーバの場合,起動時間は多少減らせるかもしれない.
swarm tutorialをdindでやってみる
お題
dockerのオーケストレーションツールのひとつにswarmというのがある
複数のdockerホストをクラスタリングしたり,ロードバランシングしたりできるらしい.
これ系は今はKubernetesが優勢だとは思うが,デフォルトでdocker engineに組み込まれているということもあり,せっかくなので少し使ってみようと思った
やること
素人なので,いつも通りチュートリアルをやる.
docker swarm tutorialは公式のものだと以下が見つかった
注意書きにある通り,ここにはplay-with-dockerでできるチュートリアルをローカルでやる手順が書かれている.
具体的にはdocker-machine createというコマンドで,dockerがインストールされたVMを複数台立て,swarmでクラスタリングし,webというサービスをデプロイしている.
MacやWindows(linuxでもいいけど)のふつうのPCにvirtualboxをインストールして,ここに書かれている通りやればだいたいうまくいくと思われる.
自分はなんとなくホストPCのMacに手を入れたくなかったので,前回使ってみたdind(docker in docker)を使って似たようなことができないか試してみた
結果,docker-machineを使ったチュートリアルとだいたい同じような挙動を追うことができた
完成形を図にするとたぶんこんなかんじ
以下,上記の公式チュートリアルのbash scriptとだいたい同じことをdindでやった場合の手順を書いていく. (今回の記事ではクラスタを立てるところまでで,次回の記事でこのクラスタにサービスをデプロイしたり,nodeを退出させたりするところをやる予定.)
注意
- あくまでチュートリアルなので,セキュリティ設定等は考慮していない
- dindはdockerと異なる仕組みで動いているため,環境によってはこの通り動かない
環境
Docker 18.09.3 on Ubuntu 18.04 (virtualbox on macOS)
ubuntuにdockerをインストールする手順はこちら:
koimedenshi.hatenablog.com
手順
元のbash scriptを参考に作成したbash scriptはこちら.
細かい点を除けば,docker-machine
がやっていることをdocker container run
やdocker exec -it
で置き換えているだけ
このスクリプトをローカルに保存して実行すれば,上図のようなdindクラスタが構築される
#!/bin/bash ## output debug_output # exec 5> debug_output.txt # BASH_XTRACEFD="5" # PS4='$LINENO: ' # set -x # create user defined bridge network echo "======> Creating $nw network ..."; nw=dind-net docker network create -d bridge $nw # Swarm mode using Docker in Docker managers=3 workers=3 # create manager machines echo "======> Creating $managers manager machines ..."; for node in $(seq 1 $managers); do echo "======> Creating manager$node machine ..."; docker run -it --privileged --network $nw --name manager$node --hostname manager$node -d docker:stable-dind; done # create worker machines echo "======> Creating $workers worker machines ..."; for node in $(seq 1 $workers); do echo "======> Creating worker$node machine ..."; docker run -it --privileged --network $nw --name worker$node --hostname worker$node -d docker:stable-dind; done # list all machines docker container ls # initialize swarm mode and create a manager echo "======> Initializing first swarm manager ..." docker exec -it manager1 docker swarm init --listen-addr $(docker exec -it manager1 hostname -i | tr -d '\r') --advertise-addr $(docker exec -it manager1 hostname -i | tr -d '\r') # get manager and worker tokens manager_token=$(docker exec -it manager1 docker swarm join-token manager -q | tr -d '\r') worker_token=$(docker exec -it manager1 docker swarm join-token worker -q | tr -d '\r') echo "manager_token: $manager_token" echo "worker_token: $worker_token" # other masters join swarm for node in $(seq 2 $managers); do echo "======> manager$node joining swarm as manager ..."; docker exec -it manager$node \ docker swarm join \ --token $manager_token \ --listen-addr $(docker exec -it manager$node hostname -i | tr -d '\r') \ --advertise-addr $(docker exec -it manager$node hostname -i | tr -d '\r') \ $(docker exec -it manager1 hostname -i | tr -d '\r'); done # show members of swarm docker exec -it manager1 docker node ls # workers join swarm for node in $(seq 1 $workers); do echo "======> worker$node joining swarm as worker ..." docker exec -it worker$node \ docker swarm join \ --token $worker_token \ --listen-addr $(docker exec -it worker$node hostname -i | tr -d '\r') \ --advertise-addr $(docker exec -it worker$node hostname -i | tr -d '\r') \ $(docker exec -it manager1 hostname -i | tr -d '\r'); done # show members of swarm docker exec -it manager1 docker node ls
順を追って説明する.
まずユーザ定義ネットワークを作成する.
ネットワークタイプはbridgeを選択
# create user defined bridge network echo "======> Creating $nw network ..."; nw=dind-net docker network create -d bridge $nw
swarmに参加させるノードコンテナを起動.
swarmでは必ずmanagerかworkerかのいずれかのロールを割り当てる.
それぞれの役割は読んで字のごとくという感じ?(雑)
manager node, worker nodeをそれぞれ3台ずつ,dindコンテナで起動する
# Swarm mode using Docker in Docker managers=3 workers=3 # create manager machines echo "======> Creating $managers manager machines ..."; for node in $(seq 1 $managers); do echo "======> Creating manager$node machine ..."; docker run -it --privileged --network $nw --name manager$node --hostname manager$node -d docker:stable-dind; done # create worker machines echo "======> Creating $workers worker machines ..."; for node in $(seq 1 $workers); do echo "======> Creating worker$node machine ..."; docker run -it --privileged --network $nw --name worker$node --hostname worker$node -d docker:stable-dind; done # list all machines docker container ls
- --privilegedをつけるのは元のチュートリアルに倣っている.これをつけないと自分以外のコンテナの設定を変えることができない.
- --name と --hostname で二回名前を指定しており冗長な感じもするが,--name がコンテナ名,--hostname がホスト名.前者はコンテナ同士の名前解決に用いる名前で,後者はこの後に出てくる
node ls
などの際に表示するホスト名となる. - 使用するコンテナイメージは
docker:stable-dind
.(dindの中のdockerバージョンは"DockerVersion": "18.06.1-ce"
)
この時点では6台のdockerホストを立てただけ.ここからswarmクラスタを構築していく.
manager1を最初のswarmの参加者に指定する(リーダーになる)
# initialize swarm mode and create a manager echo "======> Initializing first swarm manager ..." docker exec -it manager1 docker swarm init --listen-addr $(docker exec -it manager1 hostname -i | tr -d '\r') --advertise-addr $(docker exec -it manager1 hostname -i | tr -d '\r')
--listen-addrおよび--advertise-addrに自身のホストIPを指定する.
manager1がswarmクラスタのmanager nodeになったので,クラスタ参加に必要なトークンを払い出す.
# get manager and worker tokens manager_token=$(docker exec -it manager1 docker swarm join-token manager -q | tr -d '\r') worker_token=$(docker exec -it manager1 docker swarm join-token worker -q | tr -d '\r') echo "manager_token: $manager_token" echo "worker_token: $worker_token"
見ての通り,ロールによってトークンが異なる.
払い出したトークンで,他のnodeをswarmクラスタに参加させる.
# other masters join swarm for node in $(seq 2 $managers); do echo "======> manager$node joining swarm as manager ..."; docker exec -it manager$node \ docker swarm join \ --token $manager_token \ --listen-addr $(docker exec -it manager$node hostname -i | tr -d '\r') \ --advertise-addr $(docker exec -it manager$node hostname -i | tr -d '\r') \ $(docker exec -it manager1 hostname -i | tr -d '\r'); done # show members of swarm docker exec -it manager1 docker node ls # workers join swarm for node in $(seq 1 $workers); do echo "======> worker$node joining swarm as worker ..." docker exec -it worker$node \ docker swarm join \ --token $worker_token \ --listen-addr $(docker exec -it worker$node hostname -i | tr -d '\r') \ --advertise-addr $(docker exec -it worker$node hostname -i | tr -d '\r') \ $(docker exec -it manager1 hostname -i | tr -d '\r'); done # show members of swarm docker exec -it manager1 docker node ls
正常に終了していれば,6台のノードがmanager/workerいずれかのロールでswarmクラスタに参加している状態となる.
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION vjgd1myk9r10msdhi1x835kou * manager1 Ready Active Leader 18.09.3 10ddklb0bysyhpadxjr6lvyar manager2 Ready Active Reachable 18.09.3 q0cxi5rkcayiuac2kopjsg1j1 manager3 Ready Active Reachable 18.09.3 i78pa70deqx1aspefqvmjs4bj worker1 Ready Active 18.09.3 r2zv3coxlzvn6oh9xul8bf8ss worker2 Ready Active 18.09.3 quzmoin1qesj7gnqh9ffvq0st worker3 Ready Active 18.09.3
以上,dindでswarmクラスタを立てるところまでを実施した.
次回はこのクラスタにサービスをデプロイしたり,nodeを退出させたりするところをやる.
完結編はこちら↓
うまくいかないとき
環境によりけりなので,エラーを潰していくしかない.
先述の通りdindは完全ではないらしいので,最初から無理という可能性もある.
その場合は元のチュートリアルをやるか,ふつうにplay-with-dockerをやるしかないかも
後片付け
用済みであれば,docker stop -> docker rm / network rmすればok
docker stop manager1 manager2 manager3 worker1 worker2 worker3 docker rm manager1 manager2 manager3 worker1 worker2 worker3 docker network rm dind-net
P.S.
正直docker-machineの方が便利かも.
docker in docker(dind)を使ってみる
お題
Dockerにはdind = docker-in-dockerというものがあって,dockerホストの中にdockerホストを立てることができる.
https://hub.docker.com/_/docker
今回は上記URLのHow to use this image
をベースに,dindを触ってみる.
なぜ
コンテナを用いたサービスを冗長化したい場合,複数のdockerホストを立てクラスタリングするというのが定石らしい.
これを手元でやってみようとなった時,ホストの数だけ仮想マシンを立てる方法もあるが,
そのようなリソースがない場合,dindを使うと仮想マシン1台でもお手軽に体験できる.
(より正直に言うと,swarmやkubernetesなどのツールをお手軽に使ってみたいという願望がある)
注意
上記のDockerHubページに,使う前にここを読んでねと以下のリンクが置いてある.
jpetazzo.github.io
debian系以外ではダメかも・・という話と,内側のdockerを外側のdockerと同じ感覚で使おうとすると色々問題がおきる模様.
便利だけど色々問題もあるから,検証目的や継続利用の場合は慎重に(というか他の方法を考えた方がいいよ)という感じ.
今回はお試しなのでまあOKということで.
環境
Docker (18.09.3) on Ubuntu 18.04 (Oracle VirtualBox VM on macOS)
ubuntuにdockerをインストールする手順はこちら:
koimedenshi.hatenablog.com
手順
DockerHubを参考にする.
https://hub.docker.com/_/docker
ホスト端末のターミナル上で以下のコマンドを実行し,dindコンテナを起動する.
docker run -it --privileged --name some-docker -d docker:stable-dind
--privilegedオプションで特権を与える.これがないと正しく動作しない.ホストマシンの操作権を与えることになるので注意.
少しややこしいが,イメージ名が"docker"でタグが"stable-dind" .
docker execコマンドで,作成したdindコンテナのshellに接続
docker exec -it some-docker sh
OSの情報を確認すると,Alpine Linuxとなっている
/ # cat /etc/*release 3.9.2 NAME="Alpine Linux" ID=alpine VERSION_ID=3.9.2 PRETTY_NAME="Alpine Linux v3.9" HOME_URL="https://alpinelinux.org/" BUG_REPORT_URL="https://bugs.alpinelinux.org/"
docker versionコマンドを実行すると,dind内のdockerホストのバージョン情報が確認できる.
/ # docker version Client: Docker Engine - Community Version: 18.09.3 API version: 1.39 Go version: go1.10.8 Git commit: 774a1f4 Built: Thu Feb 28 06:32:01 2019 OS/Arch: linux/amd64 Experimental: false Server: Docker Engine - Community Engine: Version: 18.09.3 API version: 1.39 (minimum version 1.12) Go version: go1.10.8 Git commit: 774a1f4 Built: Thu Feb 28 06:40:51 2019 OS/Arch: linux/amd64 Experimental: false
この時のstable-dindのバージョンは18.09.3となっており,実際にインストールされているものと一致している.
dindコンテナ内におけるコンテナ起動
dind内でhello-worldコンテナを起動してみる.
docker execコマンドでsome-dockerコンテナのshellに接続:
docker exec -it some-docker sh
dind内でhello-worldコンテナを起動:
/ # docker container run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 1b930d010525: Pull complete Digest: sha256:2557e3c07ed1e38f26e389462d03ed943586f744621577a99efb77324b0fe535 Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. 〜省略〜
dind内であっても,通常のdockerホストでの操作と同じ感覚でコンテナイメージを取得して起動できた.
この時イメージはどこから取得しているのかというと,ホストがローカルに保存しているイメージをダウンロードするのではなく,ふつうに外部のdockerレジストリ(https://index.docker.io/v1/)から取得していると思われる.
dind内のdocker設定を見るとレジストリとしてdocker公式レジストリが設定されている.
/# docker info 〜省略〜 Registry: https://index.docker.io/v1/ 〜省略〜
storage-driverの設定
--storage-driverはvfsを選択するようになっている.「vfsはスペックとしては微妙だが,唯一安定して動作するため,しかたなくこれにしている.
実用的にはホスト端末での--storage-driverと同じ値を設定するのが良い」的なことが書かれている.
ホスト端末での--storage-driverはdocker infoで確認できる.
~ $ docker info Containers: 11 Running: 1 Paused: 0 Stopped: 10 Images: 21 Server Version: 18.09.3 Storage Driver: overlay2 #ここ 〜以下略〜
本環境ではoverlay2となっているので,それに合わせたdindコンテナを立てる場合は以下のコマンドとなる.
docker run --privileged --name some-overlay-docker -d docker:stable-dind --storage-driver=overlay2
別コンテナからdindへの接続
dind自体はホスト端末(ここではubuntu)上のdockerから見ればコンテナのひとつなので,ホストのdockerに立てた別のコンテナから--linkオプションで接続できる.
(例)新たにedge-dindコンテナを立て,先ほど立てた"some-docker"コンテナに接続する場合:
docker run --privileged --link some-docker:docker --name some-docker-edge-dind -d docker:edge-dind
データをどこに保存するか
様々な方法があるが,dindコンテナ内にするか,ホストのディレクトリをマウントして保存先とするかが挙げられている.
ホストディレクトリ"/my/own/var-lib-docker"をマウントする場合のコンテナ起動方法は:
docker run --privileged --name some-docker -v /my/own/var-lib-docker:/var/lib/docker -d docker:dind
この辺りも使い方次第でベストプラクティス的なものは変わるかもしれない.
まとめ
dindによって複数のdockerホストを単一の仮想マシン上に構築することができた.
これを使えばswarmによるクラスタ管理などをお手軽に体験できそう.
Dockerの仮想ネットワーク内でOSSのIDS(suricata)を試す
お題
ふとsuricataというOSSのIDSソフトを試してみたくなり,Dockerの仮想ネットワークの範囲で出来そうだったので,簡単なサーバ+クライアント構成を構築してみた.
suricataはコンフィグ次第でIPSモードなど色々な機能を実現できるが,今回はHTTPサーバの前段に設置して攻撃アクセスを検知する最低限の機能を再現する.
ネットで探すとsuricataのコンテナイメージやDockerfileも出てくるが,今回はインストールするところからやりたいというのもあり,あえてそれらは使用せず,公式Ubuntuイメージにインストールすることにした.
suricataとは
suricataはLinux/Mac/FreeBSD/UNIX/Windowsなど様々なプラットフォームにインストールでき,セキュリティの分野ではsnortと並んで有名なIDS/IPSソフトである(多分).
注意
- 後半に攻撃コマンドが含まれるが,自分の所轄外の外部ネットワークなどに対し実行してはいけない.
- 今回の構成でセキュリティを担保できるわけではないので,この設定だけで公開サーバを立てたりしてはいけない.あくまで自己責任で.
環境
Docker 18.09.1 on Ubuntu 18.04 (Oracle VirtualBox VM on macOS)
UbuntuでのDockerインストール方法はこちら↓
koimedenshi.hatenablog.com
構成
以下のコンテナおよびネットワークを構築し,簡単なサーバ(IDS有)+クライアント構成を組み立てる.
* suricataがインストールされた被害者役サーバコンテナ
* 攻撃役端末コンテナ
* 上記のコンテナを接続する,仮想ブリッジネットワーク
手順
仮想ネットワーク作成
まず,コンテナを接続するためのユーザ定義ブリッジネットワークisolated_nw
を作成する.
デフォルトのブリッジネットワークに接続してもよいが,なんとなく分けた方が管理しやすいかと思ったので.
docker network create --driver bridge isolated_nw
攻撃用端末の構築
次に攻撃役端末コンテナを構築する.
ベースイメージはcurlなどのコマンドを使えればなんでもよいが,ここではdebianのstable-slim
イメージを引用する.
-dit
オプションでインタラクティブなシェルを立ち上げる.
また--network
オプションで作成したisolated_nw
に接続する.
docker container run -dit --name debian-slim --network isolated_nw debian:stable-slim
作成したコンテナのbashに接続し,疎通確認やHTTPアクセスで使用するコマンドをインストールする
docker exec -it debian-slim bash #/ apt-get update #/ apt-get install curl wget iproute2 iputils-ping
ipコマンドをインストールしたので,サーバのifの名前とipアドレスを確認する.(後で使う)
/# ip addr show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 7: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:12:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.18.0.3/16 brd 172.18.255.255 scope global eth0 valid_lft forever preferred_lft forever
この例では,eth0に172.18.0.3というIPが割り当てられている.
被害者端末HTTPサーバ(NGINX+suricata)の構築
続いて被害者役のサーバコンテナを構築する.
-dit
と--net=
オプション指定は攻撃者役コンテナと同様だが,新たに--cap-add=NET_ADMIN
というオプションを指定する.
--cap-add=NET_ADMIN
オプションはコンテナにネットワーク周りのroot権限を付与するオプションで(雑な説明かも),これを入れないと後ほど出てくるsuricataの起動時に権限がないと怒られる.
部分的な権限付与とはいえコンテナにホスト端末の設定変更を許可することになるので,不特定多数が触るような環境では注意する.
ベースイメージはubuntu:18.04を指定する.これもdebianなどでもいけるかもしれないが,suricataの対応バージョンが異なってくるかもしれない.
ubuntu:18.04はsuricataの最新バージョン4.xに対応していた.
docker container run -dit --name ubuntu-suricata --cap-add=NET_ADMIN --net=isolated_nw ubuntu:18.04
コンテナが起動したらbashに接続し,必要なパッケージをインストールする.
software-properties-commonについてはadd-apt-repositoryというaptレポジトリ追加のためのコマンドを後で実行するので追加している.
サイズが大きいし,もう少しスマートな方法があるかもしれない.
suricataで保護するHTTPサーバとして(これも立ち上がればなんでも良いが)NGINXをインストールしておく.
docker exec -it ubuntu-suricata bash # コンテナのbashに接続 /# apt-get update /# apt-get install -y iproute2 iputils-ping software-properties-common vim curl tail jq nginx
ipコマンドをインストールしたので,サーバのifの名前とipアドレスを確認する.(後で使う)
/# ip addr show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 5: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.18.0.2/16 brd 172.18.255.255 scope global eth0 valid_lft forever preferred_lft forever
この例では,eth0に172.18.0.2というIPが割り当てられている.
その後,suricataをバイナリパッケージからインストールする.
suricataに開発オプションを指定する場合はソースからインストールする必要があるが,今回はとりあえず動けばいいのでバイナリパッケージを使う.
add-apt-repositoryコマンドでsuricataの安定板のppaを追加し,apt-getでインストールする.
/# add-apt-repository ppa:oisf/suricata-stable (確認メッセージが出るのでENTERを押す) /# apt-get update /# apt-get install -y suricata
以下のエラーが出るが,suricataが自動的に起動されなかったため?
とりあえず無視してもたぶん動く.
invoke-rc.d: could not determine current runlevel invoke-rc.d: policy-rc.d denied execution of start.
インストールが完了したら,suricataの初期設定を行う.
まずsuricataのルール(検知パターン.シグネチャ的なもの)をダウンロードする.
これも方法は色々あるが,公式ドキュメントにはsuricata-updateコマンドを使用する手順が記載されていたので,そちらを踏襲する.
suricata-updateはその名の通りsuricataルールをアップデートするコマンドで,suricataのインストールと同時にインストールされる(というのはバージョン4.1以降の話で,それ以前のバージョンでは手動でインストールする必要がある).
/# suricata-update
これで/var/lib/suricata/rules/にルールが保存されるはず.
次にsuricataの初期コンフィグを行う.
suricataの設定変更は/etc/suricata/suricata.yaml
を編集する.
色々と細かい設定ができるが,ここでは何かしらの攻撃アクセスを検知できれば良いので,最低限の設定として検査対象のIPアドレスを指定する.
検査対象のIPアドレスはsuricata.yamlのHOME_NET
およびEXTERNAL_NET
により制限できる.
suricataはHOME_NET
に指定されたIPを保護するネットワークとして認識し,EXTERNAL_NET
を外部ネットワークとして認識する.
一般的にはHOME_NET
に保護するサーバの属するネットワーク(プライベートIP)を指定し,EXTERNAL_NET
にはそれ以外(!$HOME_NET)
を設定するものと思われるが,今回は攻撃者が被害者と同じネットワークに属しているため,サーバのIPと攻撃者端末のIPを単体で指定する.
まずHOME_NET
にubuntu-suricataのIPアドレス(ここでは172.18.0.3)を指定する.
suricata.yamlの以下の行をコメントアウトする.
HOME_NET: "[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]"
そして以下のようにIPを指定する.
#HOME_NET: "[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]" HOME_NET: "[172.18.0.3/32]"
次にEXTERNAL_NET
にdebian-slimのIPアドレス(ここでは172.18.0.2)を指定する.
以下の行をコメントアウトして
EXTERNAL_NET: "!$HOME_NET"
以下のように新たに指定して保存する.
#EXTERNAL_NET: "!$HOME_NET" EXTERNAL_NET: "[172.18.0.2/32]"
設定を変更したら,以下のコマンドでコンフィグ確認を行う.
#/ suricata -T
エラーが出たら適宜修正する.
先に行ったルールのダウンロードがされていない場合,ここでエラーが出る.
終わったら,以下のコマンドでsuricataを実行する.
-cオプションでコンフィグファイル(先ほど編集したsuricata.yaml)を指定し,-iオプションで検査するif(ここではeth0)を指定する.
#/ suricata -c /etc/suricata/suricata.yaml -i eth0 --init-errors-fatal X/X/2019 -- 13:27:14 - <Notice> - This is Suricata version 4.1.2 RELEASE X/X/2019 -- 13:27:31 - <Notice> - all 1 packet processing threads, 4 management threads initialized, engine started. #suricataが正常に起動した
起動したら,HTTPサーバを立ち上げる. ホストマシンで別のターミナルを開き,コンテナに接続してNGINXを起動する.
docker exec -it ubuntu-suricata bash #/ service nginx status #/ service nginx start #/ curl http://localhost/ (NGINXのデフォルトページが返ってくることを確認)
HTTPサーバを立ち上げたら,suricataログの表示準備する.
ホストマシンで別のターミナルを開き,コンテナに接続して以下のコマンドを実行する.
suricataログはアラートも含め/var/log/suricata/eve.jsonに書き込まれる.
デフォルトの設定だとアップデート通信などアラート以外の通信も書き込まれて見づらいので,jqで表示をフィルタする.
docker exec -it ubuntu-suricata bash #/ tail -f /var/log/suricata/eve.json | jq -r -c 'select(.event_type=="alert")' (event_type=="alert"のみが表示・更新される)
suricata検知の検証
監視準備ができたので,いよいよ攻撃コードを送ってみる.
攻撃者であるdebian-slimコンテナに接続し,curlで攻撃コードを実行する.
攻撃コードはsuricataルールに含まれていればなんでも良いが,ここではWindows Serverなどに対するリモートコマンド実行を想定したリクエストを送ってみる(間違って外部に実行しないこと)
docker exec -it debian-slim bash #/ curl http://172.18.0.3/cmd.exe (cmd.exeは存在しないので404エラーが返ってくる)
検知していれば,eve.jsonに次のようなログが追加される.
送信元IPや宛先IP,シグネチャ名,url, User-Agentなどが記載されている.
検知したシグネチャ名は"ET WEB_SERVER cmd.exe In URI - Possible Command Execution Attempt"となっている.
{"timestamp":"2019-X-XT13:40:15.681575+0000","flow_id":881816844067101,"in_iface":"eth0","event_type":"alert","src_ip":"172.18.0.2","src_port":51516,"dest_ip":"172.18.0.3","dest_port":80,"proto":"006","tx_id":0,"alert":{"action":"allowed","gid":1,"signature_id":2009361,"rev":5,"signature":"ET WEB_SERVER cmd.exe In URI - Possible Command Execution Attempt","category":"Attempted Information Leak","severity":2,"metadata":{"updated_at":["2010_07_30"],"created_at":["2010_07_30"]}},"http":{"hostname":"172.18.0.3","url":"/cmd.exe","http_user_agent":"curl/7.52.1","http_content_type":"text/html","http_method":"GET","protocol":"HTTP/1.1","status":404,"length":178},"app_proto":"http","flow":{"pkts_toserver":4,"pkts_toclient":3,"bytes_toserver":353,"bytes_toclient":548,"start":"2019-02-22T13:40:15.680221+0000"}}
少し見づらいので,たくさんのログを処理する場合は整形するなどしてもよいし,GUI(snorbyなど)と連携してもよいと思う.
おわりに
以上で最低限攻撃検知するところまで再現できた.
/etc/suricata/rules/に含まれているルールを読んでどんな攻撃を検知するかを試したり,自分なりのカスタムルールを追加してみたり色々遊ぶと勉強になると思う.
なおsuricataを攻撃遮断をするIPSモードで実行する場合はソースファイルをオプション付きでコンパイルする必要があるっぽいので,今回作った環境では実現できないかもしれない(できるかもしれないが,試していない)
参考
suricataのインストール方法などは公式ドキュメントに記載されている.
suricata.readthedocs.io
インストール完了後の基本設定は以下の公式ドキュメントを参考にした.
redmine.openinfosecfoundation.org
Docker network tutorial (host編)
Docker公式ドキュメントのhost network tutorialをやってみる.
docs.docker.com
[注意] host networkはDocker Linuxのみ対応で,MacやWindows環境には未対応とのこと.
環境
Docker 18.09.1 on Ubuntu 18.04 (Oracle VirtualBox VM on macOS)
インストール方法はこちら↓
koimedenshi.hatenablog.com
やること
Dockerホストのネットワーク上でport:80で待ち受けるNginxサービスを立てる.
ネットワークの観点においてはDockerホストから隔離されていないが,ストレージや名前空間など,ネットワーク以外のリソースはホストから独立して存在することになる.
手順
- 以下の--rmオプション付きコマンドを実行し,nginxコンテナを起動する
$ docker run --rm -d --network host --name my_nginx nginx
--rmオプションで立ち上げたコンテナはexit/stopすると同時に削除される.
-dオプションでプロセスをバックグラウンドで実行状態にする.
- Webブラウザでhttp://localhost:80 にアクセスし,Nginxページが表示されることを確認
- ホストマシン上で以下のコマンドを実行し,インターフェースが追加されていないことを確認
$ ip addr show (既存のIFのみ. ブリッジネットワークだと,ここでインターフェースが追加される)
- 以下のコマンドを実行して,ポート80番がどのプロセスに紐づいているかを確認
$ sudo netstat -tulpn | grep :80 tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 26149/nginx: master
- (チュートリアルにはない手順)network inspectコマンドでhostネットワークの設定を確認
$ docker network inspect host [ { "Name": "host", "Id": "6dc567989d6f78e9586117f18de3e4318cf3dcf80fe4dcdb06cb64b48cd546b2", "Created": "2018-12-01T15:49:43.284522247+09:00", "Scope": "local", "Driver": "host", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": { "7362beaa0c7725455893330ddcf3f690db53628fc06bf9cd98343cb4f5cbbb55": { "Name": "my_nginx", "EndpointID": "125632f721623e64896c4f0bff4b6cf394f84f652b79c72a6196a26fd1a68e64", "MacAddress": "", "IPv4Address": "", "IPv6Address": "" } }, "Options": {}, "Labels": {} } ]
bridgeネットワークの時と違って,コンテナのIPアドレスやMACアドレスが設定されていない.
(ホストとネットワーク資源を共有するため,なくてもつながる)
- 終わったら,以下のコマンドを実行してコンテナを削除
$ docker container stop my_nginx
まとめ
hostネットワークに接続されたNginxコンテナを起動した.
チュートリアルにあまり詳しいことが書かれておらず,使い方もよくわからなかった.知識不足.
Docker17.06以降のバージョンでは, swarmモードでもhostネットワークを利用できる.
その場合,例えば80番ポートで立ち上げることのできるサービスは1個のみなどといった制約が生じることになるそうな.
以上
Docker network tutorial (bridge編)
Docker公式ドキュメントのbridge network tutorialをやってみた.
docs.docker.com
環境
Docker 18.09.1 on Ubuntu 18.04 (Oracle VirtualBox VM on macOS)
インストール方法はこちら↓
koimedenshi.hatenablog.com
- default bridge network
デフォルトブリッジはその名の通りデフォルトで用意されているネットワーク.
ネットワークを指定せずにコンテナを立ち上げると, ここに接続される.
ここでは2台のalpineコンテナをデフォルトブリッジに接続する.
初期状態の確認
$ docker network ls NETWORK ID NAME DRIVER SCOPE f0e09069add5 bridge bridge local 6dc567989d6f host host local dd596238709d none null local
2台のalpineコンテナの立ち上げ
$ docker run -dit --name alpine1 alpine ash $ docker run -dit --name alpine2 alpine ash
起動したコンテナの確認
$ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4081fada5d19 alpine "ash" 2 minutes ago Up 2 minutes alpine2 f9926cee8b76 alpine "ash" 2 minutes ago Up 2 minutes alpine1
ブリッジネットワークの接続状況をinspectで確認
$ docker network inspect bridge [ { "Name": "bridge", "Id": "f0e09069add5f694454f4b91aa92b249834c51f990436233d3d1302cd14f1bcc", "Created": "2019-01-22T09:55:52.056584987+09:00", "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": { "4081fada5d19e08d10b7a903261c12f66d42d962b639851256525babc933c7f7": { "Name": "alpine2", "EndpointID": "f94f5c25a244f5f921e066f00950cc9926675d131c84cfdbfec86319e398ce78", "MacAddress": "02:42:ac:11:00:03", "IPv4Address": "172.17.0.3/16", "IPv6Address": "" }, "f9926cee8b761f44da6f8646132c6e6d2a1b4ece6c7d46e49b8f6e98d577efee": { "Name": "alpine1", "EndpointID": "784c9240218a496ce26d6301354ec0051fbd5f9ef657ded18736e079bf89ed6f", "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": {} } ]
alpine1コンテナ, alpine2コンテナはbridgeネットワークに接続され, デフォルトゲートウェイ172.17.0.1/16を介してDocker hostと通信する.
実際にコンテナの中から確認する.
attachコマンドでalpine1に接続する.
$ docker attach alpine1 / #
ipコマンドでインターフェースの確認
/ # ip addr show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 13: eth0@if14: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever
loopbackとeth0(172.17.0.2/16)が立ち上がっている.
外向けにpingを打ってみる
/ # ping -c 2 google.com PING google.com (216.58.197.142): 56 data bytes 64 bytes from 216.58.197.142: seq=0 ttl=61 time=9.415 ms 64 bytes from 216.58.197.142: seq=1 ttl=61 time=10.116 ms --- google.com ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 9.415/9.765/10.116 ms
/ # ping -c 2 172.17.0.3 PING 172.17.0.3 (172.17.0.3): 56 data bytes 64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.117 ms 64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.099 ms --- 172.17.0.3 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.099/0.108/0.117 ms
'alpine2'にpingを打つと失敗する
/ # ping -c 2 'alpine2' ping: bad address 'alpine2'
デフォルトブリッジではコンテナ名指定で疎通しないことがわかる.
alpine1のシェルを抜けてから(CTRL+p + qの順に押す),使用したコンテナを削除する
$ docker container stop alpine1 alpine2 $ docker container rm alpine1 alpine2
デフォルトブリッジのチュートリアル終わり.
- user-defined bridge networks
次にユーザ定義ネットワークのチュートリアルを行う.
まずalpine-netという名前のネットワークを作成
$ docker network create --driver bridge alpine-net 3fe4ed300746ac93ff0eb283f239d3482aeedc88a9aa0e2eb1354a6ca79527ac
ネットワークが追加されたことを確認
$ docker network ls NETWORK ID NAME DRIVER SCOPE 3fe4ed300746 alpine-net bridge local b005254ef24e bridge bridge local 6dc567989d6f host host local dd596238709d none null local
inspectコマンドでalpine-netの接続状況を確認. この段階では何もつながっていない
$ docker network inspect alpine-net [ { "Name": "alpine-net", "Id": "3fe4ed300746ac93ff0eb283f239d3482aeedc88a9aa0e2eb1354a6ca79527ac", "Created": "2019-01-24T11:04:19.021413436+09:00", "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": {}, "Options": {}, "Labels": {} } ]
ネットワークアドレスは172.19.0.0/16となっている.
自動生成されるアドレス帯は環境によって異なる.
以下のコマンドで4台のコンテナを起動する.
$ docker run -dit --name alpine1 --network alpine-net alpine ash 26673686e6ff88c401c6e5b9dd007d1519ac603f86d1251195392a846f398ac5 $ docker run -dit --name alpine2 --network alpine-net alpine ash 6d36c4792f2b5c16b4a381a930d51b7456ab216efcc7af4363f9204c3a27a16a $ docker run -dit --name alpine3 alpine ash 7747f25eb4e7729b83741be135f9fdc13c84695519abd77544f1f01fbe400d17 $ docker run -dit --name alpine4 --network alpine-net alpine ash f39f29ac4f23bd0bb41bf39e80961d542a23f3cb79f91b93bbc00bee690a2e1b
$ docker network connect bridge alpine4 $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f39f29ac4f23 alpine "ash" 31 seconds ago Up 29 seconds alpine4 7747f25eb4e7 alpine "ash" 49 seconds ago Up 48 seconds alpine3 6d36c4792f2b alpine "ash" About a minute ago Up About a minute alpine2 26673686e6ff alpine "ash" About a minute ago Up About a minute alpine1
ざっくり言うと以下のようなネットワーク構成になっている
inspectコマンドでalpine-netを確認すると,コンテナが3台追加されている
$ docker network inspect alpine-net [ { "Name": "alpine-net", "Id": "3fe4ed300746ac93ff0eb283f239d3482aeedc88a9aa0e2eb1354a6ca79527ac", "Created": "2019-01-24T11:04:19.021413436+09:00", "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": { "26673686e6ff88c401c6e5b9dd007d1519ac603f86d1251195392a846f398ac5": { "Name": "alpine1", "EndpointID": "10d86d5b8ac9a132fa018c1ef2af71fa605ebcc48db24c1176628f36fc8cee08", "MacAddress": "02:42:ac:13:00:02", "IPv4Address": "172.19.0.2/16", "IPv6Address": "" }, "6d36c4792f2b5c16b4a381a930d51b7456ab216efcc7af4363f9204c3a27a16a": { "Name": "alpine2", "EndpointID": "ceb49779dff5690117bc9ab18995bf443a8f77d2c956a2152179561676ca0a3c", "MacAddress": "02:42:ac:13:00:03", "IPv4Address": "172.19.0.3/16", "IPv6Address": "" }, "f39f29ac4f23bd0bb41bf39e80961d542a23f3cb79f91b93bbc00bee690a2e1b": { "Name": "alpine4", "EndpointID": "49f46643172030e0de68dd093aca27a45d6c8ddf606cdc8a7c1eee8228b8f360", "MacAddress": "02:42:ac:13:00:04", "IPv4Address": "172.19.0.4/16", "IPv6Address": "" } }, "Options": {}, "Labels": {} } ]
alpine1の中からpingを打ってみると,今度はコンテナ名でも通じることがわかる.
$ docker container attach alpine1 / # ping -c 2 alpine2 --- alpine2 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.095/0.097/0.099 ms / # ping -c 2 alpine4 PING alpine4 (172.19.0.4): 56 data bytes 64 bytes from 172.19.0.4: seq=0 ttl=64 time=0.255 ms 64 bytes from 172.19.0.4: seq=1 ttl=64 time=0.156 ms --- alpine4 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.156/0.205/0.255 ms / # ping -c 2 alpine1 PING alpine1 (172.19.0.2): 56 data bytes 64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.073 ms 64 bytes from 172.19.0.2: seq=1 ttl=64 time=0.122 ms --- alpine1 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.073/0.097/0.122 ms
この機能はautomatic service discoveryという.
一方alpine3はネットワークが異なるため,名前解決できないし,IPアドレスでも到達できない.
/ # ping -c 2 alpine3 ping: bad address 'alpine3' / # ping -c 2 172.17.0.2 PING 172.17.0.2 (172.17.0.2): 56 data bytes --- 172.17.0.2 ping statistics --- 2 packets transmitted, 0 packets received, 100% packet loss
CTRL+p+qでalpine1を抜けて,今度はalpine4からpingを打ってみる.
$ docker container attach alpine4 / # ping -c 2 alpine1 PING alpine1 (172.19.0.2): 56 data bytes 64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.067 ms 64 bytes from 172.19.0.2: seq=1 ttl=64 time=0.086 ms --- alpine1 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.067/0.076/0.086 ms / # ping -c 2 alpine2 PING alpine2 (172.19.0.3): 56 data bytes 64 bytes from 172.19.0.3: seq=0 ttl=64 time=0.090 ms 64 bytes from 172.19.0.3: seq=1 ttl=64 time=0.097 ms --- alpine2 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.090/0.093/0.097 ms / # ping -c 2 alpine3 ping: bad address 'alpine3' / # ping -c 2 172.17.0.2 PING 172.17.0.2 (172.17.0.2): 56 data bytes 64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.115 ms 64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.155 ms --- 172.17.0.2 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.115/0.135/0.155 ms / # ping -c 2 alpine4 PING alpine4 (172.19.0.4): 56 data bytes 64 bytes from 172.19.0.4: seq=0 ttl=64 time=0.035 ms 64 bytes from 172.19.0.4: seq=1 ttl=64 time=0.105 ms --- alpine4 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.035/0.070/0.105 ms
デフォルトブリッジとユーザ定義ブリッジの両方に接続しているので,すべてのコンテナに到達できる.
ただしalpine3はデフォルトブリッジのため,名前解決はできない.
ブリッジネットワークに接続しているので,全てのコンテナがグローバルアドレスへ到達可能.
/ # ping -c 2 google.com PING google.com (172.217.25.238): 56 data bytes 64 bytes from 172.217.25.238: seq=0 ttl=61 time=10.855 ms 64 bytes from 172.217.25.238: seq=1 ttl=61 time=9.578 ms --- google.com ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 9.578/10.216/10.855 ms
終わったら作成したコンテナとネットワークを削除
$ docker container stop alpine1 alpine2 alpine3 alpine4 $ docker container rm alpine1 alpine2 alpine3 alpine4 $ docker network rm alpine-net
まとめ
ブリッジネットワークのチュートリアルを実施した.
商用環境ではdefault bridge networkは非推奨とのこと.
逆に開発環境でコンテナ1台お手軽に試すとかならdefaultでも十分そう.
以上