06 K8S 集群网络


1
2
3
4
5
6
7
8
9
10
11
12
13
# 查看cni网桥的接入
[root@k8s-master1 opt]# yum install bridge-utils -y
[root@k8s-node2 ~]# brctl show cni0
bridge name bridge id STP enabled interfaces
cni0 8000.b6051fb07b10 no veth1938bf5f
veth70d9e02c
vethcc33255a
vethce666639

# docker 网桥
[root@k8s-node2 ~]# brctl show docker0
bridge name bridge id STP enabled interfaces
docker0 8000.0242c8d4d7fe no
1
# 网络插件 就是解决跨主机node上的pod通信
1
2
3
4
5
6
# kubelet 创建 容器
kubelet -> dockerapi (dockershim)

# pod的形成
kubelet -> dockerapi -> 容器1
kubelet -> 二进制文件 -> 网络配置 -> 容器1
1
2
3
4
# cni 网络规范:
# 接入 第三方网络插件
# kubelet 去调用网络插件 为容器 创建 网络
# 实现解耦,可以接入任何网络组建

交换和路由

1
2
3
# 两个问题: 
1. 一个局域网内,主机A 和 主机B 之间通信,数据包传输流程
2. 不在一个局域网之内,主机A 和 主机B 通信流程

交换技术

  • 路由器:网络出口
  • 核心层:主要完成数据高效转发、链路备份等
  • 汇聚层:网络策略、安全、工作站交换机的接入、VLAN之间通信等功能
  • 接入层:工作站的接入
  1. 交换机工作在OSI参考模型的第二层,即数据链路层。
  2. 交换机拥有一条高带宽的背部总线交换矩阵,在同一时间可进行多个端口对之间的数据传输。

交换技术分为2层和3层:

  • 2层:主要用于小型局域网,仅支持在数据链路层转发数据,对工作站接入。

  • 3层:三层交换技术诞生,最初是为了解决广播域的问题,多年发展,三层交换机书已经成为构建中大型网络的主要力量。

广播域

  1. 交换机在转发数据时会先进行广播,这个广播可以发送的区域就是一个广播域。
  2. 交换机之间对广播帧是透明的,所以交换机之间组成的网络是一个广播域。
  3. 路由器的一个接口下的网络是一个广播域,所以路由器可以隔离广播域。

ARP(地址解析协议,在IPV6中用NDP替代)

  1. 发送这个广播帧是由ARP协议实现,ARP是通过IP地址获取物理地址的一个TCP/IP协议。

三层交换机

  1. 前面讲的二层交换机只工作在数据链路层,路由器则工作在网络层。
  2. 而功能强大的三层交换机可同时工作在数据链路层和网络层,并根据 MAC地址或IP地址转发数据包。

VLAN(Virtual Local Area Network):虚拟局域网

  1. VLAN是一种将局域网设备从逻辑上划分成一个个网段。
  2. 一个VLAN就是一个广播域,VLAN之间的通信是通过第3层的路由器来完成的。
  3. VLAN应用非常广泛,基本上大部分网络项目都会划分vlan。
  4. VLAN的主要好处:
  • 分割广播域,减少广播风暴影响范围。
  • 提高网络安全性,根据不同的部门、用途、应用划分不同网段

重点1: 在一个2层交换机下的两台服务器的通信流程

1
2
3
4
5
主机A(10)
主机B(20)
网段:192.168.31.0/24
源IP 和 目的IP 都是一个子网
四元组: 源IP 源MAC 目的IP 目的MAC ?
  1. 在本机查找ARP缓存表,是否存在要发送的数据的目的地之 -> 主机B的MAC地址
  2. 如果没有,本机会发送ARP广播包,达到2层交换机,询问20(主机B)的MAC地址是多少
  3. 交换机也会查本地ARP缓存表
    • 如果有 就直接响应主机A,主机A得到后重新封装数据包中的目的MAC
    • 如果没有,会发送除主机A之外的所有主机,每个主机都判断目的IP是不是自己,主机B发现是自己将mac地址响应回给交换机,交换机返回给主机A,并保存到自己的缓存表,不同的则将包丢弃
  4. 主机A获取到主机B的MAC地址,将目的MAC封装在包内发送给交换机,交换机再转发给主机B。
  5. ARP缓存表记录着经过二层传输的源IP和目的IP的MAC地址,以便下次传输能直接使用。

  6. 主机A发送的广播包 是否会被所有的交换机收到?

    • 交换机A 和 交换机B 并不是直连的 无法收到
    • 交换机A 可以通过 3层交换机 转发给 交换机B ,如果没有3层交换机,上面是路由器就无法收到,路由器隔离广播域。
    • 3层交换机 既可以处理2层数据包也可以处理3层数据包

重点2: 不在一个局域网之内,主机A 和 主机B 通信流程

1
2
3
1. 主机A 和 主机B 不在一个Vlan 
2. Vlan1 192.168.31.0/24
3. Vlan2 192.168.32.0/24
  1. 目的地址与本机不在同一个子网,会走默认网关
1
2
3
4
[root@k8s-master1 ~]# ip route
default via 172.31.239.253 dev eth0 # 默认网关
10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink
...
  1. 网关会查询路由表

  2. 路由器的路由表得知接口是哪个,到达哪个交换机,最后到服务器上。此时交换机会记录mac地址

    • 目的地址 192.168.32.0 网关 接口 B

Kubernetes 网络模型

  1. Kubernetes 要求所有的网络插件实现必须满足如下要求:

    • 一个Pod一个IP
    • 所有的 Pod 可以与任何其他 Pod 直接通信,无需使用 NAT 映射
    • 所有节点可以与所有 Pod 直接通信,无需使用 NAT 映射
    • Pod 内部获取到的 IP 地址与其他 Pod 或节点与其通信时的 IP 地址是同一个。

Kubernetes 网络组件之 Flannel

  1. Flannel是CoreOS维护的一个网络组件,Flannel为每个Pod提供全局唯一的IP,Flannel使用ETCD来存储Pod子网与Node IP之间的关系。
  2. flanneld守护进程在每台主机上运行,并负责维护ETCD信息和路由数据包。
1
2
3
4
5
1. k8s中所有的pod的IP地址都必须是唯一的,如果不唯一,数据包就不知道发送给谁,因为是全联通。
2. 如何保证POD的IP唯一,就是再每个node上都分配一个 子网 ,每个node都由单独的子网(不同网段的Vlan)
3. Flannel 会预先设计一个大的子网,再从大子网将node分配成小子网, 这些信息都会被存储在 etcd中
4. 每个子网与node绑定都有关系记录,用于二层数据包传输
5. flanneld守护进程在每台主机上运行,维护路由规则和etcd中的路由信息

Flannel 部署

1
https://github.com/coreos/flannel
1
2
3
4
5
6
7
8
9
# 每个node节点都以 DaemonSet 形式部署 ,保证每个node节点上运行守护进程
# flanneld 守护进程 负责本地路由表设定和维护etcd中的数据,比如将本地分配的子网上报给etcd

[root@k8s-master1 ~]# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-6d8cfdd59d-k5wl9 1/1 Running 16 9d
kube-flannel-ds-amd64-2k5kz 1/1 Running 9 9d
kube-flannel-ds-amd64-gvs6b 1/1 Running 16 9d
kube-flannel-ds-amd64-hwglz 1/1 Running 16 9d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 查看 yaml 文件
# 1. 使用 ConfigMap 存储了 flannel的配置文件 -> 子网的配置文件
# 2. 重要的两个配置:
# 预先规划好大子网,然后填写进去,大子网一定不能与物理网络冲突
# 还需要对应好 工作模式,VXLAN 是默认的

[root@k8s-master1 prometheus-k8s]# vim /tmp/k8s/kube-flannel.yaml

# 大子网配置 和 工作模式(封装数据包的方式)
...
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 1. 部署后,配置信息会在每个node上的 
[root@k8s-master1 cni]# cd /etc/cni/net.d/
[root@k8s-master1 net.d]# ls
10-flannel.conflist

[root@k8s-master1 net.d]# cat 10-flannel.conflist
{
"cniVersion": "0.2.0",
"name": "cbr0",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
1
2
3
4
5
6
1. flannel 的网络配置在
[root@k8s-master1 net.d]# cat /var/run/flannel/subnet.env
FLANNEL_NETWORK=10.244.0.0/16 # 大子网
FLANNEL_SUBNET=10.244.2.1/24 # 被分配的小子网 可被分配255个小子网
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true
1
2
3
3. 二进制文件,kubelet会调用这个二进制文件 为创建的每个pod 配置网络信息,从小子网网段里分配IP地址
[root@k8s-master1 net.d]# ls -l /opt/cni/bin/firewall
-rwxr-xr-x 1 root root 5968249 Aug 15 18:05 /opt/cni/bin/firewall

Flannel 工作模式及原理

Flannel支持多种数据转发方式

  • UDP:最早支持的一种方式,由于性能最差,目前已经弃用。
  • VXLAN:Overlay Network方案 ? ,源数据包封装在另一种网络包里面进行路由转发和通信 - 隧道方案
  • Host-GW:Flannel 通过在各个节点上的Agent进程,将容器网络的路由信息刷到主机的路由表上,这样一来所有的主机都有整个容器网络的路由数据了。 - 路由方案
  • 大多数网络插件都具有这两种方案,比如 Calico

VXLAN

  1. 二进制部署 支持 cni
1
2
3
4
5
6
7
8
9
10
[root@k8s-master1 net.d]# cat /opt/kubernetes/cfg/kube-controller-manager.conf
# 允许node自动分配网络
--allocate-node-cidrs=true \
# 指定 pod 网络网段 需要与 Flannel 的网段对应上 kube-flannel.yaml 中的 Network
--cluster-cidr=10.244.0.0/16 \


# kubelet 配置
[root@k8s-master1 net.d]# cat /opt/kubernetes/cfg/kubelet.conf
--network-plugin=cni \
  1. 为了能够在二层网络上打通“隧道”,VXLAN 会在宿主机上设置一个特殊的网络设备作为“隧道”的两端。
  2. 这个设备就叫作 VTEP,即:VXLAN Tunnel End Point(虚拟隧道端点)。解决跨网段的数据通信。
  3. 下图flannel.1的设备就是VXLAN所需的VTEP设备。示意图如下:

1
2
3
4
# 查看 每个node上flannel分配的子网
[root@k8s-node1 ~]# ifconfig
# flannel.1 虚拟网卡
# 当前这个node上创建的pod 都是从这个子网网段拿到IP,所以每个POD的IP地址不唯一

VXLAN 数据包的传输流程

1.容器路由: 容器根据路由表从eth0发出,通过 veth 到达cni0也就到达了node服务器

1
2
3
4
root@my-nginx-67f56d94f7-lwl9c:/# ip route
default via 10.244.2.1 dev eth0 # 默认路由
10.244.0.0/16 via 10.244.2.1 dev eth0
10.244.2.0/24 dev eth0 proto kernel scope link src 10.244.2.61

veth pair(对)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
1. veth 设备对 网线的一头在容器,一头在node上
[root@k8s-master1 ~]# vim /opt/demo/my-nginx.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: my-nginx
name: my-nginx
spec:
replicas: 3
selector:
matchLabels:
app: my-nginx
template:
metadata:
labels:
app: my-nginx
spec:
containers:
- image: nginx:1.7.9
name: nginx

[root@k8s-master1 demo]# kubectl apply -f my-nginx.yaml

[root@k8s-master1 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-nginx-67f56d94f7-975h7 1/1 Running 0 8m49s 10.244.0.67 k8s-node2 <none> <none>
my-nginx-67f56d94f7-lngj2 1/1 Running 0 8m49s 10.244.1.55 k8s-node1 <none> <none>
my-nginx-67f56d94f7-lwl9c 1/1 Running 0 8m49s 10.244.2.61 k8s-master1 <none> <none>

# 进入master1里面的一个容器
[root@k8s-master1 demo]# kubectl exec -it my-nginx-67f56d94f7-lwl9c bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 创建容器时, node上 会被创建一个新的网络接口 
# veth6cdc3884 被挂到了 cni0 上,veth6cdc3884 就是新创建容器的虚拟网卡。
# cni0 就像是一个二层交换机,所有的pod的网络都会到达这里
# cni0 就是 flannel帮助创建的 拥有独立的IP地址和子网
# 如果当前node上还有其他的pod 他们都通过这个 cni0 就可以通信,因为cni0就好比一个二层交换机
# 同节点pod通信 走 cni0
# 不同节点 需要走 宿主机路由表 走默认网关: ip route default via 172.31.239.253 dev eth0


# 安装网桥工具
yum install bridge-utils

[root@k8s-master1 ~]# brctl show
# [root@k8s-master1 ~]# brctl show cni0

bridge name bridge id STP enabled interfaces
cni0 8000.1e9a263ac57d no veth6cdc3884 # 好比交换机的一个端口
vethe28b9770
docker0 8000.02427b6ab66d no

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 进入容器,里面的 网卡 eth0@if10 和 node上的 veth6cdc3884 就是 一对 veth pair
# veth pair 是一种成对出现的特殊网络设备,可以把它们想象成由一根虚拟网线连接起来的一对网卡,
# 网卡的一头(eth0@if10)在容器中,另一头(veth6cdc3884)挂在网桥 cni0 上,
# 其效果就是将 eth0@if10 也挂在了 cni0 上。
# eth0@if10 也已经配置cni0提供的网段

node上的cni0
7: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
link/ether 1e:9a:26:3a:c5:7d brd ff:ff:ff:ff:ff:ff
inet 10.244.2.1/24 brd 10.244.2.255 scope global cni0

# 容器的网络被cni0分配,所以容器的默认路由就是 cni0
3: eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue state UP
link/ether 86:8b:cb:72:68:d2 brd ff:ff:ff:ff:ff:ff
inet 10.244.2.61/24 brd 10.244.2.255 scope global eth0
valid_lft forever preferred_lft forever

root@my-nginx-67f56d94f7-lwl9c:/# ip route
default via 10.244.2.1 dev eth0 # 默认路由
10.244.0.0/16 via 10.244.2.1 dev eth0
10.244.2.0/24 dev eth0 proto kernel scope link src 10.244.2.61
1
2
3
4
5
6
7
8
9
10
11
12
13
# 不同节点 需要走 宿主机路由表 走默认网关
[root@k8s-master1 ~]# ip route

default via 172.31.239.253 dev eth0
10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink # flannel 生成
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink # flannel 生成
10.244.2.0/24 dev cni0 proto kernel scope link src 10.244.2.1 # flannel 生成

169.254.0.0/16 dev eth0 scope link metric 1002
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 # docker0 flannel并没有走
172.31.224.0/20 dev eth0 proto kernel scope link src 172.31.228.50 # 默认自带

# 现在数据已经到了宿主机上,数据包里面的数据 源IP 10.244.1.10 目的IP 10.244.2.10

2.主机路由: 数据包进入到宿主机虚拟网卡cni0,根据路由表转发到flannel.1虚拟网卡,也就是,来到了隧道的入口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. 宿主机发现目的IP 10.244.2.10 本机是无法处理,需要走默认网关
2. 他会查看哪个是匹配 10.244.2.10 我这里和图上的实例有区别,因为本机node是10.244.2.0/24 实际上会有匹配的

default via 172.31.239.253 dev eth0

10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink # flannel 生成
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink # flannel 生成
10.244.2.0/24 dev cni0 proto kernel scope link src 10.244.2.1 # flannel 生成

# 目的 10.244.1.0/24 下一跳 10.244.1.0 发给 flannel.1 onlink

6: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
link/ether e6:1e:c6:83:6d:8c brd ff:ff:ff:ff:ff:ff
inet 10.244.2.0/32 scope global flannel.1

# 1. 数据到达 node后 根据路由表 转给了 flannel.1
# 2. flannel.1 交给 VXLAN ,VXLAN 来进行封包

3. VXLAN封装: 而这些VTEP设备(二层)之间组成二层网络必须要知道目的MAC地址。这个MAC地址从哪获取到呢?其实在flanneld进程启动后,就会自动添加其他节点ARP记录,可以通过ip命令查看,如下所示:

1
2
3
[root@k8s-master1 ~]# ip neigh show dev flannel.1
10.244.0.0 lladdr fa:8c:e4:15:e4:20 PERMANENT
10.244.1.0 lladdr 36:9d:f8:71:19:e0 PERMANENT
1
2
3
4
5
6
7
8
# 数据包解析:
源IP: 10.244.1.10
源MAC: 自己
目的IP: 10.244.2.10
目的MAC : flannel.1 提供,flannel每个节点都存储下一跳网关的MAC地址 ,ip neigh show dev flannel.1 可以看到

# 对于宿主机来说这个帧没有实际意义,宿主机发不出去
# 需要二次封包

4. 二次封包: 知道了目的MAC地址,封装二层数据帧(容器源IP和目的IP)后,对于宿主机网络来说这个帧并没有什么实际意义。接下来,Linux内核还要把这个数据帧进一步封装成为宿主机网络的一个普通数据帧,好让它载着内部数据帧,通过宿主机的eth0网卡进行传输。

5. 封装到UDP包发出去: 现在能直接发UDP包嘛?到目前为止,我们只知道另一端的flannel.1设备的MAC地址,却不知道对应的宿主机地址是什么。

  1. flanneld进程也维护着一个叫做FDB的转发数据库,可以通过bridge fdb命令查看:
1
2
3
[root@k8s-master1 ~]# bridge fdb show  dev flannel.1
36:9d:f8:71:19:e0 dst 172.31.228.52 self permanent
fa:8c:e4:15:e4:20 dst 172.31.228.53 self permanent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 根据图中 udp数据包解析 将原来的包上面再加一次层 :
# 目的就是让这个包直接传输到目的node主机上

# 里面一层 容器到容器数据包
源IP: 10.244.1.10
源MAC: 自己
目的IP: 10.244.2.10
目的MAC : flannel.1 提供 : ip neigh show dev flannel.1

# 外面一层 两个宿主机
源IP: 192.168.31.62
源MAC: 自己
目的IP: 192.168.31.63
目的MAC : flannel.1 提供 : bridge fdb show dev flannel.1 对应目的ip的mac地址

[root@k8s-master1 ~]# bridge fdb show dev flannel.1

36:9d:f8:71:19:e0 dst 172.31.228.52 self permanent
fa:8c:e4:15:e4:20 dst 172.31.228.53 self permanent

可以看到,上面用的对方flannel.1的MAC地址对应宿主机IP,也就是UDP要发往的目的地。使用这个目的IP进行封装。

6. 数据包到达目的宿主机 Node1的eth0网卡发出去,发现是VXLAN数据包,把它交给flannel.1设备。flannel.1设备则会进一步拆包,取出原始二层数据帧包,发送ARP请求,经由cni0网桥转发给container。

  1. 数据包头部里面会有 VXLAN 标记,加了编号VNI,flannel.1的1就是编号,交给flannel.1去处理
  2. 拿到源IP和目的IP,去判断,一看是cni0网桥的交给cni0
  3. cni0进行广播 拿着ip进行广播, 最后转发到容器里
  4. 最复杂的地方在于flannel.1的vtep 对数据的封包与解封包
  5. flannel.1 这种封包 会导致效率下降 ,VXLAN这种形式就是Overlay Network方案,源数据包封装到另一层网络里,叠加网络
  6. VXLAN 只是实现封包解封包的技术
  7. VXLAN 在大型网络架构中,效率并不高

Host-GW

  1. host-gw模式相比vxlan简单了许多,直接添加路由,将目的主机当做网关,直接路由原始封包。
  2. 没有 flannel.1 阶段了
  3. host-gw模式 就像把每一个node主机当做网关

设置flannel使用host-gw模式

1
2
3
4
5
6
7
8
[root@k8s-master1 ~]# vim /tmp/k8s/kube-flannel.yaml 
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "host-gw"
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
# 更新 flannel 部署
# 更新网络相关操作,切换模式,一定要再停机维护的时候进行,因为网络会受到修改,以免出现网络冲突
# 肯定要再模拟测试环境完成后 再上线更改

[root@k8s-master1 ~]# kubectl delete -f /tmp/k8s/kube-flannel.yaml
[root@k8s-master1 ~]# kubectl apply -f /tmp/k8s/kube-flannel.yaml

[root@k8s-master1 ~]# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-6d8cfdd59d-k5wl9 1/1 Running 17 10d
kube-flannel-ds-amd64-5gfps 1/1 Running 0 2m5s
kube-flannel-ds-amd64-6hpzc 1/1 Running 0 2m5s
kube-flannel-ds-amd64-ht9ll 1/1 Running 0 2m5s
1
2
3
4
5
6
7
8
9
当你设置flannel使用host-gw模式,flanneld会在宿主机上创建节点的路由表:
[root@k8s-master1 ~]# ip route
default via 172.31.239.253 dev eth0
10.244.0.0/24 via 172.31.228.53 dev eth0
10.244.1.0/24 via 172.31.228.52 dev eth0
10.244.2.0/24 dev cni0 proto kernel scope link src 10.244.2.1
169.254.0.0/16 dev eth0 scope link metric 1002
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.31.224.0/20 dev eth0 proto kernel scope link src 172.31.228.50
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. 目的 IP 地址属于 10.244.1.0/24 网段的 IP 包,应该经过本机的 eth0 设备发出去(即:dev eth0);
2. 并且,它下一跳地址是 172.31.228.52(即:via172.31.228.52)。
3. 一旦配置了下一跳地址,那么接下来,当 IP 包从网络层进入链路层封装成帧的时候,eth0 设备就会使用下一跳地址对应的 MAC 地址,作为该数据帧的目的 MAC 地址。
4. 不会再使用 flannel.1 这个设备,cni0网桥的数据包会走路由表,因为发送的目的IP地址并非同一网段,需要走路由表
5. 宿主机重新封包 目的地址就是 路由表中对应的地址 比如 172.31.228.52 (测试里面的 192.168.31.63 )
6. 他看到 172.31.228.52 和自己node服务器所在同一个网段,也就是二层传输,需要获取目的的mac地址
7. 如果不知道 172.31.228.52 的mac地址,就会走arp广播 也就是一个局域网之内的主机A和主机B通信

# 根据二层数据转发的包
源IP: 10.244.1.10
源MAC: 自己
目的IP: 10.244.1.20
目的MAC : ARP广播包响应回来的
8. 而 Node 2 的内核网络栈从二层数据帧里拿到 IP 包后,会“看到”这个 IP 包的目的 IP 地址是 10.244.1.20,即 container-2 的 IP 地址。这时候,根据 Node 2 上的路由表,该目的地址会匹配到第二条路由规则(也就是 10.244.1.0 对应的路由规则),从而进入 cni0 网桥,进而进入到 container-2 当中。
# 这里写的乱的原因是 图和实际的数据不一致,不过大概的路线没错,即走本地路由到达对应node,二层路由
# 限制就是 每个node节点的 二层能通 (在同一个网段)
# 性能更好,没有封包解封包

小总结

  1. 如果追求性能 并且 node的二层网络能通 可以使用 Host-GW 工作模式
  2. 如果node不能通过二层通信,需要路由转发(可能不同Vlan),使用 VXLAN
  3. 用户访问 - svc(nodeport) -> iptables/ipvs -> node1\node2\node3 ,如果访问的pod正好是本机的node那么直接走本地的路由访问,
    如果到了其他node,那么就是跨主机网络通信,需要走路由表,如果工作模式是vxlan,那么要走flannel.1进行封包解封包流程
    重点是看哪种工作流程和路由规则,一定要搞清楚里面的概念很多,加油吧
  4. Host-GW 二层包转发

阿里云主机的 host-gw方案不通

  1. 阿里云不通,但是本地虚拟机是通的,测试环境迁移到虚拟机
  2. Calico BGP 也没通,测试下虚拟机 通不通

Kubernetes 网络方案之 Calico

  1. Calico是一个纯三层的数据中心网络方案,Calico支持广泛的平台,包括Kubernetes、OpenStack等。
  2. Calico 在每一个计算节点利用 Linux Kernel 实现了一个高效的虚拟路由器( vRouter) 来负责数据转发,而每个 vRouter 通过 BGP 协议负责把自己上运行的 workload 的路由信息向整个 Calico 网络内传播。
  3. 此外,Calico 项目还实现了 Kubernetes 网络策略,提供ACL功能。
  4. 访问控制列表(ACL)是一种基于包过滤的访问控制技术,它可以根据设定的条件对接口上的数据包进行过滤,允许其通过或丢弃。

BGP 概述

  1. 实际上,Calico项目提供的网络解决方案,与Flannel的host-gw模式几乎一样。
  2. 也就是说,Calico也是基于路由表实现容器数据包转发,但不同于Flannel使用flanneld进程来维护路由信息的做法,而Calico项目使用BGP协议来自动维护整个集群的路由信息。
  3. GP英文全称是Border Gateway Protocol,即边界网关协议,它是一种自治系统间的动态路由发现协议,与其他 BGP 系统交换网络可达信息。
1
2
3
4
5
6
7
8
9
10
11
# 维护路由信息
Calico BGP协议 Flannel Host-GW工作方式基本一致,BGP完成数据交换,大型网络架构的动态协议,技术高大上,性能牛逼
Flannel flanneld进程 Flannel 自己做数据交换

# BGP 协议 在集群规模达到一定量时 性能更棒
BGP 机房多线(双线)
BGP 协议 涉及到 静态路由和动态路由,路由选择路径转发,根据路由表,理由表又分为静态和动态
静态路由表 由人工添加维护
动态路由表 相互感知
当网络架构中的路由很多时,甚至跨公司跨机房,需要手动配置?人工两很大,到达一定的规模 就会使用动态路由协议 BGP就是其中一个
目的: 动态感知整个网络路由拓扑 路由之间相互学习

为了能让你更清楚理解BGP,举个例子:

  1. 在这个图中,有两个自治系统(autonomous system,简称为AS):AS 1 和 AS 2。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 什么是自治系统
1. 在互联网中,一个自治系统(AS)是一个有权自主地决定在本系统中应采用何种路由协议的小型单位。
2. 这个网络单位可以是一个简单的网络也可以是一个由一个或多个普通的网络管理员来控制的网络群体,
3. 它是一个单独的可管理的网络单元(例如一所大学,一个企业或者一个公司个体)。
4. 一个自治系统有时也被称为是一个路由选择域(routing domain)。
5. 一个自治系统将会分配一个全局的唯一的16位号码,有时我们把这个号码叫做自治系统号(ASN)。

简单来说:就是两个不依赖其他公司的网络系统,独立运行管理

6. 在正常情况下,自治系统之间不会有任何来往。
7. 如果两个自治系统里的主机,要通过 IP 地址直接进行通信,我们就必须使用路由器把这两个自治系统连接起来。
8. BGP协议就是让他们互联的一种方式。

BGP的作用: 让两个独立的网络互通,只要两个路由相通,BGP来负责动态添加路由,就可以实现,动态学习相互的路由表信息。
减少人工添加路由

Calico BGP实现

  1. 在了解了 BGP 之后,Calico 项目的架构就非常容易理解了,Calico主要由三个部分组成:
1
2
3
4
- Felix:以DaemonSet方式部署,运行在每一个Node节点上,主要负责维护宿主机上路由规则以及ACL规则。
- BGP Client(BIRD):主要负责把 Felix 写入 Kernel 的路由信息分发到集群 Calico 网络。
- Etcd:分布式键值存储,保存Calico的策略和网络配置状态。
- calicoctl:允许您从简单的命令行界面实现高级策略和网络。

Calico 部署

1. 删除Flannel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 切换网络别忘记停机维护 夜深人静
# 正式环境先做好配置再删除
[root@k8s-master1 ~]# kubectl delete -f /tmp/k8s/kube-flannel.yaml

# 下载部署文件
[root@k8s-master1 k8s]# cd /tmp/k8s/
[root@k8s-master1 k8s]# curl https://docs.projectcalico.org/v3.9/manifests/calico-etcd.yaml -o calico.yaml

# 下载完后还需要修改里面配置项:
1. 配置连接etcd地址,如果使用https,还需要配置证书。(ConfigMap,Secret)
2. 根据实际网络规划修改Pod CIDR(CALICO_IPV4POOL_CIDR)
3. 选择工作模式(CALICO_IPV4POOL_IPIP),支持BGP,IPIP

[root@k8s-master1 k8s]# vim calico.yaml
# 在k8s中 etcd一定要独立部署
1
2
3
4
5
6
7
8
# 1 添加证书
# 证书位置 [root@k8s-master1 ~]# cd /opt/etcd/ssl/
# 放在secret 需要base64编码
[root@k8s-master1 ssl]# cat /opt/etcd/ssl/ca.pem |base64 -w 0
[root@k8s-master1 ssl]# cat /opt/etcd/ssl/server-key.pem |base64 -w 0
[root@k8s-master1 ssl]# cat /opt/etcd/ssl/server.pem |base64 -w 0

# 复制粘贴到 calico.yaml 中

1
2
3
4
# 2 指定读取位置
etcd_ca: "/calico-secrets/etcd-ca"
etcd_cert: "/calico-secrets/etcd-cert"
etcd_key: "/calico-secrets/etcd-key"
1
2
3
# 3. 指定etcd连接字符串
[root@k8s-master1 ssl]# cat /opt/kubernetes/cfg/kube-apiserver.conf
https://172.31.228.50:2379,https://172.31.228.52:2379,https://172.31.228.53:2379

1
2
3
4
5
6
7
# 4. 修改 Pod CIDR  
# 要与 [root@k8s-master1 ssl]# cat /opt/kubernetes/cfg/kube-controller-manager.conf 中的 --cluster-cidr=10.244.0.0/16 \ 一致
[root@k8s-master1 k8s]# vim calico.yaml
/CALICO_IPV4POOL_CIDR

- name: CALICO_IPV4POOL_CIDR
value: "10.244.0.0/16"
1
2
3
4
5
6
7
8
9
10
11
# 5. 修改工作模式,也有两种 IPIP 和 BGP(应用最多)
# 不修改的话 默认是 IPIP 注释掉就是BGP

# Enable IPIP
- name: CALICO_IPV4POOL_IPIP
value: "Always"

# 修改成 Never 或者 off 关闭 默认就走BGP
# Enable IPIP
- name: CALICO_IPV4POOL_IPIP
value: "Never"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 6. 清理网络 每台node都要做
# 修改后保存,再删除 flannel网络
# 还需要删除 flannel的虚拟网卡配置 以免冲突
[root@k8s-master1 ssl]# ip link delete cni0
[root@k8s-master1 ssl]# ip link delete flannel.1

# 把之前 host-gw 路由也删除 保持纯净路由表
[root@k8s-master1 ssl]# ip route
default via 172.31.239.253 dev eth0
10.244.0.0/24 via 172.31.228.53 dev eth0
10.244.1.0/24 via 172.31.228.52 dev eth0
169.254.0.0/16 dev eth0 scope link metric 1002
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.31.224.0/20 dev eth0 proto kernel scope link src 172.31.228.50

[root@k8s-master1 ssl]# ip route delete 10.244.0.0/24 via 172.31.228.53 dev eth0
[root@k8s-master1 ssl]# ip route delete 10.244.1.0/24 via 172.31.228.52 dev eth0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 7. 部署 calico
[root@k8s-master1 k8s]# kubectl apply -f /tmp/k8s/calico.yaml

[root@k8s-master1 k8s]# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
calico-kube-controllers-5cc7b68d7c-qszdt 1/1 Running 0 6m16s
calico-node-pbs44 1/1 Running 0 6m16s
calico-node-qdbz4 1/1 Running 0 6m16s
calico-node-tk4lg 1/1 Running 0 6m16s
coredns-6d8cfdd59d-k5wl9 1/1 Running 23 10d

calico-node 在每台node上都部署,包含了BGP Client(BIRD)和 Felix
calico-kube-controllers 从etcd获取网络规则和策略

# 原先运行的pod 如果要是要 calico 网络 都需要重建,切换网络的影响还是挺大的
# 我之前的 jenkins和nfs pvc 都需要重建了
[root@k8s-master1 demo]# kubectl delete -f my-nginx.yaml
[root@k8s-master1 demo]# kubectl apply -f my-nginx.yaml

# 重建pod后相当于让网络做了路由学习 查看路由
# 阿里云主机不通 我改为 本地虚拟机 多创建几个pod

[root@k8s-master1 demo]# kubectl get pods --all-namespaces -o wide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
default my-nginx-77fdbc8556-g9hqp 1/1 Running 0 12m 10.244.36.65 k8s-node1 <none> <none>
default my-nginx-77fdbc8556-m7mp7 1/1 Running 0 12m 10.244.159.128 k8s-master1 <none> <none>
default my-nginx-77fdbc8556-pdn9z 1/1 Running 0 12m 10.244.169.128 k8s-node2 <none> <none>
default my-nginx2-8f86ff956-2tksg 1/1 Running 0 11m 10.244.169.129 k8s-node2 <none> <none>
default my-nginx2-8f86ff956-4krd6 1/1 Running 0 11m 10.244.159.129 k8s-master1 <none> <none>
default my-nginx2-8f86ff956-b2jld 1/1 Running 0 11m 10.244.36.66 k8s-node1 <none> <none>
ingress-nginx nginx-ingress-controller-2lmzq 1/1 Running 0 87m 192.168.0.101 k8s-master1 <none> <none>
ingress-nginx nginx-ingress-controller-mnf44 1/1 Running 0 87m 192.168.0.103 k8s-node2 <none> <none>
ingress-nginx nginx-ingress-controller-v22ld 1/1 Running 0 87m 192.168.0.102 k8s-node1 <none> <none>
kube-system calico-kube-controllers-5cc7b68d7c-xb5rf 1/1 Running 0 35m 192.168.0.102 k8s-node1 <none> <none>
kube-system calico-node-hwt8c 1/1 Running 0 35m 192.168.0.103 k8s-node2 <none> <none>
kube-system calico-node-v7c4g 1/1 Running 0 35m 192.168.0.101 k8s-master1 <none> <none>
kube-system calico-node-wfp2f 1/1 Running 0 35m 192.168.0.102 k8s-node1 <none> <none>
kube-system coredns-6d8cfdd59d-6wwkz 1/1 Running 0 13m 10.244.36.64 k8s-node1 <none> <none>
kubernetes-dashboard dashboard-metrics-scraper-566cddb686-57z4p 0/1 CrashLoopBackOff 11 87m 10.244.0.2 k8s-node2 <none> <none>
kubernetes-dashboard kubernetes-dashboard-c4bc5bd44-ntrlj 0/1 CrashLoopBackOff 10 87m 10.244.1.2 k8s-master1 <none> <none>

[root@k8s-master1 demo]# ping 10.244.36.65
[root@k8s-master1 demo]# ping 10.244.169.129

[root@k8s-master1 demo]# ip route

10.244.36.64/26 via 192.168.0.102 dev eth0 proto bird
10.244.159.128 dev cali196b9d660e1 scope link
blackhole 10.244.159.128/26 proto bird
10.244.159.129 dev cali642ae61a996 scope link
10.244.169.128/26 via 192.168.0.103 dev eth0 proto bird

Calico 管理工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
下载工具:
https://github.com/projectcalico/calicoctl/releases

[root@k8s-master1 opt]# ls -l calicoctl
-rw-r--r-- 1 root root 37090848 Jan 10 17:45 calicoctl
[root@k8s-master1 opt]# chmod +x calicoctl
[root@k8s-master1 opt]# mv calicoctl /usr/bin/

# 查看BGP节点的建立状态 ipv4 看到其他节点
[root@k8s-master1 k8s]# /usr/bin/calicoctl node status
Calico process is running.

IPv4 BGP status
+---------------+-------------------+-------+----------+-------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+---------------+-------------------+-------+----------+-------------+
| 192.168.0.102 | node-to-node mesh | up | 02:10:36 | Established |
| 192.168.0.103 | node-to-node mesh | up | 02:13:43 | Established |
+---------------+-------------------+-------+----------+-------------+

IPv6 BGP status
No IPv6 peers found.


# calicoctl 也有很多其他操作,更多的是向etcd里操作 需要配置文件 上面这个结果是tcp连接做的并没有读取etcd
[root@k8s-master1 opt]# netstat -anlp|grep bird

# 如果有其他操作比如修改工作模式,需要做配置文件
[root@k8s-master1 opt]# mkdir /etc/calico
[root@k8s-master1 k8s]# vim /etc/calico/calicoctl.cfg

apiVersion: projectcalico.org/v3
kind: CalicoAPIConfig
metadata:
spec:
datastoreType: "etcdv3"
etcdEndpoints: "https://192.168.0.101:2379,https://192.168.0.102:2379,https://192.168.0.103:2379"
etcdKeyFile: "/opt/etcd/ssl/server-key.pem"
etcdCertFile: "/opt/etcd/ssl/server.pem"
etcdCACertFile: "/opt/etcd/ssl/ca.pem

1
2
3
4
5
6
7
8
9
10
11
# 向etcd读取数据
[root@k8s-master1 opt]# calicoctl get nodes
NAME
k8s-master1
k8s-node1
k8s-node2

# 查看 IPAM的IP地址池:
[root@k8s-master1 opt]# calicoctl get ippool
NAME CIDR SELECTOR
default-ipv4-ippool 10.244.0.0/16 all()

Calico BGP 原理剖析

  1. Pod 1 访问 Pod 2大致流程如下:
1
2
3
4
5
6
7
8
9
10
1. 数据包从容器1出 到达Veth Pair另一端(宿主机上,以cali前缀开头);
2. 宿主机根据路由规则,将数据包转发给下一跳(网关);
3. 到达Node2,根据路由规则将数据包转发给cali设备,从而到达容器2。

[root@k8s-master1 jenkins]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-nginx-77fdbc8556-nzhhs 1/1 Running 0 48m 10.244.159.128 k8s-master1 <none> <none>
my-nginx-77fdbc8556-q759m 1/1 Running 0 48m 10.244.169.128 k8s-node2 <none> <none>
my-nginx-77fdbc8556-s7r75 1/1 Running 0 48m 10.24.436.64 k8s-node1 <none> <none>
nfs-client-provisioner-769f87c8f6-hw9m7 1/1 Running 0 45m 10.244.36.65 k8s-node1 <none> <none>

1
2
3
4
5
1. 创建pod kubelet 调用 calico 二进制程序创建网络,配置IP地址
[root@k8s-node1 tmp]# ls -l /opt/cni/bin/calico-ipam 和 calico # yaml文件中镜像准备

# 子网配置信息
[root@k8s-node1 tmp]# cat /etc/cni/net.d/10-calico.conflist
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
1. 到达cali之后就是到达了宿主机node服务器身上
2. 查看路由表

[root@k8s-node1 net.d]# ip route
default via 172.31.239.253 dev eth0
10.244.36.64 dev cali358923ad8fb scope link
blackhole 10.244.36.64/26 proto bird
10.244.36.65 dev calia8a14e44e0f scope link
10.244.159.128/26 via 172.31.228.50 dev eth0 proto bird
10.244.169.128/26 via 172.31.228.53 dev eth0 proto bird
...

3. 比如node1数据通往node2 10.24.436.64 -> 10.244.169.128
# 此时数据包里面:
源IP: 10.24.436.64
目的IP: 10.244.169.128

路由规则走:
10.244.169.128/26 via 172.31.228.53 dev eth0 proto bird # 走eth0口 下一条 172.31.228.53(node2的eth0地址)

4. 数据包到达node2后
# 首先看veth对
# 查看node2的路由规则
[root@k8s-node2 kubernetes]# ip route
default via 172.31.239.253 dev eth0
10.244.36.64/26 via 172.31.228.52 dev eth0 proto bird
10.244.159.128/26 via 172.31.228.50 dev eth0 proto bird
10.244.169.128 dev cali3480203cfd9 scope link # 这一条
blackhole 10.244.169.128/26 proto bird
...

# 数据目的地址是 10.244.169.128的 都会到达 cali3480203cfd9 也就是那块虚拟网卡cali
# cali3480203cfd9 又是容器的 veth 在宿主机上的一端 然后就发往容器了

5. Calico 没有网桥 没有cni0 数据怎么到达node上呢?
因为 cali3480203cfd9 cali这个虚拟网卡里面有个 arp代理机制

容器1 -> cali -> node1 -> 路由表 -> node2 -> cali -> 容器2 很像host-gw 2层网络互通
# 如果2层IP不能互通,就需要使用 IPIP模式 也就是node1 与 node2 无法直接互通的情况
  1. 其中,这里最核心的“下一跳”路由规则,就是由 Calico 的 Felix 进程负责维护的。
  2. 这些路由规则信息,则是通过 BGP Client 也就是 BIRD 组件,使用 BGP 协议传输而来的。
  3. 不难发现,Calico 项目实际上将集群里的所有节点,都当作是边界路由器来处理,它们一起组成了一个全连通的网络,互相之间通过 BGP 协议交换路由规则。
  4. 这些节点,我们称为 BGP Peer。

Route Reflector 模式(RR)

1
2
参考:
https://docs.projectcalico.org/master/networking/bgp
1
2
3
4
5
6
7
8
[root@k8s-master1 ~]# calicoctl node status

+---------------+-------------------+-------+----------+-------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+---------------+-------------------+-------+----------+-------------+
| 192.168.0.102 | node-to-node mesh | up | 02:10:36 | Established |
| 192.168.0.103 | node-to-node mesh | up | 02:13:43 | Established |
+---------------+-------------------+-------+----------+-------------+
  1. Node-to-Node 在100台以下
  2. Calico 维护的网络在默认是(Node-to-Node Mesh)全互联模式,Calico集群中的节点之间都会相互建立连接,用于路由交换。
  3. 但是随着集群规模的扩大,mesh模式将形成一个巨大服务网格,连接数成倍增加。
1
2
3
4
5
# 默认连接
b1 -> b2 b3
b2 -> b1 b3
b3 -> b1 b2
... 增加一个加点 每个几点都要n+1 上百个建立连接就大了
  1. 这时就需要使用 Route Reflector(路由器反射)模式解决这个问题。
  2. 确定一个或多个Calico节点充当路由反射器,让其他节点从这个RR节点获取路由信息。
1
2
3
4
# 路由反射 至少2-3个 有备用
b1 -> 路由反射节点1
b2 -> 路由反射节点2
b3 -> 路由反射节点1

1. 关闭 node-to-node BGP网格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[root@k8s-master1 demo]# vim bgp.yaml

apiVersion: projectcalico.org/v3
kind: BGPConfiguration
metadata:
name: default
spec:
logSeverityScreen: Info
nodeToNodeMeshEnabled: false
asNumber: 63400

# 执行这部时,整个网络会断开 请在停机维护时操作
[root@k8s-master1 demo]# calicoctl apply -f bgp.yaml
Successfully applied 1 'BGPConfiguration' resource(s)

[root@k8s-master1 demo]# calicoctl get bgpconfig
NAME LOGSEVERITY MESHENABLED ASNUMBER
default Info false 63400

[root@k8s-master1 demo]# ping 10.244.169.132
PING 10.244.169.132 (10.244.169.132) 56(84) bytes of data.
# 网络已经不通

ASN号可以通过获取 # calicoctl get nodes --output=wide

[root@k8s-master1 demo]# calicoctl get nodes --output=wide
NAME ASN IPV4 IPV6
k8s-master1 (63400) 192.168.0.101/24
k8s-node1 (63400) 192.168.0.102/24
k8s-node2 (63400) 192.168.0.103/24

2. 配置指定节点充当路由反射器

  1. 为方便让BGP Peer轻松选择节点,通过标签选择器匹配。
  2. 给路由器反射器节点打标签:
1
2
3
4
5
6
7
8
9
10
11
12
13
kubectl label node my-node route-reflector=true

[root@k8s-master1 demo]# kubectl label node k8s-node2 route-reflector=true
node/k8s-node2 labeled

[root@k8s-master1 demo]# calicoctl node status
Calico process is running.

IPv4 BGP status
No IPv4 peers found.

IPv6 BGP status
No IPv6 peers found.
1
2
3
4
5
6
7
8
9
10
11
12
13
# 然后配置路由器反射器节点 routeReflectorClusterID:

[root@k8s-master1 demo]# calicoctl get node k8s-node2 -o yaml > node.yaml
[root@k8s-master1 demo]# vim node.yaml
...
spec:
bgp:
ipv4Address: 192.168.0.103/24
routeReflectorClusterID: 244.0.0.1 # 集群ID
...

[root@k8s-master1 demo]# calicoctl apply -f node.yaml
Successfully applied 1 'Node' resource(s)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 将非路由反射器节点 连接路由反射器 获取交换信息
[root@k8s-master1 demo]# vim bgp2.yaml

apiVersion: projectcalico.org/v3
kind: BGPPeer
metadata:
name: peer-with-route-reflectors
spec:
nodeSelector: all()
# 所有的node 都去找 route-reflector == 'true' 这个标签
peerSelector: route-reflector == 'true'


[root@k8s-master1 demo]# calicoctl apply -f bgp2.yaml
Successfully applied 1 'BGPPeer' resource(s)

[root@k8s-master1 demo]# calicoctl get bgppeer
NAME PEERIP NODE ASN
peer-with-route-reflectors all() 0

# 查看节点的连接状态 只显示路由反射器的节点
[root@k8s-master1 demo]# calicoctl node status
Calico process is running.

IPv4 BGP status
+---------------+---------------+-------+----------+-------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+---------------+---------------+-------+----------+-------------+
| 192.168.0.103 | node specific | up | 02:56:50 | Established |
+---------------+---------------+-------+----------+-------------+

[root@k8s-master1 demo]# ip route
default via 192.168.0.2 dev eth0 proto static metric 100
10.244.36.64/26 via 192.168.0.102 dev eth0 proto bird
blackhole 10.244.159.128/26 proto bird
10.244.159.130 dev cali1f778af678e scope link
10.244.169.128/26 via 192.168.0.103 dev eth0 proto bird

[root@k8s-master1 demo]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-nginx-77fdbc8556-6qfxs 1/1 Running 0 28s 10.244.169.130 k8s-node2 <none> <none>
my-nginx-77fdbc8556-wc4mr 1/1 Running 0 28s 10.244.159.130 k8s-master1 <none> <none>
my-nginx-77fdbc8556-z9cpp 1/1 Running 0 28s 10.244.36.67 k8s-node1 <none> <none>

# 网络可以通
[root@k8s-master1 demo]# ping 10.244.169.130
[root@k8s-master1 demo]# ping 10.244.36.67
PING 10.244.36.67 (10.244.36.67) 56(84) bytes of data.
64 bytes from 10.244.36.67: icmp_seq=1 ttl=63 time=1.01 ms
1
2
3
4
5
6
7
8
9
10
11
12
# 使用路由反射器解决当bgp越来越大时的路由消耗
# RP模式可以设置多个节点,再其他的node上打个标签 就会自动加入进来
# 一般两个节点以上
kubectl label node k8s-node1 route-reflector=true
calicoctl get node k8s-node2 -o yaml > node.yaml
增加:routeReflectorClusterID: 244.0.0.1 # 集群ID

# 不允许跨VLAN 2层传输 源地址和目的地址都是容器
# 增加路由器 每个集群pod的网段和节点下一跳 才可以跨vlan 需要验证
# 得把容器的网段写成静态路由

# IPIP 支持跨子网

IPIP 模式

  1. 在前面提到过,Flannel host-gw 模式最主要的限制,就是要求集群宿主机之间是二层连通的。
  2. 而这个限制对于 Calico 来说,也同样存在。

修改为IPIP模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@k8s-master1 demo]# calicoctl get ipPool -o yaml > ipip.yaml
[root@k8s-master1 demo]# vim ipip.yaml
...
cidr: 10.244.0.0/16
ipipMode: Always
...

# 也可以在原来的yaml里面修改 再重新创建

[root@k8s-master1 demo]# calicoctl apply -f ipip.yaml
Successfully applied 1 'IPPool' resource(s)

[root@k8s-master1 demo]# calicoctl get ippool -o wide
NAME CIDR NAT IPIPMODE VXLANMODE DISABLED SELECTOR
default-ipv4-ippool 10.244.0.0/16 true Always Never false all()
1
2
3
4
5
6
7
8
9
10
11
12
# ip route 变化 tunl0 隧道网卡 有自己的IP 10.244.159.131
[root@k8s-master1 demo]# ip route
10.244.36.64/26 via 192.168.0.102 dev tunl0 proto bird onlink
blackhole 10.244.159.128/26 proto bird
10.244.159.130 dev cali1f778af678e scope link
10.244.169.128/26 via 192.168.0.103 dev tunl0 proto bird onlink

# bgp支持2层网络,如果要实现3层,那么需要路由器
# 原先的数据包中 源IP是容器IP 目的IP也是容器IP 但是路由器里没有相应规则,所以数据不可达
# 要想实现数据可达,需要一层路由器,并完成路由表信息
# IPIP 模式 可以对数据包进行隧道 与 VXLAN类似
# IPIP 也将原始数据包 进行封装和解封装

IPIP示意图:

Pod 1 访问 Pod 2大致流程如下:

1
2
3
4
5
1. 数据包从容器1出,到达Veth Pair另一端(宿主机上,以cali前缀开头);
2. 进入IP隧道设备(tunl0),由Linux内核IPIP驱动封装在宿主机网络的IP包中(新的IP包目的地之是原IP包的下一跳地址,即192.168.31.63),这样,就成了Node1 到Node2的数据包;
3. 数据包经过路由器三层转发到Node2;
4. Node2收到数据包后,网络协议栈会使用IPIP驱动进行解包,从中拿到原始IP包;
5. 然后根据路由规则,根据路由规则将数据包转发给cali设备,从而到达容器2。
1
2
3
4
5
6
7
8
9
[root@k8s-master1 demo]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-nginx-77fdbc8556-gfqd5 1/1 Running 0 12m 10.244.36.72 k8s-node1 <none> <none>
my-nginx-77fdbc8556-sm5ln 1/1 Running 0 12m 10.244.159.135 k8s-master1 <none> <none>
my-nginx-77fdbc8556-tbm4n 1/1 Running 0 12m 10.244.169.132 k8s-node2 <none> <none>

# 10.244.36.72 -> 10.244.169.132 流程:
# 10.244.36.72 -> pod calif -> 10.244.169.132/26 via 192.168.0.103 dev tunl0 proto bird onlink
# -> 10.244.169.132 dev cali8567c6b7944 scope link -> pod calif -> 10.244.169.132
1
2
3
4
5
6
7
8
9
10
# 里面一层 容器到容器数据包 原始IP包
源IP: 10.244.1.10
目的IP: 10.244.2.10

# 外面一层 两个宿主机 TCP
源IP: 192.168.31.62
目的IP: 192.168.32.63

# IPIP与 VXLAN一样 工作在三层
# bgp 与 host-gw 工作在二层
  1. 不难看到,当 Calico 使用 IPIP 模式的时候,集群的网络性能会因为额外的封包和解包工作而下降。
  2. 所以建议你将所有宿主机节点放在一个子网里,避免使用 IPIP。

CNI 网络方案优缺点及最终选择

1
2
3
4
5
6
先考虑几个问题: 
- 需要细粒度网络访问控制? Calico 支持 acl , Flannel 不支持 acl
- 追求网络性能? bgp 或者 host-gw
- 服务器之间是否可以跑BGP协议? 公有云不支持bgp协议
- 集群规模多大? 简单的 Flannel
- 是否有维护能力? Calico 路由多 一旦出现问题 查看起来非常难

办公网络与K8S网络如何互通

  1. 测试环境 k8s 集群
  2. 开发人员 办公网络
  3. 微服务通过 podip 调用
  4. 办公网络 如何 访问 podip 打通网络

网络策略

为什么需要网络隔离?

  1. CNI插件插件解决了不同Node节点Pod互通问题,从而形成一个扁平化网络,默认情况下,Kubernetes 网络允许所有 Pod 到 Pod 的流量
  2. 在一些场景中,我们不希望Pod之间默认相互访问,例如:

    • 应用程序间的访问控制。例如微服务A允许访问微服务B,微服务C不能访问微服务A
    • 开发环境命名空间不能访问测试环境命名空间Pod
    • 当Pod暴露到外部时,需要做Pod白名单
    • 多租户网络环境隔离
  3. 所以,我们需要使用network policy对Pod网络进行隔离。支持对Pod级别和Namespace级别网络访问控制。

  4. acl Pod网络入口方向隔离

    • 基于Pod级网络隔离:只允许特定对象访问Pod(使用标签定义),允许白名单上的IP地址或者IP段访问Pod
    • 基于Namespace级网络隔离:多个命名空间,A和B命名空间Pod完全隔离。
  5. acl Pod网络出口方向隔离

    • 拒绝某个Namespace上所有Pod访问外部
    • 基于目的IP的网络隔离:只允许Pod访问白名单上的IP地址或者IP段
    • 基于目标端口的网络隔离:只允许Pod访问白名单上的端口

1
2
3
4
5
6
1. 对 default 空间下 带标签  role: db 的pod 做网络策略
2. 入口策略: 这个网段 cidr: 172.17.0.0/16 除了 172.17.1.0/24 以外的ip 都可以访问
3. myproject 这个命名空间 可以访问
4. 标签有 role: frontend 的pod 可以访问
5. 可以访问的port是 6379端口
6. 出口策略: 可以访问 10.0.0.0/24 这个网段的 5978端口
1
2
3
4
5
配置解析:
- podSelector:用于选择策略应用到的Pod组。
- policyTypes:其可以包括任一Ingress,Egress或两者。该policyTypes字段指示给定的策略用于Pod的入站流量、还是出站流量,或者两者都应用。如果未指定任何值,则默认值为Ingress,如果网络策略有出口规则,则设置egress。
- Ingress:from是可以访问的白名单,可以来自于IP段、命名空间、Pod标签等,ports是可以访问的端口。
- Egress:这个Pod组可以访问外部的IP段和端口。

入站、出站网络流量访问控制案例

Pod访问限制

  1. 准备测试环境,1组web pod,2个client pod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@k8s-master1 demo]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-nginx-77fdbc8556-gfqd5 1/1 Running 0 53m 10.244.36.72 k8s-node1 <none> <none>
