flannel 是最简单的一款 CNI 实现了(缺点是不支持 NetworkPolicy),大多数都是使用 Linux 自带的能力和内核模块(并且就一个进程),先学会它搞懂基础,后面去学其他的 CNI 会有很多相似之处。
支持 etcd v2 和 kubernetes API,etcd 只要不是非常老的版本都默认是 v3 API 存储了,而 etcd 官方不建议 v2 v3 数据共同存储,所以 K8S 部署都是使用后者(选项--kube-subnet-mgr)作为后端存储。
最常见的是前面三个了。查看当前 flannel 模式可以通过每个节点上:
$ cat /run/flannel/subnet.env FLANNEL_NETWORK=10.244.0.0/16 # pod 的 overlay 网络 FLANNEL_SUBNET=10.244.0.1/24 # 当前节点分配到的 CIDR,默认是 kube-controller-manager 设置的 node-mask=24 FLANNEL_MTU=1450 # 网卡 MTU FLANNEL_IPMASQ=true # 是否对 Pod 的流量开启 SNAT |
配置来源于 configmap:
$ kubectl -n kube-system get cm kube-flannel-cfg -o yaml apiVersion: v1 data: cni-conf.json: | { "name": "cbr0", "cniVersion": "0.3.1", "plugins": [ { "type": "flannel", "delegate": { "hairpinMode": true, "isDefaultGateway": true } }, { "type": "portmap", "capabilities": { "portMappings": true } } ] } net-conf.json: | { "Network": "10.244.0.0/16", "Backend": { "Type": "vxlan", #<----- 全局设置模式 "Port": 8475 } } kind: ConfigMap ... |
host-gw docker 跨节点的章节介绍了,此模式下 flanneld 进程本机添加路由转发到其他节点上,另外在在每个节点上添加类似的 iptables 规则,假设下面第一个节点的
$ iptables -t nat -S | grep FLANNEL -N FLANNEL-POSTRTG -A POSTROUTING -m comment --comment "flanneld masq" -j FLANNEL-POSTRTG # kube-proxy 的 masq mark,这里防止重复 SNAT -A FLANNEL-POSTRTG -m mark --mark 0x4000/0x4000 -m comment --comment "flanneld masq" -j RETURN # 本机访问其他节点的 PodIP 不做 SNAT -A FLANNEL-POSTRTG -s 10.244.0.0/24 -d 10.244.0.0/16 -m comment --comment "flanneld masq" -j RETURN # 外部 Pod 访问本机 Pod 的不做 SNAT -A FLANNEL-POSTRTG -s 10.244.0.0/16 -d 10.244.0.0/24 -m comment --comment "flanneld masq" -j RETURN # 其他节点手动指定 本机Pod CIDR 到 node IP 的时候,不需要 SNAT -A FLANNEL-POSTRTG ! -s 10.244.0.0/16 -d 10.244.0.0/24 -m comment --comment "flanneld masq" -j RETURN # PodIP 访问外面(非组播地址),例如其他 nodeIP 和 公网 IP 做 SNAT,等同于 docker -p 桥接网络访问外面需要做 SNAT -A FLANNEL-POSTRTG -s 10.244.0.0/16 ! -d 224.0.0.0/4 -m comment --comment "flanneld masq" -j MASQUERADE # 其他节点手动添加 集群POD CIDR 路由到本机上的时候,由于开了转发,此处做 SNAT -A FLANNEL-POSTRTG ! -s 10.244.0.0/16 -d 10.244.0.0/16 -m comment --comment "flanneld masq" -j MASQUERADE |
一些遇到过的问题
|
假设有两个节点 Node1 和 Node2,其中 Node1 的 PodA 要跟 Node2 的 PodB 通信,则它们之间的通信过程如下图所示:
整个过程:
网上很多说 vxlan 封包解包并不是 flanneld 进程处理而是内核处理的,可以每个节点 kill -STOP <flanneld_pid> 后测试跨节点网络。 |
因为额外封装了,会有 50 字节开销,所以 flanneld 源码会把 flanneld 的 mtu 剪掉 50,就像俄罗斯套娃一样,想要把小的放到外面那层内部去,你必须限制 小的外面 <= 大的内部体积。
flannel.1 就是 vxlan 的 vtep (虚拟隧道端点),flanneld 进程会给 K8S 打上 annotation 写上自己的 MAC 地址:
$ kubectl get node -o yaml | grep flannel.alpha flannel.alpha.coreos.com/backend-data: '{"VNI":1,"VtepMAC":"02:2f:70:44:d7:3f"}' flannel.alpha.coreos.com/backend-type: vxlan flannel.alpha.coreos.com/kube-subnet-manager: "true" flannel.alpha.coreos.com/public-ip: 192.168.50.2 flannel.alpha.coreos.com/backend-data: '{"VNI":1,"VtepMAC":"26:4a:e8:aa:01:d8"}' flannel.alpha.coreos.com/backend-type: vxlan flannel.alpha.coreos.com/kube-subnet-manager: "true" flannel.alpha.coreos.com/public-ip: 192.168.50.3 flannel.alpha.coreos.com/backend-data: '{"VNI":1,"VtepMAC":"46:92:51:8e:a2:c9"}' flannel.alpha.coreos.com/backend-type: vxlan flannel.alpha.coreos.com/kube-subnet-manager: "true" flannel.alpha.coreos.com/public-ip: 192.168.50.4 $ ip link show flannel.1 50: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default link/ether 02:2f:70:44:d7:3f brd ff:ff:ff:ff:ff:ff |
然后 flanneld 进程会从 K8S 获取其他节点的 flannel.1 MAC 地址和 public-ip :
$ ip neigh show dev flannel.1 10.244.2.0 lladdr 46:92:51:8e:a2:c9 PERMANENT 10.244.1.0 lladdr 26:4a:e8:aa:01:d8 PERMANENT # 推荐使用绝对路径,特别是 cni-plugins 放到 PATH 里会执行成 cni-plugins 的 bridge 二进制了 $ /sbin/bridge fdb | grep flannel 26:4a:e8:aa:01:d8 dev flannel.1 dst 192.168.50.3 self permanent 46:92:51:8e:a2:c9 dev flannel.1 dst 192.168.50.4 self permanent $ cat /run/flannel/subnet.env FLANNEL_NETWORK=10.244.0.0/16 FLANNEL_SUBNET=10.244.0.1/24 FLANNEL_MTU=1450 FLANNEL_IPMASQ=true |
在云上跨 VPC 部署 flannel 的时候,需要设置 flanneld 的 -public-ip, k3s 则是 --flannel-external-ip,或者 kubectl edit node 改 Annotations。
udp 没有放行,例如一些云虚拟化上,可以部署之前 nc 测下 8472 udp 收发包:
# server nc -l -u 8472 # client nc -u <dest_public_ip> 8472 |
或者看下 flannel.1 状态,例如 RX 接收为 0 说明收不到其他节点发送的,通常是 udp 没放行:
$ ip -s a s flannel.1 .... RX: bytes packets errors dropped overrun mcast 0 0 0 0 0 0 |
跨主机能 ping 通但是上层应用层协议不通(例如 curl 其他节点 Pod 的 url),一般是虚拟化网络层面被处理了,例如下面两个问题:
和 vxlan 类似,Type: ipip 网卡名是 flannel.ipip,内部 vxlan 换成 IP 报文,而 IP 头部是 20 字节,所以网卡 MTU 1500-20=1480:
$ ip r s default via 192.168.50.1 dev eth0 proto static 192.168.50.0/24 dev eth0 proto kernel scope link src 192.168.50.2 10.244.1.0/24 via 192.168.50.3 dev flannel.ipip onlink 10.244.2.0/24 via 192.168.50.4 dev flannel.ipip onlink 10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1 $ cat /run/flannel/subnet.env FLANNEL_NETWORK=10.244.0.0/16 FLANNEL_SUBNET=10.244.0.1/24 FLANNEL_MTU=1480 FLANNEL_IPMASQ=true $ ip -d a s flannel.ipip 102: flannel.ipip@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default link/ipip 192.168.50.2 brd 0.0.0.0 promiscuity 0 minmtu 0 maxmtu 0 ipip any remote any local 192.168.50.2 ttl inherit nopmtudisc numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 inet 10.244.0.0/32 scope global flannel.ipip valid_lft forever preferred_lft forever inet6 fe80::5efe:a0d:5bd4/64 scope link valid_lft forever preferred_lft forever |
抓包,因为是套娃,不像 vxlan 直接抓 udp 端口过滤就行,是 IP 报文套 IP 报文,所以抓包比较麻烦,过滤条件看第二层 IP 报文的源目 IP hex 对比,例如本机 ping 另一个节点 PodIP 10.187.0.14 在线转换成 hex 是 0x0abb000e:
$ tcpdump -nn -i ens160 'ip[32:4] = 0x0abb000e or ip[36:4] = 0x0abb000e' -w ipip.pcap tcpdump: listening on ens160, link-type EN10MB (Ethernet), capture size 262144 bytes ^C13 packets captured 26 packets received by filter 0 packets dropped by kernel |
宿主机 IP 打码了,可以看到右边套娃了一层 IP 协议:
$ tcpdump -nn -r ipip.pcap reading from file ipip.pcap, link-type EN10MB (Ethernet) 16:31:16.668984 IP xx.xx.xx.212 > xx.xx.xx.213: IP 10.187.2.0.62435 > 10.187.0.14.53: Flags [P.], seq 3040765315:3040765386, ack 1650546132, win 501, options [nop,nop,TS val 950814887 ecr 2423449147], length 71 43526+ [1au] AAAA? etcd3.default.default.svc.cluster2.local. (69) (ipip-proto-4) 16:31:16.669352 IP xx.xx.xx.213 > xx.xx.xx.212: IP 10.187.0.14.53 > 10.187.2.0.62435: Flags [P.], seq 1:168, ack 71, win 502, options [nop,nop,TS val 2423457143 ecr 950814887], length 167 43526 NXDomain*- 0/1/1 (165) (ipip-proto-4) 16:31:16.669391 IP xx.xx.xx.212 > xx.xx.xx.213: IP 10.187.2.0.62435 > 10.187.0.14.53: Flags [.], ack 168, win 501, options [nop,nop,TS val 950814888 ecr 2423457143], length 0 (ipip-proto-4) 16:31:16.669738 IP xx.xx.xx.212 > xx.xx.xx.213: IP 10.187.2.0.62435 > 10.187.0.14.53: Flags [P.], seq 71:134, ack 168, win 501, options [nop,nop,TS val 950814888 ecr 2423457143], length 63 49916+ [1au] AAAA? etcd3.default.svc.cluster2.local. (61) (ipip-proto-4) 16:31:16.670012 IP xx.xx.xx.213 > xx.xx.xx.212: IP 10.187.0.14.53 > 10.187.2.0.62435: Flags [P.], seq 168:327, ack 134, win 502, options [nop,nop,TS val 2423457144 ecr 950814888], length 159 49916*- 0/1/1 (157) (ipip-proto-4) 16:31:16.670031 IP xx.xx.xx.212 > xx.xx.xx.213: IP 10.187.2.0.62435 > 10.187.0.14.53: Flags [.], ack 327, win 501, options [nop,nop,TS val 950814888 ecr 2423457144], length 0 (ipip-proto-4) 16:31:16.672805 IP xx.xx.xx.212 > xx.xx.xx.213: IP 10.187.2.0.62435 > 10.187.0.14.53: Flags [P.], seq 134:205, ack 327, win 501, options [nop,nop,TS val 950814891 ecr 2423457144], length 71 63476+ [1au] A? etcd2.default.default.svc.cluster2.local. (69) (ipip-proto-4) 16:31:16.673063 IP xx.xx.xx.213 > xx.xx.xx.212: IP 10.187.0.14.53 > 10.187.2.0.62435: Flags [P.], seq 327:494, ack 205, win 502, options [nop,nop,TS val 2423457147 ecr 950814891], length 167 63476 NXDomain*- 0/1/1 (165) (ipip-proto-4) 16:31:16.673084 IP xx.xx.xx.212 > xx.xx.xx.213: IP 10.187.2.0.62435 > 10.187.0.14.53: Flags [.], ack 494, win 501, options [nop,nop,TS val 950814892 ecr 2423457147], length 0 (ipip-proto-4) 16:31:21.156396 IP xx.xx.xx.212 > xx.xx.xx.213: IP 10.187.2.0 > 10.187.0.14: ICMP echo request, id 10, seq 1, length 64 (ipip-proto-4) 16:31:21.156626 IP xx.xx.xx.213 > xx.xx.xx.212: IP 10.187.0.14 > 10.187.2.0: ICMP echo reply, id 10, seq 1, length 64 (ipip-proto-4) 16:31:22.178953 IP xx.xx.xx.212 > xx.xx.xx.213: IP 10.187.2.0 > 10.187.0.14: ICMP echo request, id 10, seq 2, length 64 (ipip-proto-4) 16:31:22.179206 IP xx.xx.xx.213 > xx.xx.xx.212: IP 10.187.0.14 > 10.187.2.0: ICMP echo reply, id 10, seq 2, length 64 (ipip-proto-4) |
flannel 支持设置 "Directrouting": true 来让二层节点走 host-gw,三层走 vxlan/IPIP 模式,flanneld 进程存在意义是节点增删改查维护本机配置,你也可以不用 CNI,自己写脚本 ip 命令配置实现,说这个是帮助你。
kube-controller-manager 设置了:allocate-node-cidrs 和 cluster-cidr 参数时,kube-controller-manager 会为每个 node 确定 pod ip 范围:
--node-cidr-mask-size-ipv4=24 |
默认 IPv4 掩码是 24位,意味着每个节点最多 253 个 Pod (0,1,255 IP 不使用)。
flanneld 刚启动时,在 RegisterNetwork (调用 kubeSubnetManager.AcquireLease) 中获取当前 node 的 Spec.PodCIDR,并把需要的一些信息写入到 node 的 annotation。
子网信息再写入到 /run/flannel/subnet.env (main.WriteSubnetFile),由 flannel CNI 读取,用于分配 pod ip。
bridge 二进制第一次运行的时候会在 node 上生成一个 Linux bridge 设备(非 hostNetwork 的 Pod 调度上来时候),默认名字是 cni0。这个 bridge 就是一个虚拟交换机,新生成的 pod 网卡会通过 veth 设备连接到这个 bridge 上面。
bridge 每次被调用的时候,会给 pod 创建 veth,将 veth 连接到 cni0,并且调用 host-local 从本机 subnet 中分配 ip。
几个跟 flannel CNI 有关的文件或目录: