容器的本质是一个特殊的进程,特殊在为期创建了 Namespace 隔离运行环境,并用 CGroup 为其控制资源开销。借助这两个技术我们可以成功实现应用容器化,但如何让多个容器在网络环境相互通信,以及访问外部网络,或者让外部网络访问特定容器等问题,则还需要利用一些 Linux 网络虚拟化技术。
在 Linux 提供的众多 Namespace 中,我们可以其中 Network Namespace 来给容器配置独立的网络视图。
现在让我们从 linux 提供的一些网络虚拟化技术来实现一个简易版的"docker"。
网络命名空间隔离
通过一个小实验 demo 来体验网络命名空间
通过使用命令 readlink proc/$$/ns/net
来查看宿主机的网络空间
我们也可以创建 Network Namespace
,通过 ip netns
工具来创建
在这两个网络命名空间上创建两个 bash 容器 进行观察, 发现 网络空间的值改变了
查看接口
查看路由表
发现这个网络命名空间里的网络都被重置了,同样的 fangcong2 这个空间应该也如此。
不同的容器( fangcong1 和 fangcong2 )拥有自己独立的网络协议栈,包括网络设备、路由表、ARP 表、iptables 规则、socket 等,所有的容器都会以为自己运行在独立的网络环境中。
然后在测试一下 在 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))
}
然后在两个自定义的网络命名空间里运行
可以看到在同一主机下,起了两个 8081 端口却并没有发生冲突。
测试一下 web 服务器的可用性
发现访问不通,因为还没有配置网卡
容器点对点通信
上面的小 demo 证明当两个容器处于不同的 Network Namespace 中 他们的网络是隔离的,他们也无法进行网络通信。在现实世界里如果两台计算机需要互通,只需要一根网线即可。那么容器呢?
在 Linux 网络虚拟化技术中提供了软件来模拟硬件网卡的方式,跟网线有两端一样,veth
也是成对出现的,被称为 veth pair
,只要将一对 Veth 分别放入两个 Network Namespace
中,这两个 Network Namespace
就可以互相通信了。
创建 veth pair
将 veth1
放入 fangcong1
,另一端 veth2 放入 fangcong2
然后就可以在容器里看到对应的网络设备了
然后分别为两个网卡设置 ip 地址,使其位于同一个子网 172.17.0.0/24
然后启用网卡
测试互访
到这里容器点对点通信就成功解决了。
容器间互访
但是在真实的网络世界里 不可能只有两台计算机,肯定不是一个简单的二层网络,也没有足够多的网口进行彼此之间的两两互联,所以就发明了二层交换机(或网桥)。
容器也是如此 ,肯定会有 3 个或者以上的容器需要互访。那么就不能单单靠 veth
了。则需要用到 linux 为我们提供的网桥虚拟化方式: Bridge
。
先创建一个 fangcong3
的命名空间:
然后将之前的 veth1 和 veth2 去除
创建 Bridge
然后创建三对 veth
将 veth1
插入 fangcong1
、veth1-br
插入 br0
、veth2
插入 fangcong2
、veth2-br
插入 br0
、 veth3
插入 fangcong3
、veth3-br
插入 br0
(记得启用 veth*-br ):
分别在三个容器中,为各自的网卡设置 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
测试三台容器互访
至此我们就实现了同宿主机上的多容器间互访,但现在距离 docker 还很远,所以还欠缺了以下几种网络方案。
容器和宿主机互通
目前为止,我们的实验都是处于同一子网中。但实际的应用场景,更多的是需要容器可以与外部进行互通。
在现实世界中,二层交换机只能解决同一子网内的数据流向,对于不同子网,就需要使用三层路由器(或网关)来转发。
不过和之前 Linux 提供了交换机的虚拟化实现 Bridge 不同,Linux 并没有提供一个虚拟的路由器设备。因为 Linux 其自身就已经具备了路由器的功能,可以直接用来充当路由器,更准确地说,在 Linux 中,一个 Network Namespace 就可以承担一个路由器的功能。
现在我们三个容器 fangcong1,fangcong2,fangcong3
三个容器处于同一子网 172.17.0.0/24
中,与宿主机不在同一子网。宿主机的 ip 是 103.239.101.157
查看 fangcong1 容器的路由表
可以看到只有自己子网的路由规则。
我们在宿主机上给网卡 br0 设置 IP,让他充当三层网关来参与容器网络的路由转发寻路。(三台容器都通过 veth 绑定到这个网卡上了)
宿主机自动产生一条直连路由
加上这条路由导致我的 docker 服务不通了,因为访问流量导入了 br0 的网卡,不走下面 docker服务
默认的 docker0
的网桥了
删掉刚才配置的 ip, 直连路由也自动消失,站点恢复。
回到实验,我们加回接口 ip,这时候通过新增的直连路由就可以实现宿主机访问容器了。
反过来,容器访问宿主机,也可以通过配置容器隔离的路由表来实现,这里我将 br0 的 ip 改成了 172.17.0.2
避免和 docker0
冲突
至此,我们通过三层路由表转发实现了容器与宿主机的网络通信
容器访问其它主机(外网)
待续
外部访问容器(容器端口映射)
待续
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于