Calico的全称为ProjectCalico

Calico是三层虚拟网络解决方案(BGP)。每一个节点都是一个vRouter。每个节点上的pod都被当作该路由器后端的一个终端设备为其分配一个IP地址。而各节点上的vRouter都需要通过BGP协议学习生成路由规则,从而实现各节点上的pod之间互联互通。

BGP通信模型

BGP通信模型有两种:

  • BGP peer(小规模网络使用):点对点BGP,如果一个网络中有10个BGP,那是1:9的通信模型,这样将形成n*(n-1)个通信网络。所以在次模型下如果网络规模较大BGP路由学习报文将会占据很大的网络带宽,因而在大规模网络下需要使用另外一种通信模型
  • BGP Reflector(大规模网络使用):反射器模型,所有节点都将自己所有拥有的路由信息汇总给Reflector,由Reflector用1:n-1的方式向外进行反射,所以称之为BGP反射器。

小规模网络中BGP peer不存在单点问题,BGP peer宕机会有其他的进行替代。

大规模网络中BGP Reflector需要做冗余。

BGP模型要求所有节点在同一个二层网络中。不一定所有的底层网络都支持BGP,如在阿里云上购买了虚拟机自己部署k8s网络时,阿里云底层的网络不支持BGP协议。

为了确保公有云上也能使用calico,calico还提供了另外两种解决方案:

  • Overlay Network:
    • IPIP:用IP报文来封装IP报文,因此其开销更小。
    • VXLAN:类似于Flannel的VXLAN启用DirectRouting的网络模型,Calico也支持混合使用路由和叠加网络模型。如果节点在同一子网内使用BGP,如果跨子网则使用VxLan。

Calico的架构

在整个k8s集群上有多个节点,每个节点存在多个pod,这些Pod与Flannel插件中host-gw模型的Pod有一个不同之处在于flannel的pod在连如网络时使用的是将网卡一半在pod一半接入网桥实现的,而calico在接入网络时并非使用一对虚拟网卡来接入,而是直接接入了宿主机的内核。其需要借助内核中的Iptables和Routes表来完成其中的部分功能。

整个Calico有以下几个组件组成:

  1. 每个节点都需要运行以下组件:
    • Felix:需要运行于各节点之上的守护进程,主要负责完成接口管理、路由规划、acl规划(也就是网络策略,其需要借助iptables来实现)、状态报告。
    • BIRD:是vRouter的关键实现,整个BGP的路由表是由BIRD生成的,而路由规划是Felix完成的。而BIRD自身可以扮演两种角色,默认之启用了一种:
      • BGP客户端(默认启用):需要运行于每个节点,负责将Felix生成的路由信息载入内核并通告到整个网络中;
      • BGP Reflector:专用反射各BGP客户端发来路由信息;将 N –> N-1 转为 N –> 1 的模型
  2. 在节点之外需要运行以下组件:
    • etcd:Calico也和Flannel一样需要依靠etcd来存一些自身的状态数据,其也可以像Flannel一样将API Server当为自身的存储后端。大规模集群中建议额外部署etcd专用于Calico集群,以免和k8s性能上冲突。
    • Route Reflector:路由反射器
    • Calico编排系统插件:Calico自己不仅仅支持给k8s提供虚拟网络,它也支持openshift、openstack。所以Calico是一个通用的虚拟网络,不仅仅能适用到k8s上。要让calico能适用于k8s,需要一个calico的编排系统插件让etcd和calico插件之间能都双向转换通信。

Calico部署组件

Calico部署到K8S集群上时,需要用到两个组件:

  • calico-node:类似于Flanneld需要运行于每个节点之上。calico-node中封装了Felix和BIRD。
  • calico-kube-controller:运行于k8s集群上的中央控制系统。由它来负责Calico和整个kubernetes的协同,也包括其他核心功能的实现。

Calico部署

Calico有两种部署方式,一是让calico/node独立运行于Kubernetes集群之外,但calico/kube-controllers依然需要以Pod资源运行中集群之上;另一种是以CNI插件方式配置Calico完全托管运行于Kubernetes集群之上,类似于我们前面曾经部署托管Flannel网络插件的方式。对于后一种方式,Calico提供了在线的部署清单,它分别为50节点及以下规模和50节点以上规模的Kubernetes集群使用Kubernetes API作为Datastore提供了不同的配置清单,也为使用独立的etcd集群提供了专用配置清单。但这3种类型的配置清单中,Calico默认启用的是基于IPIP隧道的叠加网络,因而它会在所有流量上使用IPIP隧道而不是BGP路由。以下配置定义在部署清单中DaemonSet/calico-node资源的Pod模板中的calico-node容器之上。

