Dockerネットワークをブリッジ接続

当方ではコンテナ仮想化環境としてDockerを利用しています。
Dockerホストの所属するネットワークセグメントには当該サーバしか存在しておらず、十分なIPの空きがあるためDocker標準のIPマスカレードによる外部との接続は管理が煩雑と感じました。(ネットワーク構成参考)

また、ホストサーバで動作しているSamba4のADサービスがdocker0のIP(172.16.0.1)をDNSに自動登録してしまい内部DNSに弊害が発生したため、単純にホストネットワークにブリッジ接続する構成に変更しました。

当方環境 : CentOS7 , Docker version 1.12.1, build 23cf638

本構成にした場合、dockerfileによるコンテナ作成(docker buildコマンド)が正常に動作しないことを確認しています。(docker0ブリッジを固定で利用するため) また、環境によっては外部との通信が正常にできない場合があるようで調査中です。

ネットワーク構成(変更前・変更後)

本サーバではDockerの他にOpenVPNサーバもtapモード(ブリッジ)で動作しているため、複雑なネットワーク構成となっていました。
docker-br-before

これを変更し、dockerコンテナは単純にブリッジ接続で外部と通信できるようにしてみます。
docker-br-after

IP情報は一応マスキングしています。適宜読み替えてください。
IPアドレス(物理NICに付与されていたもの) : AAA.BBB.CCC.2/24
デフォルトゲートウェイ : AAA.BBB.CCC.1

Dockerの設定変更

以下の記事を参考にしました。

NIC再設定に伴い一旦接続できなくなる可能性があるため、ローカルコンソールでの作業をお勧めします。

Dockerブリッジの作成

Dockerネットワークを新規生成します。これによりdocker管理下のセグメントshared_nwと、ブリッジデバイスbr0が作成されます。

# docker network create --driver bridge --subnet=AAA.BBB.CCC.0/24 --gateway=AAA.BBB.CCC.2 \
--opt "com.docker.network.bridge.name"="br0" shared_nw

# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
xxxxxxxxxxxx        bridge              bridge              local
xxxxxxxxxxxx        host                host                local
xxxxxxxxxxxx        none                null                local
xxxxxxxxxxxx        shared_nw           bridge              local

ホストインタフェースをブリッジに接続

物理NIC(enp3s0)からIPを削除した後、作成したブリッジにホストインタフェースをぶら下げます。
また、デフォルトゲートウェイが消えているため追加します。

# ifconfig $eth 0.0.0.0 promisc up
# brctl addif br0 enp3s0

# brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.02421d0de15f       no              enp3s0

起動スクリプトの改修

上記のブリッジ接続設定は再起動で消えてしまうため、ホスト起動時に自動的に設定されるよう以下の起動スクリプトを作成しました。

ついでにdocker0インタフェースも無効化しておきます。
以下の内容で /etc/docker/docker-startup として作成しました。

#!/bin/sh
#################################
# Set up Ethernet bridge on Linux
# Requires: bridge-utils
#################################

# ブリッジインタフェースの定義
br="br0"

# ブリッジされる物理NICの定義
eth="enp3s0"
eth_ip="AAA.BBB.CCC.2"
eth_netmask="255.255.255.0"
eth_broadcast="AAA.BBB.CCC.255"
gw="AAA.BBB.CCC.1"

# 物理NICからIP削除
ifconfig $eth 0.0.0.0 promisc up

# ブリッジの紐づけ
brctl addif $br $eth

# デフォルトゲートウェイの再設定
route add default gw $gw

# docker0の無効化
ip link set dev docker0 down
brctl delbr docker0

# IP転送有効化
/bin/echo 1 > /proc/sys/net/ipv4/ip_forward

また、シャットダウンスクリプトも作成します。

#!/bin/sh
####################################
# Tear Down Ethernet bridge on Linux
####################################

# ブリッジインタフェースの定義
br="br0"

# オリジナルNICの定義
eth="enp3s0"
ip="AAA.BBB.CCC.DDD"
gw="AAA.BBB.CCC.1"

