openshift-sdn的由来和现状
openshift-sdn是红帽推出的一款容器集群网络计划。始终集成于openshift平台中。 但红帽将我的项目代码进行了开源。
实际上,咱们通过一些批改,齐全能够将openshift-sdn作为一款通用的容器集群的网络计划。
openshift-sdn官网倡议应用network-operator工具进行网络部署,实际上在该我的项目中咱们甚至能够扒出一套根本残缺的部署模板。基于这套模板咱们能够间接部署openshift-sdn。
为了加深大家的了解,本文咱们会具体地介绍整个计划的性能、应用和原理。咱们置信,如果你齐全了解了本文的内容,你也能在集群的openshift-sdn网络呈现故障时,能熟能生巧地进行排障。
openshift-sdn的性能
openshift-sdn依赖于了openvswitch技术,也就是虚构交换机,在k8s集群的每个节点上都要求部署好openvswitch并启动服务:
systemctl status openvswitch-switch.service
openshift-sdn通过构建和保护一套流表,以及一些路由和iptables策略,就实现了根本的容器网络需要:
- 集群中跨节点的pod通信
- pod到service的通信
- pod到内部网络的通信
除此之外,还提供了丰盛的扩大能力:
- 提供multi-tenant模式,反对namespace维度的租户隔离
- 提供networkpolicy模式,反对k8s networkpolicy
- 反对在上述两种模式下,在pod间应用多播流量
能够说openshift-sdn的性能曾经趋于齐备。
openshift-sdn的组成
openshift-sdn包含了管控面和数据面。
- ctrl。 管控面,是一套deployment,用于自动化地给每个节点调配网段,并记录到crd中
- node。 数据面,是一套daemonset,用于依据crd变动,构建节点网络数据面。包含路由、网卡、流表、iptables规定。
openshift-sdn的用法
根底用法
没有任何非凡的操作,布局好集群里pod、service的网段、 并部署好openshift-sdn组件后,咱们就能够部署pod了
租户隔离
在应用mulit-tenant模式时,集群中每个namespace都会被创立出一个同名的netnamespace
,这是openshift-sdn设计的crd,咱们看看外头记录了啥:
kubectl get netnamespaces kube-system -o yaml apiVersion: network.openshift.io/v1kind: NetNamespacemetadata: creationTimestamp: 2020-07-08T09:47:15Z generation: 1 name: kube-system resourceVersion: "33838361" selfLink: /apis/network.openshift.io/v1/netnamespaces/kube-system uid: 017460a8-c100-11ea-b605-fa163e6fe7d6netid: 4731218netname: kube-system
整体看下来,惟一有意义的字段就是netid了,这个整型示意了全局惟一的id,当不同的netnamespace,彼此之间的netid不同时,他们对应的namespace下的pod,就彼此不通。
当某个netnamespace的netid为0,示意这个netnamespace下的pod能够与任何namespace下的pod互通。
通过这种逻辑,咱们能够基于namespace来设计租户,实现租户隔离
集群的扩大
如果集群的pod IP不够用了怎么办?这是泛滥开源的容器网络计划的独特问题。openshift-sdn提供了一个灵便的扩大机制。
方才提到集群部署时要先布局好集群pod的CIDR和service的CIDR,当部署好openshift-sdn后,咱们能够看到:
# kubectl get clusternetwork default -o yaml apiVersion: network.openshift.io/v1clusterNetworks:- CIDR: 10.178.40.0/21 hostSubnetLength: 10hostsubnetlength: 10kind: ClusterNetworkmetadata: creationTimestamp: 2020-07-09T03:04:22Z generation: 1 name: default resourceVersion: "36395511" selfLink: /apis/network.openshift.io/v1/clusternetworks/default uid: e3b4a921-c190-11ea-b605-fa163e6fe7d6network: 10.178.40.0/21pluginName: redhat/openshift-ovs-multitenantserviceNetwork: 10.178.32.0/21vxlanPort: 4789
openshift-sdn设计的一个CRD,名为ClusterNetwork,这个CRD的对象记录了集群里应用的网络网段,当集群里有多个这种ClusterNetwork对象时,openshift-sdn只会取名为default
的那个对象。
关注外面的内容,咱们发现clusterNetworks
是一个数组,他的每个成员都能够定义一个CIDR和hostsubnetlength。也就是说,咱们批改了他,就能够给集群裁减网段。
这里咱们看到在构造体中还有两个字段:hostsubnetlength
和network
,值别离与clusterNetworks
数组的惟一一个成员的字段绝对应。这是openshift-sdn
的历史遗留问题,新近版本不反对配置clusterNetworks
数组,前面增加后,这两个字段只有当数组长度为1时,会进行一次校验。
咱们将default这个ClusterNetwork
的内容改成:
# kubectl get clusternetwork default -o yaml apiVersion: network.openshift.io/v1clusterNetworks:- CIDR: 10.178.40.0/21 hostSubnetLength: 10- CIDR: 10.132.0.0/14 hostSubnetLength: 9hostsubnetlength: 10kind: ClusterNetworkmetadata: creationTimestamp: 2020-07-09T03:04:22Z generation: 2 name: default resourceVersion: "36395511" selfLink: /apis/network.openshift.io/v1/clusternetworks/default uid: e3b4a921-c190-11ea-b605-fa163e6fe7d6network: 10.178.40.0/21pluginName: redhat/openshift-ovs-multitenantserviceNetwork: 10.178.32.0/21vxlanPort: 4789
但这仅仅批改了管制面,数据面的批改还没有做,节点上此时基本不晓得有这个新增的网段。
对于数据面的改变,官网的做法是:将每个node进行驱赶:kubectl drain $nodename
, 而后重启node, 重启后节点上ovs流表会清空、ovs-node 组件会重启,并重新配置流表和路由、iptables规定。
这样对数据面的影响未免太大了!当前我IP不够用了, 还要把集群里每个node重启一次,相当于所有在用的业务容器都要至多重建一次!有没有优雅一点的计划呢?
优雅扩大
咱们对openshift-sdn进行了深刻的钻研和社区追踪,并聚焦于如何优雅地、不影响业务容器地、实现网段的扩大。
咱们实际发现,老节点上node组件重启后,就会从新同步最新的clusternetwork信息,将新的网段配置到节点的路由表,和ovs流表中, 然而,已有的容器还是无法访问新退出的网段。
进行具体的排查,咱们发现老的容器里,拜访新网段会走的路由是:
default via 10.178.40.1 dev eth0
失常来说,拜访集群pod cidr的路由是:
10.178.40.0/21 dev eth0 scope link
于是咱们写了个工具,在老节点上运维了一把,往已有的容器中退出达到新网段的路由。如:
10.132.0.0/14 dev eth0
测试了一下网络终于通了~
在重复的实际后,咱们应用该计划对用户的业务集群进行了网段扩容。
然而咱们不禁产生了疑难,为啥拜访新的网段,不能够走网关呢?咱们意识到:为了更好地反对,有必要进行更深刻的理解。openshift-sdn的官网文档对此没有特地粗疏的解释,因而咱们决定从新梳理一遍了一通源码和流表,好好地整顿分明,openshift-sdn,到底是怎么做的?
openshift-sdn的设计
CRD
openshift-sdn给集群减少了一些CRD,包含
- clusternetworks.network.openshift.io 记录集群里的pod的CIDR
- egressnetworkpolicies.network.openshift.io 记录集群里的出站规定
- hostsubnets.network.openshift.io 记录集群里某个node上的CIDR
- netnamespaces.network.openshift.io 记录集群里的网络租户空间
组件
openshift-sdn的组件蕴含了中心化的控制器,去中心化的agent和CNI插件,agent会间接影响节点上的数据面,他们各自负责的次要内容包含:
controller
- 负责配置集群级别的pod cidr,对应openshift-sdn的CRD:clusterNetwork
- 给新退出的node调配子段,对应openshift-sdn的CRD:hostSubnet
- 察看k8s集群中namespace、networkpolicy等对象的变更,同步地更新openshift-sdn的CRD:netnamespaces、egressnetworkpolicies(专门针对出站的networkpolicy)
agent
- 每次启动时获取集群clusterNetwork,与本地流表做比照,当发现有出入,就会重新配置本地的集群网络流表、节点上的路由、以及iptables规定
- 察看集群中openshift-sdn的CRD:hostSubnet的变动,配置达到其余node的流表
- 察看集群中openshift-sdn的CRD:netnamespaces、egressnetworkpolicies的变动,配置相应的租户隔离和出站限度的流表
- 生成节点上的CNI二进制文件,并提供IP调配性能
- 针对本节点的每个pod,配置对应的流表
CNI
- 负责被kubelet调用,以进行容器网络的配置和解除
- 会向agent申请和开释IP
- 会配置容器外部的IP和路由
openshift-sdn的数据面原理
路由配置和跳转
咱们在一个k8s集群中部署了openshift-sdn网络,通过对路由、流表、iptables的剖析,能够勾画出网络的架构。
首先看容器里的内容。当咱们应用openshift-sdn时,须要先提供整个集群布局的pod IP CIDR,以及每个node上能够从CIDR里调配多少IP作为子段。咱们这里布局10.178.40.0/21
为集群的pod cidr, 每个节点上能够调配2^10个IP ,这样集群里只能反对两个节点。两个节点的IP段别离为:10.178.40.0/22
和10.178.44.0/22
随便创立一个pod,进入容器中查看IP和路由:
# docker exec -it bfdf04f24e01 bashroot@hytest-5db48599dc-95gfh:/# ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever3: eth0@if95: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1350 qdisc noqueue state UP group default link/ether 0a:58:0a:b2:28:0f brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.178.40.15/22 brd 10.178.43.255 scope global eth0 valid_lft forever preferred_lft foreverroot@hytest-5db48599dc-95gfh:/# ip r default via 10.178.40.1 dev eth0 10.178.40.0/22 dev eth0 proto kernel scope link src 10.178.40.15 10.178.40.0/21 dev eth0 224.0.0.0/4 dev eth0
能够看到IP:10.178.40.15/22
是处于网段10.178.40.0/22
中的。路由表的含意,从底向上为:
- 第四条路由:224.0.0.0/4为组播段,这是一条组播路由
- 第三条路由:示意IP所在的二层播送域。也就是整个node分到的CIDR,也就是说,一个node上所有的pod彼此是二层互联的。
- 第二条路由:集群级别的pod CIDR的路由,联合第三条规定,咱们能够确认,当pod拜访集群里任何一个podIP时,都会间接从eth0收回
- 第一条路由:默认路由,这里设置了一个网关地址10.178.40.1, pod拜访其余目标地址时,须要经由网关转发。
到此为止,咱们晓得了容器里的配置,要想理解更多,就要接着看宿主机配置(为了可读性咱们不展现一些无关的网卡和路由):
# ip a 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1400 qdisc pfifo_fast state UP group default qlen 1000 link/ether fa:16:3e:6f:e7:d6 brd ff:ff:ff:ff:ff:ff inet 10.173.32.63/21 brd 10.173.39.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::f816:3eff:fe6f:e7d6/64 scope link valid_lft forever preferred_lft forever85: vxlan_sys_4789: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 65485 qdisc noqueue master ovs-system state UNKNOWN group default qlen 1000 link/ether ae:22:fc:f9:77:92 brd ff:ff:ff:ff:ff:ff inet6 fe80::ac22:fcff:fef9:7792/64 scope link valid_lft forever preferred_lft forever86: ovs-system: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether 8a:95:6e:5c:65:cb brd ff:ff:ff:ff:ff:ff87: br0: <BROADCAST,MULTICAST> mtu 1350 qdisc noop state DOWN group default qlen 1000 link/ether 0e:52:ed:b2:b2:49 brd ff:ff:ff:ff:ff:ff88: tun0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1350 qdisc noqueue state UNKNOWN group default qlen 1000 link/ether 06:60:ae:a8:f5:22 brd ff:ff:ff:ff:ff:ff inet 10.178.40.1/22 brd 10.178.43.255 scope global tun0 valid_lft forever preferred_lft forever inet6 fe80::460:aeff:fea8:f522/64 scope link valid_lft forever preferred_lft forever95: vethadbc25e1@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1350 qdisc noqueue master ovs-system state UP group default link/ether 06:48:6c:da:8f:4b brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::448:6cff:feda:8f4b/64 scope link valid_lft forever preferred_lft forever
# ip r default via 10.173.32.1 dev eth0 10.173.32.0/21 dev eth0 proto kernel scope link src 10.173.32.63 10.178.32.0/21 dev tun0 10.178.40.0/21 dev tun0 scope link
宿主机的IP是位于eth0上的10.173.32.63
,咱们看到机器上还有一些非凡的网卡:
- ovs-system 所有ovs网桥在内核中有一个对立名字,即ovs-system,咱们不须要太关注
- br0 ovs服务创立的一个以太网交换机,也就是一个ovs网桥
- vethadbc25e1 应用vethpair做容器网卡虚拟化,在宿主机上会呈现一个网卡
- vxlan_sys_4789 ovs网桥上的一个端口(port),用来做vxlan封装
- tun0 tun0的IP是10.178.40.1,也就是容器里的默认网关。用来转发到node、service、内部网络的流量
通过执行以下命令能够看到:
# ovs-vsctl show fde6a881-3b54-4c50-a86f-49dcddaa5a95 Bridge "br0" fail_mode: secure Port "vethadbc25e1" Interface "vethadbc25e1" Port "tun0" Interface "tun0" type: internal Port "br0" Interface "br0" type: internal Port "vxlan0" Interface "vxlan0" type: vxlan options: {dst_port="4789", key=flow, remote_ip=flow} ovs_version: "2.8.4"
tun0、vxlan0、各个veth,都是在ovs网桥上开的端口,当这些端口收到包时,会间接被内核态的datapath监听并进行流表的规定匹配,以确定包最终的解决形式。
veth是与容器内的eth0直连的,容器里的包通过这对vethpair发送到宿主机,并且间接被datapath接管。
宿主机上有一个vxlan0,专门用来封装/解封vxlan协定的包。在ovs流表中,会将须要封装的包发给vxlan0进行封装。
当pod拜访其余节点的pod时,流表会将包引向vxlan0,IP地址封装为node的IP,封装好之后,能够间接通过宿主机的网络发到对端节点所在的node。
宿主机上有一个tun0,在宿主机的路由中,能够看到:
10.178.32.0/21 dev tun0
示意的是k8s集群里service 的网段,通过tun0收回10.178.40.0/21 dev tun0 scope link
示意的是,k8s里的集群pod CIDR,通过tun0收回。
所以当node拜访集群里任何一个pod/service,都要走tun0, tun0 是openvswitch在虚构交换机上开启的一个端口(port),从tun0流入的数据包(pod发给对端的包),会被内核态的datapath监听到,并去走内核态的、缓存好的流表规定。流表规定记录了一个数据包应该如何被正确地解决。
ovs-vswitchd 实质是一个守护过程,是 OvS 的核心部件。ovs-vswitchd 和 Datapath 一起实现 OvS 基于流表(Flow-based Switching)的数据交换。它通过 OpenFlow 协定能够与 OpenFlow 控制器通信,应用 ovsdb 协定与 ovsdb-server 数据库服务通信,应用 netlink 和 Datapath 内核模块通信。ovs-vswitchd 反对多个独立的 Datapath,ovs-vswitchd 须要加载 Datapath 内核模块能力失常运行。ovs-vswitchd 在启动时读取 ovsdb-server 中的配置信息,而后主动配置 Datapaths 和 OvS Switches 的 Flow Tables,所以用户不须要额定的通过执行 ovs-dpctl 指令工具去操作 Datapath。当 ovsdb 中的配置内容被批改,ovs-vswitched 也会自动更新其配置以保持数据同步。ovs-vswitchd 也能够从 OpenFlow 控制器获取流表项。
接下来咱们就要看流表是如何配置的~
ovs流表规定
通过执行:ovs-ofctl dump-flows br0 -O openflow13 table=XX
命令咱们能够看到ovs中某个表的流规定, table0是这个规定汇合的入口。所以咱们能够从table=0开始看起
# ovs-ofctl dump-flows br0 -O openflow13 table=0 cookie=0x0, duration=82110.449s, table=0, n_packets=0, n_bytes=0, priority=250,ip,in_port=tun0,nw_dst=224.0.0.0/4 actions=drop cookie=0x0, duration=82110.450s, table=0, n_packets=2, n_bytes=84, priority=200,arp,in_port=vxlan0,arp_spa=10.178.40.0/21,arp_tpa=10.178.40.0/22 actions=move:NXM_NX_TUN_ID[0..31]->NXM_NX_REG0[],goto_table:10 cookie=0x0, duration=82110.450s, table=0, n_packets=1, n_bytes=98, priority=200,ip,in_port=vxlan0,nw_src=10.178.40.0/21 actions=move:NXM_NX_TUN_ID[0..31]->NXM_NX_REG0[],goto_table:10 cookie=0x0, duration=82110.450s, table=0, n_packets=0, n_bytes=0, priority=200,ip,in_port=vxlan0,nw_dst=10.178.40.0/21 actions=move:NXM_NX_TUN_ID[0..31]->NXM_NX_REG0[],goto_table:10 cookie=0x0, duration=82110.450s, table=0, n_packets=2, n_bytes=84, priority=200,arp,in_port=tun0,arp_spa=10.178.40.1,arp_tpa=10.178.40.0/21 actions=goto_table:30 cookie=0x0, duration=82110.450s, table=0, n_packets=1, n_bytes=98, priority=200,ip,in_port=tun0 actions=goto_table:30 cookie=0x0, duration=82110.450s, table=0, n_packets=0, n_bytes=0, priority=150,in_port=vxlan0 actions=drop cookie=0x0, duration=82110.450s, table=0, n_packets=37, n_bytes=2678, priority=150,in_port=tun0 actions=drop cookie=0x0, duration=82110.450s, table=0, n_packets=4, n_bytes=168, priority=100,arp actions=goto_table:20 cookie=0x0, duration=82110.450s, table=0, n_packets=2, n_bytes=196, priority=100,ip actions=goto_table:20 cookie=0x0, duration=82110.450s, table=0, n_packets=0, n_bytes=0, priority=0 actions=drop
咱们次要关注规定的后半段,从priority
开始到action
之前的一串,是匹配逻辑:
- priority 示意优先级,同一个表中,咱们总是先看优先级更高的规定,不匹配再去找低的规定。同优先级的规定还有很多的过滤条件。
- ip/arp 示意数据包的协定类型,有:arp、ip、tcp、udp
- in_port示意从ovs网桥的哪个port收到的这个包
- nw_src/nw_dst 顾名思义,就是包的源IP和目标IP
之后的actions
,示意针对后面的规定失去的包,要进行如何解决,个别有:
drop
抛弃goto_table:**
转到某个表持续匹配规定set_field:10.173.32.62->tun_dst
示意封装包目标地址load:0x483152->NXM_NX_REG1[]
寄存器赋值操作,用来将某个租户的vnid保留到寄存器,后续做租户隔离的判断,这里将0x483152记录到REG1中,REG0示意源地址所属的vnid,REG1示意目标地址,REG2示意包要从哪个port收回(ovs上每个port都有id)output:***
示意从ovs网桥上的某个端口设施收回 比方vxlan0move:NXM_NX_REG0[]->NXM_NX_TUN_ID[0..31]
示意将REG0中的值拷贝到封装包的vnid字段中
例——容器拜访service的解决流程
大抵分明流表里的次要语法后,咱们能够联合一个机器上的ovs流表内容,剖析一下从pod拜访service的时候,整个解决链路:
- 容器拜访service(比方clusterIP:10.178.32.32),通过容器内路由间接收回,宿主机上的veth因为是ovs网桥上的一个port,所以包间接达到内核datapath,也就是进入table0
- table0中抉择了
cookie=0x0, duration=17956.652s, table=0, n_packets=20047, n_bytes=1412427, priority=100,ip actions=goto_table:20
进入table20 - table20中抉择了
cookie=0x0, duration=17938.360s, table=20, n_packets=0, n_bytes=0, priority=100,ip,in_port=vethadbc25e1,nw_src=10.178.40.15 actions=load:0->NXM_NX_REG0[],goto_table:21
规定,进入table21,而且做了load操作,给REG0设置值为0,意思是这个数据包的源IP能适配任何租户 - table21中记录的是k8s networkpolicy生成的对应的策略,因为咱们没有用,所以只能抉择
cookie=0x0, duration=18155.706s, table=21, n_packets=3, n_bytes=182, priority=0 actions=goto_table:30
进入table30 - 在table30中抉择了:
cookie=0x0, duration=12410.821s, table=30, n_packets=0, n_bytes=0, priority=100,ip,nw_dst=10.178.32.0/21 actions=goto_table:60
- 在table60中抉择了:
cookie=0x0, duration=12438.404s, table=60, n_packets=0, n_bytes=0, priority=100,udp,nw_dst=10.178.32.32,tp_dst=53 actions=load:0x483152->NXM_NX_REG1[],load:0x2->NXM_NX_REG2[],goto_table:80
。 留神这里咱们在action中做了load操作,告知将目标地址的vnid设置为4731218,这个值是ovs通过service所属的namespace的信息失去的,是multi-tenant的个性;并设置了REG2,示意:如果包要收回,就要从id为2的port收回 - 在table80中,咱们持续判断,如果REG0的值为0,或REG1的值为0,或REG0的值等于REG1的值,就示意这个包能够收回,于是从REG2对应的port收回。这里REG2的值为2,咱们在机器上执行
ovs-vsctl list interface
, 能够看到ofport
值为2的设施是tun0.也就是说包是从tun0收回。 - 包开始走宿主机的路由和iptbales规定,通过k8s的service负载平衡,做了一次DNAT,此时变成了pod拜访pod的包。依据路由查找,发现还是要发给tun0,另外,openshift-sdn还会做一次masquerade,通过
-A OPENSHIFT-MASQUERADE -s 10.178.40.0/21 -m comment --comment "masquerade pod-to-service and pod-to-external traffic" -j MASQUERADE
这条iptables规定实现,这样源IP就不再是pod而是node的IP【openshift-sdn反对开启ct反对,开启ct反对后,就不须要做这个额定的masq了,但开启该性能要求ovs达到2.6的版本】 - 再次进入到流表。还是走table0
- 这次咱们适配了
cookie=0x0, duration=19046.682s, table=0, n_packets=21270, n_bytes=10574507, priority=200,ip,in_port=tun0 actions=goto_table:30
间接进入table30 - 假如包被iptablesDNAT为另一个节点上的pod(10.178.44.22),那么table30中应该走
cookie=0x0, duration=13508.548s, table=30, n_packets=1, n_bytes=98, priority=100,ip,nw_dst=10.178.40.0/21 actions=goto_table:90
- table90中找到了到另一个节点的cidr的流表规定:
cookie=0xb4e80ae4, duration=13531.936s, table=90, n_packets=1, n_bytes=98, priority=100,ip,nw_dst=10.178.44.0/22 actions=move:NXM_NX_REG0[]->NXM_NX_TUN_ID[0..31],set_field:10.173.32.62->tun_dst,output:vxlan0
,意味着要从vxlan0这个port收回,并且咱们记录了tun_dst为10.173.32.62, 还将此时的REG0,也就是源IP的vnid记录到包中,作为封装包中的内容。 - vxlan0这个port做了一个封装,将包封装了源IP和目标IP,目标IP为另一个节点的IP地址(tun_dst:10.173.32.62)。封装好后从vxlan0收回
- 走机器上的路由,通过机器所在的网络发送到对端。
- 在对端节点上,内核判断到包有一个vxlan的协定头,交给对端节点的vxlan0解封,因为vxlan0也是ovs网桥上的一个port,所以解封后送入datapath进行流表解析
- 这里有两条规定都适配这个包,两个规定优先级还一样,
cookie=0x0, duration=14361.893s, table=0, n_packets=1, n_bytes=98, priority=200,ip,in_port=vxlan0,nw_src=10.178.40.0/21 actions=move:NXM_NX_TUN_ID[0..31]->NXM_NX_REG0[],goto_table:10
,cookie=0x0, duration=14361.893s, table=0, n_packets=0, n_bytes=0, priority=200,ip,in_port=vxlan0,nw_dst=10.178.40.0/21 actions=move:NXM_NX_TUN_ID[0..31]->NXM_NX_REG0[],goto_table:10
当遇到这种状况时,抉择哪一条规定是咱们无奈确定的,也就是说可能轻易选一条,然而此处两个规定都导向了table10。并且还将封包中的vnid取出,复制到REG0这个寄存器里 - table10里做了源地址的校验:
cookie=0xc694ebd2, duration=19282.596s, table=10, n_packets=3, n_bytes=182, priority=100,tun_src=10.173.32.63 actions=goto_table:30
.封装的包的源地址是不是非法的?如果不非法,那么就应该drop掉,如果没问题就进入table30 - table30中依据
cookie=0x0, duration=19341.929s, table=30, n_packets=21598, n_bytes=10737703, priority=200,ip,nw_dst=10.178.44.0/22 actions=goto_table:70
,匹配了目标IP,进入table70 - table70中,依据
cookie=0x0, duration=19409.718s, table=70, n_packets=21677, n_bytes=10775797, priority=100,ip,nw_dst=10.178.44.22 actions=load:0x483152->NXM_NX_REG1[],load:0x5->NXM_NX_REG2[],goto_table:80
进入table80,并且咱们将目标端的vnid设置为了0x483152。 目标端进口的port的id为0x5 - table80中,还是一样,判断vnid彼此是否兼容,因为咱们发包时就设置了REG0为0,所以即使REG1不为0且不等于REG0,也一样是放行的。所以从id为5的port进来。
- 对端node上,id为5的port,对应的就是pod的hostveth,因而这个包从veth收回, veth收回的包会间接被容器net namespace里的一端(eth0)收到,至此,拜访service的包达到了后端某个pod。
演绎
整个openshift-sdn的流示意用意如下:
咱们一一解释一下每个table次要的负责内容:
table10
:由vxlan收包并解决时,会走table10表,10表会判断封包的源IP是否是其余节点的nodeIP,如果不是就抛弃table20
: 由veth收到的包会进入20表,也就是pod收回的包,会进入20表,20表中次要是做了源IP的vnid的设置table21
: table20处理完毕后会进入table21,在外面会解决k8s networkpolicy的逻辑,如果判断这个包的拜访门路是通的,就会进入30表table30
:30表值次要的选路表,这里会判断协定是ip还是arp:- 判断arp包的起源或目标,申请本地pod IP的arp,到40,申请其余节点pod IP的arp到50
- 判断ip包的目标地址属于哪个段,属于本机段、集群段、service IP段,会别离走70、90、60表
table40
:将申请本地podIP的arp申请从对应的veth收回table50
: 对于申请集群里网段的IP的arp申请,封装后通过vxlan0收回table60
: 查看要拜访的具体是哪个service,依据service所属的namespace的租户id,配置包的目标vnid,并配置目标进口为tun0,进入table80table70
: 拜访本机其余pod IP时,查看pod所属的namespace的租户id,配置包的目标vnid,并配置目标进口为目标pod的veth,进入table80table80
: 依据REG进行vnid的校验,REG0=REG1或REG0=0或REG1=0时,校验通过table90
: 记录了集群里每个node的网段对应的nodeIP,在该表里设置要封装的内容:- 源IP对应的vnid要设置到封装包的字段中
- 目标地址的node的IP要设置为封装包的目标地址
table120
: 收到组播时做的逻辑判断table110
: 收回组播时做的逻辑判断table100
: 拜访内部IP时做的判断,通常只会单纯的设置走tun0table110
: 拜访内部IP时做的networkpolicy判断
基于下面的整顿,咱们能够晓得,在应用openshift-sdn的时候,集群里各种网络拜访的链路:
- 同节点的pod与pod拜访:包从客户端pod的veth,到宿主机的ovs网桥,间接达到对端pod的veth
- 跨节点的pod与pod拜访:包从客户端pod的veth,到宿主机的ovs网桥,走vxlan0端口封装后,通过宿主机的协定栈,从宿主机的物理网卡收回,到对端pod所在宿主机的物理网卡,被辨认为vxlan,进入对端机器的ovs网桥,而后到对端pod的veth
- pod拜访node:包从客户端pod的veth,到宿主机ovs网桥,因为node的物理网卡IP与pod的网络不在一个立体,所以间接走table100,而后从tun0口收回,通过宿主机的协定栈,进行路由转发,最初走宿主机所在的网络达到某个node的物理网卡
- pod拜访其余内部网络(out-of-clusternetwork)也都是走tun0
- node拜访本节点的pod:依据宿主机的路由,包从tun0收回,进入宿主机的ovs网桥,送达对端pod的veth
- node拜访其余节点的pod:依据宿主机路由,从tun0收回,进入宿主机的ovs网桥,送达vxlan0进行封装,而后走宿主机的路由和网络,到对端pod所在宿主机的物理网卡,被辨认为vxlan,进入对端机器的ovs网桥,而后到对端pod的veth
- pod拜访service: 包从客户端pod的veth,到宿主机ovs网桥,从tun0收回,通过宿主机协定栈,受iptables规定做了DNAT和MASQUERADE,至此变成了node拜访其余节点的pod
- service的后端回包给pod:因为上一步,pod拜访service时,做了MASQUERADE,所以service后端会认为是某个node拜访了本人,回包给客户端pod所在的node,node上收到后对照conntrack表,确认是之前连贯的响应包,于是对包的源地址和目标地址做了批改(对应之前做的DNAT和MASQUERADE),变成了serviceIP拜访客户端pod的包。依据node上的路由,走tun0,进入ovs网桥后,间接送到pod的veth
留神这里的第二点,pod到pod是不须要走tun0的,也就是说,集群里所有的cluster network对应的cidr,都被视为一个“二层”,不须要依赖网关的转发。上文中咱们在扩大集群网段时,须要在老容器里加一条直连路由,起因就在这:
老容器发包到新容器时,走网关转发,包的目标MAC是老节点的tun0的mac,这个包间接被流表封装收回到对端,对端解封后送到对端容器,对端容器会发现包的目标MAC本地没有,因而必定会抛弃。所以咱们不能让这种pod-to-pod的拜访链路走网关,而应该是通过直连路由。
流表查看工具
如果你感觉一条一条地看流表,特地麻烦,那么有一个很不便的实际办法,比方:
先通过ovs-vsctl list interface
命令查看到IP在ovs网桥上对应的网口的id。
ovs-vsctl list interface |less_uuid : e6ca4571-ac3b-46d4-b155-c541affa5a96admin_state : upbfd : {}bfd_status : {}cfm_fault : []cfm_fault_status : []cfm_flap_count : []cfm_health : []cfm_mpid : []cfm_remote_mpids : []cfm_remote_opstate : []duplex : fullerror : []external_ids : {ip="10.178.40.15", sandbox="6c0a268503b577936a34dd762cc6ca7a3e3f323d1b0a56820b2ef053160266ff"}ifindex : 95ingress_policing_burst: 0ingress_policing_rate: 0lacp_current : []link_resets : 0link_speed : 10000000000link_state : uplldp : {}mac : []mac_in_use : "06:48:6c:da:8f:4b"mtu : 1350mtu_request : []name : "vethadbc25e1"ofport : 12ofport_request : []options : {}other_config : {}statistics : {collisions=0, rx_bytes=182, rx_crc_err=0, rx_dropped=0, rx_errors=0, rx_frame_err=0, rx_over_err=0, rx_packets=3, tx_bytes=2930, tx_dropped=0, tx_errors=0, tx_packets=41}status : {driver_name=veth, driver_version="1.0", firmware_version=""}type : ""...
如上,咱们看到10.178.40.15
这个IP所在的端口,ofport
字段是12。 接着,执行:
ovs-appctl ofproto/trace br0 'ip,in_port=12,nw_src=10.178.40.15,nw_dst=10.173.32.62'
在这条命令中,咱们模仿往某个port(id为12)塞一个包,源IP是10.178.40.15,目标IP是10.173.32.62。
输入是:
Flow: ip,in_port=12,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=00:00:00:00:00:00,nw_src=10.178.40.15,nw_dst=10.173.32.62,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0bridge("br0")------------- 0. ip, priority 100 goto_table:2020. ip,in_port=12,nw_src=10.178.40.15, priority 100 load:0->NXM_NX_REG0[] goto_table:2121. priority 0 goto_table:3030. ip, priority 0 goto_table:100100. priority 0 goto_table:101101. priority 0 output:2Final flow: unchangedMegaflow: recirc_id=0,eth,ip,in_port=12,nw_src=10.178.40.15,nw_dst=10.173.32.62,nw_frag=noDatapath actions: 3
会把整个链路走的所有的表,以及最初从哪个口收回,做的封装(此例中不做封装,Final flow=unchanged)全副显示进去。
结语
本文咱们由浅入深地介绍了openshift-sdn这个网络计划,理解了他的架构和用法,并深刻地摸索了它的实现。 ovs流表的浏览和跟踪是一个比拟吃力的活,但当咱们啃下来之后,会发现openshift-sdn的流表设计还是比拟简洁易懂的,心愿读完本文的你能有所播种~
援用
https://blog.csdn.net/Jmilk/j...
https://www.cnblogs.com/sammy...
https://docs.openshift.com/co...
https://docs.openshift.com/co...
本文由博客群发一文多发等经营工具平台 OpenWrite 公布