k8s 网络基础

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:  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:  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:  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  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
        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.png

 none模式(–net=none)Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。

  • 这种网络模式下容器只有lo回环网络,没有其他网卡;
  • 封闭的网络能很好的保证容器的安全性。

2.4 bridge模式

docker bridge.png

 bridge模式是docker容器的默认模式,当Docker进程启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器在bridge模式下会连接到这个虚拟网桥上,并由网桥自动分配ip。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。

 下面说明这个模式下的工作方式:

  1. 从docker0子网中分配一个IP给容器使用,并设置docker0的IP地址为容器的默认网关;
  2. 在主机上创建一对虚拟网卡veth pair设备,Docker将veth pair设备的一端放在新创建的容器中,并命名为eth0(容器的网卡),另一端放在主机中,以vethxxx这样类似的名字命名,并将这个网络设备加入到docker0网桥中;
  3. 实现端口转发功能则采用在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).png

 Pod1和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
  1. 数据从源容器中发出后,经由所在主机的docker0虚拟网卡转发到flannel0虚拟网卡;
  2. flanneld服务监听在网卡的另外一端。Flannel通过Etcd服务维护了一张节点间的路由表;
  3. 源主机的flanneld服务将原本的数据内容UDP封装;
  4. 根据自己的路由表投递给目的节点的flanneld服务,数据到达以后被解包;
  5. 然后直接进入目的节点的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网卡

你可能感兴趣的:(k8s 网络基础)