my-nginx-77fdbc8556-sm5ln 1/1 Running 0 53m 10.244.159.135 k8s-master1 <none> <none>
my-nginx-77fdbc8556-tbm4n 1/1 Running 0 53m 10.244.169.132 k8s-node2 <none> <none>

[root@k8s-master1 demo]# kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
my-nginx-77fdbc8556-gfqd5 1/1 Running 0 54m app=my-nginx,pod-template-hash=77fdbc8556
my-nginx-77fdbc8556-sm5ln 1/1 Running 0 54m app=my-nginx,pod-template-hash=77fdbc8556
my-nginx-77fdbc8556-tbm4n 1/1 Running 0 54m app=my-nginx,pod-template-hash=77fdbc8556

1. 限制当前命名空间下 app=my-nginx pod 做网络隔离
2. 只允许default命名空间携带run=client1标签的Pod访问 80端口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@k8s-master1 demo]# vim ns.yaml 

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
spec:
podSelector:
matchLabels:
app: my-nginx
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
project: default
- podSelector:
matchLabels:
run: client1
ports:
- protocol: TCP
port: 80
1
2
3
4
5
隔离策略配置:
1. Pod对象: default命名空间携带app: my-nginx标签的Pod
2. 允许访问端口: 80
3. 允许访问对象: default命名空间携带run=client1标签的Pod
4. 拒绝访问对象: 除允许访问对象外的所有对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 先在不做策略的时候 测试访问 run client1 和 run client2 
[root@k8s-master1 demo]# kubectl run client1 --generator=run-pod/v1 --image=busybox --command -- sleep 36000
[root@k8s-master1 demo]# kubectl run client2 --generator=run-pod/v1 --image=busybox --command -- sleep 36000

