Open vSwitch在Openshift中的应用
前言#
Openshift SDN是由Overlay网络OVS(Open vSwitch)建立的,其中使用的插件如下:
- ovs-subnet: 默认插件,提供一个扁平化的Pod网络以实现Pod与其他任何Pod或Service的通信;
- ovs-multitenant:实现多租户管理,隔离不同Project之间的网络通信。每个Project都有一个NETID(即VxLAN中的VNID),可以使用oc get netnamspaces命令查看;
- ovs-networkpolicy:基于Kubernetes中的NetworkPolicy资源实现网络策略管理。
在Openshift集群中的节点上,有以下几个网络设备:
br0
:OpenShift创建和管理的OVS网桥,它会使用OpenFlow流表来实现数据包的转发和隔离;vxlan0
:VxLAN隧道端点,即VTEP(Virtual Tunnel End Point),用于集群内部Pod之间的通信;tun0
:节点上所有Pod的默认网关,用于Pod与集群外部和Pod与Service之间的通信;veth
:Pod通过veth-pair
连接到br0
网桥的端点。
ovs-ofctl -O OpenFlow13 show br0命令可以查看br0
上的所有端口及其编号:
[root@node1 ~]# ovs-ofctl -O OpenFlow13 show br0
OFPT_FEATURES_REPLY (OF1.3) (xid=0x2): dpid:0000ea00372f1940
n_tables:254, n_buffers:0
capabilities: FLOW_STATS TABLE_STATS PORT_STATS GROUP_STATS QUEUE_STATS
OFPST_PORT_DESC reply (OF1.3) (xid=0x3):
1(vxlan0): addr:72:23:a0:a9:14:a7
config: 0
state: 0
speed: 0 Mbps now, 0 Mbps max
2(tun0): addr:62:80:67:c6:38:58
config: 0
state: 0
speed: 0 Mbps now, 0 Mbps max
8381(vethd040c191): addr:7a:d9:f4:12:94:5f
config: 0
state: 0
current: 10GB-FD COPPER
speed: 10000 Mbps now, 0 Mbps max
...
LOCAL(br0): addr:76:ab:cf:6f:e1:46
config: PORT_DOWN
state: LINK_DOWN
speed: 0 Mbps now, 0 Mbps max
OFPT_GET_CONFIG_REPLY (OF1.3) (xid=0x5): frags=nx-match miss_send_len=0
考虑到Openshift集群的复杂性,我们分别按以下几种场景分析数据包的流向:
- 节点内Pod互访:Pod to Local Pod
- Pod跨节点互访:Pod to Remote Pod
- Pod访问Service:Pod to Service
- Pod与集群外部互访:Pod to External
由于高版本(3.11以上)的Openshift不再以守护进程而是以Pod的形式部署OVS组件,不方便对OpenFlow流表进行查看,因此本文选用的集群版本为3.6:
[root@node1 ~]# oc version
oc v3.6.173.0.5
kubernetes v1.6.1+5115d708d7
features: Basic-Auth GSSAPI Kerberos SPNEGO
Server https://test-cluster.ocp.koktlzz.com:8443
openshift v3.6.173.0.5
kubernetes v1.6.1+5115d708d7
另外,实验用集群并未开启ovs-multitenant,即未进行多租户隔离。整个集群Pod网络是扁平化的,所有Pod的VNID都为默认值0。
Pod to Local Pod#
数据包首先通过veth-pair
送往OVS网桥br0
,随后便进入了br0
上的OpenFlow流表。我们可以用ovs-ofctl -O OpenFlow13 dump-flows br0命令查看流表中的规则,同时为了让输出结果更加简洁,略去cookie和duration的信息:
-
table=0, n_packets=62751550874, n_bytes=25344802160312, priority=200,ip,in_port=1,nw_src=10.128.0.0/14,nw_dst=10.130.8.0/23 actions=move:NXM_NX_TUN_ID[0..31]->NXM_NX_REG0[],goto_table:10
-
table=0, n_packets=1081527047094, n_bytes=296066911370148, priority=200,ip,in_port=2 actions=goto_table:30
-
table=0, n_packets=833353346930, n_bytes=329854403266173, priority=100,ip actions=goto_table:20
table0中关于IP数据包的规则主要有三条,其中前两条分别对应流入端口
in_port
为1号端口vxlan0
和2号端口tun0
的数据包。这两条规则的优先级priority
都是200,因此只有在两者均不符合情况下,才会匹配第三条规则。由于本地Pod发出的数据包是由veth
端口进入的,因此将转到table20; -
table=20, n_packets=607178746, n_bytes=218036511085, priority=100,ip,in_port=8422,nw_src=10.130.9.154 actions=load:0->NXM_NX_REG0[],goto_table:21
-
table=21, n_packets=833757781068, n_bytes=329871389393381, priority=0 actions=goto_table:30
table20会匹配源地址
nw_src
为10.130.9.154且流入端口in_port
为8422的数据包,随后将Pod1的VNID 0作为源VNID存入寄存器0中,经由table21转到table30; -
table=30, n_packets=1116329752668, n_bytes=294324730186808, priority=200,ip,nw_dst=10.130.8.0/23 actions=goto_table:70
-
table=30, n_packets=59672345347, n_bytes=41990349575805, priority=100,ip,nw_dst=10.128.0.0/14 actions=goto_table:90
-
table=30, n_packets=21061319859, n_bytes=29568807363654, priority=100,ip,nw_dst=172.30.0.0/16 actions=goto_table:60
-
table=30, n_packets=759636044089, n_bytes=280576476818108, priority=0,ip actions=goto_table:100
table30中匹配数据包目的地址
nw_dst
的规则有四条,前三条分别对应本节点内Pod的CIDR网段10.130.8.0/23、集群内Pod的CIDR网段10.128.0.0/14和Service的ClusterIP网段172.30.0.0/16。第四条优先级最低,用于Pod对集群外部的访问。由于数据包的目的地址10.130.9.158符合第一条规则,且第一条规则的优先级最高,因此将转到table70; -
table=70, n_packets=597219981, n_bytes=243824445346, priority=100,ip,nw_dst=10.130.9.158 actions=load:0->NXM_NX_REG1[],load:0x20ea->NXM_NX_REG2[],goto_table:80
table70匹配目的地址
nw_dst
为Pod2 IP 10.130.9.158的数据包,并将Pod2的VNID 0作为目的VNID存入寄存器1中。同时端口号0x20ea
被保存到寄存器2中,然后转到table80; -
table=80, n_packets=1112713040332, n_bytes=293801616636499, priority=200 actions=output:NXM_NX_REG2[]
table80比较寄存器0和寄存器1中保存的源/目的VNID。若二者一致,则根据寄存器2中保存的端口号将数据包送出。
端口号0x20ea
是一个十六进制数字,即十进制数8426。而Pod2正是通过8426号端口设备vethba48c6de
连接到br0
上,因此数据包便最终通过它流入到了Pod2中。
[root@node1 ~]# ovs-ofctl -O OpenFlow13 show br0 | grep 8426
8426(vethba48c6de): addr:e6:b2:7e:42:41:91
[root@node1 ~]# ip a | grep vethba48c6de
8442: vethba48c6de@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master ovs-system state UP
Pod to Remote Pod#
Packet in Local Pod#
数据包依然首先通过veth-pair
送往OVS网桥br0
,随后便进入了br0
上的OpenFlow流表:
-
table=0, n_packets=830232155588, n_bytes=328613498734351, priority=100,ip actions=goto_table:20
-
table=20, n_packets=1901, n_bytes=299279, priority=100,ip,in_port=6635,nw_src=10.130.9.154 actions=load:0->NXM_NX_REG0[],goto_table:21
-
table=21, n_packets=834180030914, n_bytes=330064497351030, priority=0 actions=goto_table:30
与Pod to Local Pod的流程一致,数据包根据规则转到table30;
-
table=30, n_packets=59672345347, n_bytes=41990349575805, priority=100,ip,nw_dst=10.128.0.0/14 actions=goto_table:90
-
table=30, n_packets=1116329752668, n_bytes=294324730186808, priority=200,ip,nw_dst=10.130.8.0/23 actions=goto_table:70
数据包的目的地址为Pod2 IP 10.131.8.206,不属于本节点Pod的CIDR网段10.130.8.0/23,而属于集群Pod的CIDR网段10.128.0.0/14,因此转到table90;
-
table=90, n_packets=15802525677, n_bytes=6091612778189, priority=100,ip,nw_dst=10.131.8.0/23 actions=move:NXM_NX_REG0[]->NXM_NX_TUN_ID[0..31],set_field:10.122.28.8->tun_dst,output:1
table90根据目的IP的所属网段10.131.8.0/23判断其位于Node2上,于是将Node2 IP 10.122.28.8设置为
tun_dst
。并且从寄存器0中取出VNID的值,从1号端口vxlan0
输出。
vxlan0
作为一个VTEP设备(参见Overlay Network),将根据table90发来的信息,对数据包进行一层封装:
- 目的地址(dst IP) -->
tun_dst
--> 10.122.28.8 - 源地址(src IP) --> Node1 IP --> 10.122.28.7
- 源VNID -->
NXM_NX_TUN_ID[0..31]
--> 0
由于封装后的数据包源/目的地址均为节点IP,因此从Node1的网卡流出后,可以通过物理网络设备转发到Node2上。
Packet in Remote Pod#
Node2上的vxlan0
对数据包进行解封,随后从br0
上的1号端口进入OpenFlow流表中:
-
table=0, n_packets=52141153195, n_bytes=17269645342781, priority=200,ip,in_port=1,nw_src=10.128.0.0/14,nw_dst=10.131.8.0/23 actions=move:NXM_NX_TUN_ID[0..31]->NXM_NX_REG0[],goto_table:10
table0判断数据包的流入端口
in_port
、源IP所属网段nw_src
和目的IP所属网段nw_dst
均符合该条规则,于是保存数据包中的源VNID到寄存器0后转到table10; -
table=10, n_packets=10147760036, n_bytes=4060517391502, priority=100,tun_src=10.122.28.7 actions=goto_table:30
table10确认VxLAN隧道的源IP
tun_src
就是节点Node1的IP地址,于是转到table30; -
table=30, n_packets=678759566065, n_bytes=172831151192704, priority=200,ip,nw_dst=10.131.8.0/23 actions=goto_table:70
table30确认数据包的目的IP(即Pod2 IP)存在于Node2中Pod的CIDR网段内,因此转到table70;
-
table=70, n_packets=193211683, n_bytes=27881218388, priority=100,ip,nw_dst=10.131.8.206 actions=load:0->NXM_NX_REG1[],load:0x220->NXM_NX_REG2[],goto_table:80
table70发现数据包的目的IP与Pod2 IP相符,于是将Pod2的VNID作为目的VNID存于寄存器1中,将
0x220
(十进制数544)保存在寄存器2中,然后转到table80; -
table=80, n_packets=676813794014, n_bytes=172576112594488, priority=200 actions=output:NXM_NX_REG2[]
table80会检查保存在寄存器0和寄存器1中的源/目的VNID,若相等(此例中均为0),则从544号端口输出。
br0
上的554端口对应的网络接口是vethe9f523a9
,因此数据包便最终通过它流入到了Pod2中。
[root@node2 ~]# ovs-ofctl -O OpenFlow13 show br0 | grep 544
544(vethe9f523a9): addr:b2:a1:61:00:dc:3b
[root@node2 ~]# ip a show vethe9f523a9
559: vethe9f523a9@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master ovs-system state UP
link/ether b2:a1:61:00:dc:3b brd ff:ff:ff:ff:ff:ff link-netnsid 54
inet6 fe80::b0a1:61ff:fe00:dc3b/64 scope link
valid_lft forever preferred_lft forever
Pod to Service#
在本例中,Pod1通过Service访问其后端的Pod2,其ClusterIP为172.30.107.57,监听的端口为8080:
[root@node1 ~]# oc get svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
myService 172.30.107.57 <none> 8080/TCP 2y
-
table=30, n_packets=21065939280, n_bytes=29573447694924, priority=100,ip,nw_dst=172.30.0.0/16 actions=goto_table:60
数据包在送到OpenFlow流表table30前的步骤与Pod to Local Pod和Pod to Remote Pod中的情况一致,但数据包的目的地址变为了myService的ClusterIP。因此将匹配
nw_dst
中的172.30.0.0/16网段,转到table60; -
table=60, n_packets=0, n_bytes=0, priority=100,tcp,nw_dst=172.30.107.57,tp_dst=8080 actions=load:0->NXM_NX_REG1[],load:0x2->NXM_NX_REG2[],goto_table:80
table60匹配目的地址
nw_dst
为172.30.107.57且目的端口为8080的数据包,并将Pod1的VNID 0保存到寄存器1中,将0x2
(十进制数字2)保存到寄存器2中,转到table80; -
table=80, n_packets=1113435014018, n_bytes=294106102133061, priority=200 actions=output:NXM_NX_REG2[]
table80首先检查目的Service的VNID是否与寄存器1中的VNID一致,然后根据寄存器2中的数字将数据包从2号端口
tun0
送出,最后进入节点的iptables规则中。
由于Service的实现依赖于NAT,因此我们可以在iptables的NAT表中查看到与之相关的规则:
[root@node1 ~]# iptables -t nat -nvL
Chain OUTPUT (policy ACCEPT 4753 packets, 489K bytes)
pkts bytes target prot opt in out source destination
2702M 274G KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
Chain KUBE-SERVICES (2 references)
pkts bytes target prot opt in out source destination
4 240 KUBE-SVC-QYWOVDCBPMWAGC37 tcp -- * * 0.0.0.0/0 172.30.107.57 /* demo/myService:8080-8080 cluster IP */ tcp dpt:8080
本机产生的数据包首先进入OUTPUT
链,然后匹配到自定义链KUBE-SERVICES
。由于其目的地址为Service的ClusterIP 172.30.107.57,因此将再次跳转到对应的KUBE-SVC-QYWOVDCBPMWAGC37
链:
Chain KUBE-SVC-QYWOVDCBPMWAGC37 (1 references)
pkts bytes target prot opt in out source destination
1 60 KUBE-SEP-AF5DIL6JV3XLLV6G all -- * * 0.0.0.0/0 0.0.0.0/0 /* demo/myService:8080-8080 */ statistic mode random probability 0.50000000000
1 60 KUBE-SEP-ADAJHSV7RYS5DUBX all -- * * 0.0.0.0/0 0.0.0.0/0 /* demo/myService:8080-8080 */
Chain KUBE-SEP-ADAJHSV7RYS5DUBX (1 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ all -- * * 10.131.8.206 0.0.0.0/0 /* demo/myService:8080-8080 */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* demo/myService:8080-8080 */ tcp to:10.131.8.206:8080
Chain KUBE-SEP-AF5DIL6JV3XLLV6G (1 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-MARK-MASQ all -- * * 10.128.10.57 0.0.0.0/0 /* demo/myService:8080-8080 */
23 1380 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* demo/myService:8080-8080 */ tcp to:10.128.10.57:8080
KUBE-SVC-QYWOVDCBPMWAGC37
链下有两条完全相同的匹配规则,对应了该Service后端的两个Pod。KUBE-SEP-ADAJHSV7RYS5DUBX
链和 KUBE-SEP-AF5DIL6JV3XLLV6G
链能够执行DNAT操作,分别将数据包的目的地址转化为Pod IP 10.131.8.206和10.128.10.57。在一次通信中只会有一条链生效,这体现了Service的负载均衡能力。
完成OUTPUT
DNAT的数据包将进入节点的路由判断。由于当前目的地址已经属于集群内Pod的CIDR网段10.128.0.0/14,因此将再次从tun0
端口再次进入OVS网桥br0
中。
[rootnode1 ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.122.28.1 0.0.0.0 UG 0 0 0 eth0
10.122.28.0 0.0.0.0 255.255.255.128 U 0 0 0 eth0
10.128.0.0 0.0.0.0 255.252.0.0 U 0 0 0 tun0
169.254.0.0 0.0.0.0 255.255.0.0 U 1008 0 0 eth0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
172.30.0.0 0.0.0.0 255.255.0.0 U 0 0 0 tun0
不过数据包在进入br0
之前,还需要经过iptables中的POSTROUTING
链,完成一次MASQUERADE操作:数据包的源地址转换为其流出端口的IP,即tun0
的IP 10.130.8.1。
[root@node1 ~]# iptables -t nat -nvL
Chain POSTROUTING (policy ACCEPT 5083 packets, 524K bytes)
pkts bytes target prot opt in out source destination
2925M 288G OPENSHIFT-MASQUERADE all -- * * 0.0.0.0/0 0.0.0.0/0 /* rules for masquerading OpenShift traffic */
Chain OPENSHIFT-MASQUERADE (1 references)
pkts bytes target prot opt in out source destination
321M 19G MASQUERADE all -- * * 10.128.0.0/14 0.0.0.0/0 /* masquerade pod-to-service and pod-to-external traffic */
[root@prod-node11 ~]# ip a | grep tun0
16: tun0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN qlen 1000
inet 10.130.8.1/23 scope global tun0
本例中Service的后端Pod均在Pod1所在的节点外,因此数据包第二次进入OpenFlow流表时匹配的规则与Pod to Remote Pod一致。完整的数据包传递流程如下图所示:
Pod2返回的数据包在到达Node1后将被vxlan0
解封装,然后根据其目的地址tun0
进入OpenFlow流表:
table=0, n_packets=1084362760247, n_bytes=297224518823222, priority=200,ip,in_port=2 actions=goto_table:30
table=30, n_packets=20784385211, n_bytes=4742514750371, priority=300,ip,nw_dst=10.130.8.1 actions=output:2
数据包从2号端口tun0
流出后进入节点的iptables规则,随后将触发iptables的Connection Tracking操作:根据 /proc/net/nf_conntrack文件中的记录进行“DeNAT”。返回数据包的源/目的地址从Pod2 IP 10.131.8.206和tun0 IP 10.130.8.1,变回Service的ClusterIP 172.30.107.57和Pod1 IP 10.130.9.154。
[root@node1 ~]# cat /proc/net/nf_conntrack | grep -E "src=10.130.9.154.*dst=172.30.107.57.*dport=8080.*src=10.131.8.206"
ipv4 2 tcp 6 431986 ESTABLISHED src=10.130.9.154 dst=172.30.107.57 sport=80 dport=8080 src=10.131.8.206 dst=10.130.8.1 sport=8080 dport=80 [ASSURED] mark=0 secctx=system_u:object_r:unlabeled_t:s0 zone=0 use=2
Pod to External#
数据包依然首先通过veth-pair
送往OVS网桥br0
,随后便进入了br0
上的OpenFlow流表:
table=0, n_packets=837268653828, n_bytes=331648403594327, priority=100,ip actions=goto_table:20
table=20, n_packets=613807687, n_bytes=220557571042, priority=100,ip,in_port=8422,nw_src=10.130.9.154 actions=load:0->NXM_NX_REG0[],goto_table:21
table=21, n_packets=837674296060, n_bytes=331665441915651, priority=0 actions=goto_table:30
table=30, n_packets=759636044089, n_bytes=280576476818108, priority=0,ip actions=goto_table:100
table=100, n_packets=761732023982, n_bytes=282091648536325, priority=0 actions=output:2
数据包从tun0
端口流出后进入节点的路由表及iptables规则:
Chain POSTROUTING (policy ACCEPT 2910 packets, 299K bytes)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
2940M 289G OPENSHIFT-MASQUERADE all -- * * 0.0.0.0/0 0.0.0.0/0 /* rules for masquerading OpenShift traffic */
Chain OPENSHIFT-MASQUERADE (1 references)
pkts bytes target prot opt in out source destination
322M 19G MASQUERADE all -- * * 10.128.0.0/14 0.0.0.0/0 /* masquerade pod-to-service and pod-to-external traffic */
访问集群外部显然需要通过节点的默认网关,因此数据包将从节点网卡eth0
送出。而在POSTROUTING
链中,数据包的源地址由Pod IP转换为了eth0
的IP 10.122.28.7。完整流程如下图所示:
参考文献#
OpenShift SDN - Networking | Architecture | OpenShift Container Platform 3.11
作者:koktlzz
出处:https://www.cnblogs.com/koktlzz/p/14766670.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现