文章目录
深入理解Linux网络学习总结: https://blog.csdn.net/qq_41822345/article/details/128882826
引入:当前大火的容器并不是新技术,而是基于Linux的一些基础组件的诞生和演化出来的。
我们写出来的程序是如何在容器上运行的——不得不理解容器网络虚拟化技术的三大基础:veth、网络命名空间、Bridge。总的来说:veth实现连接、Bridge实现转发、网络命令空间实现隔离、路由表控制发送时的设备选择、iptables实现nat等功能。
本章知识点均以 是什么、怎么用、原理 展开。
思考:[以下问题的答案均在本文中]
1、容器中的eth0和宿主机上的eth0是一个东西?
2、veth设备对什么?它是如何工作的?
3、Linux是如何实现虚拟网络环境的?
4、Linux是如何保证同宿主机上多个虚拟网络环境可以相互独立工作?
5、同一宿主机上的多个容器之间是如何通信的?
6、Linux上的容器是如何和外部机器通信的?
一、容器与宿主机的通信——veth设备对
1、veth是什么
计算机之间通过eth0网卡进行通信,而Docker网络虚拟化则是通过veth设备对
——软件虚拟出来的设备来模拟“网卡”。
veth是Docker网络虚拟化最基础的技术。它模拟了在物理世界里的两块连接在一起的网卡。
事实上,本机网络IO里的lo回环设备也是这样一个用软件虚拟出来的设备。veth总是成双成对的出现,所有我们通常称它为veth设备对
。
2、veth怎么用
在Linux下,可以通过使用ip link命令
创建一对veth。这里link表示link layer
,即链路层。这个命令可以用于管理、查看网络接口,包括物理网络接口和虚拟网络接口。
-
step1:在链路层创建设备对veth0和veth1。
# 创建 ip link add veth0 type veth peer name veth1 # 查看 ip link show
-
step2:为veth设备对配置IP。
ip addr add 192.168.1.1/24 dev veth0 ip addr add 192.168.1.2/24 dev veth1
-
step3:将veth设备对启动起来。
ip link set veth0 up ip link set veth1 up # 启动之后就可以通过ifconfig命令查看 ifconfig
-
step4:关闭反向过滤rp_filter,并打开accept_local,接收本机IP数据包。
echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter echo 0 > /proc/sys/net/ipv4/conf/veth0/rp_filter echo 0 > /proc/sys/net/ipv4/conf/veth1/rp_filter echo 1 > /proc/sys/net/ipv4/conf/veth0/accept_local echo 1 > /proc/sys/net/ipv4/conf/veth1/accept_local
-
step5:验证,两个veth之间是否可以完成通信。
# 通过veth0来ping设备veth1[192.168.1.2] ping 192.168.1.2 -I veth0 # 通过veth1来ping设备veth0[192.168.1.1] ping 192.168.1.1 -I veth1 # 首次通信时源端首先发出一个arp request,目的端回复arp reply。接下来的都是ping包
3、veth原理
从实现上来看,虚拟设备veth
和本机回环设备lo
非常相似。只不过和lo设备相比,veth是为了虚拟化技术而生的,它多了个结对的概念。在内核源码创建(veth_newlink函数
)veth设备时,一次性会创建两个网络设备,并把对方分别设置成了各自的peer。在发送数据的过程中,先找到发送设备的peer,然后发起软中断让对方收取数据包。
二、网络隔离——网络命名空间
1、namespace是什么
网络虚拟化中最重要的一步——隔离。
用非常火热的Docker来举例,那就是不能让容器A用到容器B的设备,甚至连看都不能看一眼。只有这样才能保证不同的容器之间复用硬件资源的同时,还不能影响到其它容器的正常运行。
在Linux上实现隔离的技术手段就是命名空间(namespace)。通过命名空间可以隔离容器的进程PID(pid_namespace
)、文件系统挂载点(mnt_namespace
)、主机名(uts_namespace
)等多种资源。本文重点学习的是网络命名空间(net_namespace
,简称netns
)。
网络命名空间则是将veth网络设备
、进程
、socket
、路由表
、iptables
以及套接字(socket)
等隔离开,在一台机器上虚拟出多个逻辑上独立的网络栈。使得不同的网络空间都好像运行在独立的网络中一样。
2、namespace怎么用
下面以一个简单的例子来说明网络命名是如何使用的:这里对于veth来说,它包含了两个设备,这两个设备可以放在不同的网络命名空间中,这就是Docker容器和其宿主机或者其它容器通信的基础。
-
step1:创建一个新的网络命名空间net1。
# 创建 ip netns add net1 # 查看 ip netns exec net1 route ip netns exec net1 iptables -L ip netns exec net1 ip link list
-
step2:再创建一对veth,将其中一个veth放到新命名空间net1中。
ip link add veth1 type veth peer name veth1_p ip link set veth1 netns net1 # 查看 ip link list ip netns exec net1 ip link list
-
step3:将这对veth分别配置IP并启动。
ip addr add 192.168.0.100/24 dev veth1_p ip netns exec net1 ip addr add 192.168.0.101/24 dev veth1 ip link set dev veth1_p up ip netns exec net1 ip link set dev veth1_p up
-
step4:分别在宿主机和命名空间net1查看当前的各种网络设备。
ifconfig ip netns exec net1 ifconfig
-
step5:验证。两个veth【一个在宿主机上,一个在命名空间net1上】之间是否可以完成通信。
ip netns exec net1 ping 192.168.0.100 -I veth1
3、ns原理
在Linux中,很多平时我们熟悉的概念都是归属到某一个特定的网络命名空间中的,比如进程
、网卡设备
、socket套接字
等。如下图:
Linux中每个进程(线程)都是task_struct
来表示。每个task_struct
都要关联到一个命名空间对象nsproxy
,而nsproxy
又包含了网络命名空间(netns)
。对于网卡设备和socket套接字来说,自己的成员变量来直接表明自己的归属。
-
进程与网络命名空间
Linux存在一个默认的命名空间,Linux的1号进程初试使用该默认空间。Linux上其它所有进程都是由1号进程派生出来的。
在派生clone的时候如果没有特别指定,所有的进程都将共享默认命名空间。
在clone函数如果指定创建新进程的flag标志位CLONE_NEWNET
,那么该进程将会创建并使用新的netns。内核提供了三种创建ns的方式:
clone
、setns
和unshare
,这里以clone为例。创建ns的过程中,会初始化每个ns专属的路由表
、IP表
、filter表
以及各种内核参数
等数据结构。 -
网络收发如何使用ns
大致的原理就是socket上记录了其归属的网络命名空间。需要查找路由表之前先找到该命名空间,再找到网络命名空间里的路由表,然后再开始执行查找。
内核网络代码只有一套,并没有隔离。只是为不同的空间创建不同的struct net对象,从而每个struct net中都有独立的路由表、iptable等,每个设备、每个socket上也有指针指明自己归属于哪个网络命名空间,这种方法从逻辑上看起来就好像实现了多个独立的协议栈一样。
有了网络命名空间的隔离,Docker容器都能拥有自己独立的网卡设备、路由规则、iptables,从而使它们的网络功能不会相互影响。
三、容器与容器的通信——Bridge
1、bridge是什么
所谓网络虚拟化,其实用一句话来概括就是用软件来模拟实现真实的物理网络连接。
Bridge是由软件实现交换机的技术,它模拟了计算机中交换机的角色,可以把Linux上【不同的网络空间下】各种网卡设备连接在一起,让它们之间可以通信。
2、bridge怎么用
下面以一个简单的例子来说明bridge是如何使用的:创建两个不同的网络命名空间、两套veth设备对、bridge设备,再将设备对挂载到bridge上。这就是Docker中网络系统工作的基本原理。
-
step1:先创建第一个命名空间,并配齐相应veth设备。
ip netns add net1 ip link add veth1 type veth peer name veth1_p ip link set veth1 netns net1 ip netns exec net1 ip addr add 192.168.0.101/24 dev veth1 ip netns exec net1 ip link set dev veth1 up # 查看是否配置成功 ip netns exec net1 ip link list ip netns exec net1 ip link ifconfig
-
step2:再创建第二个命名空间,并配齐相应veth设备。
ip netns add net2 ip link add veth2 type veth peer name veth2_p ip link set veth2 netns net2 ip netns exec net2 ip addr add 192.168.0.102/24 dev veth2 ip netns exec net2 ip link set dev veth2 up # 查看是否配置成功 ip netns exec net2 ip link list ip netns exec net2 ip link ifconfig
-
step3:创建一个bridge设备。将step1和step2创建的两对veth剩下的一头插入bridge设备。
brctl addr br0 ip link set dev veth1_p master br0 ip link set dev veth2_p master br0
-
step4:为bridge设备也配上ip。并启动veth设备和bridge设备。
ip addr add 192.168.0.10/24 dev br0 ip link set veth1_p up ip link set veth2_p up ip link set br0 up # 查看当前bridge状态,上述操作是否成功。 brctl show
-
step5:验证。从网络空间net1中的veth1设备ping网络空间net2中的veth2设备
ip netns exec net1 ping 192.168.0.102 -I veth1
3、bridge原理
所谓网络虚拟化,其实用一句话来概括就是用软件来模拟实现真实的物理网络连接。
Linux内核中的bridge模拟实现了物理网络中的交换机的角色。和物理网络类似,可以将虚拟设备插入bridge。 不过和物理网络有点不一样的是,一对veth插入Bridge的那端其实就不是设备了,可以理解为退化成了一个网线插头。当Bidge接入了多对veth以后,就可以通过自身实现的网络包转发的功能来让不同的veth之间互相通信了。
回到Docker的使用场景上来举例,完整的Docker1 和Docker2通信的过程。如下图:
- Bridge工作过程汇总:
1、Docker1
往veth1
上发送数据。
2、由于veth1_p
是veth1
的对端,所以这个虚拟设备上可以收到包。
3、veth
收到包以后发现自己是连在Bridge
上的,于是进入bidge
转发处理逻辑。在Bridge
设备上寻找要转发到的端口,这时找到了veth2_p
开始发送。bridge
完成了自己的转发工作。
4、veth2
作为veth2_p
的对端,收到了数据包。
5、Docker2
就可以从veth2
设备上收到数据了。
- Bridge本机网络发送与接收详细流程:
那我们再继续拉大视野,从两个Docker的用户态来开始。Docker1向Docker2发送数据大致流程:
Docker1
在需要发送数据的时候,先通过send系统调用
发送,这个发送会执行到协议栈
进行协议头的封装等处理。经由邻居子系统
找到要使用的设备(veth1
)后,从这个设备将数据发送出去,veth1
的对端veth1_p
会收到数据包。
收到数据包的veth1_p
是一个连接在bridge
上的设备,这时候bridge
会接管该veth
的数据接收过程。从自己连接的所有设备中查找目的设备。找到veth2_p
以后,调用该设备的发送西数将数据发送出去。同样,veth2_p
的对端veth2
将收到数据。
其中veth2
收到数据后,将和lo
、etho
等设备一样,进入正常的数据接收处理过程。Docker2
中的用户态进程将能够收到Docker1
发送过来的数据了。
四、容器与外网的通信——路由与NAT技术
学习完前几节内容,我们可以通过veth设备对
、网络命名空间
和Bridge设备
在一台Linux上就能虚拟多个网络环境出来,也还可以让新建网络环境之间、和宿主机之间都可以通信。那么虚拟网络环境和外部网络是如何通信的——路由和NAT技术。
1、路由与NAT是什么
路由表控制以及iptables实现的NAT等功能可以使得虚拟网络通过宿主机的网卡和外部机器进行通信。
- 路由
路由:就是通过路由表指定的路由规则选择哪张网卡(虚拟网卡设备也算)将数据写进去。
Linux有多张路由表,最重要的也是最常用是local
和main
。
local路由表
:记录本网络命名空间中的网卡设备IP的路由规则。
main路由表
:其它路由规则一般都记录在main路由表里。
# 查看路由表
ip route list table local
ip route list table main
# 或者
route -n
除了本机发送,转发也会涉及路由过程。如果Linux收到数据包之后发现目的地址并不是本地[默认是丢弃,需要配置才能转发],就可以根据路由表的配置选择把这个数据包从自己的某个网卡设备转发出去。
- iptables与NAT
Linux内核网络栈在运行上基本属于纯内核的东西,但是为了迎合各种各样用户层不同的需求,内核层开放了一些钩子出来供用户层来干预。其中iptables
就是一个非常常用的干预内核行为的工具,它在内核里埋下了五个钩子入口,就是俗称的五链
。
在iptables中,根据实现的功能不同,又分成了四张表:raw
、mangle
、nat
、filter
。其中nat表的实现就是我们常说的NAT(Network Address Translation)
功能。其中NAT又分为SNAT(Source NAT)
和DNAT(Destination NAT)
两种。
SNAT 解决的是内网地址访问外部网络的问题。它是通过在POSTROUTING
里修改来源IP来实现的。
DNAT解決的是内网的服务要能够被外部访问到的问题。它是通过PPEROUTING
修改目标IP实现的。
POSTROUTING和PPEROUTING详见本章节原理部分。
2、路由与NAT怎么用
如下图,以纯手工的方式搭建一个可以和Docker类似可以与外部通信的虚拟网络。
举例:在宿主机上创建一个ns,通过这个ns去ping通另外一个宿主机,完成与外部的通信。
-
step1:先创建一个网络命名空间net1。一个veth对,为其配置上ip,并启动起来。
ip netns add net1 ip link add veth1 type veth peer name veth1_p ip link set veth1 netns net1 ip netns exec net1 ip addr add 192.168.0.2/24 dev veth1 ip netns exec net1 ip link set dev veth1 up # 查看是否配置成功 ip netns exec net1 ip link list ip netns exec net1 ip link ifconfig
-
step2:创建一个bridge,也配置上ip,并启动起来。
brctl addr br0 ip addr add 192.168.0.1/24 dev br0 ip link set dev veth1_p master br0 ip link set veth1_p up ip link set br0 up
-
step3:给当前网络命名空间添加默认路由规则。【让数据包从veth路由到Bridge】
ip netns exec net1 route add default gw 192.168.0.1 veth1
-
step4:打开转发相关的配置。【让数据包从Bridge转发到宿主机的eth0网卡】
sysctl net.ipv4.conf.all.forwarding=1 iptables -P FORWARD ACCEPT
-
step5:NAT转发。【实现内部虚拟网络访问外网——SNAT:将网络命名空间中请求的IP(192.168.0.2)转换成外部网络认识的IP(10.153.x.x)】
iptables -t nat -A POSTROUNTING -s 192.168.0.0/24 ! -o br0 -j MASQUERADE
-
step6:验证。从网络命名空间中的veth设备ping外网的IP。
ip netns exec net1 ping 10.153.x.x
3、路由与NAT原理
Linux内核网络栈在运行上基本属于纯内核的东西,但是为了迎合各种各样用户层不同的需求,内核层开放了一些钩子出来供用户层来干预。其中iptables就是一个非常常用的干预内核行为的工具,它在内核里埋下了五个钩子入口,就是俗称的五链。
- Linux在接收数据的时候,在IP层进入
ip_rcv
中处理。再执行路由判断,发现是本机的话就进入ip_local_deliver
进行本机接收,最后送往TCP协议层。在这个过程中,埋了两个HOOK,第一个是PRE_RQUTING
,这段代码会执行到iptables
中pre_routing
里的各种表。发现是本地接收后接着又会执行到LOCAL_IN
,这会执行到iptables
中配置的input
规则。 - 在发送数据的时候,查找路由表我到出口设备后,依次通过
_ip_local_out
、ip_ouput
等函数将包送到设备层。 在这两个函数中分别过了OUTPUT
和PREROUTING
的各种规则。 - 在转发数据的时候,Linux收到数据包发现不是本机的包可以通过查找自己的路由表找到合适的设备把它转发出去。先在
ip_rcv
中将包送到ip_forward
函数数中处理,最后在ip_output
函数数中将包转发出去。在这个过程中分别过 了PREROUTING
、FORWARD
和POSTROUTING
三个规则。
综上所述,iptables里的五个链在内核网络模块中的位置就可以归纳成下图。【核心】
五、本章总结
当前大火的容器技术并不是新技术,而是基于Linux的一些基础组件诞生和演化出来的。Kubernetes、Istio等项目中的网络方案看似复杂,其实追根溯源也是对路由选择、iptables等技术的不同应用方式而已。
veth实现连接、Bridge实现转发、网络命令空间实现隔离、路由表控制发送时的设备选择、iptables实现nat等功能。
-
Veth
模拟了现实物理网络中一对连接在一起可以相互通信的网卡。 -
Bridge
模拟了交换机的角色,可以把Linux上的各种网卡设备连接在一起,让它们之间可以通信。 -
网络命名空间netns
则是将veth网络设备、进程、socket、路由表、iptables等隔离开,在一台机器上虚拟出多个逻辑上的网络栈。 -
路由
就是根据路由表(local路由表和main路由表)选择哪张网卡(虚拟设备也算网卡)将数据写进去。 -
iptables
则是通过内核代码埋下的五个钩子入口(俗称五链
)实现四个功能来干预内核的行为。五链:INPUT、OUTPUT、PREROUTINE、FORWARD、POSTROUTINE
四个功能:NAT、filter、raw、mangle
内核行为:数据的接收、数据的转发、数据的发送
NAT分为SNAT(内网访问外网)和DNAT(内网暴露服务让外网访问) -
路由表控制以及iptables实现的
NAT
等功能可以使得虚拟网络通过宿主机的网卡和外部机器进行通信。