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

本贴最后更新于 370 天前,其中的信息可能已经时移世改

容器的本质是一个特殊的进程,特殊在为期创建了 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 为我们提供的网桥虚拟化方式: 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

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

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

待续

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

待续

  • 容器化
    2 引用
  • Linux

    Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。它能运行主要的 Unix 工具软件、应用程序和网络协议,并支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

    915 引用 • 931 回帖
  • 网络
    128 引用 • 177 回帖 • 3 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...