[root@k8s-master1 demo]# kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELS
client1 1/1 Running 0 3m20s run=client1
client2 1/1 Running 0 4s run=client2
my-nginx-77fdbc8556-gfqd5 1/1 Running 0 64m app=my-nginx,pod-template-hash=77fdbc8556
my-nginx-77fdbc8556-sm5ln 1/1 Running 0 64m app=my-nginx,pod-template-hash=77fdbc8556
my-nginx-77fdbc8556-tbm4n 1/1 Running 0 64m app=my-nginx,pod-template-hash=77fdbc8556

[root@k8s-master1 demo]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
client1 1/1 Running 0 73s 10.244.169.133 k8s-node2 <none> <none>
my-nginx-77fdbc8556-gfqd5 1/1 Running 0 61m 10.244.36.72 k8s-node1 <none> <none>
my-nginx-77fdbc8556-sm5ln 1/1 Running 0 61m 10.244.159.135 k8s-master1 <none> <none>
my-nginx-77fdbc8556-tbm4n 1/1 Running 0 61m 10.244.169.132 k8s-node2 <none> <none>

[root@k8s-master1 demo]# kubectl exec -it client1 sh
# 可以ping通pod
/ # ping 10.244.36.72
PING 10.244.36.72 (10.244.36.72): 56 data bytes
64 bytes from 10.244.36.72: seq=0 ttl=62 time=33.420 ms

