五张图带你搞懂容器网络的事变道理

发布日期:2022-08-07 04:34    点击次数:128

 

运用容器总是感到像运用魔法同样。关于那些理解底层道理的人来说容器很好用,然则关于不睬解的人来说就是个噩梦。很幸运的是,我们已经研究容器技能很久了,以至告成揭秘容器只是断绝并受限的 Linux 过程,运行容器着实不需求镜像,以及另外一个方面,构建镜像需求运行一些容器。

今朝是时光经管容器网络成就了。或许更正确地说,单主机容器网络成就。本文会回覆这些成就:

 怎么虚拟化网络资源,让容器觉得本身拥有独占网络?  怎么让容器们战役共处,之间不会互相纷扰扰攘侵略,并且兴许彼此通信?  沉着器外部怎么拜访外部世界(比喻,互联网)?  从外部世界怎么拜访某台古板上的容器呢(比喻,端口宣布)?

终究终局很分明,单主机容器网络是已知的 Linux 功用的俭朴组合:

 网络命名空间(namespace)  虚拟 Ethernet动作举措(veth)  虚拟网络交换机(网桥)  IP路由和网络地点翻译(NAT)

并且不需求任何代码就能让这样的网络魔法发生……

条件条件

肆意 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)。我们会重点介绍根抵见解,并运用最俭朴的器材脱离达深造目标。

network 命名空间隔离容器

Linux 网络栈蕴含哪些部份?显明,是一系列网络动作举措。另有其它吗?兴许还蕴含一系列的路由划定端方。并且不要遗记,netfilter hook,蕴含由iptables划定端方定义的。

我们可以或许倏地创立一个着实不宏壮的脚本 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 rule:

$ 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 命名空间是网络命名空间(network namespace)。从 man ip-netns 可以或许看到,“网络命名空间是网络栈逻辑上的另外一个本来,它有本身的路由,防火墙划定端方和网络动作举措。”为了简化起见,这是本文运用的仅有的命名空间。我们并无创立齐全断绝的容器,而是将领域限定在网络栈上。

创立网络命名空间的一种编制是 ip 器材,它是 iproute2 的一部份: 

$ 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 chain,只要一个 loopback 的网络动作举措。

运用虚拟的 Ethernet 动作举措(veth)将容器跟尾到主机上

假设我们没法和某个专有的网络栈通信,那末它看下来就没什么用。幸运的是,Linux 供应了好用的器材——虚拟 Ethernet动作举措。从 man veth 可以或许看到,“veth 动作举措是虚拟 Ethernet 动作举措。他们可以或许作为网络命名空间之间的通道(tunnel),从而创立跟尾到另外一个命名空间里的物理网络动作举措的桥梁,然则也可以作为独立的网络动作举措运用。”

虚拟 Ethernet 动作举措平日都成对出现。不消耽心,先看一下创立的脚本: 

$ sudo ip link add veth0 type veth peer name ceth0 

用这条俭朴的敕令,我们就能创立一对互联的虚拟 Ethernet 动作举措。默认抉择了 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 都在主机的网络栈(也称为 root 网络命名空间)上。将 netns0 命名空间跟尾到 root 命名空间,需求将一个动作举措留在 root 命名空间,另外一个挪到 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 地点,个中一个动作举措上孕育发生的包会连忙出当初其配对动作举措里,从而跟尾起两个命名空间。从 root 命名空间起头: 

$ 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 

查抄连通性: 

# 在 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 路由表里没有这类包的路由。仅有的 entry 是怎么抵达 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 有好几种编制直立路由表。个中一种是间接从网络接口上提取路由。记着,命名空间创立后, netns0 里的路由表是空的。然则随后我们增加了 ceth0 动作举措并且分派了IP地点 172.18.0.0/16。因为我们运用的不是俭朴的 IP 地点,而是地点和子网掩码的组合,网络栈可以或许从个中提取出路由信息。目标地是 172.18.0.0/16 的每个网络包都市经由过程 ceth0 动作举措。然则别的包会被扬弃。近似的,root 命名空间也有了个新的路由: 

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

