Dockerのネットワーク機能を学ぶ

目次

Dockerのネットワーク機能とは?

  • 公式の概要によれば、Docker間、または外部のサービスとのネットワーク接続はかんたんで、Docker上で動いていることを考慮する必要は全くないと書いてあります。
  • またDockerエンジンが異なるホストOS上で動いていたとしても関係ないとのこと。
  • どういうことなのか、詳しくみていきましょう。

ネットワーク・ドライバ

  • Dockerのネットワーク機能は、ケース毎に用意された複数のドライバがあり、またそれらが付け外し容易なものとして準備されている点に大きく支えられています。
  • いくつかのドライバを見ていきましょう。

bridge

  • デフォルトのネットワーク・ドライバ
  • ブリッジ・ネットワークは、スタンドアローン・コンテナ内でアプリケーションが動作する時、そのコンテナが通信するために使います。
  • Dockerデーモンが同じであれば、デフォルトのbridgeドライバを用いるコンテナは同じブリッジ・ネットワークに属し、相互に通信が可能である。

host

  • スタンドアローン・コンテナ用
  • コンテナがホスト上のネットワークを直接使う。
  • 例えば、TCP80をコンテナのポートに割り当て、hostドライバーを用いた場合は、ホストIPのTCP80へのリクエストはコンテナに届けられる。

overlay

  • 異なるDockerデーモン上で動作するスタンドアローン・コンテナ間で通信できるようにします。
  • また、異なるDockerデーモンとswarmサービスが相互に通信可能にします。
  • この方法では、コンテナ間のOSレベルのルーティング設定を不要にします。
  • swarmサービスとは、Docker Engineに組み込まれたSwarmKitを使ったコンテナオーケストレーション層の実装を指すらしい。

ipvlan

  • IPVlanネットワークは、IPv4とIPv6の両IPアドレス割り当てを、ユーザーがまとめて管理するためのもの。
  • レイヤー2VLANタギングやIP vlan L3ルーティングも完全に操作可能になるようVLANドライバは構築されています。

macvlan

  • macvlanネットワークは、コンテナにたいして MACアドレスを割り当て可能にし、ネットワーク上の物理デバイスとして見えるようにする。
  • レガシーなアプリケーションとやりとりする場合に用いられるらしい。

none

  • 指定されたコンテナの全てのネットワーク機能を無効化する。
  • 通常はカスタム・ネットワーク・ドライバとの競合を避けるために利用される。

その他のネットワーク・プラグイン

  • サードパーティ製のプラグインが利用できる。
  • Docker Hub もしくは、サードパーティベンダによって提供されている。

ブリッジ・ネットワーク

  • ネットワーク機能におけるブリッジ・ネットワークとは、ネットワーク・セグメント間にトラフィックを転送するリンク層の装置。
  • Dockerにおけるブリッジ・ネットワークとは、ソフトウェアを使い、同じブリッジ・ネットワークにあるコンテナに接続できるようにするもの。
  • また、ネットワークの隔離も提供し、異なるブリッジ・ネットワークのコンテナとは接続されません。
  • Dockerのブリッジ・ドライバはホストマシン上のルールを自動設定します。
  • Dockerを起動するとデフォルト・ブリッジ・ネットワークが自動的に作成されます。これはコンテナのデフォルトネットワークとなります。
  • ユーザー定義ブリッジ・ネットワークが存在する場合は、そちらが優先されます。

ブリッジ・ネットワークの境界

  • ブリッジ・ネットワークの対象は、同じDockerデーモンホスト上で動作するコンテナです。
  • 一般的にはホストマシン上に動作しているデーモンは一つと考えられるので、同一ホスト上のコンテナは同じブリッジ・ネットワークに属すると捉えて良いでしょう。
  • 異なるブリッジ・ネットワーク間で通信を行いたい場合は、オーバーレイ・ネットワークを利用します。