# 可以访问80 下载页面
/ # wget 10.244.36.72
Connecting to 10.244.36.72 (10.244.36.72:80)
saving to 'index.html'
index.html 100% |********************************************************************************************************************************************************************************************| 612 0:00:00 ETA
'index.html' saved

[root@k8s-master1 demo]# kubectl exec -it client2 sh
/ # wget 10.244.36.72
saving to 'index.html'
index.html 100% |********************************************************************************************************************************************************************************************| 612 0:00:00 ETA
'index.html' saved
1
2
3
4
5
# 增加规则 
# Calico 支持 acl , Flannel 不支持 acl , Flannel 没法做 NetworkPolicy
[root@k8s-master1 demo]# kubectl apply -f ns.yaml
networkpolicy.networking.k8s.io/test-network-policy created
# 规则是 只有携带 run=client1 标签的pod 可以访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 测试
[root@k8s-master1 demo]# kubectl exec -it client2 sh
/ # rm -rf index.html
/ # wget 10.244.36.72
Connecting to 10.244.36.72 (10.244.36.72:80) # 不通,无法访问

[root@k8s-master1 demo]# kubectl exec -it client1 sh
/ # rm -rf index.html
/ # wget 10.244.36.72
Connecting to 10.244.36.72 (10.244.36.72:80)
saving to 'index.html'
index.html 100% |********************************************************************************************************************************************************************************************| 612 0:00:00 ETA
'index.html' saved