这里,就能回覆第一个成就了。我们相识了怎么断绝,虚拟化并且跟尾Linux网络栈。

运用虚拟网络 switch(网桥)跟尾容器

容器化思想的驱动力是高效的资源同享。所以,一台古板上只运行一个容器着实不罕见。相反,终究目标是尽管即便地在同享的情形上运行更多的断绝过程。因而,假设根据上述 veth 规划,在同一台主机上搁置多个容器的话会发生什么呢?让我们查验测验增加第二个容器。 

# 从 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 没法连通 root 命名空间!       $ ping -c 2 172.18.0.21       PING 172.18.0.21 (172.18.0.21) 56(84) bytes of data.       From 172.18.0.20 icmp_seq=1 Destination Host Unreachable       From 172.18.0.20 icmp_seq=2 Destination Host Unreachable       --- 172.18.0.21 ping statistics ---       2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 55ms pipe 2       # 然则路由是存在的!       $ ip route       172.18.0.0/16 dev ceth1 proto kernel scope link src 172.18.0.20       # 脱离 netns1      $ exit        # 从 root 命名空间没法连通 netns1       $ ping -c 2 172.18.0.20       PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data.       From 172.18.0.11 icmp_seq=1 Destination Host Unreachable       From 172.18.0.11 icmp_seq=2 Destination Host Unreachable   --- 172.18.0.20 ping statistics ---   2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 23ms pipe 2       # 从netns0可以或许连通 veth1       $ sudo nsenter --net=/var/run/netns/netns0       $ ping -c 2 172.18.0.21       PING 172.18.0.21 (172.18.0.21) 56(84) bytes of data.       64 bytes from 172.18.0.21: icmp_seq=1 ttl=64 time=0.037 ms       64 bytes from 172.18.0.21: icmp_seq=2 ttl=64 time=0.046 ms       --- 172.18.0.21 ping statistics ---       2 packets transmitted, 2 received, 0% packet loss, time 33ms       rtt min/avg/max/mdev = 0.037/0.041/0.046/0.007 ms       # 然则仍然没法连通 netns1       $ ping -c 2 172.18.0.20       PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data.     From 172.18.0.10 icmp_seq=1 Destination Host Unreachable       From 172.18.0.10 icmp_seq=2 Destination Host Unreachable       --- 172.18.0.20 ping statistics ---       2 packets transmitted, 0 received, +2 errors,公司新闻 100% packet loss, time 63ms pipe 2 

晕!有地方犯错了……netns1 有成就。它没法跟尾到 root,并且从 root 命名空间里也没法拜访到它。然则,因为两个容器都在沟通的 IP 网段 172.18.0.0/16 里,从 netns0 容器可以或许拜访到主机的 veth1。

这里花了些时光来找到启事,不过很分明遇到的是路由成就。先查一下 root 命名空间的路由表: 

$ 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 

在增加了第二个 veth 对当前,root 的网络栈晓得了新路由 172.18.0.0/16 dev veth1 proto kernel scope link src 172.18.0.21,然则从前已经存在该网络的路由了。当第二个容器查验测验 ping veth1 时,选中的是第一个路由划定端方,这导致网络没法连通。假设我们删除第一个路由 sudo ip route delete 172.18.0.0/16 dev veth0 proto kernel scope link src 172.18.0.11,尔后从头查抄连通性,该当就没有成就了。netns1 可以或许连通,然则 netns0 就不行了。

假设我们为 netns1 抉择别的的网段,该当就均可以或许连通。然则,多个容器在同一个 IP 网段上该当是公正的运用处景。因而,我们需求调整 veth 规划。

