最近在看kubernetes网络和容器网络相关知识,对Linux 虚拟网络和Docker网络模型做了一些探究,以下是学习记录,请各位看官多提宝贵意见~
目前主流的linux内核都支持6种名称空间:
所以,现在的内核之上,只要在当前的用户空间有对应工具支持,就可以操作这6种名称空间。
而网络名称空间,主要用于协议栈的隔离(网络设备,端口)。
假如物理机上有4个网卡,有两个名称空间。我们可以将某个网卡关联给特定的ns使用,则其他的ns就看不到这个设备,但是1个网卡只能属于1个ns,所以4个网卡可以属于4个ns,都能配置ip地址与外部通信。
假如ns数量超过网卡数量怎么办?
每个ns内部的进程也需要通过物理网络通信怎么办?
使用虚拟网卡设备,用纯软件的方式模拟一组设备来使用。
linux内核级别支持两种设备的模拟,一种是二层设备(普通物理网卡就是二层设备,工作在链路层,封装物理报文,在网络设备之间进行报文转发),一种是三层设备
而linux内核之上支持二层虚拟设备的创建(与物理网卡设备功能相同),从而创建虚拟网卡接口。而这种虚拟网卡接口很独特,是成对出现的。可以模拟为一根网线的两头,想象为一头插在主机之上,一头插在交换机之上,相当于让主机插入到交换机上,而linux内核原生支持二层虚拟网桥设备,也就是可以用软件构造一个交换机(brctl–bridge utils),所以二层虚拟网络设备,一个接口在名称空间内,一个接口在网桥上,模拟主机与交换机的连接
这就是虚拟化网络,从网络设备,到通信设备都是使用纯软件方式实现。我们在一台主机上实现,是虚拟化技术中一种简单的实现。著名的OVS程序(OpenVSwitch),可以模拟实现3层设备(vlan,vxlan,sdn流控,gre等技术)。
云计算环境下,网络虚拟化往往是将网络平面,控制平面,传输平面剥离开,实现将控制平面集中到专业设备上,以便于全局调度,实现SDN。在构建云计算中心的虚拟化网络时,既需要硬件支持,也需要在每个主机上构建复杂的虚拟化网络,因为一个物理机可能需要多个虚拟机或者容器。
下面回顾一下传统的物理桥接网络(类比vmware workstation 中的bridge网络也可)
早期的二层网络中,bridge 可以连接不同的 LAN 网,如下图所示。当主机 1 发出一个数据包时,LAN 1 的其他主机和网桥 br0 都会收到该数据包。网桥再将数据包从入口端复制到其他端口上(本例中就是另外一个端口)。因此,LAN 2 上的主机也会接收到主机 A 发出的数据包,从而实现不同 LAN 网上所有主机的通信。
随着网络技术的发展,传统 bridge 衍生出适用不同应用场景的模式,其中最典型要属 Linux bridge 模式,它是
Linux Kernel网络模块的一个重要组成部分,用以保障不同虚拟机之间的通信,或是虚拟机与宿主机之间的通信,如下图所示 :
最为常见的vmware虚拟机上,使用的bridge模式做网络桥接,其实现原理为:
将宿主机物理网卡作为交换机,物理机的网卡mac就是交换机的mac。
每个虚拟机的网卡其实是linux的一对软网卡,一端在交换机,一端在虚拟机空间。
通过网卡(交换机),在同一网段的多个虚拟机(可以跨节点)可以相互通信。
而发往物理机的报文,由于网卡mac是作为交换机mac,所以是直接发往物理网卡,在宿主机上还会给宿主机做一个软网卡,用于接收宿主机报文,宿主机网卡收到报文看到mac地址是自己的(此时物理网卡被征用为交换机,所以通过程序将报文转走到宿主机虚拟网卡),将报文转给软网卡,从而经过内核协议栈给宿主机名称空间使用。
Linux bridge 模式下,Linux Kernel 会创建出一个虚拟网桥 ,用以实现主机网络接口与虚拟网络接口间的通信。从功能上来看,Linux bridge 像一台虚拟交换机,所有桥接设置的虚拟机分别连接到这个交换机的一个接口上,接口之间可以相互访问且互不干扰,这种连接方式对物理主机而言也是如此。
在桥接的作用下,虚拟网桥会把主机网络接口接收到的网络流量转发给虚拟网络接口,于是后者能够接收到路由器发出的 DHCP(动态主机设定协议,用于获取局域网 IP)信息及路由更新。这样的工作流程,同样适用于不同虚拟网络接口间的通信。具体的实现方式如下所示:
虚拟机与宿主机通信: 用户可以手动为虚拟机配置IP 地址、子网掩码,该 IP 需要和宿主机 IP 处于同一网段,这样虚拟机才能和宿主机进行通信。
虚拟机与外界通信: 如果虚拟机需要联网,还需为它手动配置网关,该网关也要和宿主机网关保持一致。
Docker 利用 veth pair技术,在宿主机上创建了两个虚拟网络接口 veth0 和 veth1(veth pair 技术的特性可以保证无论哪一个 veth 接收到网络报文,都会无条件地传输给另一方)。
容器与宿主机通信 : 在桥接模式下,Docker Daemon 将 veth0 附加到 docker0 网桥上,保证宿主机的报文有能力发往 veth0。再将 veth1 添加到 Docker 容器所属的网络命名空间[注释2],保证宿主机的网络报文若发往 veth0 可以立即被 veth1 收到。
容器与外界通信 : 容器如果需要联网,则需要采用 NAT [注释2] 方式。准确的说,是 NATP (网络地址端口转换) 方式。NATP 包含两种转换方式:SNAT 和 DNAT 。
当宿主机以外的世界需要访问容器时,数据包的流向如下图所示:
由于容器的 IP 与端口对外都是不可见的,所以数据包的目的地址为宿主机的 ip 和端口,为 192.168.1.10:24 。
数据包经过路由器发给宿主机 eth0,再经 eth0 转发给 docker0 网桥。由于存在 DNAT 规则,会将数据包的目的地址转换为容器的 ip 和端口,为 172.17.0.n:24 。
宿主机上的 docker0 网桥识别到容器 ip 和端口,于是将数据包发送附加到 docker0 网桥上的 veth0 接口,veth0 接口再将数据包发送给容器内部的 veth1 接口,容器接收数据包并作出响应。
当容器需要访问宿主机以外的世界时,数据包的流向为下图所示:
此时数据包的源地址为容器的 ip 和端口,为 172.17.0.n:24,容器内部的 veth1 接口将数据包发往 veth0 接口,到达 docker0 网桥。
宿主机上的 docker0 网桥发现数据包的目的地址为外界的 IP 和端口,便会将数据包转发给 eth0 ,并从 eth0 发出去。由于存在 SNAT 规则,会将数据包的源地址转换为宿主机的 ip 和端口,为 192.168.1.10:24 。
由于路由器可以识别到宿主机的 ip 地址,所以再将数据包转发给外界,外界接受数据包并作出响应。这时候,在外界看来,这个数据包就是从 192.168.1.10:24 上发出来的,Docker 容器对外是不可见的。
而docker0 之所以能将对外报文转发到eth0,是因为在一个主机内,通过路由表规则进行转发,在主机内docker0可以reach到eth0所对应的IP。
linux内核本身就可以作为一个路由器,而eth0的地址是本地的,所以可以直接路由到达。
brctl addbr br0 #创建网桥
ifconfig br0 192.168.10.1/24 up #给网桥一个ip
ifconfig -a #观察本地网络接口
ip link add veth1.1 type veth peer name veth1.2 #创建两对虚拟网卡
ip link add veth2.1 type veth peer name veth2.2
ip netns add netns1
ip netns add netns2 #创建两个netns
ip netns list #查看netns
#将两个虚拟网卡放到ns中
ip link set dev veth1.2 netns netns1
ip link set dev veth2.2 netns netns2
#配置虚拟网卡的ip
ip netns exec netns1 ifconfig veth1.2 192.168.10.201/24 up
ip netns exec netns2 ifconfig veth2.2 192.168.10.202/24 up
ip netns exec netns1 ifconfig #查看netns1的网络接口
#将一对网卡的一端接入网桥
brctl addif br0 veth1.1
brctl addif br0 veth2.1
#激活两个位于br0上的veth设备
ifconfig veth1.1 up
ifconfig veth2.1 up
#配置两个ns的默认路由,模拟docker容器中的默认路由
ip netns exec netns1 route add default gw 192.168.10.1
ip netns exec netns2 route add default gw 192.168.10.1
#进入netns2 ping netns1的网卡,通过br0可通
ip netns exec netns2 ping 192.168.10.201
参考链接:
http://blog.daocloud.io/docker-bridge/