# client1 可以下载访问
# client2 无法下载访问
# 实际环境 多加上几组标签,精确粒度限制

命名空间隔离

  1. 两个开发团队,都有自己的命名空间,他们之间的pod都不可以相互访问
1
2
3
4
[root@k8s-master1 demo]# kubectl run client3 --generator=run-pod/v1 --image=busybox -n kube-system --command -- sleep 36000
# 取消刚才的限制
[root@k8s-master1 demo]# kubectl delete -f ns.yaml
networkpolicy.networking.k8s.io "test-network-policy" deleted
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@k8s-master1 demo]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
client1 1/1 Running 0 27m 10.244.169.133 k8s-node2 <none> <none>
client2 1/1 Running 0 24m 10.244.169.134 k8s-node2 <none> <none>
my-nginx-77fdbc8556-gfqd5 1/1 Running 0 88m 10.244.36.72 k8s-node1 <none> <none>
my-nginx-77fdbc8556-sm5ln 1/1 Running 0 88m 10.244.159.135 k8s-master1 <none> <none>
my-nginx-77fdbc8556-tbm4n 1/1 Running 0 88m 10.244.169.132 k8s-node2 <none> <none>

[root@k8s-master1 demo]# kubectl get pods -o wide -n kube-system
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
calico-kube-controllers-5cc7b68d7c-xb5rf 1/1 Running 1 7h15m 192.168.0.102 k8s-node1 <none> <none>
calico-node-hwt8c 1/1 Running 1 7h15m 192.168.0.103 k8s-node2 <none> <none>
calico-node-v7c4g 1/1 Running 1 7h15m 192.168.0.101 k8s-master1 <none> <none>
calico-node-wfp2f 1/1 Running 1 7h15m 192.168.0.102 k8s-node1 <none> <none>
client3 1/1 Running 0 8m18s 10.244.159.136 k8s-master1 <none> <none>
coredns-6d8cfdd59d-6wwkz 1/1 Running 1 6h54m 10.244.36.69 k8s-node1 <none> <none>

