見逃せません!コンテナ ネットワークの原理を理解するのに役立つ 5 つの図

コンテナの操作は常に魔法のように感じられます。コンテナーは、基礎となる原理を理解している人にとっては素晴らしいものですが、理解していない人にとっては悪夢です。幸いなことに、私たちはコンテナ テクノロジを長い間研究しており、コンテナは単に分離され制限された Linux プロセスであることを明らかにすることにも成功しました。コンテナの実行にはイメージは必要ありませんが、一方で、イメージの構築にはいくつかのコンテナの実行が必要です。

今こそコンテナ ネットワークに取り組む時期です。より正確には、単一ホストのコンテナー ネットワークの問題です。この記事では、次のような質問に答えます。

  • コンテナが排他的なネットワークを持っていると認識できるようにネットワーク リソースを仮想化するにはどうすればよいでしょうか?

  • コンテナを互いに干渉せずに平和的に共存させ、相互に通信させるにはどうすればよいでしょうか?

  • コンテナ内から外部の世界 (インターネットなど) にアクセスするにはどうすればよいですか?

  • マシン上のコンテナに外部からアクセスするにはどうすればよいでしょうか (ポート公開など)?

最終結果は明らかで、単一ホストのコンテナ ネットワーキングは、既知の Linux 機能を単純に組み合わせたものになります。

  • ネットワーク名前空間 (名前空間)

  • 仮想イーサネットデバイス (veth)

  • 仮想ネットワークスイッチ(ブリッジ)

  • IP ルーティングとネットワーク アドレス変換 (NAT)

  • そして、このような Web の魔法を実現するのにコードは必要ありません...

前提条件

どの Linux ディストリビューションでも使用できます。この記事の例はすべて、vagrant CentOS 8 の仮想マシン上で実行されます。

$ vagrant init centos/8 $ vagrant up $ vagrant ssh 
[vagrant@localhost ~]$ uname -a Linux localhost.localdomain 4.18.0-147.3.1.el8_1.x86_64

わかりやすくするために、この記事ではコンテナ化ソリューション (Docker や Podman など) を使用します。私たちは基本的な概念に焦点を当て、学習目標を達成するために最もシンプルなツールを使用します。

ネットワーク名前空間はコンテナを分離します

Linux ネットワーク スタックの構成要素は何ですか? 明らかに、一連のネットワーク デバイスです。他に何か?一連のルーティング ルールが含まれる場合もあります。そして、iptables ルールで定義されたものを含む netfilter フックも忘れないでください。

単純なスクリプトをすぐに作成できますinspect-net-stack.sh

#!/usr/bin/env bash echo  "> Network devices" ip link 
echo -e "\n> Route table" ip route 
echo -e "\n> Iptables rules" iptables --list-rules

スクリプトを実行する前に、iptable ルールを変更しましょう。

$ sudo iptables -N ROOT_NS

その後、マシン上で上記のスクリプトを実行すると、出力は次のようになります。

$ sudo ./inspect-net-stack.sh     > Network devices     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     2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 52:54:00:e3:27:77 brd ff:ff:ff:ff:ff:ff     > Route table     default via 10.0.2.2 dev eth0 proto dhcp metric 100     10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100     > Iptables rules     -P INPUT ACCEPT     -P FORWARD ACCEPT     -P OUTPUT ACCEPT     -N ROOT_NS

私たちがこれらの出力に興味があるのは、これから作成しようとしている各コンテナーに独自の個別のネットワーク スタックがあることを確認したいからです。

おそらくすでにご存知かと思いますが、コンテナーの分離に使用される Linux 名前空間の 1 つはネットワーク名前空間です。man ip-netns より、「ネットワーク名前空間は、独自のルート、ファイアウォール ルール、およびネットワーク デバイスを備えた、ネットワーク スタックの論理的な 2 番目のコピーです。」 わかりやすくするために、これがこの記事で使用される唯一の名前空間です。完全に分離されたコンテナーを作成するのではなく、範囲をネットワーク スタックに制限します。

ネットワーク名前空間を作成する 1 つの方法は、iproute2 の一部である ip ツールです。

