Kubernetes Network 由浅入深

181 阅读5分钟

Kubernetes Network 由浅入深

主要是学习深入剖析Kubernetes专栏课的笔记。

一、单机容器网络

名词

  • 网络栈:网络栈包括了网卡(Network Interface)、回环设备(Loopback Device)、路由表(Routing Table)和Iptables规则,对于一个进程来说,这些要素,就构成了它发起请求和响应网络请求的基本环境。
  • 网桥(Bridge):bridge是一个虚拟网络设备,所以具有网络设备的特征,可以配置IPMAC地址;Bridger是一个虚拟交换机,具有和物理交换机类似的功能。它是工作在数据链路层的设备。
  • Veth Pair: 虚拟网线,用来连接容器到网桥上的;它被创建出来以后,总是以两张虚拟网卡(Veth Peer)的形式成对出现,并且,从其中一个网卡发出的数据包会自动出现在与之对应的网卡上,哪怕是这两张_*网卡_在不同的Network Namespace中。
  • ARP: 是一个通过三层的IP地址找到对应二层MAC地址的协议。
  • CAM表:虚拟交换机(这里是网桥)通过MAC地址学习维护的端口和MAC地址的对应表。

Host网络

作为一个容器,在启动时可以通过指定-net=host,使用宿主机的Network Namespace

$ docker run -d -net=host --name nginx-1 nginx

使用Host网络的优点是网络性能较好,直接使用宿主机的网络栈,缺点是会引入共享网络资源的问题,比如端口冲突。所以,在多数情况下,我们都希望能使用自己Network Namespace里的网络栈,拥有属于自己的IP和端口。

如何通信

单节点容器网络通信

如上图,描述了单节点容器网络的通信流程,下面主要按C1->C2的访问流程来详细描述交互流程:

# 先创建两个容器,用于模拟发起请求,启动两个centos容器,并在里面安装net-tools工具,才可以使用ifconfig命令
# 创建C1,并安装net-tools
$ docker run -d -it --name c1 centos /bin/bash
$ docker exec -it c1 bash
$ [root@60671509044e /]# yum install -y net-tools
# 创建C2,并安装net-tools
$ docker run -d -it --name c2 centos /bin/bash
$ docker exec -it c2 bash
$ [root@94a6c877b01a /]# yum install -y net-tools

  • 容器C1C2启动之后,在容器中都有一条默认的路由规则,当前容器网段的所有请求都会走eth0网卡设备。
    • C1
