随着 Kubernetes 社区的发展,实际生产环境中使用 Kubernetes 越来越多,用户对 CNI (Container Network Interface) 的要求也越来越多。Fabric 作为博云自研的一款成熟的 CNI 产品,旨在提供能适应多种场景,功能丰富、易用且性能卓越的容器网络管理平台。
Fabric 的 cache 精简很多,只保留网络需要的信息,尽力保证 cache 与 K8S 控制器间状态一致。当 OpenFlow 控制器的查询请求来时,通过在 cache 里查找决策该数据包的走向和动作。
区别于 K8S 的控制器,其采用了一种叫做状态机的设计模型,整个系统就是一个无限循环,在这个循环内部围绕着各种状态进行操作,致力于将资源状态与控制器期望的状态保持一致。那么所围绕着的状态表,即可理解成 K8S 的 cache。
我们先抛开 Svc 的 NodePort 模式、Fabric 的 Underlay 模式,单纯来看一看 Fabric 的 Overlay 模式下使用 ClusterIP 是什么样的,如下图:
每个 POD 内均有一个网卡,这个其实就是 Linux 的一种虚拟网卡类型,名为 Veth。Veth 网卡创建时必须成对存在,在图中,一端在容器里就是 ETH0 接口,一端在其他命名空间(例如宿主命名空间)就是图中的 VETH01 接口。所有的 VETH 接口都是二层的接口,因此他们可以被桥接。Fabric 使用了 Open vSwitch 这一开源软件的虚拟网桥,将众多的 Veth 接口桥接在了一起,通过控制流表的形式,在 Node 间构建出一个大二层的集群网络。
此处我们假设 POD 3 访问一个 ClusterIP,该 ClusterIP 的两个 Endpoint 分别是 POD 1 和 POD 2,那么当 POD 3 发起到 ClusterIP 的访问时,Open vSwitch 会对数据包进行 DNAT 操作,让它变成直接访问到 POD 1 或者 POD 2 的包,我们可以理解为 Open vSwitch 在中间对数据包进行了一定的修改,起到了中间桥梁的作用,进而再经过VXLAN封装后发送到 NODE 1 解封装,发送到对应的 POD。Fabric 的 cache 中包含了目标 MAC 所在的 Node IP,因此能够在 VXLAN 封装阶段找到发往的 Node。
三、Fabric-trace 简介
fabric-trace 是专门用于辅助定位容器网络问题的工具,是 fabric-admin 的重要组成部分。该工具位于名为 fabric-node 的 Pod 的 fabric 容器中。由于 fabric-node 被部署为一个 daemonset,因此每个 Node 上都会有该工具。我们可以直接使用下边的命令操作:
/opt/cni/bin/fabric-admin
Available Commands: dump dump the variable health fabric-node health from command line help Help about any command trace trace tools for fabric network cni upgrade upgrade fabric network cni version print client and server version info
接下来,我们就分别介绍一下dump cache和trace这两个子命令。
四、Dump Cache 子命令
dump cache 可以导出 fabric 控制面的缓存信息,该信息最终决定了流量的转发方式。目前,dump cache 共支持如下几种 cache 导出:
- [NetworkPolicy] nppods: Pod 的基本网络信息,包含 MAC 地址、所在的 Node IP、网络 CRD 等
- [NetworkPolicy] npchangedips
- [NetworkPolicy] npready
- [NetworkPolicy] npservices: 不同命名空间中的 Svc IP
- [NetworkPolicy] npnodes: 所有的 Node 信息
- [NetworkPolicy] nppolicies: 容器的网络策略
- [NetworkPolicy] nppodpolicies: 容器的网络策略
- [Proxy] pxchangedids
- [Proxy] pxservices: Svc 信息,包括唯一 ID、端口等
- [Proxy] pxeps: Endpoint 信息,包括端口、Endpoint IP、协议等
- [Federation] fedcluster: 联邦集群信息
- [Federation] fedchangedips
我们就捡常用的几个加粗的来看看。
1. nppods
可以看到,Pod 的 cache 里均包含了一个 Node 的 IP (HostIP),这个 IP 将会用于封装 VXLAN 的外层目标 IP。在多网卡多 IP 的环境中,就有可能会出现 Node 的 IP 识别出现问题,导致网络不通,此时就可以通过 dump 该 cache 来定位。
====================== 192.168.1.116 ====================== <- 这里的就是Pod的IP地址 (string) (len=4) "%!v(MISSING)\n" (*cache.PodCache)(0xc00031ea20)({ Namespace: (string) (len=7) "default", <- 这里是命名空间 Name: (string) (len=22) "nginx-66b6c48dd5-krps8", <- 这里是容器名 HostIP: (string) (len=13) "<Node的IP地址>", <- 这里是容器所在的Node的IP MacAddress: (string) (len=17) "5c:e1:c9:e2:54:14", <- 容器的MAC地址 Network: (string) (len=10) "network-v4", <- 容器所在的网络CRD的名称 Ports: (map[string]string) { } })
2. npservices
====================== default ====================== <- 这里就是Svc的命名空间 (string) (len=4) "%!v(MISSING)\n" ([]string) (len=2 cap=2) { (string) (len=10) "10.255.0.1", <- 下边就是该命名空间下的所有的Svc IP (string) (len=14) "10.255.188.118" }
3. npnodes
====================== <Node的IP地址> ====================== <- Node的IP (string) (len=4) "%!v(MISSING)\n" (*cache.NodeCache)(0xc000651da0)({ Name: (string) (len=5) "fab-2", <- Node的名称(默认为主机名) BridgeMac: (string) (len=17) "c6:2c:ff:f3:13:46" <- boc0网桥的MAC地址 })
4. pxservices
====================== 10.255.188.118 ====================== <- Svc IP (string) (len=4) "%!v(MISSING)\n" (*cache.service)(0xc0006555f0)({ groupID: (uint16) 4, namespace: (string) (len=7) "default", <- Svc的命名空间 name: (string) (len=5) "nginx", <- Svc的名称 ports: (map[int32]string) (len=1) { (int32) 80: (string) (len=2) "80" <- Svc所关联的端口,可以有多个端口 } })
====================== default/nginx ====================== <- Svc名称 (string) (len=4) "%!v(MISSING)\n" (*cache.endpoint)(0xc00018a1a0)({ targets: (map[string]*cache.target) (len=2) { (string) (len=5) "TCP80": (*cache.target)(0xc000375da0)({ port: (int32) 80, <- Svc关联的端口 protocol: (string) (len=3) "TCP", <- Svc关联的端口的协议类型 subIPs: ([]string) (len=3 cap=4) { (string) (len=13) "192.168.1.116", <- Endpoint IP,也就是这个Deployment下的Pod的IP (string) (len=13) "192.168.1.129", (string) (len=13) "192.168.1.238" } }), } })
五、Trace 子命令
trace 子命令中共支持两条子命令。一条是 flow,可以用于追踪流信息(类似抓包);另一条是 np,可以用于验证网络策略。这两条子命令均具有相同的参数,如下:
--detail show detail information -d, --dst string destination IP address --dst-mac string destination mac address --dst-port string destination port -h, --help help for flow -p, --protocol string traffic protocol -s, --src string source IP address --src-mac string source mac address --src-port string source port
因此,我们就可以直接使用该子命令验证是否能够互通和验证网络策略,而无需再去人工抓包观察。
使用 flow 子命令,我们可以追踪到数据包所途径的关键网卡,其原理是 fabric 程序根据不同的流量场景计算出该流量将途经的所有网卡,然后启动抓包进程从对应的网桥端口模拟发送数据包,最后汇总信息展示追踪结果。下边是我随便挑选了一个 Pod 去访问 nginx svc 的 ClusterIP 得到的信息。通过追踪的结果,我们即可知道数据包到哪里丢失了,非常有助于排错。
# /opt/cni/bin/fabric-admin trace flow --dst 10.255.188.118 --src 192.168.1.129 --protocol tcp --dst-port 80 <- 访问的10.255.188.118就是nginx svc的ClusterIP,源地址192.168.1.129则是一个Pod Footprints: ================================================================== # Open vSwitch收到的报文的抓包信息 Timestamp: 1649490728.250303 Interface: boc0 Reply: false InterfaceType: ovs-br InterfaceIP: Host: <Node的IP地址> Packet: c2:3d:62:b2:af:b2 > 5c:e4:f7:dc:0f:d6 Ether / IP / TCP 192.168.1.129:49975 > 192.168.1.238:http S ================================================================== # Endpoint Pod收到的报文的抓包信息 Timestamp: 1649490728.257538 Interface: 5ce4f7dc0fd6 Reply: false InterfaceType: pod InterfaceIP: 192.168.1.238 Host: <Node的IP地址> Packet: c2:3d:62:b2:af:b2 > 5c:e4:f7:dc:0f:d6 Ether / IP / TCP 192.168.1.129:49975 > 192.168.1.238:http S ================================================================== # Endpoint Pod回复的报文的抓包信息 Timestamp: 1649490728.253276 Interface: 5ce4f7dc0fd6 Reply: true InterfaceType: pod InterfaceIP: 192.168.1.238 Host: <Node的IP地址> Packet: 5c:e4:f7:dc:0f:d6 > c6:2c:ff:f3:13:46 Ether / IP / TCP 192.168.1.238:http > 192.168.1.129:49975 SA ================================================================== # 回复报文到达原始请求Pod的抓包信息 Timestamp: 1649490728.258958 Interface: c23d62b2afb2 Reply: true InterfaceType: pod InterfaceIP: 192.168.1.129 Host: <Node的IP地址> Packet: 5c:e4:f7:dc:0f:d6 > c2:3d:62:b2:af:b2 Ether / IP / TCP 10.255.188.118:http > 192.168.1.129:49975 SA ================================================================== ROUTE: 192.168.1.129:0 X=====> 10.255.188.118:0 (tcp)[PERMITTED] INFO: the traffic is permitted by default, no network policy defined for IP "192.168.1.129" or "192.168.1.116" ====Trace Flow Result: SUCCESS
2. np
使用 np 子命令,即可看到 NetworkPolicy 是否允许该报文通过。这样一来,我们就能很快地 debug NetworkPolicy。
# /opt/cni/bin/fabric-admin trace np --dst 10.255.188.118 --src 192.168.1.129 --protocol tcp --dst-port 80 ROUTE: 192.168.1.129:0 X=====> 10.255.188.118:0 (tcp)[PERMITTED] INFO: the traffic is permitted by default, no network policy defined for IP "192.168.1.129" or "192.168.1.116"
1. NetworkPolicy 配置错误排查
我安装了这么一套环境,其中有四个 Pod,一个为 fedora,三为 nginx。
NAME READY STATUS RESTARTS AGE IP NODE fedora 1/1 Running 1 2d21h 192.168.1.120 fab-2 nginx-66b6c48dd5-2vp8n 1/1 Running 1 2d21h 192.168.1.238 fab-2 nginx-66b6c48dd5-krps8 1/1 Running 1 2d21h 192.168.1.116 fab-2 nginx-66b6c48dd5-q6cl7 1/1 Running 1 2d21h 192.168.1.129 fab-2
下面是我的 Pod,我就使用fedora这个 Pod 去访问nginx-66b6c48dd5-krps8这个 Pod。
# curl -v 192.168.1.1 --connect-timeout 10 * Trying 192.168.1.1:80... * Connection timed out after 10001 milliseconds * Closing connection 0 curl: (28) Connection timed out after 10001 milliseconds
发生了超时,那么这个时候,可以用 fabric-admin 的 trace flow 功能。
# /opt/cni/bin/fabric-admin trace flow -s 192.168.1.120 -d 192.168.1.116 -p tcp --dst-port 80 Footprints: ================================================================== Timestamp: 0.000000 Interface: 5ce1c9e25414 Reply: false InterfaceType: pod InterfaceIP: 192.168.1.116 Host: <Node的IP地址> Packet: ================================================================== Timestamp: 0.000000 Interface: 5ce1c9e25414 Reply: true InterfaceType: pod InterfaceIP: 192.168.1.116 Host: <Node的IP地址> Packet: ================================================================== Timestamp: 0.000000 Interface: 6a3a0cac14fb Reply: true InterfaceType: pod InterfaceIP: 192.168.1.120 Host: <Node的IP地址> Packet: ================================================================== Suggestions: Make sure the target is running, otherwise check `npc_data`. ROUTE: 192.168.1.120:0 X=====> 192.168.1.116:0 (tcp)[REJECTED] INFO: the traffic is rejected by the ingress rules: default/test-network-policy ====Trace Flow Result: FAILED
2. Svc 端口配置错误排查
NAME TYPE CLUSTER-IP PORT(S) nginx ClusterIP 10.255.188.118 80/TCP
通过 curl 去访问 nginx 的 svc。
[root@fedora /]# curl -v 10.255.188.118 --connect-timeout 10 * Trying 10.255.188.118:80... * Connection timed out after 10001 milliseconds * Closing connection 0 curl: (28) Connection timed out after 10001 milliseconds
发现是超时,那么此时我们可以使用 fabric-admin 的 dump cache 来查看该 Service 的情况。
# /opt/cni/bin/fabric-admin dump cache pxservices ====================== 10.255.188.118 ====================== (string) (len=4) "%!v(MISSING)\n" (*cache.service)(0xc0006ab0b0)({ groupID: (uint16) 4, namespace: (string) (len=7) "default", name: (string) (len=5) "nginx", ports: (map[int32]string) (len=1) { (int32) 80: (string) (len=4) "8080" } }) # /opt/cni/bin/fabric-admin dump cache pxeps ====================== default/nginx ====================== (string) (len=4) "%!v(MISSING)\n" (*cache.endpoint)(0xc00000f330)({ targets: (map[string]*cache.target) (len=2) { (string) (len=7) "TCP8080": (*cache.target)(0xc0005306c0)({ port: (int32) 8080, protocol: (string) (len=3) "TCP", subIPs: ([]string) (len=3 cap=4) { (string) (len=13) "192.168.1.116", (string) (len=13) "192.168.1.129", (string) (len=13) "192.168.1.238" } }), (string) (len=7) "TCPHTTP": (*cache.target)(0xc0005306c0)({ port: (int32) 8080, protocol: (string) (len=3) "TCP", subIPs: ([]string) (len=3 cap=4) { (string) (len=13) "192.168.1.116", (string) (len=13) "192.168.1.129", (string) (len=13) "192.168.1.238" } }) } })
目前,Fabric 有着非常丰富且强大的功能,但由于篇幅所限,本文就只简单讲解了其技术设计和 debug 工具。后续 Fabric 会持续优化已有的功能,并积极开发更多实用的新功能。在此之后,我们还会分享一些 Fabric 的进阶的功能的设计方案和相关特性。