$ sudo ip netns add netns0 $ ip netns netns0如何使用刚才创建的命名空间呢?一个很好用的命令 nsenter。进入一个或多个特定的命名空间,然后执行指定的脚本:
$ sudo nsenter --net=/var/run/netns/netns0 bash     # 新建的 bash 进程在 netns0 里 $ sudo ./inspect-net-stack.sh     > Network devices 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00     > Route table     > Iptables rules     -P INPUT ACCEPT     -P FORWARD ACCEPT     -P OUTPUT ACCEPT

上記の出力から、bash プロセスが完全に異なるネットワーク スタックである netns0 名前空間で実行されていることは明らかです。ルーティング ルールやカスタム iptables チェーンはなく、ループバック ネットワーク デバイスのみが存在します。

 

60af30c6a36a22bcd832f19dea8243b6.png

仮想イーサネット デバイス (veth) を使用してコンテナをホストに接続します

独自のネットワークスタックと通信できない場合、それは役に立たないと思われます。幸いなことに、Linux には仮想イーサネット デバイスという便利なツールが用意されています。man veth からわかるように、「veth デバイスは仮想イーサネット デバイスです。これらはネットワーク名前空間間のトンネルとして機能し、別の名前空間内の物理ネットワーク デバイスへのブリッジを作成できますが、ネットワーク デバイスによって使用されるスタンドアロンとしても機能します。」

仮想イーサネット デバイスは通常、ペアで提供されます。心配しないで、まず作成されたスクリプトを見てください。

$ sudo ip link add veth0 type veth peer name ceth0

この簡単なコマンドを使用して、相互接続された仮想イーサネット デバイスのペアを作成できます。デフォルトでは、veth0 および ceth0 という名前が選択されます。​​​​​​​​

$ 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 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000  link/ether 52:54:00:e3:27:77 brd ff:ff:ff:ff:ff:ff 5: ceth0@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000   link/ether 66:2d:24:e3:49:3f brd ff:ff:ff:ff:ff:ff 6: veth0@ceth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000  link/ether 96:e8:de:1d:22:e0 brd ff:ff:ff:ff:ff:ff

veth0 と ceth0 は両方とも、ホストのネットワーク スタック (ルート ネットワーク名前空間とも呼ばれます) 上に作成されます。netns0 名前空間をルート名前空間に接続するには、1 つのデバイスをルート名前空間に残し、もう 1 つを netns0: に移動する必要があります。

$ sudo ip link set ceth0 netns netns0     # 列出所有设备,可以看到 ceth0 已经从 root 栈里消失了    $ 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    2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000    link/ether 52:54:00:e3:27:77 brd ff:ff:ff:ff:ff:ff    6: veth0@if5: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000     link/ether 96:e8:de:1d:22:e0 brd ff:ff:ff:ff:ff:ff link-netns netns0

デバイスが有効になり、適切な IP アドレスが割り当てられると、1 つのデバイスで生成されたパケットはすぐにそのパートナーに表示され、2 つの名前空間の橋渡しになります。ルート名前空間から開始します:

$ sudo ip link set veth0 up $ sudo ip addr add 172.18.0.11/16 dev veth0

次に netns0:

$ sudo nsenter --net=/var/run/netns/netns0 $ ip link set lo up $ ip link set ceth0 up $ ip addr add 172.18.0.10/16 dev ceth0 $ 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 5: ceth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000  link/ether 66:2d:24:e3:49:3f brd ff:ff:ff:ff:ff:ff link-netnsid 0

 

b2fec0a16e5717b83cade5cff308dbfa.png

接続を確認します:

# 在 netns0 里 ping root 的 veth0  $ ping -c 2 172.18.0.11  PING 172.18.0.11 (172.18.0.11) 56(84) bytes of data.  64 bytes from 172.18.0.11: icmp_seq=1 ttl=64 time=0.038 ms  64 bytes from 172.18.0.11: icmp_seq=2 ttl=64 time=0.040 ms  --- 172.18.0.11 ping statistics ---  2 packets transmitted, 2 received, 0% packet loss, time 58ms  rtt min/avg/max/mdev = 0.038/0.039/0.040/0.001 ms  # 离开 netns0 $ exit   # 在root命名空间里ping ceth0  $ ping -c 2 172.18.0.10  PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data.  64 bytes from 172.18.0.10: icmp_seq=1 ttl=64 time=0.073 ms  64 bytes from 172.18.0.10: icmp_seq=2 ttl=64 time=0.046 ms  --- 172.18.0.10 ping statistics ---  2 packets transmitted, 2 received, 0% packet loss, time 3ms  rtt min/avg/max/mdev = 0.046/0.059/0.073/0.015 ms

同時に、netns0 名前空間から他のアドレスにアクセスしようとすると、成功しません。

# 在 root 命名空间    $ ip addr show dev eth0    2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000     link/ether 52:54:00:e3:27:77 brd ff:ff:ff:ff:ff:ff     inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute eth0   valid_lft 84057sec preferred_lft 84057sec    inet6 fe80::5054:ff:fee3:2777/64 scope link      valid_lft forever preferred_lft forever     # 记住这里 IP 是 10.0.2.15    $ sudo nsenter --net=/var/run/netns/netns0    # 尝试ping主机的eth0    $ ping 10.0.2.15    connect: Network is unreachable    # 尝试连接外网   $ ping 8.8.8.8    connect: Network is unreachable

これもわかりやすいですね。netns0 ルーティング テーブルには、そのようなパケットのルートがありません。唯一のエントリは、172.18.0.0/16 ネットワークにアクセスする方法です。

# 在netns0命名空间:     $ ip route     172.18.0.0/16 dev ceth0 proto kernel scope link src 172.18.0.10

Linux にはルーティング テーブルを構築する方法がいくつかあります。その 1 つは、ネットワーク インターフェイスからルートを直接抽出することです。名前空間の作成後、netns0 のルーティング テーブルは空であることに注意してください。ただし、ceth0 デバイスを追加し、IP アドレス 172.18.0.0/16 を割り当てました。これは、単純な IP アドレスを使用しているのではなく、ネットワーク スタックがルーティング情報を抽出できるアドレスとサブネット マスクの組み合わせを使用しているためです。172.18.0.0/16 宛てのすべてのネットワーク パケットは ceth0 デバイスを通過します。ただし、他のパケットはドロップされます。同様に、ルート名前空間にも新しいルートがあります。

# 在root命名空间:     $ ip route     # ... 忽略无关行 ...     172.18.0.0/16 dev veth0 proto kernel scope link src 172.18.0.11

ここで、最初の質問に答えることができます。Linux ネットワーク スタックを分離、仮想化、接続する方法について説明しました。

仮想ネットワーク スイッチ (ブリッジ) を使用してコンテナーを接続する

コンテナ化の考え方の原動力は、効率的なリソース共有です。したがって、1 台のマシン上で 1 つのコンテナーだけを実行することは一般的ではありません。代わりに、最終目標は、共有環境でできるだけ多くの分離されたプロセスを実行することです。では、上記の veth スキームに従って複数のコンテナーを同じホストに配置するとどうなるでしょうか? 2 つ目のコンテナを追加してみましょう。​​​​​​​​

# 从 root 命名空间     $ sudo ip netns add netns1     $ sudo ip link add veth1 type veth peer name ceth1     $ sudo ip link set ceth1 netns netns1     $ sudo ip link set veth1 up     $ sudo ip addr add 172.18.0.21/16 dev veth1     $ sudo nsenter --net=/var/run/netns/netns1     $ ip link set lo up     $ ip link set ceth1 up     $ ip addr add 172.18.0.20/16 dev ceth1

気絶!何かが間違っています... netns1 に問題があります。ルートに接続できず、ルート名前空間からアクセスすることもできません。ただし、両方のコンテナが同じ IP ネットワーク セグメント 172.18.0.0/16 内にあるため、ホストの veth1 には netns0 コンテナからアクセスできます。

原因を特定するのに時間がかかりましたが、明らかにルーティングの問題でした。まず、ルート名前空間のルーティング テーブルを確認します。

$ ip route     # ... 忽略无关行... #     172.18.0.0/16 dev veth0 proto kernel scope link src 172.18.0.11     172.18.0.0/16 dev veth1 proto kernel scope link src 172.18.0.21
2 番目の veth ペアを追加した後、root のネットワーク スタックは新しいルート 172.18.0.0/16 dev veth1 proto kernelscope link src 172.18.0.21 について認識しますが、そのネットワークには以前からルートが存在していました。2 番目のコンテナが veth1 に ping を試行すると、最初のルーティング ルールが選択され、ネットワークに到達できなくなります。最初のルート sudo ip Route delete 172.18.0.0/16 dev veth0 proto kernelscope link src 172.18.0.11 を削除して接続を再確認すると、問題はないはずです。netns1 は接続できますが、netns0 は接続できません。

