容器虚拟化网络漫谈(上)

在这篇文章中,我们将会越过 Docker ,直接探索 Linux 为我们所提供的相关能力,以便在遇到虚拟化网络相关问题时,能更加心中有数,游刃有余。

容器虚拟化网络漫谈(上)

当我们安装好 Docker 后,它会为我们处理好所有与网络虚拟化有关的事,但在这背后,Docker 实际上是利用了多个 Linux 提供的特性来完成这一切,这也是当初 Docker 刚发布时被批评为「新瓶装旧酒」的原因,因为实际上 Docker 只是包装了操作系统的能力并提供了一套易用的 API,并没有发明什么新东西。

ps: 当然我并不同意这个观点 :)

这篇文章中会大量使用 ip 这个命令,ip 命令来自于 iproute2 包,一般系统会默认安装,如果没有的话,请自行安装,此外 ip 命令因为需要修改系统网络设置所以需要 root 权限,所以请不要在生产环境或者重要的系统中尝试,以防产生预期外的错误。

一、Linux Namespace

Namespace 是 Linux 提供的一种对于系统全局资源的隔离机制;从进程的视角来看,同一个namespace中的进程看到的是该namespace自己独立的一份全局资源,这些资源的变化只在本 namespace 中可见,对其他namespace没有影响。Docker 就是采用namespace 机制实现了对网络,进程空间等的隔离。不同的 container属于不同namespace,实现了container 之间的资源互相隔离,互不影响。

通过 namespace 可以隔离容器的进程 PID、文件系统挂载点、主机名等多种资源。不过我们今天只关注网络 namespace,简称 netns。它可以为不同的命名空间从逻辑上提供独立的网络协议栈,具体包括网络设备、路由表、arp表、iptables、以及套接字(socket)等。使得不同的网络空间就都好像运行在独立的网络中一样。

首先我们先尝试创建一个新的的 netns:

ip netns add netns1

接下来我们检查一下这个 netns 的路由表、Iptables及网络设备等信息:

[root@centos ~]$ ip netns exec netns1 route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface

[root@centos ~]$ ip netns exec netns0 iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

[root@centos ~]$ ip netns exec netns1 ip link list
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

可以看到,由于是新创建的 netns,以上所有信息都为空,只存在一个状态为 down 的 lo 设备

通过 netns 我们可以隔离不同容器之间的协议栈,避免相互影响和污染,接下我们会尝试如何在不同的 netns 相互通信。

二、Veth

Linux 提供了一种用软件来模拟硬件网卡的方法: Veth(Virtual Ethernet devices),Veth 是 Linux 中一种虚拟出来的网络设备,veth 总是成对出现,所以一般也叫 veth-pair,其作用非常简单:如果 v-a 和 v-b 是一对 veth 设备,v-a 发送的数据会由 v-b 收到。反之亦然,其实说白了,Veth就是一根“网线”,你从一头发数据,自然可以从另一头收到数据。

veth 的两头都直接连着网络协议栈,所以你创建一个veth对,主机上就会多两个网卡,实际上这种虚拟设备我们并不陌生,我们本机网络 IO 里的 lo 回环设备(127.0.0.1)也是这样一个虚拟设备。唯一的区别就是 veth 总是成对地出现。

在 Linux 下我们可以使用 ip 命令创建一对 veth,使用 ip link add 创建一对 veth0@veth1 的网卡, ip link 表示这是一个链路层的接口:

$ ip link add veth1 type veth peer name veth1-peer

使用 ip link show 来进行查看,此时可以看到,veth1@veth1-peer 相互连接:

[root@centos ~]$ ip link show
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 mq state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:09:10:27 brd ff:ff:ff:ff:ff:ff
5: veth1-peer@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 06:20:02:e7:bf:bd brd ff:ff:ff:ff:ff:ff
6: veth1@veth1-peer: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether aa:c4:57:ee:46:60 brd ff:ff:ff:ff:ff:ff

将 veth1 这头添加到我们刚才创建的 netns1 中:

$ ip link set veth1-peer netns netns1

此时再进行检查会发现 veth1 已经不见了,因为这个设备已经到了 netns0 中:

[root@centos ~]$ ip link show
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 mq state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:97:55:37 brd ff:ff:ff:ff:ff:ff
4: veth0@if3: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state LOWERLAYERDOWN mode DEFAULT group default qlen 1000
    link/ether de:2d:ab:06:fb:16 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    
