Dockerコンテナのネットワーク構成

モチベーション

  • Dockerコンテナのネットワークを理解するための足がかりとして,netnsについてまとめたい.
  • Dockerコンテナが構成するネットワークについてまとめたい.
  • -pオプションで何も考えずにポートを外に出すのは危険だよと言いたい

OSI参照モデルで役割を把握しておく

OSI参照モデルを覚える必要は無いと思うが,どのレイヤーがどのような役割を行っているのかを理解しておく必要はあると思う.

レイヤー呼称
L7アプリケーション層HTTP,FTP,DNS,SNMP
L6プレゼンテーション層HTTP,FTP,DNS,SNMP
L5セッション層HTTP,FTP,DNS,SNMP,TCP
L4トランスポート層TCP,UDP
L3インターネット層IP,ICMP,ARP
L2リンク層Ethernet,Wi-Fi
L1物理層UTP,STP

L2はブリッジ周辺

  • Ethernetヘッダの制御.IPパケットに,MACアドレスなどを埋め込んだEthernetヘッダをつける/とる.
  • ブリッジはこの辺.

L3はルーター周辺

  • IPヘッダの制御.TCPセグメントやICMPセグメントにIPヘッダをつける/とる.
    • pingtracerouteはL3で処理される.
    • 経由するルータの数をTTL(Time To Live)として指定することで,TTL=0になるとそのパケットは破棄される.
  • 192.168.1.1を255.255.255.0(サブネットマスク)でマスクすると192.168.1.0(ネットワークID)が取り出せる(マスクされなかった部分はホストIDと呼ぶ).これを192.168.1.1/24(先頭3byteでマスクするという意味)と表現しても良い.
    • ネットワークIDが異なる通信はデフォルトゲートウェイに解決してもらう
      • 192.168.1.1/24なクライアントから192.168.1.2/24なサーバーへは,直接問い合わせることができる.
      • 192.168.1.1/24なクライアントから192.168.2.1/24なサーバーへは,ゲートウェイを通って問い合わせる.
  • IPアドレスとMACアドレスを対応付けしたAPRテーブルにより.IPアドレスからMACアドレスを解決したりする.
  • ルーターはこの辺.

L4はソケット周辺

  • 主にソケットの制御を行う.TCPとかUDPとか.セッション管理的な事も行っているのでL5とラップしている.
    • アプリとポートの紐付け
    • 3way Handshakeによるコネクションの確立
    • ACKによるパケットの保証と再送
    • 送受信パッファの空き状況による送信パケット数の調整=フロー制御
    • 受信側でのパケットの整列=順序制御
    • パケットロスに対する送信パケット数の調整=輻輳制御

L7,L6,L5はアプリケーション周辺

  • 例えばWEBアプリケーションだとすると,
    • HTTPのヘッダにはContent-Type,User-Agent,Cookieが埋め込まれているので,L7,L6,L5の役割をすべて担っていると考えて良い.
    • HTTPのメソッドはGET/POST/PUT/DELETE/HEAD/OPTIONS/TRACE/CONNECTの8種類あるが,CRUDで大体済む.
CRUDHTTPメソッド
CreatePOST/PUT
ReadGET
UpdatePUT
DeleteDELETE

つまり,サーバー/クライアントアプリケーションはL7,L6,L5,L4らへん

以下はC言語での実装例であるが,基本的にはどのサーバー/クライアントアプリケーションもL7,L6,L5,L4らへんを実装したものである.

サーバー側

  • TCPソケットを作成する.socket()
  • ソケットに,IPアドレスやポート番号を紐付ける.bind()
  • コネクションの数を設定し,接続可能状態にする.listen()
  • 以下をループする
    • コネクション確立により,クライアント用ソケットを作成する.accept()
    • クライアント用ソケットとクライアント間でデータを送受信する.send()/recv()
    • クライアント用ソケットを閉じて接続終了にする.close()
  • ソケットを閉じる.close()

クライアント側

  • DNSによってIPアドレスを解決する.getaddrinfo()
  • TCPソケットを作成する.socket()
  • コネクションを確立させる.connect()
  • サーバーとデータを送受信する.recv()/send()
  • 接続を終了する.close()

ifconfigとかiptablesとかで見ているのはL3,L2らへん