5341a628f2d2e89213d2c78bbfcb79c7.png

netns1 に別のネットワーク セグメントを選択すると、接続できるはずです。ただし、同じ IP ネットワーク セグメント上の複数のコンテナは合理的な使用シナリオであるはずです。したがって、veth スキームを調整する必要があります。

そして、もう 1 つの仮想化ネットワーク テクノロジである Linux ブリッジについても忘れないでください。Linux ブリッジはネットワーク スイッチのように機能します。接続されているインターフェイス間でネットワーク パケットを転送します。また、スイッチであるため、これらの転送は L2 層で完了します。

このツールを試してみてください。ただし、以前の構成の一部は不要になったため、まず既存の設定をクリアする必要があります。ネットワーク名前空間を削除します:

$ sudo ip netns delete netns0 $ sudo ip netns delete netns1 $ sudo ip link delete veth0 $ sudo ip link delete ceth0 $ sudo ip link delete veth1 $ sudo ip link delete ceth1

両方のコンテナをすばやく再構築します。新しい veth0 および veth1 デバイスには IP アドレスを割り当てていないことに注意してください。

$ sudo ip netns add netns0 $ sudo ip link add veth0 type veth peer name ceth0 $ sudo ip link set veth0 up $ sudo ip link set ceth0 netns netns0 
$ sudo nsenter --net=/var/run/netns/netns0 $ ip link set lo up $ ip link set ceth0 up $ ip addr add 172.18.0.10/16 dev ceth0 $ exit 
$ sudo ip netns add netns1 $ sudo ip link add veth1 type veth peer name ceth1 $ sudo ip link set veth1 up $ sudo ip link set ceth1 netns netns1 
$ sudo nsenter --net=/var/run/netns/netns1 $ ip link set lo up $ ip link set ceth1 up $ ip addr add 172.18.0.20/16 dev ceth1 $ exit

ホスト上に新しいルートがないことを確認してください。

$ ip route default via 10.0.2.2 dev eth0 proto dhcp metric 100 10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100

最後にブリッジ インターフェースを作成します。

$ sudo ip link add br0 type bridge $ sudo ip link set br0 up

veth0 と veth1 をブリッジに接続します。

$ sudo ip link set veth0 master br0 $ sudo ip link set veth1 master br0

52673b27c5c386c49c56379137fdf204.png

コンテナ間の接続を確認します。

$ sudo nsenter --net=/var/run/netns/netns0 $ ping -c 2 172.18.0.20 PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data. 64 bytes from 172.18.0.20: icmp_seq=1 ttl=64 time=0.259 ms 64 bytes from 172.18.0.20: icmp_seq=2 ttl=64 time=0.051 ms --- 172.18.0.20 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 2ms rtt min/avg/max/mdev = 0.051/0.155/0.259/0.104 ms$ sudo nsenter --net=/var/run/netns/netns1 $ ping -c 2 172.18.0.10 PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data. 64 bytes from 172.18.0.10: icmp_seq=1 ttl=64 time=0.037 ms 64 bytes from 172.18.0.10: icmp_seq=2 ttl=64 time=0.089 ms --- 172.18.0.10 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 36ms rtt min/avg/max/mdev = 0.037/0.063/0.089/0.026 ms

とても良い!よく働く。この新しいスキームでは、veth0 と veth1 を構成する必要がまったくありません。ceth0 および ceth1 エンドポイントに割り当てる必要があるのは、2 つの IP アドレスだけです。ただし、これらはすべて同じイーサネットに接続されているため (仮想スイッチに接続されていることに注意してください)、L2 層で接続されます。

$ sudo nsenter --net=/var/run/netns/netns0 $ ip neigh 172.18.0.20 dev ceth0 lladdr 6e:9c:ae:02:60:de STALE $ exit 
$ sudo nsenter --net=/var/run/netns/netns1 $ ip neigh 172.18.0.10 dev ceth1 lladdr 66:f3:8c:75:09:29 STALE $ exit