别忘了另有 Linux 网桥——另外一种虚拟化网络技能!Linux 网桥浸染近似于网络 switch。它会在跟尾到其上的接口间转发网络包。并且因为它是 switch,它是在 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 

查抄容器间的连通性: 

$ 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 端点分派两个 IP 地点。然则因为它们都跟尾在沟通的 Ethernet上(记着,它们跟尾到虚拟 switch上),之间在 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 路由和地点假装(masquerading))

容器间可以或许通信。然则它们能和主机,比喻root命名空间,通信吗? 

$ 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  

root 命名空间不克不迭和容器通信: 

# 首先运用 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 

要直立 root 和容器命名空间的连通性,我们需求给网桥网络接口分派 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`也做上述设置 

这个篡改根抵上把主机变成为了路由,并且网桥接口变成为了容器间的默认网关。

很好,我们将容器跟尾到 root 命名空间上。今朝,延续查验测验将它们跟尾到外部世界。Linux 上默认 disable 了网络包转发(比喻,路由功用)。我们需求先启用这个功用: 

# 在 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 模块让我们只需求一条敕令就能实现这通通: 

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

敕令极度俭朴。在 nat 表里增加了一条 POSTROUTING chain 的新路由,会替代假装全体源于 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 ACCEPT 

Docker 默认限定全体流量,随后仅仅为已知的门路启用路由。

以下是在 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命名空间和全体容器接口之间有链接,固然可以或许跟尾告成): 

# 从 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。又是i ptables来辅助! 

# 外部流量        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 网络驱动

我们可以或许怎么运用这些知识呢?比喻,可以或许试着理解 Docke r网络情势[1]。

从 --network host 情势起头。试着相比一下敕令 ip link 和 sudo docker run -it --rm --network host alpine ip link 的输出。它们险些同样!在 host 情势下,Docker 俭朴地没有运用网络命名空间隔离,容器就在 root 网络命名空间里事变,并且和主机同享网络栈。

下一个情势是--network none。sudo docker run -it --rm --network host alpine ip link 的输出只要一个  loopback 网络接口。这和从前创立的网络命名空间,没有增加 veth 动作举措前很类似。

最后是 --network bridge(默认)情势。这正是我们前文查验测验创立的情势。巨匠可以或许试试ip 和iptables敕令,划分从主机和容器的角度窥察一下网络栈。

rootless 容器和网络

Podman 容器打点器的一个很好的特点是关注于 rootless 容器。然则,你兴许留心到,本文运用了良多 sudo 敕令。分化,没有 root 权限没法设置网络。Podman 在 root 网络上的规划[2] 和Docker极度类似。然则在 rootless 容器上,Podman 运用了  slirp4netns[3] 名目:

从 Linux 3.8 起头,非特权用户可以或许创立 user_namespaces(7) 的同时创立 network_namespaces(7)。然则,非特权网络命名空间着实不是颇有效,因为在主机和网络命名空间之间创立 veth(4) 仍然需求root权限

slirp4netns 可以或许用齐全非特权的编制将网络命名空间跟尾到 Internet 上,经由过程网络命名空间里的一个TAP动作举措跟尾到用户态的TCP/IP栈(slirp)。

rootless 网络是颇无限的:“从技能上说,容器本身没有 IP 地点,因为没有 root 权限,没法实现网络动作举措的联络纠葛。此外,从 rootless 容器 ping 是不会事变的,因为它缺乏 CAP_NET_RAW 安好才能,而这是 ping 敕令必需的。”然则它仍然比齐全没有跟尾要好。

结论

本文介绍的构造容器网络的规划仅仅是兴许规划的一种(兴许是最为普及运用的一种)。另有良多其它编制,由平易近间或许第三方插件实现,然则全体这些规划都重大寄托于 Linux 网络虚拟化技能[4]。因而,容器化可以或许觉得是一种虚拟化技能。 

 



 




Powered by 欧冠赛程网址推荐 @2013-2022 RSS地图 HTML地图