Dockerコンテナをブリッジ接続で使う

Docker with Bridge Network

前の記事に引き続きDockerネタです。
Dockerコンテナを LAN 接続で利用する場合には幾つかの方法がありますが、この記事ではブリッジ接続を行って、LANと同一のネットワークセグメント上にDockerコンテナを配置して利用する方法に関して書きます。

2015/4/23 スクリプトを一部変更

はじめに

花粉にやられはじめて、ボーっとしてきている今日この頃、みなさまいかがお過ごしですか?

今回もDockerネタです。Dockerコンテナに固定IPを割り振ったり、DHCPでアドレスを割り当てたりしようとして試行錯誤した結果に関して書きます。

Dockerコンテナを起動してアドレス割り当てを行うためのスクリプトに関しても載せたいと思います。

Docker コンテナに固定IPを割り振る場合、pipework 等を利用する事が多いようですが、以下ではその方法ではなく nsenter ( util-linux-ng ) を利用します。

参考

※写真 : Humpback Whale - Wikimedia Commons: ( by Wanetta Ayers )

ネットワーク構成

Dockerのネットワークに関しては色々解説されたサイトがあるので、詳細はそちらを参照下さい。

普通に Docker ホストを構成した場合は、ネットワーク構成は下図のようになっているかと思います。

Dockerネットワーク

ブリッジ接続

上記を以下のようなブリッジ接続に変更して利用します。
Docker ホストや作業用PC等と同じ LAN (セグメント) 上にコンテナも配置する事で、ルーティング等の追加設定等を行わずに Docker コンテナにアクセスする事が狙いです。
-p ( --publish ) オプションによるポートフォワードも行わずにコンテナで起動しているサービスへアクセスできるようにします。

Dockerネットワーク-ブリッジ

30分で仮想サーバ(KVMホスト)環境を構築する のネットワーク構成と同じような感じになります。

環境

環境は以下。
DockerコンテナのOSもCentOSを想定しています。

OS CentOS 6.6 (Final)
Docker docker-io-1.4.1-3
nsenter util-linux-ng-2.17.2

手順

前提

Docker は既にインストール済みのものとします。
また、nsenter コマンドが実行可能である事。未インストールの場合、util-linux-ng パッケージに含まれるのでインストールして下さい。
( 私の場合は Dockerホストを構築した時点でインストールされていた )

ブリッジインターフェースを作成

Building your own bridge を参考に、docker0 を削除し、ブリッジインターフェースを作成します。

  1. Dockerを停止し、 docker0 を削除
    # service docker stop
    # ip link set dev docker0 down
    # brctl delbr docker0
    
  2. 自前でブリッジインターフェース(br0)を作成

    作成方法は 30分で仮想サーバ(KVMホスト)環境を構築するのネットワーク設定と同様に作成しました。

    # vi /etc/sysconfig/network-scripts/ifcfg-br0
    
    以下内容で作成。
    ( ※各値は環境に応じて変更する )
    DEVICE=br0
    TYPE=Bridge
    BOOTPROTO=static
    IPADDR=192.168.1.10
    NETMASK=255.255.255.0
    NETWORK=192.168.1.0
    BROADCAST=192.168.1.255
    DELAY=0
    DNS1=192.168.1.1
    GATEWAY=192.168.1.1
    ONBOOT=yes
    
    上記ブリッジ接続を利用するようにeth0の設定を変更
    # vi /etc/sysconfig/network-scripts/ifcfg-eth0
    
    以下のように変更
    DEVICE="eth0"
    BOOTPROTO=none
    NM_CONTROLLED="yes"
    ONBOOT="yes"
    TYPE="Ethernet"
    HWADDR=xx:xx:xx:xx:xx:xx
    BRIDGE=br0
    
  3. ネットワーク再起動
    # /etc/rc.d/init.d/network restart
    
  4. トラフィック転送設定
    # vi /etc/sysconfig/network/iptables
    
    以下を追加
    -I FORWARD -m physdev --physdev-is-bridged -j ACCEPT 
  5. iptables 再起動
    # /etc/rc.d/init.d/iptables restart
    
  6. ブリッジインターフェースの状態確認
    # ip addr show br0
    13: br0:  mtu 1500 qdisc noqueue state UNKNOWN
        link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
        inet 192.168.1.10/24 brd 192.168.1.255 scope global br0
        inet6 xxxx:xx:xx:xx:xx:xx:xx:xx/64 scope global dynamic
           valid_lft 2591881sec preferred_lft 604681sec
        inet6 xxxx::xxx:xxx:xxx:xxx/64 scope link
           valid_lft forever preferred_lft forever
    

Docker の起動設定

Docker を作成したブリッジインターフェースを利用する形で起動するように設定します。