素晴らしいです。コンテナを隣接コンテナに変える方法を学習しました。これにより、コンテナが相互に干渉せず、通信できるようになります。

外部への接続(IPルーティングとアドレスマスカレード(マスカレード))

コンテナは相互に通信できます。しかし、ルート名前空間などのホストと通信できるのでしょうか? ​​​​​​​​

$ sudo nsenter --net=/var/run/netns/netns0 $ ping 10.0.2.15 # eth0 address connect: Network is unreachable
ここで、netns0 にルートがないことは明らかです。
$ ip route 172.18.0.0/16 dev ceth0 proto kernel scope link src 172.18.0.10

ルート名前空間はコンテナと通信できません:

# 首先使用 exit 离开netns0: $ ping -c 2 172.18.0.10 PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data. From 213.51.1.123 icmp_seq=1 Destination Net Unreachable From 213.51.1.123 icmp_seq=2 Destination Net Unreachable --- 172.18.0.10 ping statistics --- 2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 3ms 
$ ping -c 2 172.18.0.20 PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data. From 213.51.1.123 icmp_seq=1 Destination Net Unreachable From 213.51.1.123 icmp_seq=2 Destination Net Unreachable --- 172.18.0.20 ping statistics --- 2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 3ms

ルートとコンテナー名前空間間の接続を確立するには、ブリッジ ネットワーク インターフェイスに IP アドレスを割り当てる必要があります。

$ sudo ip addr add 172.18.0.1/16 dev br0

IP アドレスがブリッジ ネットワーク インターフェイスに割り当てられると、ホストのルーティング テーブルに追加のルートが作成されます。

$ ip route # ...忽略无关行 ... 172.18.0.0/16 dev br0 proto kernel scope link src 172.18.0.1 
$ ping -c 2 172.18.0.10 PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data. 64 bytes from 172.18.0.10: icmp_seq=1 ttl=64 time=0.036 ms 64 bytes from 172.18.0.10: icmp_seq=2 ttl=64 time=0.049 ms 
--- 172.18.0.10 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 11ms rtt min/avg/max/mdev = 0.036/0.042/0.049/0.009 ms 
$ ping -c 2 172.18.0.20 PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data. 64 bytes from 172.18.0.20: icmp_seq=1 ttl=64 time=0.059 ms 64 bytes from 172.18.0.20: icmp_seq=2 ttl=64 time=0.056 ms 
--- 172.18.0.20 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 4ms rtt min/avg/max/mdev = 0.056/0.057/0.059/0.007 ms

コンテナはブリッジ インターフェイスに ping できる場合もありますが、それでもホストの eth0 に接続できません。デフォルト ルートをコンテナに追加する必要があります。

$ sudo nsenter --net=/var/run/netns/netns0 $ ip route add default via 172.18.0.1 $ ping -c 2 10.0.2.15 PING 10.0.2.15 (10.0.2.15) 56(84) bytes of data. 64 bytes from 10.0.2.15: icmp_seq=1 ttl=64 time=0.036 ms 64 bytes from 10.0.2.15: icmp_seq=2 ttl=64 time=0.053 ms --- 10.0.2.15 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 14ms rtt min/avg/max/mdev = 0.036/0.044/0.053/0.010 ms     # 为`netns1`也做上述配置

この変更により、基本的にホストがルーターに変わり、ブリッジ インターフェイスがコンテナ間のデフォルト ゲートウェイに変わります。

 

1364155375271b2608474e20266ded94.png

コンテナをルート名前空間にアタッチしました。さあ、外の世界とつながってみてください。Linux では、ネットワーク パケット転送 (ルーティング機能など) はデフォルトで無効になっています。まずこの機能を有効にする必要があります。

# 在 root 命名空间 sudo bash -c 'echo 1 > /proc/sys/net/ipv4/ip_forward'

接続を再度確認します。

$ sudo nsenter --net=/var/run/netns/netns0 $ ping 8.8.8.8 # hung住了...