官方部署文档:

https://docs.projectcalico.org/getting-started/kubernetes/self-managed-onprem/onpremises

需要注意:

calico的部署以50个节点为界限,分为3种部署方式

此处以50个节点以下方式来部署calico

1.下载calico的资源清单

1
curl https://docs.projectcalico.org/manifests/calico.yaml -O

2.应用配置清单

在应用配置清单前需要注意如果Pod的CIDR为192.168.0.0/16,那么此配置清单可以直接kubectl apply进行部署,如果非此网段则需要对calico.yaml进行修改。

此前部署k8s集群时,部署了flannel并且使用的Pod的CIDR10.244.0.0/16,现在则需要将其配置清单内的CALICO_IPV4POOL_CIDR进行修改,以确保其和Pod网段相同。

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
root@k8s-master01:~# vim calico.yaml
# 在IPv4类型的地址池上启用的IPIP及其类型,支持3种可用值Always(全局流量)、Cross-SubNet(跨子网流量)和Never3种可用值
- name: CALICO_IPV4POOL_IPIP
value: "Always"
# 是否在IPV4地址池上启用VXLAN隧道协议,取值及意义与Flannel的VXLAN后端相同;但在全局流量启用VXLAN时将完全不再需要BGP网络,建议将相关的组件禁用
- name: CALICO_IPV4POOL_VXLAN
value: "Never"


# 需要注意的是,Calico分配的地址池需要同Kubernetes集群的Pod网络的定义保持一致。Pod网络通常由kubeadm init初始化集群时使用--pod-network-cidr选项指定的网络,而Calico在其默认的配置清单中默认使用192.168.0.0/16作为Pod网络,因而部署Kubernetes集群时应该规划好要使用的网络地址,并设定此二者相匹配。对于曾经使用了flannel的默认的10.244.0.0/16网络的环境而言,我们也可以选择修改资源清单中的定义,从而将其修改为其他网络地址。以下配置片断取自Calico的部署清单,它定义在DaemonSet/calico-node资源的Pod模板中的calico-node容器之上。

# IPV4地址池的定义,其值需要与kube-controller-manager的“--cluster-network”选项的值保持一致,以下环境变量默认处于注释状态
- name: CALICO_IPV4POOL_CIDR
value: "10.244.0.0/16"

# Calico默认以26位子网掩码切分地址池并将各子网配置给集群中的节点,若需要使用其他的掩码长度,则需要定义如下环境变量
- name: CALICO_IPV4POOL_BLOCK_SIZE
value: "24"

# Calico默认并不会从Node.Spec.PodCIDR中分配地址,但可通过将如下变量设置为“true”并结合host-local这一IPAM插件以强制从PodCIDR中分配地址
- name: USE_POD_CIDR
value: "true"

# 在地址分配方面,Calico在JSON格式的CNI插件配置文件中使用专有的calico-ipam插件,该插件并不会使用Node.Spec.PodCIDR中定义的子网作为节点本地用于为Pod分配地址的地址池,而是根据Calico插件为各节点的配置的地址池进行地址分配。若期望为节点真正使用地址池吻合PodCIDR的定义,则需要在部署清单中DaemonSet/calico-node资源的Pod模板中的calico-node容器之上将USE_POD_CIDR环境变量的值设置为true,并修改ConfigMap/calico-config资源中cni_network_config键中的plugins.ipam.type的值为host-local,且使用podCIDR为子网,具体配置如下所示。

"ipam": {
"type": "host-local",
"subnet": "usePodCidr"
},

3.资源清单修改完毕后可以进行部署,但若之前使用了flannel插件则需要将其删除。

1
2
3
root@k8s-master01:~# kubectl apply -f calico.yaml

# 需要注意若原先安装了flannel插件,部署完calico后需要重启节点,否则会有flannel的规则残留。

IPIP模型

calico默认使用的是IPIP模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查看路由信息
root@k8s-master01:~# ip route list
default via 172.16.11.1 dev eth0 proto static
blackhole 10.244.0.0/24 proto bird # blackhole表示当前节点。
10.244.0.2 dev calib670545fd4e scope link
10.244.0.3 dev calibd15e6c1979 scope link
10.244.0.4 dev cali5411bb555f9 scope link # 此处可以看到pod的数据流出是直接到达内核的
10.244.0.5 dev caliddaf626788e scope link
10.244.0.6 dev cali0db5c029d8e scope link
10.244.1.0/24 via 172.16.11.81 dev tunl0 proto bird onlink
10.244.2.0/24 via 172.16.11.82 dev tunl0 proto bird onlink
10.244.3.0/24 via 172.16.11.83 dev tunl0 proto bird onlink
172.16.11.0/24 dev eth0 proto kernel scope link src 172.16.11.71
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown

# 可以看到出现tunl0接口,现在报文发送时会发送给tunl0接口

查看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
root@k8s-master01:~# kubectl get ippools -o yaml
apiVersion: v1
items:
- apiVersion: crd.projectcalico.org/v1
kind: IPPool
metadata:
annotations:
projectcalico.org/metadata: '{"uid":"fecd039d-88d1-4d69-ba56-3d24173d4652","creationTimestamp":"2021-08-05T08:02:43Z"}'
creationTimestamp: "2021-08-05T08:02:44Z"
generation: 1
name: default-ipv4-ippool
resourceVersion: "4394285"
uid: 76b6e014-3b0b-4625-9fd7-9f6fb2d3a284
spec:
blockSize: 24 # 地址块大小为24位
cidr: 10.244.0.0/16 # pod网段为10.244.0.0/16
ipipMode: Always # ipipMode为Always
natOutgoing: true
nodeSelector: all()
vxlanMode: Never # vxlanMode为Never
kind: List
metadata:
resourceVersion: ""
selfLink: ""

# 结合ipipMode和vxlanMode可以看出当前模型为ipip

验证ipip工作逻辑

1.在k8s-node01上进行抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
root@k8s-node01:~# tcpdump -i eth0 -nn ip host k8s-node01 and host k8s-node03
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
02:33:55.805615 IP 172.16.11.81 > 172.16.11.83: IP 10.244.1.16.49490 > 10.244.3.16.10016: Flags [P.], seq 1133911456:1133911482, ack 3769557439, win 85, options [nop,nop,TS val 3535292140 ecr 2812652948], length 26 (ipip-proto-4)
02:33:55.806152 IP 172.16.11.83 > 172.16.11.81: IP 10.244.3.16.10016 > 10.244.1.16.49490: Flags [P.], seq 1:27, ack 26, win 84, options [nop,nop,TS val 2812654948 ecr 3535292140], length 26 (ipip-proto-4)
02:33:55.806235 IP 172.16.11.81 > 172.16.11.83: IP 10.244.1.16.49490 > 10.244.3.16.10016: Flags [.], ack 27, win 85, options [nop,nop,TS val 3535292140 ecr 2812654948], length 0 (ipip-proto-4)
02:33:55.825457 IP 172.16.11.81 > 172.16.11.83: IP 10.244.1.16.33862 > 10.244.3.16.10001: Flags [P.], seq 1753095916:1753095942, ack 3391595475, win 3524, options [nop,nop,TS val 3535292159 ecr 2812652968], length 26 (ipip-proto-4)
02:33:55.825894 IP 172.16.11.83 > 172.16.11.81: IP 10.244.3.16.10001 > 10.244.1.16.33862: Flags [P.], seq 1:27, ack 26, win 4091, options [nop,nop,TS val 2812654968 ecr 3535292159], length 26 (ipip-proto-4)
02:33:55.825975 IP 172.16.11.81 > 172.16.11.83: IP 10.244.1.16.33862 > 10.244.3.16.10001: Flags [.], ack 27, win 3524, options [nop,nop,TS val 3535292160 ecr 2812654968], length 0 (ipip-proto-4)
02:33:57.513265 IP 172.16.11.81 > 172.16.11.83: IP 10.244.1.10.45486 > 10.244.3.16.10015: Flags [S], seq 494496574, win 43200, options [mss 1440,sackOK,TS val 2265387489 ecr 0,nop,wscale 9], length 0 (ipip-proto-4)
02:33:57.513602 IP 172.16.11.83 > 172.16.11.81: IP 10.244.3.16.10015 > 10.244.1.10.45486: Flags [S.], seq 2229381908, ack 494496575, win 42840, options [mss 1440,sackOK,TS val 185191869 ecr 2265387489,nop,wscale 9], length 0 (ipip-proto-4)


# 从以上抓包结果中可以看出ipip在通信时,分为外部ip和内部ip 2层。
# ipip-proto-4 表示此报文为ipip报文。

IPIP模型总结

Pod接入网络的方式与Flannel不同,它不使用网桥接入,而是直接放入宿主机内核并直接关联到宿主机上。因此它需要路由条目来支撑,每一个Pod都有个单独的路由条目,指明自己的报文要通过对端的接口(calixxxx)出来后到达宿主机上,而接口都是内核管理的,所有接口已经在宿主机内核中(tunl0),接下来由内核决定如何路由。IPIP模式下其要经过tunl0接口出去。