ここからが本題.L2,L3は物理層であるL1に依存しているため,基本的に,L1-L2-L3は一対一に対応している.しかし,同一のホスト内で異なるIPアドレスを持ったアプリケーションを実行するためには,L3(ルーター)を複数用意する必要があり,場合によっては,L3をまとめるL2(ブリッジ)も複数用意する必要がある.これを実現するには,複数のL2,L3を仮想的に用意できる機能であるnetnsを用いる.

Network Namespace(netns)でホスト内のネットワークを分離する

Network Namespace(netns)とは,ルーティングテーブルやブリッジなどのホスト内のネットワークスタックを仮想的に分離するLinuxカーネルの機能である.同一のホスト上でプロセス毎にネットワークスタックを設定することができ,分離された空間をNamespaceと呼ぶ.

Network Namespaceをつくってみる

まず,ifconfigにより,eth0やloなどのインターフェースを確認することができ,ethXは物理的なインターフェースであり,ホスト外からみたIPアドレスを指す.また,loは仮想的なインターフェースであり,ホストからみた自身のIPアドレス(一般的には127.0.0.0/8)を指す.

ip netns add <name>により.任意のnamespaceをホストに定義することができる.

$ sudo ip netns add mynamespace
$ ip netns list
mynamespace

ip netns exec <namespace> <command>により,namespaceと<command>を紐付けることができる.これにより,<command>はホストのネットワークから隔離された状態で実行される.

$ sudo ip netns exec mynamespace sh

# ifconfig <=== この時点ではactiveなインターフェースは存在しない

# ping 127.0.0.1 <=== loへのpingも通らない
connect: Network is unreachable