まだ動かない。何が悪かったのか?コンテナが外部にパケットを送信できる場合、コンテナの IP アドレスはプライベートであり、その特定の IP のルーティング ルールを知っているのはローカル ネットワークだけであるため、ターゲット サーバーはパケットをコンテナに送り返すことはできません。そして、まったく同じプライベート IP アドレス 172.18.0.10 を共有するコンテナが多数あります。この問題の解決策は、ネットワーク アドレス変換 (NAT) と呼ばれます。コンテナによって送信されたパケットは、外部ネットワークに到達する前に、送信元 IP アドレスがホスト マシンの外部ネットワーク アドレスに置き換えられます。また、ホストは既存のマッピングを追跡し、パケットをコンテナに転送する前に、以前に置き換えられた IP アドレスを復元します。複雑に聞こえますが、良いニュースがあります。iptables モジュールを使用すると、これらすべてを 1 つのコマンドで行うことができます。

$ sudo iptables -t nat -A POSTROUTING -s 172.18.0.0/16 ! -o br0 -j MASQUERADE

コマンドは非常に簡単です。POSTROUTING チェーンの新しいルートが nat テーブルに追加され、172.18.0.0/16 ネットワークから発信されるがブリッジ インターフェイスを通過しないすべてのパケットのマスカレードが置き換えられます。

接続を確認します:

$ sudo nsenter --net=/var/run/netns/netns0 $ ping -c 2 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 64 bytes from 8.8.8.8: icmp_seq=1 ttl=61 time=43.2 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=61 time=36.8 ms --- 8.8.8.8 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 2ms rtt min/avg/max/mdev = 36.815/40.008/43.202/3.199 ms

ここで使用するデフォルト ポリシーはすべてのトラフィックを許可するものであることに注意してください。これは実際の環境では非常に危険です。ホストのデフォルトの iptables ポリシーは ACCEPT です。

sudo iptables -S -P INPUT ACCEPT -P FORWARD ACCEPT -P OUTPUT ACCEPTDocker 默认限制所有流量,随后仅仅为已知的路径启用路由。
如下是在 CentOS 8 机器上,单个容器暴露了端口 5005 时,由 Docker daemon 生成的规则:
$ sudo iptables -t filter --list-rules -P INPUT ACCEPT -P FORWARD DROP -P OUTPUT ACCEPT -N DOCKER -N DOCKER-ISOLATION-STAGE-1 -N DOCKER-ISOLATION-STAGE-2 -N DOCKER-USER -A FORWARD -j DOCKER-USER -A FORWARD -j DOCKER-ISOLATION-STAGE-1 -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT-A FORWARD -o docker0 -j DOCKER -A FORWARD -i docker0 ! -o docker0 -j ACCEPT -A FORWARD -i docker0 -o docker0 -j ACCEPT -A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 5000 -j ACCEPT -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2 -A DOCKER-ISOLATION-STAGE-1 -j RETURN -A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP -A DOCKER-ISOLATION-STAGE-2 -j RETURN -A DOCKER-USER -j RETURN 
$ sudo iptables -t nat --list-rules -P PREROUTING ACCEPT -P INPUT ACCEPT -P POSTROUTING ACCEPT -P OUTPUT ACCEPT -N DOCKER -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE -A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 5000 -j MASQUERADE-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER -A DOCKER -i docker0 -j RETURN -A DOCKER ! -i docker0 -p tcp -m tcp --dport 5005 -j DNAT --to-destination 172.17.0.2:5000 
$ sudo iptables -t mangle --list-rules -P PREROUTING ACCEPT -P INPUT ACCEPT -P FORWARD ACCEPT -P OUTPUT ACCEPT -P POSTROUTING ACCEPT 
$ sudo iptables -t raw --list-rules -P PREROUTING ACCEPT -P OUTPUT ACCEPT

コンテナを外部からアクセスできるようにする (ポート公開)

コンテナ ポートを一部 (またはすべて) のホストに公開できるインターフェイスは誰もが知っています。しかし、ポート公開とは正確には何を意味するのでしょうか?

サーバーがコンテナ内で実行されていると仮定します。

$ sudo nsenter --net=/var/run/netns/netns0 $ python3 -m http.server --bind 172.18.0.10 5000

ホストからこのサーバーに HTTP リクエストを送信しようとすると、すべてが正常に動作します (ルート名前空間とすべてのコンテナー インターフェイスの間にリンクがあり、もちろん接続は成功します)。

