如前文所述,CNI 插件是 Kubernetes 网络的重要组件。目前有很多第三方 CNI 插件,Calico 就是其中之一,因为它的易用性和网络能力,得到很多工程师的青睐。它支持很多不同的平台,例如 Kubernetes、OpenShift、Docker EE、OpenStack 以及裸金属服务。Calico Node 组件以 Docker 容器的形式运行在 Kubernetes 的所有 Master 和 Node 节点上。Calico-CNI 插件会直接集成到 Kubernetes 每个节点的 Kubelet 进程中,一旦发现了新建的 Pod,就会将其加入 Calico 网络。
下面的内容会涉及安装、Calico 模块(Felix、BIRD 以及 Confd)和路由模式,但是不会包含网络策略方面的内容。
veth
对,并移入容器其实还有很多别的需求,但是上面几个点是最基础的。看看 Master 和 Worker 节点上的路由表。每个节点都有一个容器,容器有一个 IP 地址和缺省的容器路由。
上面的路由表说明,Pod 能够通过 3 层网络进行互通。什么模块负责添加路由,如何获取远端路由呢?为什么这里缺省网关是 169.254.1.1
呢?我们接下来会讨论这些问题。
Calico 的核心包括 Bird、Felix、ConfD、ETCD 以及 Kubernetes API Server。Calico 需要保存一些配置信息,例如 IP 池、端点信息、网络策略等,数据存储位置是可以配置的,本例中我们使用 Kubernetes 进行存储。
Bird 是一个 BGP 守护进程,运行在每个节点上,负责相互交换路由信息。通常的拓扑关系是节点之间构成的网格:
然而集群规模较大的时候,就会很麻烦了。可以使用 Route Reflector(部分 BGP 节点能够配置为 Route Reflector)来完成路由的传播工作,从而降低 BGP 连接数量。路由广播会发送给 Route Reflector,再由 Route Reflector 进行传播,更多信息可以参考 RFC4456。
BIRD 实例负责向其它 BIRD 实例传递路由信息。缺省配置方式就是 BGP Mesh
,适用于小规模部署。在大规模集群中,建议使用 Route Reflector
来克服这个缺点。可以使用多个 RR 来达成高可用目的,另外还可以使用外部 RR 来替代 BIRD。
ConfD 是一个简单的配置管理工具,运行在 Calico Node 容器中。它会从 ETCD 中读取数据(Calico 的 BIRD 配置),并写入磁盘文件。它会循环读取网络和子网,并应用配置数据(CIDR 键),组装为 BIRD 能够使用的配置。这样不管网络如何变化,BIRD 都能够得到通知并在节点之间广播路由。
Calico Felix 守护进程在 Calico Node 容器中运行,完成如下功能:
看看集群中所有的 Calico 模块:
是不是有点不同?veth
的一端是“悬空”的,没有连接。
数据包如何被路由到 Peer 节点的?
10.0.2.11
容器是怎样路由到一个不存在的 IP 的?容器的缺省路由指向了 169.254.1.1
。容器的 eth0 需要访问这个地址,因此在使用缺省路由的时候会对这个 IP 进行 ARP 查询。
如果能捕获 ARP 响应信息,会发现 veth
另外一侧的(cali123
) MAC 地址。所以到底是怎样响应一个没有 IP 接口的 ARP 请求的呢?答案是 proxy-arp
,如果我们检查一下主机侧的 veth
接口,会看到启用了 proxy-arp
:
$ cat /proc/sys/net/ipv4/conf/cali123/proxy_arp
1
Proxy ARP 技术能用特定网络上的代理设备来响应针对本网络不存在的 IP 地址的 ARP 查询。这个代理知道流量的目标,会以自己的 MAC 地址进行响应。如此一来,流量就转给 Proxy,通常会被 Proxy 使用其它网络接口或者隧道路由到原定目标。这种以自己 MAC 地址响应其他 IP 地址的 ARP 请求,完成代理任务的行为有时也被称为发布。
仔细看看 Worker 节点:
数据包进入内核之后,会根据路由表进行路由。
入栈流量:首先进入Worker 节点内核。 内核把数据包发给
cali123
。
Calico 支持三种路由模式,本节中会对几种模式的优劣和适用场景进行讨论。
这是一种简单的对 IP 包进行再封包的方式。传输中的数据包带有一个外层头部,其中描述了源主机和目的 IP,还有一个内层头部,包含源 Pod 和目标 IP。
目前 Azure 还不支持 IP-IP,因此这种环境中无法使用该模式,建议关掉 IP-IP 以提高性能。
这种模式下数据包是用 Pod 发出时的原始格式发出来的。因为没有封包和解包的开销,这种模式比较有性能优势。
AWS 中要使用这种模式需要关闭源 IP 校验。
Calico 3.7 以后的版本才支持 VXLAN 路由。
VXLAN 是 Virtual Extensible LAN 的缩写。VXLAN 是一种封包技术,二层数据帧被封装为 UDP 数据包。VXLAN 是一种网络虚拟化技术。当设备在软件定义的数据中心里进行通信时,会在这些设备之间建立 VXLAN 隧道。这些隧道能建立在物理或虚拟交换机之上。这些交换端口被称为 VXLAN Tunnel Endpoints(VTEPs),负责 VXLAN 的封包和解包工作。不支持 VXLAN 的设备可以连接到 VTEP,由 VTEP 提供 VXLAN 的出入转换工作。
VXLAN 对于不支持 IP-in-IP 的网络非常有用,例如 Azure 或者其它不支持 BGP 的数据中心。
在没安装 Calico 之前检查一下集群:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
controlplane NotReady master 40s v1.18.0
node01 NotReady <none> 9s v1.18.0
$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-66bff467f8-52tkd 0/1 Pending 0 32s
kube-system coredns-66bff467f8-g5gjb 0/1 Pending 0 32s
kube-system etcd-controlplane 1/1 Running 0 34s
kube-system kube-apiserver-controlplane 1/1 Running 0 34s
kube-system kube-controller-manager-controlplane 1/1 Running 0 34s
kube-system kube-proxy-b2j4x 1/1 Running 0 13s
kube-system kube-proxy-s46lv 1/1 Running 0 32s
kube-system kube-scheduler-controlplane 1/1 Running 0 33s
检查 CNI 的二进制文件和目录。其中没有任何配置文件或者 Calico 二进制,Calico 安装过程会用加载卷来填充其中的内容:
$ cd /etc/cni
-bash: cd: /etc/cni: No such file or directory
$ cd /opt/cni/bin
$ ls
bridge dhcp flannel host-device host-local ipvlan loopback macvlan portmap ptp sample tuning vlan
在 Master/Worker 节点上检查 ip route
:
$ ip route
default via 172.17.0.1 dev ens3
172.17.0.0/16 dev ens3 proto kernel scope link src 172.17.0.32
172.18.0.0/24 dev docker0 proto kernel scope link src 172.18.0.1 linkdown
在集群环境中下载并提交 calico.yaml
:
$ curl https://docs.projectcalico.org/manifests/calico.yaml -O
$ kubectl apply -f calico.yaml
看看其中的配置参数:
cni_network_config: |-
{
"name": "k8s-pod-network",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "calico", >>> Calico's CNI plugin
"log_level": "info",
"log_file_path": "/var/log/calico/cni/cni.log",
"datastore_type": "kubernetes",
"nodename": "__KUBERNETES_NODE_NAME__",
"mtu": __CNI_MTU__,
"ipam": {
"type": "calico-ipam" >>> Calico's IPAM instaed of default IPAM
},
"policy": {
"type": "k8s"
},
"kubernetes": {
"kubeconfig": "__KUBECONFIG_FILEPATH__"
}
},
{
"type": "portmap",
"snat": true,
"capabilities": {"portMappings": true}
},
{
"type": "bandwidth",
"capabilities": {"bandwidth": true}
}
]
}
# Enable IPIP
- name: CALICO_IPV4POOL_IPIP
value: "Always" >> Set this to 'Never' to disable IP-IP
# Enable or Disable VXLAN on the default IP pool.
- name: CALICO_IPV4POOL_VXLAN
value: "Never"
安装完毕之后,检查 Pod 和节点状态。
$ kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system calico-kube-controllers-799fb94867-6qj77 0/1 ContainerCreating 0 21s
kube-system calico-node-bzttq 0/1 PodInitializing 0 21s
kube-system calico-node-r6bwj 0/1 PodInitializing 0 21s
kube-system coredns-66bff467f8-52tkd 0/1 Pending 0 7m5s
kube-system coredns-66bff467f8-g5gjb 0/1 ContainerCreating 0 7m5s
kube-system etcd-controlplane 1/1 Running 0 7m7s
kube-system kube-apiserver-controlplane 1/1 Running 0 7m7s
kube-system kube-controller-manager-controlplane 1/1 Running 0 7m7s
kube-system kube-proxy-b2j4x 1/1 Running 0 6m46s
kube-system kube-proxy-s46lv 1/1 Running 0 7m5s
kube-system kube-scheduler-controlplane 1/1 Running 0 7m6s
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
controlplane Ready master 7m30s v1.18.0
node01 Ready <none> 6m59s v1.18.0
Kubelet 需要 CNI 的配置文件来设置网络:
$ cd /etc/cni/net.d/
$ ls
10-calico.conflist calico-kubeconfig
$
$
$ cat 10-calico.conflist
{
"name": "k8s-pod-network",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "calico",
"log_level": "info",
"log_file_path": "/var/log/calico/cni/cni.log",
"datastore_type": "kubernetes",
"nodename": "controlplane",
"mtu": 1440,
"ipam": {
"type": "calico-ipam"
},
"policy": {
"type": "k8s"
},
"kubernetes": {
"kubeconfig": "/etc/cni/net.d/calico-kubeconfig"
}
},
{
"type": "portmap",
"snat": true,
"capabilities": {"portMappings": true}
},
{
"type": "bandwidth",
"capabilities": {"bandwidth": true}
}
]
}
检查 CNI 的二进制文件:
$ ls
bandwidth bridge calico calico-ipam dhcp flannel host-device host-local install ipvlan loopback macvlan portmap ptp sample tuning vlan
安装 calicoctl
来获取 Calico 的更多信息并能修改 Calico 配置:
$ cd /usr/local/bin/
$ curl -O -L https://github.com/projectcalico/calicoctl/releases/download/v3.16.3/calicoctl
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 633 100 633 0 0 3087 0 --:--:-- --:--:-- --:--:-- 3087
100 38.4M 100 38.4M 0 0 5072k 0 0:00:07 0:00:07 --:--:-- 4325k
$ chmod +x calicoctl
$ export DATASTORE_TYPE=kubernetes
$ export KUBECONFIG=~/.kube/config
# Check endpoints - it will be empty as we have't deployed any POD
$ calicoctl get workloadendpoints
WORKLOAD NODE NETWORKS INTERFACE
检查 BGP Peer 的状态,会看到 Worker 节点是一个 Peer。
$ calicoctl node status
Calico process is running.
IPv4 BGP status
+--------------+-------------------+-------+----------+-------------+
| PEER ADDRESS | PEER TYPE | STATE | SINCE | INFO |
+--------------+-------------------+-------+----------+-------------+
| 172.17.0.40 | node-to-node mesh | up | 00:24:04 | Established |
+--------------+-------------------+-------+----------+-------------+
创建一个两副本 Pod,并设置 tolerations
,使之可以运行在 Master 节点:
apiVersion: apps/v1
kind: Deployment
metadata:
name: busybox-deployment
spec:
selector:
matchLabels:
app: busybox
replicas: 2
template:
metadata:
labels:
app: busybox
spec:
tolerations:
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
containers:
- name: busybox
image: busybox
command: ["sleep"]
args: ["10000"]
获取 Pod 和端点状态:
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox-deployment-8c7dc8548-btnkv 1/1 Running 0 6s 192.168.196.131 node01 <none> <none>
busybox-deployment-8c7dc8548-x6ljh 1/1 Running 0 6s 192.168.49.66 controlplane <none> <none>
$ calicoctl get workloadendpoints
WORKLOAD NODE NETWORKS INTERFACE
busybox-deployment-8c7dc8548-btnkv node01 192.168.196.131/32 calib673e730d42
busybox-deployment-8c7dc8548-x6ljh controlplane 192.168.49.66/32 cali9861acf9f07
获取 Pod 所在主机上的 VETH 信息:
$ ifconfig cali9861acf9f07
cali9861acf9f07: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1440
inet6 fe80::ecee:eeff:feee:eeee prefixlen 64 scopeid 0x20<link>
ether ee:ee:ee:ee:ee:ee txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 5 bytes 446 (446.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
获取 Pod 网络界面的信息:
$ kubectl exec busybox-deployment-8c7dc8548-x6ljh -- ifconfig
eth0 Link encap:Ethernet HWaddr 92:7E:C4:15:B9:82
inet addr:192.168.49.66 Bcast:192.168.49.66 Mask:255.255.255.255
UP BROADCAST RUNNING MULTICAST MTU:1440 Metric:1
RX packets:5 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:446 (446.0 B) TX bytes:0 (0.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
$ kubectl exec busybox-deployment-8c7dc8548-x6ljh -- ip route
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
$ kubectl exec busybox-deployment-8c7dc8548-x6ljh -- arp
获取主节点路由:
$ ip route
default via 172.17.0.1 dev ens3
172.17.0.0/16 dev ens3 proto kernel scope link src 172.17.0.32
172.18.0.0/24 dev docker0 proto kernel scope link src 172.18.0.1 linkdown
blackhole 192.168.49.64/26 proto bird
192.168.49.65 dev calic22dbe57533 scope link
192.168.49.66 dev cali9861acf9f07 scope link
192.168.196.128/26 via 172.17.0.40 dev tunl0 proto bird onlink
尝试 Ping Worker 节点来触发 ARP:
$ kubectl exec busybox-deployment-8c7dc8548-x6ljh -- ping 192.168.196.131 -c 1
PING 192.168.196.131 (192.168.196.131): 56 data bytes
64 bytes from 192.168.196.131: seq=0 ttl=62 time=0.823 ms
$ kubectl exec busybox-deployment-8c7dc8548-x6ljh -- arp
? (169.254.1.1) at ee:ee:ee:ee:ee:ee [ether] on eth0
注意上面的 MAC 地址。发出流量时,内核根据 IP 路由将数据包写入 tunl0
,Proxy ARP 的配置:
$ cat /proc/sys/net/ipv4/conf/cali9861acf9f07/proxy_arp
1
node01 $ ip route
default via 172.17.0.1 dev ens3
172.17.0.0/16 dev ens3 proto kernel scope link src 172.17.0.40
172.18.0.0/24 dev docker0 proto kernel scope link src 172.18.0.1 linkdown
192.168.49.64/26 via 172.17.0.32 dev tunl0 proto bird onlink
blackhole 192.168.196.128/26 proto bird
192.168.196.129 dev calid4f00d97cb5 scope link
192.168.196.130 dev cali257578b48b6 scope link
192.168.196.131 dev calib673e730d42 scope link
接收到数据包之后,内核会根据路由表将数据包发给对应的 veth
。
如果抓包的话会看出 IP-IP 协议。据我所知,Azure 不支持 IP-IP,也就是说我们无法在这种环境里使用 IP-IP。关闭 IP-IP 能获得更高性能,下面一节尝试一下。
更新 ippool.yaml
设置 IPIP 为 Never
,然后用 calicoctl
应用配置:
$ calicoctl get ippool default-ipv4-ippool -o yaml > ippool.yaml
$ vi ippool.yaml
...
$ calicoctl apply -f ippool.yaml
Successfully applied 1 'IPPool' resource(s)
再次检查 ip route
:
$ ip route
default via 172.17.0.1 dev ens3
172.17.0.0/16 dev ens3 proto kernel scope link src 172.17.0.32
172.18.0.0/24 dev docker0 proto kernel scope link src 172.18.0.1 linkdown
blackhole 192.168.49.64/26 proto bird
192.168.49.65 dev calic22dbe57533 scope link
192.168.49.66 dev cali9861acf9f07 scope link
192.168.196.128/26 via 172.17.0.40 dev ens3 proto bird
设备不再是 tunl0
,而是变成 Master 节点的管理界面(ens3
)。
Ping 一下 Worker 节点,验证工作情况,此时不再使用 IPIP 协议:
$ kubectl exec busybox-deployment-8c7dc8548-x6ljh -- ping 192.168.196.131 -c 1
PING 192.168.196.131 (192.168.196.131): 56 data bytes
64 bytes from 192.168.196.131: seq=0 ttl=62 time=0.653 ms
--- 192.168.196.131 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.653/0.653/0.653 ms
注意在 AWS 环境中使用这种模式需要禁用源 IP 检查。
重新进行集群初始化,并下载 calico.yaml
文件,进行如下变更:
从 livenessProbe
和 readinessProbe
中删除 bird
:
livenessProbe:
exec:
command:
- /bin/calico-node
- -felix-live
- -bird-live >> Remove this
periodSeconds: 10
initialDelaySeconds: 10
failureThreshold: 6
readinessProbe:
exec:
command:
- /bin/calico-node
- -felix-ready
- -bird-ready >> Remove this
把 calico_backend
修改为 vxlan
,不再需要 BGP:
kind: ConfigMap
apiVersion: v1
metadata:
name: calico-config
namespace: kube-system
data:
# Typha is disabled.
typha_service_name: "none"
# Configure the backend to use.
calico_backend: "vxlan"
禁用 IPIP:
# Enable IPIP
- name: CALICO_IPV4POOL_IPIP
value: "Never" >> Set this to 'Never' to disable IP-IP
# Enable or Disable VXLAN on the default IP pool.
- name: CALICO_IPV4POOL_VXLAN
value: "Never"
应用这个 YAML:
$ ip route
default via 172.17.0.1 dev ens3
172.17.0.0/16 dev ens3 proto kernel scope link src 172.17.0.15
172.18.0.0/24 dev docker0 proto kernel scope link src 172.18.0.1 linkdown
192.168.49.65 dev calif5cc38277c7 scope link
192.168.49.66 dev cali840c047460a scope link
192.168.196.128/26 via 192.168.196.128 dev vxlan.calico onlink
vxlan.calico: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1440
inet 192.168.196.128 netmask 255.255.255.255 broadcast 192.168.196.128
inet6 fe80::64aa:99ff:fe2f:dc24 prefixlen 64 scopeid 0x20<link>
ether 66:aa:99:2f:dc:24 txqueuelen 0 (Ethernet)
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 11 overruns 0 carrier 0 collisions 0
获取 Pod 状态:
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox-deployment-8c7dc8548-8bxnw 1/1 Running 0 11s 192.168.49.67 controlplane <none> <none>
busybox-deployment-8c7dc8548-kmxst 1/1 Running 0 11s 192.168.196.130 node01 <none> <none>
查看 ip route
:
$ kubectl exec busybox-deployment-8c7dc8548-8bxnw -- ip route
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
执行 Ping,触发 ARP 查询:
$ kubectl exec busybox-deployment-8c7dc8548-8bxnw -- arp
master $ kubectl exec busybox-deployment-8c7dc8548-8bxnw -- ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=116 time=3.786 ms
^C
$ kubectl exec busybox-deployment-8c7dc8548-8bxnw -- arp
? (169.254.1.1) at ee:ee:ee:ee:ee:ee [ether] on eth0
概念和前一种模式相似,区别在于数据包抵达 vxland
的时候,会把节点 IP 以及 MAC 地址封装并发送。另外 vxland
的 UDP 端口是 4789。这里会从 etcd 获取可用节点以及节点支持的 IP 范围,从而让 vxlan-calico
据此构建数据包。
VxLan 模式需要更多系统开销
本文未提供任何技术建议和推荐,文中所述皆为个人观点,不代表我所供职的企业。
文章转载自伪架构师。点击这里阅读原文了解更多。