# ip link set lo up <=== loをupする
# ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.028 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.032 ms
^C
--- 127.0.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1005ms
rtt min/avg/max/mdev = 0.028/0.030/0.032/0.002 ms
#
# ifconfig
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536 <=== loが立ち上がる
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 4  bytes 336 (336.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 4  bytes 336 (336.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

# exit

ホストとnamespaceをつなげるためには,仮想的なインターフェースであるVirtual Ethernetのペアを用意する必要がある.

$ sudo ip link add type veth <=== ホストでveth0/1のペアを用意する.ここではVirtual Ethernetのニックネームをvethとした.
$ ip link
...
117: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 36:07:ad:3a:ea:0e brd ff:ff:ff:ff:ff:ff
118: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 96:70:77:07:c0:98 brd ff:ff:ff:ff:ff:ff
    veth0/1のペアが用意された.

$ sudo ip link set veth1 netns mynamespace <=== mynamespaceにveth1を持っていく
$ ip link
...
117: veth0@if118: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 36:07:ad:3a:ea:0e brd ff:ff:ff:ff:ff:ff link-netnsid 2
    veth1が見えなくなった.

$ ip address show veth0
117: veth0@if118: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 36:07:ad:3a:ea:0e brd ff:ff:ff:ff:ff:ff link-netnsid 2
    veth0にはIPアドレスが設定されていないようだ

$ sudo ip addr add 172.19.0.1/24 dev veth0 <=== veth0にIPアドレスを設定する
$ ip address show veth0
117: veth0@if118: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 36:07:ad:3a:ea:0e brd ff:ff:ff:ff:ff:ff link-netnsid 2
    inet 172.19.0.1/24 scope global veth0 <=== IPアドレスが設定された
       valid_lft forever preferred_lft foreve

$ sudo ip link set veth0 up <=== veth0をactiveにする
$ ip address show veth0
117: veth0@if118: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state LOWERLAYERDOWN group default qlen 1000
    link/ether 36:07:ad:3a:ea:0e brd ff:ff:ff:ff:ff:ff link-netnsid 2
    inet 172.19.0.1/24 scope global veth0
       valid_lft forever preferred_lft forever
    <NO-CARRIER,BROADCAST,MULTICAST,UP>によるとUPしたらしい

$ sudo ip netns exec mynamespace bash <=== mynamespace内でbashを立ち上げる
# ifconfig
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 20  bytes 1504 (1.5 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 20  bytes 1504 (1.5 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

# ip link 
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
118: veth1@if117: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 <=== mynamespace内にveth1がつくられている
    link/ether 96:70:77:07:c0:98 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    veth0の片割れがnamespace内で見つかった

# sudo ip addr add 172.19.0.2/24 dev veth1 <=== veth1にIPアドレスを設定する
# ip addr
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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
118: veth1@if117: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 96:70:77:07:c0:98 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.19.0.2/24 scope global veth1 <=== veth1にIPアドレスが設定された
       valid_lft forever preferred_lft forever

# ip link set veth1 up
# ifconfig
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 36  bytes 2672 (2.6 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 36  bytes 2672 (2.6 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500 <=== veth1がactiveなインターフェースとして認識された
        inet 172.19.0.2  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::9470:77ff:fe07:c098  prefixlen 64  scopeid 0x20<link>
        ether 96:70:77:07:c0:98  txqueuelen 1000  (Ethernet)
        RX packets 6  bytes 516 (516.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 6  bytes 516 (516.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

# ping 172.19.0.1
PING 172.19.0.1 (172.19.0.1) 56(84) bytes of data.
64 bytes from 172.19.0.1: icmp_seq=1 ttl=64 time=0.073 ms <=== ホスト側のveth0との疎通がとれている
64 bytes from 172.19.0.1: icmp_seq=2 ttl=64 time=0.070 ms
^C
--- 172.19.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1010ms
rtt min/avg/max/mdev = 0.070/0.071/0.073/0.008 ms

ここまでで,ホスト側からnamespace内のveth1への疎通は取れるようになる.しかし,veth1から172.19.0.0/24以外には出られない. なぜなら,namespace内のルーティングテーブルには,デフォルトルート(デフォルトゲートウェイ)が設定されていないので,172.19.0.0/24以外の行き先を解決することができないからである. したがって,namespace内でデフォルトルートを設定することで,namespaceから172.19.0.0/24以外に行くことができる.

$ sudo ip netns exec mynamespace bash
# ip route
172.19.0.0/24 dev veth1 proto kernel scope link src 172.19.0.2

# ping 172.17.0.1
connect: Network is unreachable <=== ホスト側には予め用意した172.17.0.1があるはずだが見つからない

# ip route add default via 172.19.0.1 <=== 172.19.0.1をmynamespaceのデフォルトルートにする
# ip route
default via 172.19.0.1 dev veth1
172.19.0.0/24 dev veth1 proto kernel scope link src 172.19.0.2

# ping 172.17.0.1
PING 172.17.0.1 (172.17.0.1) 56(84) bytes of data.
64 bytes from 172.17.0.1: icmp_seq=1 ttl=64 time=0.047 ms <=== ホスト側の172.17.0.1が見つかった
64 bytes from 172.17.0.1: icmp_seq=2 ttl=64 time=0.054 ms
^C
--- 172.17.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1013ms
rtt min/avg/max/mdev = 0.047/0.050/0.054/0.007 ms

ここまでの設定によって,namespaceとホストを行き来することが可能になるが,ホストのeth0からWAN側へは出られない. なぜなら,NATの設定をしていないので,IPアドレスの変換が行われないからである. したがって,ホストのiptableにNATの設定を行うことで,namespaceからWANに行くことが可能になる.

# ping 185.XXX.XXX.XXX
PING 185.XXX.XXX.XXX (185.XXX.XXX.XXX) 56(84) bytes of data.
^C
--- 185.XXX.XXX.XXX ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2032ms <=== mynamespaceからWANに出られない

$ sudo iptables --table nat --append POSTROUTING --source 172.19.0.0/24 --jump MASQUERADE
$ sudo iptables -vnL -t nat

...
Chain POSTROUTING (policy ACCEPT 1 packets, 59 bytes)
 pkts bytes target     prot opt in     out     source               destination
    0     0 MASQUERADE  all  --  *      *       172.19.0.0/24        0.0.0.0/0 
    各インターフェースから出ようとする172.19.0.0/24からきたパケットのIPアドレスを,出ようとしたインターフェースのIPアドレスに変換するようにする.
...

# ping 185.XXX.XXX.XXX
PING 185.XXX.XXX.XXX (185.XXX.XXX.XXX) 56(84) bytes of data.
64 bytes from 185.XXX.XXX.XXX: icmp_seq=1 ttl=58 time=2.22 ms
64 bytes from 185.XXX.XXX.XXX: icmp_seq=2 ttl=58 time=1.04 ms
64 bytes from 185.XXX.XXX.XXX: icmp_seq=3 ttl=58 time=1.14 ms
^C
--- 185.XXX.XXX.XXX ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2006ms <=== mynamespaceからWANに出られた
rtt min/avg/max/mdev = 1.049/1.474/2.227/0.533 ms

一般に,同一のネットワークIDを持つVirtual Ethernetは一つのブリッジでまとめておく.

$ sudo ip link add mybridge type bridge <=== mybridgeというブリッジを追加する
$ sudo ip link
117: veth0@if118: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 36:07:ad:3a:ea:0e brd ff:ff:ff:ff:ff:ff link-netnsid 2
119: mybridge: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 <=== mybridgeが追加された
    link/ether da:09:90:31:b9:c3 brd ff:ff:ff:ff:ff:ff

$ sudo ip link set dev veth0 master mybridge <=== mybridgeにveth0を接続する
$ sudo ip link show master mybridge
117: veth0@if118: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master mybridge state UP mode DEFAULT group default qlen 1000
    link/ether 36:07:ad:3a:ea:0e brd ff:ff:ff:ff:ff:ff link-netnsid 2
    mybridgeにveth0が接続された

$ sudo ip link set mybridge up <=== mybridgeをupする
$ sudo ip link
117: veth0@if118: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master mybridge state UP mode DEFAULT group default qlen 1000
    link/ether 36:07:ad:3a:ea:0e brd ff:ff:ff:ff:ff:ff link-netnsid 2
119: mybridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000 <=== mybridgeがupした
    link/ether 36:07:ad:3a:ea:0e brd ff:ff:ff:ff:ff:ff

$ sudo ip addr del 172.19.0.1/24 dev veth0 <=== veth0に割り当てているルートアドレスを消す
$ sudo ip addr add 172.19.0.1/24 dev mybridge <=== mybridgeにルートアドレスを設定する

# ping 172.17.0.1
PING 172.17.0.1 (172.17.0.1) 56(84) bytes of data.
64 bytes from 172.17.0.1: icmp_seq=1 ttl=64 time=0.061 ms <=== mynamespaceから別のホストIDにアクセスできた
64 bytes from 172.17.0.1: icmp_seq=2 ttl=64 time=0.079 ms
64 bytes from 172.17.0.1: icmp_seq=3 ttl=64 time=0.092 ms
^C
--- 172.17.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2044ms
rtt min/avg/max/mdev = 0.061/0.077/0.092/0.014 ms

ここまでで,eth0(ホスト)-bridge-veth(ホスト側)-veth(プロセス側)という構成を実現できた.

コンテナがデフォルトで構成するネットワーク

ここまでやってきたnetnsによるL2,L3の仮想化作業をDockerは自動で行っている.Dockerサービスはdocker0というDockerブリッジをデフォルトで用意する.

$ docker network ls | grep bridge
dcec008f4804        bridge              bridge              local

$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "dcec008f4804...
        "Created": "2020-07-04T02:51:36.795932003+09:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "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": {}
    }
]

$ ifconfig docker0
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 ...  prefixlen 64  scopeid 0x20<link>
        ether ...  txqueuelen 0  (Ethernet)
        RX packets 33329  bytes 4334088 (4.3 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 155842  bytes 269698810 (269.6 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

ここで,docker exec -it <container> bashなどでコンテナを立ち上げると,docker0ブリッジにvethがつくられ,ホストからvethの片割れを確認することができる.

$ bridge link show
116: veth7cd7c37 state UP @if115: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master docker0 state forwarding priority 32 cost 2

$ ifconfig veth7cd7c37
veth7cd7c37: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet6 ...  prefixlen 64  scopeid 0x20<link>
        ether ...  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 12  bytes 976 (976.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

更に,立ち上げたコンテナプロセスのnamespaceに潜り込むと,コンテナ内のvethを確認することができる.

$ docker run -it --name hoge -p 8080:80 nginx bash

$ docker inspect hoge
...
            "Pid": 29942, <=== コンテナのPID
...

$ sudo ln -fs /proc/29942/ns/net /var/run/netns/container <=== コンテナ側のnetnsをホストで見れるようにsymbolic linkを張る

$ sudo ip netns exec container bash

# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.2  netmask 255.255.0.0  broadcast 172.17.255.255 <=== コンテナ内のvethが見れた.eth0にリネームされている.
        ether 02:42:ac:11:00:02  txqueuelen 0  (Ethernet)
        RX packets 15  bytes 1186 (1.1 KB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        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 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

コンテナへのパケットがiptablesによってどのようにフィルタされ,どのように外に出ていくかを知る

  • コンテナでない,ホスト上のプロセスがパケットを扱う場合は,基本的に,PREROUTING > INPUT > プロセス > OUTPUT > POSTROUTINGを通る.一方,コンテナはDockerブリッジを介してホストとパケットをやり取りするため,PREROUTING > FORWARD > POSTROUTINGを通る.

  • まず,PREROUTINGにてWANからきたパケットをコンテナに転送する

    • sudo iptables -vnL -t nat

      Chain PREROUTING (policy ACCEPT 3424 packets, 328K bytes)
      pkts bytes target     prot opt in     out     source               destination
      155K 8069K DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL
      
      Chain DOCKER (2 references)
       pkts bytes target     prot opt in     out     source               destination
          0     0 RETURN     all  --  <Dockerブリッ> *       0.0.0.0/0            0.0.0.0/0
          0     0 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0
          7   368 DNAT       tcp  --  !<Dockerブリッ> *       0.0.0.0/0            0.0.0.0/0            tcp dpt:443 to:172.18.0.2:443
          6   320 DNAT       tcp  --  !<Dockerブリッ> *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80 to:172.18.0.2:80
    • ローカルをウロウロしているパケット (ADDRTYPE match dst-type LOCAL) なら,DOCKERルールを適用する

      • DOCKERチェインでは,
        • Dockerブリッジまたはdocker0ブリッジから来たパケットに関しては何もしない(RETURN)
        • DNATにより,Dockerブリッジ以外(eth0つまりWANなど)から来た443/80ポートへのTCPパケットをすべて172.18.0.2:443/80に転送する
  • 次に,FORWARDチェインにてコンテナへのパケットをフィルタする

    • sudo iptables -vnL
    Chain FORWARD (policy DROP 0 packets, 0 bytes)
     pkts bytes target     prot opt in     out     source               destination
     110K  179M DOCKER-USER  all  --  *      *       0.0.0.0/0            0.0.0.0/0
     110K  179M DOCKER-ISOLATION-STAGE-1  all  --  *      *       0.0.0.0/0            0.0.0.0/0
     4097  459K ACCEPT     all  --  *      <Dockerブリッ>  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
      821 43588 DOCKER     all  --  *      <Dockerブリッ>  0.0.0.0/0            0.0.0.0/0
     3917 1085K ACCEPT     all  --  <Dockerブリッ> !<Dockerブリッ>  0.0.0.0/0            0.0.0.0/0
        0     0 ACCEPT     all  --  <Dockerブリッ> <Dockerブリッ>  0.0.0.0/0            0.0.0.0/0
     139K  245M ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
     3086  157K DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
    28904 4148K ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
        0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0
    
    Chain DOCKER (2 references)
     pkts bytes target     prot opt in     out     source               destination
      134  6964 ACCEPT     tcp  --  !<Dockerブリッ> <Dockerブリッ>  0.0.0.0/0            172.18.0.2           tcp dpt:443
      338 16904 ACCEPT     tcp  --  !<Dockerブリッ> <Dockerブリッ>  0.0.0.0/0            172.18.0.2           tcp dpt:80
    
    Chain DOCKER-ISOLATION-STAGE-1 (1 references)
     pkts bytes target     prot opt in     out     source               destination
     3917 1085K DOCKER-ISOLATION-STAGE-2  all  --  <Dockerブリッ> !<Dockerブリッ>  0.0.0.0/0            0.0.0.0/0
    28904 4148K DOCKER-ISOLATION-STAGE-2  all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
     180K  251M RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0
    
    Chain DOCKER-ISOLATION-STAGE-2 (2 references)
     pkts bytes target     prot opt in     out     source               destination
        0     0 DROP       all  --  *      <Dockerブリッ>  0.0.0.0/0            0.0.0.0/0
        0     0 DROP       all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
    32821 5234K RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0
    
    Chain DOCKER-USER (1 references)
     pkts bytes target     prot opt in     out     source               destination
     180K  251M RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0
    • DOCKER-USERチェインは,何よりも先に適用されるルールなので,最も優先したいルールをユーザが自由に定義することができる.
    • DOCKER-ISOLATION-STAGE-1チェインでは,コンテナ間ネットワークに関するルールが定義される,
    • <Dockerブリッジ>に行く確立したパケット(ctstate RELATED,ESTABLISHED)はACCEPTする
    • <Dockerブリッジ>に行く未確立のパケットにはDOCKERルールを適用する
      • <Dockerブリッジ>以外から来て<Dockerブリッジ>へ行く,送信先172.18.0.2:443/80なパケットをACCEPT
    • <Dockerブリッジ>からきて<Dockerブリッジ>以外へ行くパケットをACCEPT
    • <Dockerブリッジ>からきて<Dockerブリッジ>へ行くパケットをACCEPT
    • docker0についても同様.
  • 最後に,POSTROUTINGチェインを経てWANに出ていく

    • sudo iptables -vnL -t nat

      Chain POSTROUTING (policy ACCEPT 23 packets, 1364 bytes)
       pkts bytes target     prot opt in     out     source               destination
          6   418 MASQUERADE  all  --  *      !<Dockerブリッ>  172.18.0.0/16        0.0.0.0/0
         68  4149 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0
          0     0 MASQUERADE  tcp  --  *      *       172.18.0.2           172.18.0.2           tcp dpt:443
          0     0 MASQUERADE  tcp  --  *      *       172.18.0.2           172.18.0.2           tcp dpt:80
    • MASQUERADEで,Dockerブリッジ以外(eth0つまりWANなど)に行く172.18.0.0/16なパケットは,そのインターフェース(eth0など)のIPに変換する.

    • MASQUERADEで,docker0以外(eth0つまりWANなど)に行く172.17.0.0/16なパケットは,そのインターフェース(eth0など)のIPに変換する.

    • Dockerブリッジまたはdocker0に行く172.18.0.2なパケットは,送信先も172.18.0.2のまま.

コンテナ用のネットワークをつくる

  • docker run--netオプションを指定しない場合,または,docker-compose.ymlを利用しない場合はデフォルトのブリッジとしてdocker0が使われる.
    • --net=hostとすると,docker0やDockerブリッジを使わずにホストのネットワークを直接使うようになる.
    • docker-compose.ymlを利用した場合は,ユニークなbridgeが生成される.docker network lsで確認できる.
  • コンテナを立ち上げると自動でIPアドレスが振られるが,静的なIPアドレスを設定したい場合は,オリジナルのネットワークを定義することができる.
    • docker network create --driver=<driver> --subnet=<subnet> --gateway=<gateway> <network name>
      • ネットワークが作成されると,br-<network id先頭12桁>というブリッジ名がつけられる.
    • docker run --net=<network name>によって,コンテナが使うネットワークを指定できる.
    • docker network rm <network name>によって,ネットワークを削除できる.

publishとexpose

docker run-p(publish)オプションを指定したり,docker-compose.ymlでportsを指定すると,NATテーブルが自動で書き換えられる.これにより,WANとコンテナ間のアクセスが実現する.一方,セキュリティの観点から,80/443番ポートはpublishしたいが,DBコンテナとサーバーコンテナ間のポートはpublishしたくないなどと言った場合は,それぞれのコンテナを立ち上げるときに--expose <port>を指定することで,exposeなportはホスト内のみで有効となる.docker-compose.ymlならEXPOSEがそれに該当する.

docker-composeで作成されるネットワーク

  • プロジェクト名: docker-compose.ymlのカレントディレクトリ.docker-compose -p <プロジェクト名>または環境変数COMPOSE_PROJECT_NAMEで指定できる.
  • コンテナ名: <プロジェクト名><サービス名><通し番号>
  • ホスト名: <CONTAINER ID>
  • ネットワーク名: <プロジェクト名>_default

Kubernetesのネットワーク構成

あとで書く

関連記事 Related Posts

B!