# 进入c1容器,查看ip以及路由表
$ docker exec -it c1 bash
# 查看IP
$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.7  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:ac:11:00:07  txqueuelen 0  (Ethernet)
        RX packets 6698  bytes 9678058 (9.2 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 3518  bytes 195061 (190.4 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        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
# 查看路由       
$ route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         _gateway        0.0.0.0         UG    0      0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

  • C2
# 进入C2容器查看IP和路由表
$ docker exec -it c2 bash
$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.8  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:ac:11:00:08  txqueuelen 0  (Ethernet)
        RX packets 6771  bytes 9681937 (9.2 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 3227  bytes 179347 (175.1 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        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
# 查看路由
$ route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         _gateway        0.0.0.0         UG    0      0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

上述容器现实有自己的IP以及MAC地址,并且每个容器中都有默认路由_gateway指向eth0网卡;并且_gateway有对应的MAC地址已经存在于本地ARP缓存中。

  • 主机之间网络通信需要用到MAC地址,这是数据链路层识别主机的方式,C1访问C2的时候会先从本地ARP缓存中查找是否有C2容器对应的IP:172.17.0.3MAC地址。如果没有就会发起ARP协议查找MAC地址。
# c1 -> c2 ,先发起ARP请求查找MAC地址,可以在容器中查看ARP缓存对应IP的MAC
$ docker exec -it c1 bash
# 先查看本地的ARP缓存
$ [root@94a6c877b01a /]# arp
Address                  HWtype  HWaddress           Flags Mask            Iface
_gateway                 ether   02:42:2e:8d:21:d6   C                     eth0
# 执行ping命令就会发起ARP寻址请求
$ ping 172.17.0.8
# 再查询本地arp缓存,发现已经有MAC地址存在了
$ [root@60671509044e /]# arp
Address                  HWtype  HWaddress           Flags Mask            Iface
172.17.0.8               ether   02:42:ac:11:00:08   C                     eth0
_gateway                 ether   02:42:2e:8d:21:d6   C                     eth0

ARP寻址流程:C1容器发起ARP请求,进过本地路由协议之后会把请求路由到网桥上,此时网桥(Bridge)充当一个虚拟交换机,虚拟交换机会把ARP广播到其它插入到网桥的所有容器,C2收到ARP协议之后会回复MAC地址。

  • 查找到C2MAC地址之后就可以发起通信。

二、跨主机容器通信

跨主机之间容器通信按是否依赖底层网络环境来划分主要分为OverlayUnderlay两种网络结构,Overlay网络要求只是主机之间网络可达即可,不要求主机之间同处二层域;Underlay对底层的基础设施有要求,按照实现的方式对底层的网络基础设施有不同的要求,比如Flanan host-gw组件要求主机之间同处二层域,也就是主机之间要连接到一个交换机上。

名词

  • Overlay Network(覆盖网络): 在已有的宿主机网络之上,通过软件构建一个覆盖在宿主机网络之上的、可以把所有容器连通在一起的虚拟网络。
  • Tun设备(Tunnel设备):在Linux中,TUN设备是一种工作在三层(Network Layer)的虚拟网络设备;Tun设备的功能就是在操作系统内核和用户应用程序之间传递IP包,
  • VXLAN: 虚拟可扩展局域网(Virtual Extensible LAN),是LINUX内核支持的一种网络虚拟化技术,VXLAN完全在内核态实现网络数据包的封装和解封装。
  • VTEP:虚拟隧道端点设备,它既有IP,也有MAC地址。
  • BGP: 边界网关协议(Border Gateway Protocol),它是一个Linux内核原生就支持的、专门用在大规模数据中心里维护不同的自治系统之间路由信息的、无中心的路由协议。

跨主机通信

跨主机之间的容器通信,通过采用Overlay Network来实现跨主机之间的容器通信,Overlay Network的实现有多种方式。

Overlay Network

Overlay 模式

1、三层Flannel UDP

Flannel UDP模式是Flannel最开始提供的一种最简单且最易实现的容器跨主网络方案,但是因为性能最差,所以后来被弃用。但是对于理解Overlay的实现方式还是很有参考意义的。

我们以一个例子来讲述这个网络访问的流程,在这个流程中,有两台宿主机,四个容器,我们需要通过Container-1容器请求Container-4

Container-1容器向Container-4容器发起请求,Docker0是位于Root Network Namespace的,通过veth peer一头连着容器的Network Namespace一头连着位于Root Netwrok NamespaceDocker0虚拟网络设备。

  • 容器100.96.1.2访问100.96.2.2,由于目的地址不在Docker0网桥的网段内(通过ARP请求一次就知道目标容器不在此网桥上),所以这个IP会执行Container-1的默认路由规则中,容器中的默认路由规则就是如下的default via 172.17.0.1 dev eth0。对应到上图的步骤1。
# 容器中默认设置的的路由规则,
[root@94a6c877b01a /]# ip route
default via 172.17.0.1 dev eth0 
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2 
# 下一跳是172.17.0.1且从eth0设备上出去,通过查看docker的网络,172.17.0.1就是bridge设备的网关IP
lengrongfu@MacintoshdeMacBook-Pro ~ % docker network ls        
NETWORK ID     NAME                               DRIVER    SCOPE
e522990979b3   bridge                             bridge    local
# 查看网络
lengrongfu@MacintoshdeMacBook-Pro ~ % docker inspect network e522990979b3
[
    {
        "Name": "bridge",
        "Id": "e522990979b365e9df4d967c3600483e598e530361deb28513b6e75b8b66bedf",
        "Created": "2021-04-12T12:11:57.321486866Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "94a6c877b01ac3a1638f1c5cde87e7c58be9ce0aafd4a78efcb96528ab00ed94": {
                "Name": "c2",
                "EndpointID": "a5c12fb3800991228f8dc3a2a8de1d6f4865439701a83558e4430c2aebf783a8",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

  • 进入到Docker0网桥之后就按照主机上的路由取决于后续如何走。如下就是主机的路由表,访问目标IP100.96.2.2的设备会命中第二条匹配规则,意思是访问100.96.0.0/16网段的数据去flannel0设备,并且愿IP100.96.1.0。对应到上图的步骤2。
# Node1路由表
$ ip route
1 Default via 10.168.0.1 dev eth0
2 100.96.0.0/16 dev flannel0 proto kernel scope link src 100.96.1.0
3 100.96.1.0/24 dev docker0 proto kernel scope link src 100.96.1.1
4 10.168.0.0/24 dev eth0 proto kernel scope link src 10.168.0.2

Flannel0设备
  • 上述说了Flannel0是一个TUN虚拟三层网络设备,主要是在内核态和用户态之间传递IP包;继续按上述的流程分析,数据报文从内核态到达Flannel0设备之后,会被传递给创建Flannel0设备的进程也就是FlannelD进程,然后flanneld进程看到目的地址是100.96.2.2,就把数据报文发送到Node2节点上的flanneld进程监听的UDP端口上。flanneld会把要发送的数据封装为一个UDP数据包发出去。对应到上图的步骤3、4、5、6。
    • flanneld进程是怎么知道100.96.2.2这个ipNode2上呢,这是因为它利用了子网,在每个节点启动的时候都会被指定一个字网段,通过字网就能确定这个ip是属于那个节点的,子网被存在etcd里面。
  • Node2上的flanneld进程收到数据包之后,会发送到flannel0设备上,这是一个从用户态到内核态的过程,所以Linux内核网络协议栈就会负责处理这个IP包,具体的处理方法,就是通过本机的路由表来寻找这个IP包的下一步流向。对应到上图的步骤7、8。
# node2上的路由表
$ ip route
1 default via 10.168.0.1 dev eth0
2 100.96.0.0/16 dev flannel0 proto kernel scope link src 100.96.2.0
3 100.96.2.0/24 dev docker0 proto kernel scope link src 100.96.2.1
4 10.168.0.0/24 dev eth0 proto kernel scope link src 10.168.0.3

  • 通过解析出目标ip100.96.2.2,他和第三条的路由规则匹配更加精确,这条路由规则的意思是把发往100.96.2.0/24网段的的数据包发送到docker0设备上去,并且设置源IP100.96.2.1。对应到上图的步骤9。
  • 数据包进入到docker0设备之后,docker0网桥会扮演二层交换机的角色,将数据包发送给正确的veth pair对,进过此设备之后就进入到Contaniner-2的网络协议栈中。对应到上图的步骤10。

Flannel UDP模式提供的是一个三层的Overlay网络,它首选对发出端的IP包进行UDP封装,然后在接收端进行解封装拿到原始IP包,进而把这个IP包转发给目标容器。

Flannel UDP模式有严重的性能问题,主要问题是,由于使用了TUN设备,仅在发出IP包的过程中,就需要经过三次用户态和内核态之间的数据拷贝。

tun

2、三层Calico ipip
3、二层+三层VXLAN

VXLAN网络的设计思想是,在现有的三层网络之上,覆盖一层虚拟的、由内核VXLAN模块负责维护的二层网络,使得连接在这个VXLAN二层网络上的主机之间,可以像在同一个局域网那样自由通信。

为了能在二层网络上打通隧道,VXLAN会在宿主机上设置一个特殊的网络设备作为隧道的两端,这个设备就叫作VTEP,全称是:(VXLAN Tun End Poin)虚拟隧道端点。

VTEP设备的作用和flanneld进程的作用是一样的,就是做数据包的封装和解封装,只不过,它进行封装和解封装的是二层数据帧,而且这个工作流程,全都是在内核里完成的。

Underlay 模式

1、三层模式BGP

BGP

如上图是一个典型的BGP网络拓扑图,通过Route1Route2作为边界路由网关,把其它LAN的路由信息写入到当前的路由中,就实现了不同LAN下的路由信息同步,达到三层网络全通。

Calico BGP 使用

​ 在了解了BGP之后,Calico项目的架构就非常容易理解了,它把每个主机节点当做一个边界路由来看待,所以在每个节点上都保存了所有其他节点的路由信息,我们来分析一下它的实现,它由三个部分组成:

  • CalicoCNI插件,这是CaclicoKubernetes对接的部分。
  • BIRDBGP的客户端,专门负责在集群里面分发路由信息。
  • Felix,它是一个Demoset,负责在宿主机上插入路由规则(写入Linux内核的FIB转发信息表),以及维护Calico所需的网络设备等工作。

Calico BGP模式和Flannel host-gw模式不同,Calico没有创建任何的虚拟网桥设备,Calico的工作方式采用如下图来说明。

calico_bgp

Calico BGP模式的网络交互图如上所示,如container1需要访问Container3,我们来分析下网络如何到达。因为没有采用cni0虚拟网桥设备,因此veth设备对的一端是在容器的Network Namespace中的,一端是在宿主机的容器网络空间的,

  • 首先Calico CNI插件还需要在每个宿主机上为每个容器的Veth Pair设备配置一条路由规则,由于接受传入的IP包,比如:
# 192.168.0.2节点上的路由信息有
$ ip route
10.20.0.2 dev cali1 scope link
10.20.0.3 dev cali2 scope link

# 192.168.0.3节点上的路由信息有
$ ip route
10.20.1.2 dev cali3 scope link
10.20.1.3 dev cali4 scope link

  • 每个节点上还有BGP广播的其它节点路由协议,比如:
# 192.168.0.2上有一条指向192.169.0.3的路由
$ ip route
10.20.1.0/24 via 192.168.0.3 dev eth0
# 192.168.0.3上有一条指向192.168.0.2的路由
$ ip route
10.20.0.0/24 via 192.168.0.2 dev eth0

  • 默认的Calico BGP使用的是Node to Node的模式,会导致每个节点上的连接以N^2的数独增长,一般推荐少于使用100个节点的集群中。在大规模集群中,需要用到一个叫Route Reflector的模式,所有的路由会统一上报到一个中心节点,其它节点都从中心节点进行同步。

Calico BGP模式和Flannel host-gw模式一样,都有一个对基础网络设施的依赖,要求集群宿主机之间是二层可达的。假如宿主机之间处在不同的LAN下,就需要使用Calico ipipOverlay模式了。

2、二层VLAN
3、Flannel host-gw

Flannel host-gw模式一张图就可以讲清楚他们之间的实现原理。

host-gw

  • CNI0设备是一个三层交换机,具有二层交换机的功能,同时具有独立IP

flannelDaemonset的方式在每个节点上启动一个Flanneld进程,用于维护每个节点上的路由信息,实现方式是,本地

如:192.168.1.0/24 via 10.20.0.3 dev eth0路由定义了访问192.168.1.0/24网段的下一跳为10.20.0.3并从eth0设备出。

然后IP包被封装成帧发送出去的时候,会使用路由表里的下一跳来设置目的MAC地址;这样,就能通过二层网络达到目的宿主机。

因为他会使用下一跳的目的MAC地址,所以它要求宿主机之间是二层联通的,不如就没法通过使用ARP协议用IP去获取MAC地址了。

本文使用 文章同步助手 同步