author:sufei
说明:本文主要记录在学习k8s网络方面的相关知识
一、Linux虚拟网络基础
1.1 网络命名空间
Linux在内核网络栈中引入网络命名空间,将独立的网络协议栈隔离到不同的命令空间中,彼此间无法通信;
Network Namespace.png这是网络虚拟化的基础,有几点需要说明:
1、Linux操作系统,解析和封装网络包是通过一个网络协议栈完成,下层为上层服务,这个协议栈中即包括如软件也包括硬件网络设备。网络命名空间就是以软件方式隔离出单独的网络栈信息;
2、不同network namespace的软硬件资源相互不可见,好像处在物理隔离的不同物理机上一样,彼此隔离;
3、不同的网络命名空间会有自己独立的网卡、路由表、ARP 表、iptables 等和网络相关的资源
4、实验:可以借助ip netns
命令来完成对 Network Namespace 的各种操作,如:
$ ip netns help
Usage: ip netns list
ip netns add NAME
ip netns set NAME NETNSID
ip [-all] netns delete [NAME]
ip netns identify [PID]
ip netns pids NAME
ip [-all] netns exec [NAME] cmd ...
ip netns monitor
ip netns list-id
# 创建Network Namespace
$ ip netns add ns0
# 在新的Network Namespace 创建网卡信息
$ ip netns exec ns0 ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
# 开启回环网卡
$ ip netns exec ns0 ip link set lo up
1.2 Veth设备对(转移设备)
问题:什么是转移设备?
可以在不同的 Network Namespace 之间转移设备(如veth)。由于一个设备只能属于一个 Network Namespace ,所以转移后在这个 Network Namespace 内就看不到这个设备了。veth设备属于可转移设备,而很多其它设备(如lo、bridge等)是不可以转移的。
通过转移设备veth,我们可以实现不同网络命名空间的联通。类似于两条物理机之间通过网线两端互联。
veth pair 全称是 Virtual Ethernet Pair,是一个成对的端口,所有从这对端口一 端进入的数据包都将从另一端出来,反之也是一样。而veth pair就是为了在不同的 Network Namespace 直接进行通信,利用它可以直接将两个 Network Namespace 连接起来。
veth pair.png实验
# 创建veth pair
$ sudo ip link add type veth
$ ip addr
61: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether e6:39:e1:e0:3a:a0 brd ff:ff:ff:ff:ff:ff
62: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether be:41:49:42:23:6a brd ff:ff:ff:ff:ff:ff
# 指定 veth pair 两个端点的名称
$ ip link add vethfoo type veth peer name vethbar
# 实现Network Namespace间通信
# 1、创建另一个网络命名空间
$ ip netns add ns1
$ ip netns list
ns1
ns0
# 2、分别将veth两端移动到对应网络命名空间
$ ip link set veth0 netns ns0
$ ip link set veth1 netns ns1
# 3、分别为这对veth pair配置上ip地址,并启用它们
$ ip netns exec ns0 iplink set veth0 up
$ ip netns exec ns0 ip addr add 10.0.1.1/24 dev veth0
$ ip netns exec ns1 iplink set veth1 up
$ ip netns exec ns1 ip addr add 10.0.1.2/24 dev veth1
# 4、尝试在ns1中访问ns0中的ip地址
$ ip netns exec ns1 ping -c 3 10.0.1.1
PING 10.0.1.1 (10.0.1.1) 56(84) bytes of data.
64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.091 ms
64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=0.035 ms
64 bytes from 10.0.1.1: icmp_seq=3 ttl=64 time=0.037 ms
--- 10.0.1.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.035/0.054/0.091/0.026 ms
1.3 网桥
veth pair打破了 Network Namespace 的限制,实现了不同 Network Namespace 之间的通信。但veth pair有一个明显的缺陷,就是只能实现两个网络接口之间的通信。如果我们想实现多个网络接口之间的通信,就可以使用下面介绍的网桥(Bridge)技术(类似于物理交换机)。
简单来说,网桥就是把一台机器上的若干个网络接口“连接”起来。其结果是,其中一个网口收到的报文会被复制给其他网口并发送出去。以使得网口之间的报文能够互相转发。
网桥是一个二层网络设备,通过网桥可以将linux支持的不同的端口连接起来,并实现类似交换机那样的多对多的通信。
bridge.png实验:
# 创建网桥
brctl addbr br0
# 绑定网口
# 让eth0 成为br0 的一个端口
brctl addif br0 eth0
1.4 Iptables/Netfilter
Netfilter负责在内核中执行各种挂接的规则(过滤、修改、丢弃等),运行在内核 模式中;Iptables模式是在用户模式下运行的进程,负责协助维护内核中Netfilter的各种规则表;通过二者的配合来实现整个Linux网络协议栈中灵活的数据包处理机制。
iptables/netfilter(简称iptables)组成了Linux平台下的包过滤防火墙,可以完成封包过滤、封包重定向和网络地址转换(NAT)等功能。这部分主要了解两部分知识:
消息处理链
应用层不管是要发送还是接收网络消息,都需要通过linux内核提供的一系列关卡。每个”关卡“担负着不同的工作。这里的”关卡“被称为”链“。如下图:
iptables-
INPUT
:进来到应用层数据包应用此规则链中的策规则; -
OUTPUT
:应用层发出的数据包应用此规则链中的规则; -
FORWARD
:转发数据包时应用此规则链中的规则; -
PREROUTING
:对数据包作路由选择前应用此链中的规则(所有的数据包进来的时侯都先由这个链处理); -
POSTROUTING
:对数据包作路由选择后应用此链中的规则(所有的数据包出来的时侯都先由这个链处理);
规则表
我们在上面的消息处理链上定义规则,规则一般的定义为:如果数据包头符合这样的条件,就这样处理“。这些规则并不是严格按照添加顺序排列在一张规则表中,而是按照功能进行分类,存储在不同的表中,每个表存储一类规则:
- Filter
主要用来过滤数据,用来控制让哪些数据可以通过,哪些数据不能通过,它是最常用的表。 - NAT
用来处理网络地址转换的,控制要不要进行地址转换,以及怎样修改源地址或目的地址,从而影响数据包的路由,达到连通的目的。 - Mangle
主要用来修改IP数据包头,比如修改TTL值,同时也用于给数据包添加一些标记,从而便于后续其它模块对数据包进行处理(这里的添加标记是指往内核skb结构中添加标记,而不是往真正的IP数据包上加东西)。 - Raw
在Netfilter里面有一个叫做链接跟踪的功能,主要用来追踪所有的连接,而raw表里的rule的功能是给数据包打标记,从而控制哪些数据包不做链接跟踪处理,从而提高性能;
1.5 总结
- 为了实现网络隔离功能,Linux引入了network namespace实现了网络协议栈隔离,在隔离区间内各自有相应的网卡设备,使网络协议栈之间互不干扰;
- 为了实现隔离的网络命名空间之间的通信,引入了veth pair来进行联通;
- veth pair只能进行两点之间的通信需求,网桥实现了相同主机上多个Network Namespace之间的数据通信;
- iptables则可以帮助我们实现网络安全和数据包的路由转发功能
二、Docker网络模式
当我们安装docker后,在宿主机上就存在一个docker0的网卡信息,其实他就是一个网桥设备。
[11:20:10 teledb@ubuntu1804 ~]$ ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
inet6 fe80::42:7ff:fe5e:97e4 prefixlen 64 scopeid 0x20<link>
ether 02:42:07:5e:97:e4 txqueuelen 0 (Ethernet)
RX packets 5866985 bytes 55416110112 (55.4 GB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 9649674 bytes 15424321793 (15.4 GB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网关(如上面的172.17.0.1)。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的Container-IP直接通信。
Docker网桥是宿主机虚拟出来的,并不是真实存在的网络设备,外部网络是无法寻址到的,这也意味着外部网络无法通过直接Container-IP访问到容器。如果容器希望外部访问能够访问到,可以通过映射容器端口到宿主主机(端口映射),即docker run创建容器时候通过 -p 或 -P 参数来启用,访问容器的时候就通过[宿主机IP]:[容器端口]访问容器。
下面具体来说说docker容器的几种网络模式,以便后续学习k8s网络。
2.1 host模式
docker host.png在host模式下( –net=host),容器不会去建立新的网络命名空间,而直接使用宿主机的网络设备以及网络协议栈。这样自然不会虚拟出自己的网卡,配置自己的IP等。其特点如下:
- 没有独立的网络命名空间,但是其他命名空间依然独立,如文件系统、进程列表等,这些还是和宿主机隔离的;
- 可以直接使用宿主机的IP地址与外界通信容器内部的服务端口也可以使用宿主机的端口,不需要进行NAT,相当于主机上的通信进程;
- host最大的优势就是网络性能比较好,但是docker host模式上已经使用的端口,主机内进程就不能再用了。
2.2 container模式
docker container.png这个模式就是在创建容器时,指定网络(–net=container:NAME_or_ID)与之前容器在同一个网络命名空间中,而不是和宿主机共享(这也就是k8s中pod内各容器的一种网络模式)。下面说明几点:
- 新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等;
- 两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的;
- 两个容器的进程可以通过 lo 网卡设备通信。
2.3 none模式(很少使用吧)
docker none.pngnone模式(–net=none)Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。
- 这种网络模式下容器只有lo回环网络,没有其他网卡;
- 封闭的网络能很好的保证容器的安全性。
2.4 bridge模式
docker bridge.pngbridge模式是docker容器的默认模式,当Docker进程启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器在bridge模式下会连接到这个虚拟网桥上,并由网桥自动分配ip。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。
下面说明这个模式下的工作方式:
- 从docker0子网中分配一个IP给容器使用,并设置docker0的IP地址为容器的默认网关;
- 在主机上创建一对虚拟网卡veth pair设备,Docker将veth pair设备的一端放在新创建的容器中,并命名为eth0(容器的网卡),另一端放在主机中,以vethxxx这样类似的名字命名,并将这个网络设备加入到docker0网桥中;
- 实现端口转发功能则采用在iptables做了DNAT规则。
三、k8s网络实现
首先我们来看看k8s想要一个什么样的网络,也就是k8s网络设计的要求,具体如下:
- 每个 Pod 都拥有一个独立的 IP 地址,且不冲突;
- 所有 Pod 都在一个可以直接连通的、扁平的网络空间中(不管它们是否在同一个 Node(宿主机)中);
下面简单从几中不同的通信要求来看看k8s网络实现。
3.1 Pod内容器间通信
k8s pod内通信.png在 Kubernetes 的世界里,IP 是以 Pod 为单位进行分配的。一个 Pod 内部的所有容器共享一个网络堆栈。实际上就是docker container网络模式。可以直接通过本地localhost进行网络访问。这个模式在mysql容器化中就是agent容器与mysql容器的网络通信方式。
3.2 Pod直接通信
3.2.1 同一个Node
k8s pod间通信(同node).pngPod1和Pod2都是通信veth pair连接到同一个docker0网桥上,它们的IP地址都是从docker0网段上动态获取的,它们和网桥本身的IP是同一个网段的。可以通过docker0作为交换机进行通信,也就是采用的docker bridge网络模式进行通信。
3.2.3 不同Node
由于在同一个网桥docker0上即可以保证分配的pod IP不会冲突,且可以相互通信,而如果需要跨Node物理节点,则无法通过docker网络直接满足要求了,那这些要求具体有哪些呢?
- 我们需要记录Pod IP与Node IP的映射关系;
- 整个Pod IP不能冲突;
- 从Pod发出的数据包不能进行NAT转换,必须要让Pod之间知道相互对方IP
解决方案
方法一:k8s中通过在etcd中记录正在运行中pod的IP分配信息,这样我们就可以满足Pod IP与Node IP之间映射关系的记录;
方法二:可以在etcd中规划配置好所有主机docker0网桥的子网范围,从而满足Pod IP不冲突的要求;如:
节点A:10.0.1.0/24
节点B:10.0.2.0/24
节点C:10.0.3.0/24
方法三:要实现Pod跨Node通信,以k8s默认网络Flannel为例,就是采用overlay(覆盖网络)实现。具体下面说明:
问题:什么是覆盖网络?
覆盖网络就是应用层网络,是指建立在另一个网络上的网络。怎么理解呢?简单理解就是将TCP数据包装在另一种网络包里面进行路由转发和通信,另一种网络包目前可以是UDP、VxLAN、AWS VPC和GCE路由等数据转发方式。默认以UDP为例来说明flannel工作方式。
下面看看具体实现
k8s pod通信(不同node).png- 数据从源容器中发出后,经由所在主机的docker0虚拟网卡转发到flannel0虚拟网卡;
- flanneld服务监听在网卡的另外一端。Flannel通过Etcd服务维护了一张节点间的路由表;
- 源主机的flanneld服务将原本的数据内容UDP封装;
- 根据自己的路由表投递给目的节点的flanneld服务,数据到达以后被解包;
- 然后直接进入目的节点的flannel0虚拟网卡,然后被转发到目的主机的docker0虚拟网卡,最后就像本机容器通信一下的有docker0路由到达目标容器。这样整个数据包的传递就完成了。
问题:为保证各node内docker容器分配的ip地址不冲突,每个节点上的Docker会使用不同的IP地址段?如何实现的呢?
其实只是单纯的因为Flannel通过Etcd分配了每个节点可用的IP地址段后,偷偷的修改了Docker的启动参数,即通过--bip=10.1.15.1/24。它限制了所在节点容器获得的IP范围。这个IP范围是由Flannel自动分配的,由Flannel通过保存在Etcd服务中的记录确保它们不会重复。
问题:为什么在发送节点上的数据会从docker0路由到flannel0虚拟网卡,在目的节点会从flannel0路由到docker0虚拟网卡?
这其实是通过路由表实现的,如在目标node与源node执行route -n结果如下:
# 源主机
$ route -n
Destination Gateway Genmask Flags Metric Ref Use Iface
10.1.0.0 * 255.255.0.0 U 0 0 0 flannel0
10.1.15.0 * 255.255.255.0 U 0 0 0 docker0
# 目标主机
$ route -n
Destination Gateway Genmask Flags Metric Ref Use Iface
10.1.0.0 * 255.255.0.0 U 0 0 0 flannel0
10.1.20.0 * 255.255.255.0 U 0 0 0 docker0
- 现在有一个数据包要从IP为10.1.15.2的容器发到IP为10.1.20.3的容器。
- 根据数据发送节点的路由表,它只与10.1.0.0/16匹配这条记录匹配,因此数据从docker0出来以后就被投递到了flannel0;
- 同理在目标节点,由于投递的地址是一个容器,因此目的地址一定会落在docker0对于的10.1.20.0/24这个记录上,自然的被投递到了docker0网卡
网友评论