[root@centos ~]$ ip netns exec netns1 ip link list
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
5: veth1-peer@if6: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 06:20:02:e7:bf:bd brd ff:ff:ff:ff:ff:ff link-netnsid 0

接下来为这对 veth 配置 IP 并启动设备

$ ip addr add 172.16.0.1/24 dev veth1
$ ip link set dev veth1 up

# 在不同的 netns 下需要使用 ip netns exec $name 到指定的 netns 下执行
$ ip netns exec netns1 ip addr add 172.16.0.2/24 dev veth1-peer
$ ip netns exec netns1 ip link set dev veth1-peer up

当设备启动后,我们就可以通过熟悉的 ifconfig 查看到它们了:

[root@centos ~]$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.0.1.48  netmask 255.255.255.0  broadcast 10.0.1.255
        inet6 fe80::5054:ff:fe09:1027  prefixlen 64  scopeid 0x20<link>
        ether 52:54:00:09:10:27  txqueuelen 1000  (Ethernet)
        RX packets 44996  bytes 61990718 (59.1 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 8462  bytes 684565 (668.5 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
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 2  bytes 256 (256.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2  bytes 256 (256.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.16.0.1  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::a8c4:57ff:feee:4660  prefixlen 64  scopeid 0x20<link>
        ether aa:c4:57:ee:46:60  txqueuelen 1000  (Ethernet)
        RX packets 8  bytes 656 (656.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 8  bytes 656 (656.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        
        
[root@centos ~]$ ip netns exec netns1 ifconfig
veth1-peer: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.16.0.2  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::420:2ff:fee7:bfbd  prefixlen 64  scopeid 0x20<link>
        ether 06:20:02:e7:bf:bd  txqueuelen 1000  (Ethernet)
        RX packets 8  bytes 656 (656.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 8  bytes 656 (656.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

此时的网络拓扑为:

+------------------+              +------------------+
|       host       |              |      netns1      |
|                  |  veth pair   |                  |
|                +-+              +-+                |
| 172.16.0.1/24 | +--------------+  | 172.16.0.2/24  |
|    (veth1)     +-+              +-+ (veth1-peer)   |
|                  |              |                  |
|                  |              |                  |
|                  |              |                  |
+------------------+              +------------------+

现在我们可以尝试这对 veth 是否可以相互通信:

[root@centos ~]$ ip netns exec netns1 ping 172.16.0.1 -I veth1-peer -c 1
PING 172.16.0.1 (172.16.0.1) from 172.16.0.2 veth1-peer: 56(84) bytes of data.
64 bytes from 172.16.0.1: icmp_seq=1 ttl=64 time=0.031 ms

--- 172.16.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.031/0.031/0.031/0.000 ms

[root@centos ~]$ ping 172.16.0.2 -I veth1 -c 2
PING 172.16.0.2 (172.16.0.2) from 172.16.0.1 veth1: 56(84) bytes of data.
64 bytes from 172.16.0.2: icmp_seq=1 ttl=64 time=0.015 ms

--- 172.16.0.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.015/0.015/0.015/0.000 ms

如上所见,无论是从 netns 中向宿主机发起通信,还是从宿主机向 netns 中的设备发起通信,都是可以行,到目前为止我们已经实现了点对点的通信。

三、Linux Bridge

由于 veth 总是成对出现,相互绑定,在真实世界中,同一台服务器上可能有几十上百个容器需要相互通信,如果我们为每个需要相互通信的容器都建立一对 veth,那恐怕不现实,就像真实世界中我们不会为集群中的每台服务器都相互用网线连接一样。

在物理世界中我们使用交换机解决这个问题的,而 Linux 提供了让我们使用软件模拟交换机能力的功能,即:Linux Bridge,引入了 Linux Bridge 后,我们不再需要点对点的连接, 而是将所有的 veth 的另外一头都连接到 Bridge 上,由 Bridge 负责在不同的“对”之间转发数据包。这样各个容器之间就可以互相通信了。

在之前的试验中,我们创建一个 netns0,然后创建了一对 veth,一头连在 netns0中,这是一个典型的点对点通信的,接下来我们尝试使用 Bridge 来跨越多个 netns 进行通信。

首先我们再创建两个新的 netns: netns2和 netns 3,并为它们分配和启动网卡:

# 创建两个新的 netns
[root@centos ~]$ ip netns add netns2
[root@centos ~]$ ip netns add netns3

# 创建两对新的 veth
[root@centos ~]$ ip link add veth2 type veth peer name veth2-peer
[root@centos ~]$ ip link add veth3 type veth peer name veth3-peer

# 将 veth 的一头分别放进另一个 netns 中
[root@centos ~]$ ip link set veth2-peer netns netns2
[root@centos ~]$ ip link set veth3-peer netns netns3

# 分配 IP 并启动
[root@centos ~]$ ip netns exec netns2 ip addr add 172.16.0.102/24 dev veth2-peer
[root@centos ~]$ ip netns exec netns2 ip link set veth2-peer up
[root@centos ~]$ ip netns exec netns3 ip addr add 172.16.0.103/24 dev veth3-peer
[root@centos ~]$ ip netns exec netns3 ip link set veth3-peer up

好了,这样我们就在一台 Linux 就创建出来了两个虚拟的网络环境,此时如果我们直接在 netns2 中向 netns3 请求是不通的,因为 namespace 完全隔离了不同的网络,接下来我们需要使用 Bridge 将这两个虚拟网络连接起来:

首先需要创建一个 Bridge,并将两对 veth 的另外一头“插到” 这个  Bridge上:

brctl 命令来自于 bridge-utils 包,在某些操作系统上可能需要自行安装。
# 创建一个 Bridge
[root@centos ~]$ brctl addbr br0

# 将刚才两对 veth 插到 br0 上
[root@centos ~]$ ip link set dev veth2 master br0
[root@centos ~]$ ip link set dev veth3 master br0

# 同时也 br0 分配一个地址:
[root@centos ~]$ ip addr add 172.16.0.100/24 dev br0

# 启动所有的网卡
[root@centos ~]$ ip link set veth2 up
[root@centos ~]$ ip link set veth3 up
[root@centos ~]$ ip link set br0 up

# 检查操作是否成功
[root@centos ~]$ brctl show
bridge name	bridge id		STP enabled	interfaces
br0		8000.c63e968442c6	no		       veth2
							                       veth3

此时的拓扑结构为:

+------------------+     +------------------+
|      netns2      |     |     netns3       |
| 172.16.0.102/24  |     | 172.16.0.103/24  |
+---(veth2-peer)---+     +---(veth3-peer)---+
          +                        +
          |                        |
          +                        +
+------(veth2)------------------(veth2)------+
|              linux-bridge                  |
|         (br0/172.16.0.100/24)              |
+--------------------------------------------+

以上操作完成后,我们就可以尝试两个不同 netns 中是否可以相互通信了:

# 在 netns2 中使用 veth2-peer 向 nets3 中的 172.16.0.103 发起 ping
[root@centos ~]$ ip netns exec netns2 ping 172.16.0.103 -I veth2-peer -c 1
PING 172.16.0.103 (172.16.0.103) from 172.16.0.102 veth2-peer: 56(84) bytes of data.
64 bytes from 172.16.0.103: icmp_seq=1 ttl=64 time=0.038 ms

--- 172.16.0.103 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.038/0.038/0.038/0.000 ms

# 在 netns3 中使用 veth3-peer 向 nets2 中的 172.16.0.102 发起 ping
[root@centos ~]$ ip netns exec netns3 ping 172.16.0.102 -I veth3-peer -c 1
PING 172.16.0.102 (172.16.0.102) from 172.16.0.103 veth3-peer: 56(84) bytes of data.
64 bytes from 172.16.0.102: icmp_seq=1 ttl=64 time=0.023 ms

--- 172.16.0.102 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.023/0.023/0.023/0.000 ms

可以看到,通信都是成功的,到此我们就完成了不同 netns 下的通信,后续有新的 netns 出现时,只需要将一头插到 br0 上即可与其他已经在 br0 上的 netns 实现通信。

四、与外部网络通信

我们已经解决了容器之间的通信,看起来一切都很好,但是还有最后一个问题需要解决:容器对外部的通信,我们可以简单的尝试一下:

[root@centos ~]$  ip netns exec netns3 ping baidu.com -I veth3_p -c 1
ping: baidu.com: Name or service not known

可以发现在 netns 中是无法与外部通信的,只能是在 br0 绑定的网络之间进行通信,而在真实世界中,向外部发起连接,或是接受外部连接都是必要的需求,例如通过 Docker 监听端口,在容器内发起公网请求等。

为了实现这个需求,我们需要引两样新的工具:路由表和 iptables

路由的概念非常简单:应该将数据发送到哪张网卡?(虚拟网卡设备也算),如何进行路由选择的规则都写在了路由表中,Linux 中可以有多张路由表,最重要和常用的是 local 和 main,通过 route -n可以查看本地路由表,本地路由表记录了本网络命名空间(namespace)中的网卡设备 IP 的路由规则,而其他的路由表一般都写在main 中,通过以下命令可以看到之前我们试验时自动创建的路由表:

[root@centos ~]$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.0.1.1        0.0.0.0         UG    0      0        0 eth0
10.0.1.0        0.0.0.0         255.255.255.0   U     0      0        0 eth0
169.254.0.0     0.0.0.0         255.255.0.0     U     1002   0        0 eth0
172.16.0.0      0.0.0.0         255.255.255.0   U     0      0        0 br0

由于 Linux 的网络栈是运行在内核态的,我们执行的命令都是在用户态执行,所以理论上我们是无法干涉网络栈行为的。但是 Linux 为了满足各种需求,从内核态中开放了一些钩子让用户态可以进行干预,而 iptables 就是我们调用这些钩子的工具,关于 iptables 的细节和用法可以参考 iptables(8) - Linux man page,这里不再赘述。

我们先尝试解决容器向外部地址发起通信请求的需求,这里的外网是指的虚拟网络宿主机以外的网络,并不一定需要真的到达公共互联网。我们继续复用之前创建的实验环境,假设我们的宿主机eth0 网卡地址为 10.0.1.48,局域网上另外一台主机的ip 为 10.0.1.26

首先我们尝试在 netns2 中向 10.0.1.26 发起 ping,在开始之前我们先在宿主机上进行尝试确保局域网之间没有通信问题:

[root@centos ~]$ ping 10.0.1.26 -c 1
PING 10.0.1.26 (10.0.1.26) 56(84) bytes of data.
64 bytes from 10.0.1.26: icmp_seq=1 ttl=64 time=0.193 ms

--- 10.0.1.26 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.193/0.193/0.193/0.000 ms

# 进入 netns 尝试
[root@centos ~]$ ip netns exec netns2 ping 10.0.1.26 -I veth2-peer -c 1
PING 10.0.1.26 (10.0.1.26) from 172.16.0.102 veth2-peer: 56(84) bytes of data.

--- 10.0.1.26 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

如我们预料一样,在宿主机上可以与外部网站通信,而在 netns 内无法与外部网络直接通信。

此时我们检查 netns2 的路由表看看:

[root@centos ~]$ ip netns exec netns2 route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
172.16.0.0      0.0.0.0         255.255.255.0   U     0      0        0 veth2-peer

可以看到,路由表中仅有一条记录,即 Destination 为 172.16.0.0 的路由,而我们的目标 ip 是 10.0.1.26,很显然这无法匹配到这条记录,所以失败了。如果你还没忘记的话,我们的虚拟网卡的另一头是连接到 br0上的,跟据 veth 的特点,我们只能与 br0 建立联系,所以我们尝试在路由表将所有未匹配的请求都转发到 br0,建立一条默认路由:

[root@centos ~]$ ip netns exec netns2 route add default gw 172.16.0.100 veth2-peer
[root@centos ~]$ ip netns exec netns2 route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.16.0.100    0.0.0.0         UG    0      0        0 veth2-peer
172.16.0.0      0.0.0.0         255.255.255.0   U     0      0        0 veth2-peer

我们继续分析,当流量到了 br0 后会发生什么,检查一下宿主机上的路由表:

[root@centos ~]$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.0.1.1        0.0.0.0         UG    0      0        0 eth0
10.0.1.0        0.0.0.0         255.255.255.0   U     0      0        0 eth0
169.254.0.0     0.0.0.0         255.255.0.0     U     1002   0        0 eth0
172.16.0.0      0.0.0.0         255.255.255.0   U     0      0        0 br0

流量到达在宿主机的 br0 后,默认路由是继续发往下一跳地址即 0.0.0.0,而通过这条路由我们是能正常到达 10.0.1.26的,但是这里存在一个问题,10.0.1.26和我们发出请求的 br0 (172.16.0.100)  根本不在同一个子网,所以我们需要一个做一个三层转发,Liunx 本身就支持这个功能,只是默认不开启,可以通过 sysctl net.ipv4.conf.all.forwarding=1 打开。

继续分析,此时流量的出发路径应该是 veth2-peer -> br0 -> eth0 -> 10.0.1.26,当10.0.1.26响应时,它并不认识 172.16.0.0/24 这个网段,所以我们还需要进行一次NAT(Network address translation),NAT的原理非常简单: 当一个内部地址需要与外部地址进行通信时,将出口流量的源地址和端口替换为网关所拥有的,可以和外地址通信的地址,当外部流量返回时,通过检查 NAT 表匹配应该把返回的流量路由到内部网络中的哪个地址,这项技术目前被运营商大规模使用以环境公网 IPv4地址枯竭的问题。

在 Linux 中我们可以通过 iptables 实现软件 NAT,由于我们要修改的是出口流量,所以应该做一次 source NAT,即SNAT:

[root@centos ~]$ iptables -t nat -A POSTROUTING -s 172.16.0.0/24 ! -o br0 -j MASQUERADE
# -t nat:选择 nat 表,不指定,默认是 filter 表。
# -A:Append 新增一条规则,-D Delete,-L List。
# POSTROUTING:选择 POSTROUTING chain,POSTROUTING代表数据包出口阶段。
# -s 172.16.0.0/24:匹配条件,source address 是网段 172.16.0.102/24 中的 IP。
# -j MASQUERADE:匹配后执行命令,执行 MASQUERADE,也就是源 IP 地址伪装为出口网卡 IP。对于 nat 表来说,可选项有 DNAT/MASQUERADE/REDIRECT/SNAT。

此时我们再尝试 ping 一下会发现通信已经可以直接建立了:

[root@centos ~]$ ip netns exec netns2 ping 10.0.1.26 -I veth2-peer -c 1
PING 10.0.1.26 (10.0.1.26) from 172.16.0.102 veth2-peer: 56(84) bytes of data.
64 bytes from 10.0.1.26: icmp_seq=1 ttl=63 time=0.249 ms

--- 10.0.1.26 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.249/0.249/0.249/0.000 ms

解决了从内部与外部网络通信的问题后, 最后一个需要考虑的问题就是如何让外部网络可以直接与容器进行交互,172.16.0.102 这个 IP 外界是不认识它的,只有这个宿主机知道它是谁,所以很显然我们需要再做一次 NAT。

这次的目的是修改入口流量,所以需要做一次 DNAT(Destination Network Address Translation),在进行 SNAT时,我们是针对某个段做全局的NAT,无论端口,但是在进行 DNAT时,我们需要声明容器中的端口在宿主机上是对应哪个,不然无法准确的路由流量。

依然使用 iptables 进行NAT:

[root@centos ~]$ iptables -t nat -A PREROUTING  ! -i br0 -p tcp -m tcp --dport 8088 -j DNAT --to-destination 172.16.0.102:80
# -t nat:选择 nat 表,不指定,默认是 filter 表。
# -A:Append 新增一条规则,-D Delete,-L List。
# POSTROUTING:选择 POSTROUTING chain,POSTROUTING代表数据包出口阶段。
# -p 协议,tcp 或 udp
# --dport 8088,目标端口,即宿主机所监听的端口
# -s 172.16.0.0/24:匹配条件,source address 是网段 172.16.0.102/24 中的 IP。
# -j DNAT:匹配后执行命令,执行 DNAT,也就是将来源流量转发到172.16.0.102上的80端口。

执行后我们可以进行测试,首先在 netns2 中监听 80 端口:

[root@centos ~]$ ip netns exec netns2 nc -lp 80

然后登录到我们之前测试流出流量的机器 10.0.1.26,进行telnet:

[root@10.0.1.26 ~]$ telnet 10.0.1.48 8088
Trying 10.0.1.48...
Connected to 10.0.1.48.
Escape character is '^]'.

可以看到, 流量正确的导向了 netns2 这个namespace 下监听的端口。

至此我们将主机内的虚拟化网络梳理了一番,在下一期我们将探索跨主机的虚拟化网络以及Kubernetes 下各种不同的网络解决方案。

体验新一代小程序引擎 - FinClip

免费注册