# echo 'DOCKER_OPTS="-b=br0"' >> /etc/default/docker

Dockerの起動

# service docker start

ここまでの設定で、Dockerコンテナをブリッジ接続で使う事ができるようになりました。
但し、コンテナに割り当てられるIPアドレスはDockerが割り当てを行うため、固定IPを割り振ったり、DHCPから割り当てるといった事は行えません。
以下は、コンテナ起動用のスクリプトを用意する事でコンテナに固定IPを割り振る方法になります ( 必要ない方は読み飛ばして下さい )。

IP設定のための起動スクリプトの作成

起動のためのスクリプトを作成します。
機能としては、

  • Dockerイメージを指定してコンテナを起動する ( docker run )
  • 起動したコンテナのコンテナID、及び、PIDを取得
  • Dockerが割り当てたIPアドレスを削除
  • 固定IP、あるいは、DHCPで取得したIPアドレスを設定 (引数で指定する)

/root/script/run_docker.sh で作成するものとします。

※ip addr del した際にデフォルトゲートウェイ設定も削除されてしまう ( 場合がある ) ようなので、ip付け替え後にホストと同じデフォルトゲートウェイを設定する処理を追加 ( 2015/4/23 )

# vi /root/script/run_docker.sh
以下で作成
#!/bin/bash

usage() {
  echo "usage : $0 [-d] | [-a address] IMAGE NAME" 1>&2
  exit 1
}

while getopts da:h OPT
do
  case $OPT in
    d) DHCP_FLG="true"
       ;;
    a) ADDRESS_FLG="true"
       ADDRESS="$OPTARG"
       ;;
    h) usage
       ;;
    \?) usage
       ;;
  esac
done
shift $(($OPTIND -1))