# 从 root 命名空间 $ curl 172.18.0.10:5000 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"  "http://www.w3.org/TR/html4/strict.dtd"> # ... 忽略无关行 ...

しかし、外部からこのサーバーにアクセスしたい場合、どのIPを使用すればよいでしょうか? 私たちが知っている唯一の IP は、ホストの外部インターフェイス アドレス eth0:

$ curl 10.0.2.15:5000 curl: (7) Failed to connect to 10.0.2.15 port 5000: Connection refused

したがって、ホストの eth0 ポート 5000 に到着するすべてのパケットを宛先 172.18.0.10:5000 に転送できる方法を見つける必要があります。また助けていただけると幸いです! ​​​​​​​​

# 外部流量      sudo iptables -t nat -A PREROUTING -d 10.0.2.15 -p tcp -m tcp --dport 5000 -j DNAT --to-destination 172.18.0.10:5000     # 本地流量 (因为它没有通过 PREROUTING chain)     sudo iptables -t nat -A OUTPUT -d 10.0.2.15 -p tcp -m tcp --dport 5000 -j DNAT --to-destination 172.18.0.10:5000

さらに、iptables はブリッジ ネットワーク上のトラフィックをインターセプトできる必要があります。

sudo modprobe br_netfilter

テスト:

curl 10.0.2.15:5000 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"  "http://www.w3.org/TR/html4/strict.dtd">      # ... 忽略无关行 ...

Docker ネットワークドライバーを理解する

この知識をどのように活用できるでしょうか? たとえば、Docker ネットワーク モードを理解してみてください [1]。

--network host パターンから 始めます。コマンドの出力 ip link と を比較してみてくださいsudo docker run -it --rm --network host alpine ip link 。ほぼ同じです!ホスト モードでは、Docker は単にネットワーク名前空間の分離を使用せず、コンテナーはルート ネットワーク名前空間で動作し、ネットワーク スタックをホストと共有します。

次のモードは --networknone です。sudo docker run -it --rm --network host alpine ip link の出力には、ループバック ネットワーク インターフェイスが 1 つだけあります。これは、veth デバイスを追加せずに、前に作成したネットワーク名前空間と非常によく似ています。

最後に、--network Bridge (デフォルト) モードがあります。これはまさに、以前に作成しようとしたパターンです。ip および iptables コマンドを試して、それぞれホストとコンテナの観点からネットワーク スタックを観察できます。

ルートレスコンテナとネットワーキング

Podman コンテナ マネージャーの優れた機能は、ルートレス コンテナに焦点を当てていることです。ただし、この記事では多くの sudo コマンドを使用していることにお気付きかもしれません。root 権限がないとネットワークを設定できないことに注意してください。ルート ネットワーク上の Podman のスキーム [2] は Docker に非常に似ています。ただし、ルートレスコンテナでは、Podman は slirp4netns[3] プロジェクトを使用します。

Linux 3.8 以降、特権のないユーザーは user_namespaces(7) と同時に network_namespaces(7) を作成できるようになりました。ただし、ホストとネットワーク名前空間の間に veth(4) を作成するには依然として root 権限が必要であるため、特権のないネットワーク名前空間はあまり役に立ちません。

slirp4netns は、ネットワーク名前空間内の TAP デバイスを介してユーザーモード TCP/IP スタック (slirp) に接続することにより、完全に特権のない方法でネットワーク名前空間をインターネットに接続できます。

ルートレス ネットワークは非常に制限されています。「技術的に言えば、コンテナ自体には IP アドレスがありません。ルート権限がなければネットワーク デバイスの関連付けを実現できないためです。また、ルートレス コンテナには CAP_NET_RAW セキュリティ機能がないため、ルートレス コンテナからの ping は機能しません。これは ping コマンドに必要です。」 しかし、まったく接続しないよりはまだマシです。

結論は

この記事で紹介するコンテナのネットワークを組織するスキームは、考えられるスキームの 1 つにすぎません (おそらく最も広く使用されている)。公式またはサードパーティのプラグインによって実装される方法は他にもたくさんありますが、これらのソリューションはすべて Linux ネットワーク仮想化テクノロジに大きく依存しています [4]。したがって、コンテナ化は仮想化テクノロジと考えることができます。

 

おすすめ

転載: blog.csdn.net/qq_27817851/article/details/128228124