ユーザー定義ブリッジ・ネットワーク(user-defined bridge network)

  • ユーザー定義ブリッジ・ネットワークとは、デフォルト・ブリッジ・ネットワークとは別にユーザーが独自に定義したネットワークです。

  • デフォルトと比較して以下の特徴があります。

  • コンテナ間のDNS名前解決を自動で提供

    • デフォルト・ブリッジ・ネットワークは--linkオブションが無ければ、IPアドレスを用いないとコンテナ間で通信できない。
    • ユーザー定義であれば、名前もしくはエイリアスで名前解決が可能。
  • 隔離しやすい

    • --networkオプションなしのコンテナはデフォルト・ブリッジ・ネットワークに接続されるため、無関係のコンテナと通信が起き、潜在的なリスクを発生させる。
    • ユーザー定義のネットワークであれば、意図したコンテナ間のネットワーク通信に限定されるのでリスクが下げられる。
  • ネットワークへの接続・切断が容易

    • 稼働中のコンテナはいつでもユーザー定義のネットワークへ接続・切断が可能。
    • デフォルト・ブリッジ・ネットワークに接続しているコンテナは、コンテナを停止させて異なるネットワークオプションでコンテナの再作成が必要です。
  • ネットワークごとに独立した設定が可能

    • デフォルト・ブリッジ・ネットワークの設定変更はiptablesルールなど、全てのコンテナで同じ設定を使います。またDockerの再起動が必要になります。
    • ユーザー定義のネットワークはそれぞれ異なる設定が可能です。

実践編

ブリッジ・ネットワークで通信する

以下のステップを実行するためのディレクトリを作成します。

mkdir docker_example
cd docker_example

デモ用のアプリの作成

node.js を用いた簡易APIサーバーを立ち上げます。

完成図

. # docker_example
├── backend
   ├── index.js
   └── package.json
└── front
    └── request.sh

まず backend の箇所から作成を始めます。

mkdir backend
cd backend
  • npm init -yを実行してpackage.jsonを初期化します。
  • npm i expressで必要なライブラリをインストールします。

index.js に以下のコードを記述します。

const express = require('express')
const app = express()
const port = 8080

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

node index.jsでNodeサーバーを立ち上げます。

$ node index.js
Example app listening on port 8080

curl localhost:8080 でリクエストを投げて、レスポンスを確認します。

$ curl localhost:8080
Hello World!

サーバー起動を確認したら、Ctrl+c でサーバーを終了します。

次に frontend の箇所を作成します。

cd ../
mkdir frontend
cd frontend

request.shを作成します。

#!/bin/bash
curl localhost:8080

Image化

デモアプリをImage化します。

完成図

. # docker_example
├── backend
   ├── Dockerfile
   ├── .dockerignore
   ├── index.js
   └── package.json
└── front
    ├── Dockerfile
    └── request.sh

backendディレクトリで以下の作業を行います。

.dockerignoreは以下の通り。

node_modules

Dockerfileは以下の通り。

# syntax=docker/dockerfile:1
FROM node:slim
COPY . ./
RUN npm install
CMD [ "node", "./index.js" ]

この状態でdocker build -t dbn_backend:latest -f Dockerfile .を実行します。 タグ名: dbn_backend:latest でイメージが作成されます。

frontendディレクトリで以下の作業を行います。

Dockerfileは以下の通り。

# syntax=docker/dockerfile:1
FROM alpine:3.14
RUN apk add --no-cache curl
COPY . ./
CMD [ "sh", "./request.sh" ]

この状態でdocker build -t dbn_frontend:latest -f Dockerfile .を実行します。 タグ名: dbn_frontend:latest でイメージが作成されます。

ここで一度、両方のコンテナを起動してどうなるか見てみましょう。(コンテナ分のターミナルを立ち上げる必要があります。)

ターミナル1 で、以下のコマンドを実行します。

# backendディレクトリで作業しているものとする
$ docker run --rm dbn_backend:latest

Example app listening on port 8080