if [ $# -lt 2 ]; then
  usage
fi

if [ "$ADDRESS_FLG" = "true" ] && [ -z "$ADDRESS" ]; then
  echo specify address
  exit 1
fi


# run docker container from image and get container id
CONTAINER_ID=`docker run -idt --hostname=$2 --name=$2 $1 /bin/bash`
#docker start $CONTAINER_ID

if [ $? -eq 1 ]; then
  exit 1
fi

# get docker pid, ip address (allocated by docker)
PID=`docker inspect --format '{{.State.Pid}}' $CONTAINER_ID`
IPADDR=`docker inspect --format '{{.NetworkSettings.IPAddress}}' $CONTAINER_ID`
IPSUB=`docker inspect --format '{{.NetworkSettings.IPPrefixLen}}' $CONTAINER_ID`


echo -------------------------------------
echo Container ID  : $CONTAINER_ID
echo Container PID : $PID
echo Allocated IP  : $IPADDR/$IPSUB
echo -------------------------------------

# del allocated ip address
nsenter -t $PID -n ip addr del $IPADDR/$IPSUB dev eth0

if [ "$ADDRESS_FLG" = "true" ]; then
  echo set address $ADDRESS
  nsenter -t $PID -n ip addr add $ADDRESS dev eth0
elif [ "$DHCP_FLG" = "true" ]; then
  # get ip address from dhcp
  echo set address from dhcp
  nsenter -t $PID -n -- dhclient eth0
  dhclient -r
fi

# reset default gateway address
DEFGW=$(ip route | awk '/default/ { print $3 }')
nsenter -t $PID -n route add default gw $DEFGW eth0

# print ip addr
nsenter -t $PID -n ip addr

解説

やってる事をざっくり解説すると

  1. -d オプションを付けて docker run
    これでコンテナIDが取れる
  2. 取得したコンテナIDを利用して docker inspect を実行し、以下の値を取得する
    • PID
    • IPアドレス/サブネットマスク
  3. 上記情報を表示 (echo)
  4. nsenter で 割り当てられたIPアドレスを削除
  5. nsenter で IPアドレスを追加
    引数によって 固定(指定した)IP にするか、DHCPにするかは変更される。

使い方

書式は以下。"名前" はホスト名 ( --hostname ) 、コンテナ名 ( --name ) の両方に使ってます。

usage : ./run-docker.sh [-d] | [-a address] イメージ名 名前

固定IPを付与したい場合

"-a IPアドレス" オプションを指定して実行します。

# ./run-docker.sh -a 192.168.1.8/24 centos:centos6 cent6
-------------------------------------
Container ID : bc8f641ee83cd7f6b30642e8ab85335cd9e5f5980240d552f6610e6830756605
Container PID : 3173
Allocated IP : 192.168.1.2/24
-------------------------------------
set address 192.168.1.8/24
7: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
8: eth0: <NO-CARRIER,BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state DOWN
    link/ether 02:42:c0:a8:01:02 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.8/24 scope global eth0
    inet6 fe80::42:c0ff:fea8:102/64 scope link tentative
       valid_lft forever preferred_lft forever

上記例では起動時には Docker によって192.168.1.2/24 が割り当てられていますが、192.168.1.8/24 に付け直しています。

DHCPでIP付与したい場合

"-d" オプションを指定して実行します。

# ./run-docker.sh -d centos:centos6 cent6-1
-------------------------------------
Container ID : 9bbb39c30378715c6daa462010274c9ab8d066f66e6d402c06becabf7ad405f6
Container PID : 4161
Allocated IP : 192.168.1.5/24
-------------------------------------
set address from dhcp
16: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
17: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:c0:a8:01:05 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.19/24 brd 192.168.1.255 scope global eth0
    inet6 2001:c90:8222:2615:42:c0ff:fea8:105/64 scope global tentative dynamic
       valid_lft 2591997sec preferred_lft 604797sec
    inet6 fe80::42:c0ff:fea8:105/64 scope link
       valid_lft forever preferred_lft forever

確認

DHCPでアドレス付与された Dockerコンテナから疎通を確認してみます。

# docker attach cent6-1
[root@cent6-1 /]# ping 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=2.25 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=0.801 ms
^C
--- 192.168.1.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1702ms
rtt min/avg/max/mdev = 0.801/1.530/2.259/0.729 ms
[root@cent6-1 /]# ping www.google.co.jp
PING www.google.co.jp (74.125.235.207) 56(84) bytes of data.
64 bytes from kix01s01-in-f15.1e100.net (74.125.235.207): icmp_seq=1 ttl=50 time=17.2 ms
64 bytes from kix01s01-in-f15.1e100.net (74.125.235.207): icmp_seq=2 ttl=50 time=17.4 ms
^C
--- www.google.co.jp ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3281ms
rtt min/avg/max/mdev = 17.222/17.300/17.426/0.077 ms

LAN ( 192.168.1.1 )、WAN ( www.google.co.jp ) への疎通はOK。

LAN上のWindows PC から Docker コンテナへの疎通も確認します。

C:\>ping 192.168.1.19

192.168.1.19 に ping を送信しています 32 バイトのデータ:
192.168.1.19 からの応答: バイト数 =32 時間 =2ms TTL=64
192.168.1.19 からの応答: バイト数 =32 時間 <1ms TTL=64

192.168.1.19 の ping 統計:
    パケット数: 送信 = 2、受信 = 2、損失 = 0 (0% の損失)、
ラウンド トリップの概算時間 (ミリ秒):
    最小 = 0ms、最大 = 2ms、平均 = 1ms
Ctrl+C
^C

こちらも疎通OKになりました。
docker run 時の -p ( --publish ) 設定は行っていませんが ( iptables 設定は変更してるけど )、 コンテナで実行中のサービス ( http (80番) とか ) へのアクセスも可能になってます。

問題点

  • docker inspect に値が反映されない
    上記でアドレスが付け変わるのは確認できたのですが、 docker inspect で表示される値は、最初に割り当てられたままになってます。
    私の場合は実アドレスが変わっていれば構わない(今のところ)ので、無視してます :-p
  • DHCPで割り振った場合に安定動作するか怪しい
    DHCPからのIP取得は dhclient を利用しているのですが、dhclient -r を実行せずに複数回起動するとエラーになって値の取得ができない状態になってしまいました。( ( 実環境の ) カーネル上で複数起動できないようになっていると思われる )

    上記スクリプトではこれを回避するため dhclient -r を実行するようにしていますが、これだと問題が発生する可能性があります。
    ( dhclient -r はアドレスのリリース処理なので、DHCPサーバ側ではIPがリリース扱いとなり他PC等に割り振られる可能性がある )
    弊社で軽く実験したところでは Dockerコンテナ自体は DHCPで振られたアドレスが設定されたままで、アドレスはリリースはされませんでしたが、実運用する場合要検証です。
    ( dhclient を多重起動する方法があれば解決できそうな気もするが、、、知ってる方いたら教えて )
    ※DHCPから複数アドレス取得できた場合でも、リリースが正しくなされるかどうかは確認する必要あり

まとめ

今回のやり方では幾つかの問題が残っています。
特に dhclient を利用した場合には安定稼働するかどうか怪しいものに。

但し、固定IPを割り当てる分には ( docker inspect の問題はありますが ) 特に問題は発生していないため、弊社では上記方式を使っています。

上記 nsenter を利用したIPの付け替えは docker start でコンテナを再起動した後でも実行可能なので、pipework がインストールされていない環境で 再起動がかかったコンテナのIPを前のものと同じにしたいといった用途でも使えます。

Docker コンテナのアドレス管理で悩んでいる場合、試してみてはいかがでしょうか。