Table of Contents
モチベーション
- 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ヘッダをつける/とる.
ping
やtraceroute
は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なサーバーへは,ゲートウェイを通って問い合わせる.
- ネットワークIDが異なる通信はデフォルトゲートウェイに解決してもらう
- 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で大体済む.
CRUD | HTTPメソッド |
---|---|
Create | POST/PUT |
Read | GET |
Update | PUT |
Delete | DELETE |
つまり,サーバー/クライアントアプリケーションは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に転送する
- DOCKERチェインでは,
次に,
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
チェインでは,コンテナ間ネットワークに関するルールが定義される,に行く確立したパケット(ctstate RELATED,ESTABLISHED)はACCEPTする に行く未確立のパケットには DOCKER
ルールを適用する以外から来て へ行く,送信先172.18.0.2:443/80なパケットをACCEPT
からきて 以外へ行くパケットをACCEPT からきて へ行くパケットを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のネットワーク構成
あとで書く