# ブリッジインタフェースの削除
ifconfig $br down
brctl delbr $br

# IP転送の中止
/bin/echo 0 > /proc/sys/net/ipv4/ip_forward

# オリジナルNICの再設定
ifconfig $eth $ip
route add default gw $gw

本スクリプトはdocker起動時・停止時に実行されるよう、/etc/systemd/system/docker.service に以下の2行を追記しました。

[Service]
ExecStartPost=/etc/docker/docker-startup
ExecStopPost=/etc/docker/docker-shutdown

OpenVPNの設定変更

Dockerの設定変更により、OpenVPNがDockerのブリッジに依存するようになったため、OpenVPN起動はDockerの後となるよう設定します。
/etc/systemd/system/docker.service に以下の1行を追加します。

[Unit]
Before=openvpn-bridge.service

OpenVPNの起動スクリプトを変更し、ブリッジの設定変更を抑止します。

#!/bin/sh
#################################
# Set up Ethernet bridge on Linux
# Requires: bridge-utils
#################################

# ブリッジインタフェースの定義
br="br0"

# ブリッジされるTAPインタフェースの定義
# for example tap="tap0 tap1 tap2".
tap="tap0"

# TAPインタフェースの作製
for t in $tap; do
    openvpn --mktun --dev $t
done

# ブリッジインタフェーズに紐づけ
for t in $tap; do
    brctl addif $br $t
done

# tapインタフェースをプロミスキャスモードに設定
for t in $tap; do
    ifconfig $t 0.0.0.0 promisc up
done

OpenVPNシャットダウンスクリプトも変更します。

#!/bin/sh
####################################
# Tear Down Ethernet bridge on Linux
####################################

# ブリッジインタフェースの定義
br="br0"

# ブリッジされるTAPインタフェースの定義
tap="tap0"

# TAPインタフェースの削除
for t in $tap; do
    openvpn --rmtun --dev $t
done

結果

これにより、計画通りのネットワーク構成に変更することができました。

インタフェース/ルーティング/ブリッジ構成

# ifconfig -a
br0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet AAA.BBB.CCC.2  netmask 255.255.255.0  broadcast 0.0.0.0
        ether 02:42:ab:af:30:a8  txqueuelen 0  (Ethernet)
        RX packets 1006  bytes 196328 (191.7 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1151  bytes 155997 (152.3 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

enp3s0: flags=4419<UP,BROADCAST,RUNNING,PROMISC,MULTICAST>  mtu 1500
        ether c0:3f:d5:6f:6f:26  txqueuelen 1000  (Ethernet)
        RX packets 1020  bytes 211312 (206.3 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1139  bytes 154677 (151.0 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 0  (Local Loopback)
        RX packets 1080  bytes 71830 (70.1 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1080  bytes 71830 (70.1 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

tap0: flags=4419<UP,BROADCAST,RUNNING,PROMISC,MULTICAST>  mtu 1500
        ether aa:66:de:d0:48:80  txqueuelen 100  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 9  bytes 834 (834.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

# netstat -rn
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
0.0.0.0         AAA.BBB.CCC.1   0.0.0.0         UG        0 0          0 br0
AAA.BBB.CCC.0   0.0.0.0         255.255.255.0   U         0 0          0 br0

# brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.0242abaf30a8       no              enp3s0
                                                        tap0

# docker network ls
NETWORK ID           NAME                DRIVER              SCOPE
xxxxxxxxxxxx         bridge              bridge              local
xxxxxxxxxxxx         host                host                local
xxxxxxxxxxxx         none                null                local
xxxxxxxxxxxx         shared_nw           bridge              local

使い方

永続コンテナを起動する例

# docker run -d --name container1 --hostname container1 --net shared_nw --ip AAA.BBB.CCC.3 --restart=always -i -t centos

この変更により、外部から直接dockerコンテナと通信できるようになり、とても便利になりました。
コンテナのセキュリティは自分で確保しなくてはなりませんが。