从 Linux 网络虚拟化探究容器网络

我们都知道容器实现基于Namespace隔离环境,并用 CGroup 为其控制资源开销。借助这两个技术我们可以成功实现应用容器化,但如何让多个容器在网络环境相互通信,以及访问外部网络,或者让外部网络访问特定容器等问题,则还需要利用一些Linux网络虚拟化技术。

在Linux提供的众多Namespace中,我们可以其中Network Namespace 来给容器配置独立的网络视图。

现在让我们从linux提供的一些网络虚拟化技术来实现一个简易版的"docker"。

网络命名空间隔离

通过一个小实验demo来体验网络命名空间

通过使用命令 readlink proc/$$/ns/net来查看宿主机的网络空间

image.png

我们也可以创建Network Namespace,通过ip netns 工具来创建

image.png

image.png

在这两个网络命名空间上创建两个bash容器 进行观察, 发现 网络空间的值改变了

image.png

查看接口

image.png

查看路由表

image.png

发现这个网络命名空间里的网络都被重置了,同样的fangcong2这个空间应该也如此。

不同的容器( fangcong1 和 fangcong2 )拥有自己独立的网络协议栈,包括网络设备、路由表、ARP 表、iptables 规则、socket 等,所有的容器都会以为自己运行在独立的网络环境中。

image.png

然后在测试一下 在fangcong1和fangcong2 的通信情况

准备一个go web服务

package main

import (
	"fmt"
	"net/http"
	"os"
)

func main() {
	name := os.Args[1]
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("req")
		w.Write([]byte(name + "\n"))

	})
	fmt.Println(name, "listen :8081")
	panic(http.ListenAndServe(":8081", nil))

}

然后在两个自定义的网络命名空间里运行

image.png

可以看到在同一主机下,起了两个8081端口却并没有发生冲突。

测试一下web服务器的可用性

image.png

发现访问不通,因为还没有配置网卡

image.png

容器点对点通信

上面的小demo 证明当两个容器处于不同的Network Namespace中 他们的网络是隔离的,他们也无法进行网络通信。在现实世界里如果两台计算机需要互通,只需要一根网线即可。那么容器呢?

image.png

在Linux网络虚拟化技术中提供了软件来模拟硬件网卡的方式,跟网线有两端一样,veth也是成对出现的,被称为veth pair,只要将一对Veth分别放入两个Network Namespace中,这两个Network Namespace 就可以互相通信了。

image.png

创建veth pair

image.png

veth1 放入fangcong1,另一端veth2放入fangcong2

image.png

然后就可以在容器里看到对应的网络设备了

image.png

然后分别为两个网卡设置ip地址,使其位于同一个子网 172.17.0.0/24然后启用网卡

image.png

image.png

测试互访

image.png

到这里容器点对点通信就成功解决了。

容器间互访

但是在真实的网络世界里 不可能只有两台计算机,肯定不是一个简单的二层网络,也没有足够多的网口进行彼此之间的两两互联,所以就发明了二层交换机(或网桥)。

image.png

容器也是如此 ,肯定会有3个或者以上的容器需要互访。那么就不能单单靠veth了。则需要用到linux为我们提供的网桥虚拟化方式:<span> </span>Bridge

先创建一个fangcong3的命名空间:

image.png

然后将之前的veth1和veth2去除

image.png

创建Bridge

image.png

然后创建三对veth

image.png

veth1 插入 fangcong1veth1-br插入br0veth2 插入fangcong2veth2-br 插入 br0veth3 插入 fangcong3veth3-br插入br0 (记得启用 veth*-br ):

image.png

分别在三个容器中,为各自的网卡设置 IP 地址,并使其位于同一个子网 172.17.0.0/24 中,设置完后同样需要进行启用操作:

fangcong1> ip addr add 172.17.0.101/24 dev veth1
fangcong1> ip link set dev veth1 up
fangcong1> 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 10  bytes 941 (941.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 10  bytes 941 (941.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.101  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::cc78:8bff:fe0c:75ba  prefixlen 64  scopeid 0x20<link>
        ether ce:78:8b:0c:75:ba  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
fangcong2> ip addr add 172.17.0.102/24 dev veth2
fangcong2> ip lin set dev veth2 up
fangcong2> 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 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

veth2: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.102  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::64f5:52ff:fe95:3228  prefixlen 64  scopeid 0x20<link>
        ether 66:f5:52:95:32:28  txqueuelen 1000  (Ethernet)
        RX packets 3  bytes 266 (266.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 5  bytes 426 (426.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
fangcong3> ip addr add 172.17.0.103/24 dev veth3
fangcong3> ip link set dev veth3 up
fangcong3> 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 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

veth3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.103  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::a405:23ff:fe99:60e2  prefixlen 64  scopeid 0x20<link>
        ether a6:05:23:99:60:e2  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

测试三台容器互访

image.png

至此我们就实现了同宿主机上的多容器间互访,但现在距离docker还很远,因为还欠缺了以下几种网络方案。

容器和宿主机互通

目前为止,我们的实验都是处于同一子网中。但实际的应用场景,更多的是需要容器可以与外部进行互通。

在现实世界中,二层交换机只能解决同一子网内的数据流向,对于不同子网,就需要使用三层路由器(或网关)来转发。

image.png

不过和之前 Linux 提供了交换机的虚拟化实现 Bridge 不同,Linux 并没有提供一个虚拟的路由器设备。因为 Linux 其自身就已经具备了路由器的功能,可以直接用来充当路由器,更准确地说,在 Linux 中,一个 Network Namespace 就可以承担一个路由器的功能。

现在我们三个容器fangcong1,fangcong2,fangcong3三个容器处于同一子网172.17.0.0/24中,与宿主机不在同一子网。宿主机的ip是103.239.101.157

image.png

查看fangcong1容器的路由表

image.png

可以看到只有自己子网的路由规则。

我们在宿主机上给网卡br0设置IP,让他充当三层网关来参与容器网络的路由转发寻路。(三台容器都通过veth绑定到这个网卡上了)

image.png

宿主机自动产生一条直连路由

image.png

加上这条路由导致我的docker服务不通了,因为访问流量导入了br0的网卡,不走下面docker服务默认的docker0的网桥了

image.png

删掉刚才配置的ip, 直连路由也自动消失,站点恢复。

image.png

image.png

image.png

回到实验,我们加回接口ip,这时候通过新增的直连路由就可以实现宿主机访问容器了。

image.png

反过来,容器访问宿主机,也可以通过配置容器隔离的路由表来实现,这里我将br0的ip改成了172.17.0.2避免和docker0冲突

image.png

至此,我们通过三层路由表转发实现了容器与宿主机的网络通信

容器访问其它主机(外网)

待续

外部访问容器(容器端口映射)

待续

跨节点网络

待续

   
  • Docker

    Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或Windows 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口

    29 引用
  • Linux

    48 引用
  • 网络

    6 引用
  • 虚拟化

    4 引用