# 两个命名空间的pod 可以互通
[root@k8s-master1 demo]# kubectl exec -it client3 sh -n kube-system
/ # ping 10.244.36.72
PING 10.244.36.72 (10.244.36.72): 56 data bytes
64 bytes from 10.244.36.72: seq=0 ttl=62 time=0.630 ms
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 需求:
1. default命名空间下所有pod可以互相访问,
2. 其他命名空间不能访问default命名空间Pod。

[root@k8s-master1 demo]# vim ns2.yaml

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-from-other-namespaces
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- podSelector: {}


# podSelector: {}: default命名空间下所有Pod
# from.podSelector: {} : 如果未配置具体的规则,默认不允许
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 执行测试
[root@k8s-master1 demo]# kubectl apply -f ns2.yaml
networkpolicy.networking.k8s.io/deny-from-other-namespaces created

# kube-system下的client3无法ping通 default下的pod
[root@k8s-master1 demo]# kubectl exec -it client3 sh -n kube-system
/ # ping 10.244.36.72
PING 10.244.36.72 (10.244.36.72): 56 data bytes

# default下的pod 可以正常互通
[root@k8s-master1 demo]# kubectl exec -it client2 sh
/ # ping 10.244.36.72
PING 10.244.36.72 (10.244.36.72): 56 data bytes
64 bytes from 10.244.36.72: seq=0 ttl=62 time=13.809 ms

# 也可以ping通 kube-system 下的
/ # ping 10.244.159.136
PING 10.244.159.136 (10.244.159.136): 56 data bytes
64 bytes from 10.244.159.136: seq=0 ttl=62 time=0.538 ms

# 测试dns解析 需要部署 busybox镜像 指定版本 1.28.4

Flannel 实现网络隔离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. 需要 canal 插件支持
2. 阿里云可以试试 vpc 能不能跑 Calico bgp
3. 虚拟机可以跑 Calico

机房:
1. Calico bgp 打通
2. 路由反射器
3. 对接每个节点的网关路由器
4. lvs 对 pod 做负载均衡

支持:
Calico BGP 1000个节点以内
Flannel 100个节点以内
大公司有资源
小公司还是 一个业务一个圈子好一些
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. 阿里云主机 
Flannel:
vxlan 互通
host-gw 不通
Calico:
bgp 不通
Route Reflector 模式(RR)不通
ipip 未测试

2. 本地虚拟机
Flannel:
vxlan 互通
host-gw 互通
Calico:
bgp 互通
Route Reflector 模式(RR)互通
ipip: 互通