ターミナル2 で以下のコマンドを実行します。

$ docker run --rm dbn_frontend:latest

curl: (7) Failed to connect to localhost port 8080 after 0 ms: Couldn't connect to server

残念ながら、リクエストが到達できませんでした。 dbn_backend コンテナがポートを公開していないのと、dbn_frontend コンテナの送信するリクエストの宛先が不適切だったために、こうなってしまったようです。

デフォルト・ブリッジ・ネットワークを用いた構成

  • コンテナは --networkオプションを指定せず起動された場合は、デフォルト・ブリッジ・ネットワークに接続されます。
  • —networkオプションを指定せず2つのコンテナを起動し、ネットワークを利用したデモを行います。
  • frontend コンテナが backend コンテナにGETリクエストを送信するシナリオです。

デフォルト・ブリッジ・ネットワーク上で dbn_backend コンテナ のポートを公開してみましょう。

docker run --expose 8080 --rm dbn_backend:latest

これで dbn_backend コンテナ は ポート8080のリクエストを受理できるようになりましたが、IPアドレスが不明なので frontend コンテナのリクエスト送信部分に課題が残っています。

このコンテナのIPアドレスを調べたいと思います。 dbn_backend コンテナのIDを調べます。

$ docker container list

CONTAINER ID   IMAGE     ...
xxxxx   dbn_backend:latest   , ...

xxxxx の値がコンテナIDです。

以下のコマンドに渡して実行します。

$ docker inspect -f \
  '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
  <コンテナID>

172.17.x.y

172.17.x.y が dbn_backend コンテナのIPアドレスであることがわかりました。

./frontend/request.shの内容を以下のように修正します。

#!/bin/bash
curl 172.17.x.y:8080

dbn_frontend コンテナを再ビルドします。

# frontendディレクトリで作業しているものとする
docker build -t dbn_frontend:latest -f Dockerfile .

これで準備ができました。

frontend コンテナを起動してみましょう。

$ docker run --rm dbn_frontend:latest

Hello World!

backend コンテナとネットワーク通信ができ、期待したレスポンスが返ってきました!

しかし、IPアドレスを調べる必要があるため実用的ではありませんでしたね。

ユーザー定義のネットワークであればどのようになるのでしょうか?

ユーザー定義ブリッジ・ネットワークを用いた構成

  • frontend コンテナが backend コンテナにGETリクエストを送信するシナリオは同じです。
  • demo-netというユーザー定義ブリッジ・ネットワークを作成します。
  • コンテナ起動時に--networkオプションを指定します。またDNSを利用するため--nameオプションを指定し、コンテナに名前をつけます。

ユーザ定義ブリッジ・ネットワークの作成には、 docker network create コマンドを使います。

docker network create demo-net

これでユーザ定義ブリッジ・ネットワークが作成されました!

dbn_backend コンテナを demo-net ネットワークに接続してみましょう。また、ポート解放とコンテナ名の指定も行います。

$ docker run --network=demo-net \
    --expose 8080 --name backend \
    --rm dbn_backend:latest

Example app listening on port 8080

docker container list で確認すると、確かにコンテナに名前が設定されました。これがDNSで利用できます。

$ docker container list

CONTAINER ID   IMAGE         ...   PORTS      NAMES
xxxxx   dbn_backend:latest   ...   8080/tcp   backend

./frontend/request.shの内容を以下のように修正します。

#!/bin/bash
curl backend:8080

dbn_frontend コンテナを再ビルドします。

# frontendディレクトリで作業しているものとする
docker build -t dbn_frontend:latest -f Dockerfile .

これで準備ができました。

frontend コンテナを起動してみましょう。

$ docker run --network=demo-net --rm dbn_frontend:latest

Hello World!

backend コンテナのIPアドレスがDNS経由で取得され、無事に期待したレスポンスが返ってきました!!

実際に動かしてみると、ユーザ定義ブリッジ・ネットワークの方が管理・運用面で優れていることを実感できます。