本站文章大部分为作者原创,非商业用途转载无需作者授权,但务必在文章标题下面注明作者 刘世民(Sammy Liu)以及可点击的本博客地址超级链接 http://www.cnblogs.com/sammyliu/ ,谢谢合作

理解OpenShift(3):网络之 SDN

理解OpenShift(1):网络之 Router 和 Route

理解OpenShift(2):网络之 DNS(域名服务)

理解OpenShift(3):网络之 SDN

理解OpenShift(4):用户及权限管理

理解OpenShift(5):从 Docker Volume 到 OpenShift Persistent Volume

 

** 本文基于 OpenShift 3.11,Kubernetes 1.11 进行测试 ***

 

1. 概况

为了OpenShift 集群中 pod 之间的网络通信,OpenShift 以插件形式提供了三种符合Kubernetes CNI 要求的 SDN实现:

  • ovs-subnet:ovs-subnet 实现的是一种扁平网络,未实现租户之间的网络隔离,这意味着所有租户之间的pod 都可以互访,这使得该实现无法用于绝大多数的生产环境。
  • ovs-multitenant:基于 OVS 和 VxLAN 等技术实现了项目(project)之间的网络隔离。
  • ovs-networkpolicy:介于ovs-subnet 和 ovs-multitenant 之间的一种实现。考虑到 ovs-multitenant  只是实现了项目级别的网络隔离,这种隔离粒度在一些场景中有些过大,用户没法做更精细的控制,这种需求导致了ovs-networkpolicy的出现。默认地,它和ovs-subnet 一样,所有租户之间都没有网络隔离。但是,管理员可以通过定义 NetworkPolicy 对象来精细地进行网络控制。可以粗略地将它类比为OpenStack neutron 中的neutron 网络防火墙和Nova安全组。具体请查阅有关文档。

当使用 ansible 部署 OpenShift 时,默认会启用ovs-subnet,但是可以在部署完成后修改为其它两种实现。本文中的说明都是针对 ovs-multitenant。

1.1 OpenShift 集群的网络设计

要部署一个OpenShift 生产环境,主要的网络规划和设计如下图所示:

 

 

节点角色类型:

  • Master 节点:只承担 Master 角色,可不也可以承担Node 角色。主要运行 API 服务、controller manager 服务、etcd 服务、web console 服务等。
  • Infra 节点:作为 Node 角色,通过设置并应用节点标签,只用于部署系统基础服务,包括Registry、Router、Prometheus 以及 EFK 等。
  • Node 节点:作为 Node 角色,用于运行用户业务系统的Pod。

网络类型:

  • 外部网络:这是一个外部网络,用于从外部访问集群。和该网络连接的服务器或组件需要被分配公网IP地址才能被从外部访问。从内部访问外网中的服务时,比如DNS或者镜像仓库,可以通过NAT实现,而无需公网IP地址。
  • 管理网络:这是一个内部网络,用于集群内部 API 访问。
  • IPMI网络:这是一个内部网络,用于管理物理服务器。
  • SDN网络:这是一个内部网络,用于集群内部Pod 之间的通信,承载 VxLAN Overlay 流量。
  • 存储网络:这是一个内部网络,用于各节点访问基于网络的存储。

在PoC 或开发测试环境中,管理/SDN/存储网络可以合并为一个网络。

1.2 Node节点中的网络

 

节点上的主要网络设备:

  • br0:OpenShift 创建和管理的 Open vSwitch 网桥, 它会使用 OpenFlow 规则来实现网络隔离和转发。
  • vethXXXXX:veth 对,它负责将 pod 的网络命名空间连接到 br0 网桥。
  • tun0 :一OVS 内部端口,它会被分配本机的 pod 子网的网关IP 地址,用于OpenShift pod 以及Docker 容器与集群外部的通信。iptables 的 NAT 规则会作用于tun0。
  • docker0:Docker 管理和使用的 linux bridge 网桥,通过 veth 对将不受 OpenShift 管理的Docker 容器的网络地址空间连接到 docker0 上。
  • vovsbr/vlinuxbr:将 docker0 和 br0 连接起来的 veth 对,使得Docker 容器能和 OpenShift pod 通信,以及通过 tun0 访问外部网络
  • vxlan0:一OVS VXLAN 隧道端点,用于集群内部 pod 之间的网络通信。

2. 实现

2.1 pod 网络总体设置流程

Pod 网络总体设置流程如下(来源:OpenShift源码简析之pod网络配置(上)):

简单说明:

  • OpenShift 使用运行在每个节点上的 kubelet 来负责pod 的创建和管理,其中就包括网络配置部分。
  • 当 kubelet 接受到 pod 创建请求时,会首先调用docker client 来创建容器,然后再调用 docker api接口启动上一步中创建成功的容器。kubelet 在创建 pod 时是先创建一个 infra 容器,配置好该容器的网络,然后创建真正用于业务的应用容器,最后再把业务容器的网络加到infra容器的网络命名空间中,相当于业务容器共享infra容器的网络命名空间。业务应用容器和infra容器共同组成一个pod。
  • kubelet 使用 CNI 来创建和管理Pod网络(openshift在启动kubelet时传递的参数是--netowrk-plugin=cni)。OpenShift 实现了 CNI 插件(由 /etc/cni/net.d/80-openshift-network.conf 文件指定),其二进制文件是 /opt/cni/bin/openshift-sdn 。因此,kubelet 通过 CNI 接口来调用 openshift sdn 插件,然后具体做两部分事情:一是通过 IPAM 获取 IP 地址,二是设置 OVS(其中,一是通过调用 ovs-vsctl 将 infra 容器的主机端虚拟网卡加入 br0,二是调用 ovs-ofctl 命令来设置规则)。

2.2 OVS 网桥 br0 中的规则

本部分内容主要引用自 OVS 在云项目中的使用

流量规则表:

  • table 0: 根据输入端口(in_port)做入口分流,来自VXLAN隧道的流量转到表10并将其VXLAN VNI 保存到 OVS 中供后续使用,从tun0过阿里的(来自本节点或进本节点来做转发的)流量分流到表30,将剩下的即本节点的容器(来自veth***)发出的流量转到表20;
  • table 10: 做入口合法性检查,如果隧道的远端IP(tun_src)是某集群节点的IP,就认为是合法,继续转到table 30去处理;
  • table 20: 做入口合法性检查,如果数据包的源IP(nw_src)与来源端口(in_port)相符,就认为是合法的,设置源项目标记,继续转到table 30去处理;如果不一致,即可能存在ARP/IP欺诈,则认为这样的的数据包是非法的;
  • table 30: 数据包的目的(目的IP或ARP请求的IP)做转发分流,分别转到table 40~70 去处理;
  • table 40: 本地ARP的转发处理,根据ARP请求的IP地址,从对应的端口(veth)发出;
  • table 50: 远端ARP的转发处理,根据ARP请求的IP地址,设置VXLAN隧道远端IP,并从隧道发出;
  • table 60: Service的转发处理,根据目标Service,设置目标项目标记和转发出口标记,转发到table 80去处理;
  • table 70: 对访问本地容器的包,做本地IP的转发处理,根据目标IP,设置目标项目标记和转发出口标记,转发到table 80去处理;
  • table 80: 做本地的IP包转出合法性检查,检查源项目标记和目标项目标记是否匹配,或者目标项目是否是公开的,如果满足则转发;(这里实现了 OpenShift 网络层面的多租户隔离机制,实际上是根据项目/project 进行隔离,因为每个项目都会被分配一个 VXLAN VNI,table 80 只有在网络包的VNI和端口的VNI tag 相同才会对网络包进行转发)
  • table 90: 对访问远端容器的包,做远端IP包转发“寻址”,根据目标IP,设置VXLAN隧道远端IP,并从隧道发出;
  • table 100: 做出外网的转出处理,将数据包从tun0发出。

 

备注一些常用的操作命令:

  • 查询OVS 流表: ovs-ofctl -O OpenFlow13 dump-flows br0
  • 查询OVS设备: ovs-vsctl show
  • 查看OVS网桥: ovs-ofctl -O OpenFlow13 show br0
  • 查看路由表:route -n
  • 在容器中运行命令:nsenter -t <容器的PiD> -n ip a
  • 查询 iptables NAT 表:iptables -t nat -S

3. 流程

3.1 同一个节点上的两个pod 之间的互访

访问:pod 1 (ip:10.131.1.150)访问 pod2(10.131.1.152)

网络路径::pod1的eth0 → veth12 → br0 → veth34 → pod2的eth0。 

OVS 流表:

table=0, n_packets=14631632, n_bytes=1604917617, priority=100,ip actions=goto_table:20
table=20, n_packets=166585, n_bytes=12366463, priority=100,ip,in_port=96,nw_src=10.131.1.152 actions=load:0xbe3127->NXM_NX_REG0[],goto_table:21
table=21, n_packets=14671413, n_bytes=1606835395, priority=0 actions=goto_table:30
table=30, n_packets=8585493, n_bytes=898571869, priority=200,ip,nw_dst=10.131.0.0/23 actions=goto_table:70
table=70, n_packets=249967, n_bytes=16177300, priority=100,ip,nw_dst=10.131.1.152 actions=load:0xbe3127->NXM_NX_REG1[],load:0x60->NXM_NX_REG2[],goto_table:80
table=80, n_packets=0, n_bytes=0, priority=100,reg0=0xbe3127,reg1=0xbe3127 actions=output:NXM_NX_REG2[]
table=80, n_packets=0, n_bytes=0, priority=0 actions=drop #不合法的包会被丢弃

表 20 会判断包类型(IP)、源地址(nw_src)、进来端口的ID(96),将其对应的 VNI ID(这里是 0xbe3127,十进制是12464423)保存在 REG0 中。这意味着所有通过OVS 端口进入OVS br0 网桥的来自pod 的网络包都会被打上对口对应的VNID 标签。集群中所有项目对应的 VNID 可以使用 oc get netnamespaces 命令查到:

[root@master1 cloud-user]# oc get netnamespaces
NAME                                NETID      EGRESS IPS
cicd                                16604171   []
default                             0          []
demoproject2                        16577323   []
demoprojectone                      1839630    []
dev                                 12464423   []

表 70 会根据目的地址,也就是目的 pod 的地址,将网络包的目的出口标记(这里为 0x60,十进制为96)保存到REG2,同时设置其项目的 VNI ID 到 REG1(这里是0xbe3127).

根据端口的ID 96 找到veth网络设备:

96(veth0612e07f): addr:66:d0:c3:e3:be:cf
     config:     0
     state:      0
     current:    10GB-FD COPPER
     speed: 10000 Mbps now, 0 Mbps max

查找其对应的容器中的网卡。

[root@node1 cloud-user]# ip link  | grep veth0612e07f
443: veth0612e07f@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1400 qdisc noqueue master ovs-system state UP mode DEFAULT 

这与pod2容器中的 eth0 正好吻合:

3: eth0@if443: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1400 qdisc noqueue state UP 
    link/ether 0a:58:0a:83:01:98 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.131.1.152/23 brd 10.131.1.255 scope global eth0
       valid_lft forever preferred_lft forever

表80 会检查报的来源 VNI ID (REG0)和目的端口的 VNI ID (REG1),将相符的合法的包转发到表70 设置的出口,以完成转发。

3.2 不同节点上的同一个网络的两个pod 之间的互访

 

 

网络路径:节点1上的Pod1的eth0→veth1→br0→vxlan0→ 节点1的eth0网卡→ 节点2的eth0网卡→vxlan0→br0→veth1→ Pod3的eth0流表:

发送端(node1)的OVS 流表:

table=0, n_packets=14703186, n_bytes=1612904326, priority=100,ip actions=goto_table:20
table=20, n_packets=167428, n_bytes=12428845, priority=100,ip,in_port=96,nw_src=10.131.1.152 actions=load:0xbe3127->NXM_NX_REG0[],goto_table:21
table=21, n_packets=14736461, n_bytes=1613954556, priority=0 actions=goto_table:30
table=30, n_packets=1143761, n_bytes=1424533777, priority=100,ip,nw_dst=10.128.0.0/14 actions=goto_table:90
table=90, n_packets=0, n_bytes=0, priority=100,ip,nw_dst=10.128.2.0/23 actions=move:NXM_NX_REG0[]->NXM_NX_TUN_ID[0..31],set_field:172.22.122.9->tun_dst,output:1
  • 表21 同样是将源pod 的 VNI ID 保存在 REG0 中。
  • 表30 会判断目的地址是不是集群的大的 pod 的 IP CIDR。
  • 表90 会设置 VNI ID 为之前保存在 REG0 中的值,然后根据目的地址的网段(这里是 10.128.2.0/23),计算出其所在的节点的IP 地址(这里是 172.22.122.9)并设置为tun_dst,然后发到 vxlan0,它会负责根据提供的信息来做VXLAN UDP 包封装。

接收端(node2)的OVS 流表: 

table=0, n_packets=1980863, n_bytes=1369174876, priority=200,ip,in_port=1,nw_src=10.128.0.0/14 actions=move:NXM_NX_TUN_ID[0..31]->NXM_NX_REG0[],goto_table:10
table=10, n_packets=0, n_bytes=0, priority=100,tun_src=172.22.122.8 actions=goto_table:30
table=30, n_packets=16055284, n_bytes=1616511267, priority=200,ip,nw_dst=10.128.2.0/23 actions=goto_table:70
table=70, n_packets=248860, n_bytes=16158751, priority=100,ip,nw_dst=10.128.2.128 actions=load:0xbe3127->NXM_NX_REG1[],load:0x32->NXM_NX_REG2[],goto_table:80
table=80, n_packets=0, n_bytes=0, priority=100,reg0=0xbe3127,reg1=0xbe3127 actions=output:NXM_NX_REG2[]
  • 表0 会将发送到保存在 NXM_NX_TUN_ID[0..31] 中的源 VNI ID 取出来保存到 REG0.
  • 表10 会检查包的来源节点的地址。
  • 表30 会检查包的目的地址是不是本机上 pod 的网段。
  • 表70 会根据目的地址,将目的 VNI ID 保存到 REG1,将目的端口 ID 保存到 REG2
  • 表80 会检查目的 VNI ID 和源 VNI ID,如果相符的话,则将包转发到保存在 REG2 中的目的端口ID 指定的端口。然后包就会通过 veth 管道进入目的 pod。

3.3 pod 内访问外网

网络路径:PodA的eth0 → vethA → br0 → tun0 → 通过iptables实现SNAT → 物理节点的 eth0  → 互联网

NAT:将容器发出的IP包的源IP地址修改为宿主机的 eth0 网卡的IP 地址。

OVS 流表:

table=0, n_packets=14618128, n_bytes=1603472372, priority=100,ip actions=goto_table:20
table=20, n_packets=0, n_bytes=0, priority=100,ip,in_port=17,nw_src=10.131.1.73 actions=load:0xfa9a3->NXM_NX_REG0[],goto_table:21
table=21, n_packets=14656675, n_bytes=1605262241, priority=0 actions=goto_table:30
table=30, n_packets=73508, n_bytes=6820206, priority=0,ip actions=goto_table:100
table=100, n_packets=44056, n_bytes=3938540, priority=0 actions=goto_table:101
table=101, n_packets=44056, n_bytes=3938540, priority=0 actions=output:2

表20 会检查 IP 包的来源端口和IP 地址,并将源项目的 VNI ID 保存到 REG0.

表101 会将包发送到端口2 即 tun0. 然后被 iptables 做 NAT 然后发送到 eth0.

3.4 外网访问 pod

因为 Infra 节点上的 HAproxy 容器采用了 host-network 模式,因此它是直接使用宿主机的 eth0 网卡的。

下面是宿主机的路由表:

[root@infra-node1 /]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.22.122.1    0.0.0.0         UG    100    0        0 eth0
10.128.0.0      0.0.0.0         255.252.0.0     U     0      0        0 tun0
169.254.169.254 172.22.122.1    255.255.255.255 UGH   100    0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
172.22.122.0    0.0.0.0         255.255.255.0   U     100    0        0 eth0
172.30.0.0      0.0.0.0         255.255.0.0     U     0      0        0 tun0

从 HAProxy 容器内出来目的地址为业务pod(ip:10.128.2.128)的网络包,根据上面的路由表,其下一跳是 tun0,也就是说它又进入了 OVS 网桥 br0. 对应的 OVS 流表规则为:

ip,in_port=2 actions=goto_table:30
ip,nw_dst=10.128.0.0/14 actions=goto_table:90
ip,nw_dst=10.128.2.0/23 actions=move:NXM_NX_REG0[]->NXM_NX_TUN_ID[0..31],set_field:172.22.122.9->tun_dst,output:1

可见它最终又被发到了端口1 即 vxlan0,它会负责做 vxlan 封包,并通过 eth0 网卡发出去。 

3.5 汇总

总体来说,OVS 中的OpenFlow流表根据网络包的目的地址将其分为四类来处理:

  • 到本地pod的,直接在 br0 中转发。
  • 到本集群pod 的,经过 br0 后发到 vxlan0,封装为 vxlan udp 包经物理网卡发到对方节点。
  • 到本地不受OpenShift SDN管理的docker容器的,还未具体研究。
  • 到集群外的,经过 br0 后发到 tun0,经过 iptables 做SNAT,然后经物理网卡发出。

3.6. 项目(project)级别的网络隔离

3.6.1 原理

OpenShift 中的网络隔离是在项目(project)级别实现的。OpenShfit 默认的项目 『default』的 VNID (Virtual Network ID)为0,表明它是一个特权项目,因为它可以发网络包到其它所有项目,也能接受其它所有项目的pod发来的网络包。这从 table 80 的规则上可以看出来,如果来源项目的 VNID (reg0)或目标项目的 VNID(reg1)为0,都会允许包转发到pod 的端口:

table=80, n_packets=8244506, n_bytes=870316191, priority=200,reg0=0 actions=output:NXM_NX_REG2[]
table=80, n_packets=13576848, n_bytes=1164951315, priority=200,reg1=0 actions=output:NXM_NX_REG2[]

其它所有项目都会有一个非0的 VNID。在 OpenShift ovs-multitenant 实现中,非0 VNID 的项目之间的网络是不通的。

从一个本地 pod 发出的所有网络流量,在它进入 OVS 网桥时,都会被打上它所通过的 OVS 端口ID相对应的 VNID。port:VNID 映射会在pod 创建时通过查询master 上的 etcd 来确定。从其它节点通过 VXLAN发过来的网络包都会带有发出它的pod 所在项目的 VNID。

根据上面的分析,OVS 网桥中的 OpenFlow 规则会阻止带有与目标端口上的 VNID 不同的网络包的投递(VNID 0 除外)。这就保证了项目之间的网络流量是互相隔离的。

可以使用下面的命令查看namespace 的 NETID 也就是 VNID:

在我的环境里面,default 项目默认就是 global的,我还把 cicd 项目设置为 gloabl 的了,因为它也需要访问其它项目。

3.6.2 实验

下图显示了两个项目之间的三种网络状态:

  • 左图显示的是默认状态:SIT 项目和 Dev 项目之间的 pod 无法访问。根据前面对 OVS 流表的分析,表80 会检查IP 包的来源Pod的项目 VNI ID 和目标Pod的项目 VNI ID。如果两者不符合,这些IP网络包就会被丢弃。
  • 中间图显示的是打通这两个项目的网络:通过运行 oc adm pod-network join-projects 命令,将两个项目连接在一起,结果就是 DEV 项目的 VNI ID 变成了 SIT 项目的 VNI ID。这时候两个项目中的 pod 网络就通了。
  • 右图显示的是分离这两个项目的网络:通过运行 oc adm pod-network isolate-projects 命令,将两个项目分离,其结果是 DEV 项目被分配了新的 VNI ID。此时两个项目中的pod 又不能互通了。

3.7 CluserIP 类型的 Service

OpenShift Serivce 有多种类型,默认的和最常用的是 ClusterIP 类型。每个这种类型的Service,创建时都会被从一个子网中分配一个IP地址,在集群内部可以使用该IP地址来访问该服务,进而访问到它后端的pod。因此,Service 实际上是用于OpenShift 集群内部的四层负载均衡器,它是基于 iptables 实现的。

接下来我以 mybank 服务为例进行说明,它的  ClusterIP 是  172.30.162.172,服务端口是8080;它有3个后端 10.128.2.128:8080,10.131.1.159:8080,10.131.1.160:8080。

宿主机上的路由表:

[root@node1 cloud-user]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.22.122.1    0.0.0.0         UG    100    0        0 eth0
10.128.0.0      0.0.0.0         255.252.0.0     U     0      0        0 tun0   #3.7.1 中会用到
169.254.169.254 172.22.122.1    255.255.255.255 UGH   100    0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
172.22.122.0    0.0.0.0         255.255.255.0   U     100    0        0 eth0   #3.7.1 中会用到
172.30.0.0      0.0.0.0         255.255.0.0     U     0      0        0 tun0   #3.7.2 中会用到

3.7.1 从宿主机上访问服务

每当创建一个 service 后,OpenShift 会在集群的每个节点上的 iptables 中添加以下记录:

-A KUBE-SERVICES -d 172.30.162.172/32 -p tcp -m comment --comment "dev/mybank:8080-tcp cluster IP" -m tcp --dport 8080 -j KUBE-SVC-3QLA52JX7QFEEEC5

-A KUBE-SVC-3QLA52JX7QFEEEC5 -m comment --comment "dev/mybank:8080-tcp" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-AWPSVWBUXH7A2CLB
-A KUBE-SVC-3QLA52JX7QFEEEC5 -m comment --comment "dev/mybank:8080-tcp" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-ESYZLBFGDE6MOHX2
-A KUBE-SVC-3QLA52JX7QFEEEC5 -m comment --comment "dev/mybank:8080-tcp" -j KUBE-SEP-ENPHHSSNP6FR7JJI

-A KUBE-SEP-AWPSVWBUXH7A2CLB -p tcp -m comment --comment "dev/mybank:8080-tcp" -m tcp -j DNAT --to-destination 10.128.2.128:8080

-A KUBE-SVC-3QLA52JX7QFEEEC5 -m comment --comment "dev/mybank:8080-tcp" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-ESYZLBFGDE6MOHX2

-A KUBE-SEP-ENPHHSSNP6FR7JJI -p tcp -m comment --comment "dev/mybank:8080-tcp" -m tcp -j DNAT --to-destination 10.131.1.160:8080

  • 第1条:检查目的IP地址以及端口,添加comment
  • 第2到5条:以随机分配(random)方式将流量平均地转发到三条规则上
  • 第6条:第一条转发规则通过DNAT 将目的IP地址和端口修改为第一个endpoint 的IP 和地址,第7和8条相同

DNAT 后,根据路由表,下一跳将是 tun0,也就是说它会进入 OVS 网桥 br0。在进入网桥之前,如果是从pod 中发出的网络包,还会进行SNAT,将其源IP地址修改为 tun0 的IP 地址。其目的是使得返回包能回到tun0,然后能通过反SNAT 操作,将目的IP地址由 tun0 的IP 修改为原来的源IP。具体见下文的分析。

-A OPENSHIFT-MASQUERADE -s 10.128.0.0/14 -m comment --comment "masquerade pod-to-service and pod-to-external traffic" -j MASQUERADE

然后,进入网桥。在网桥中,会检查目的地址。如果是本地 pod 网段内的,那么将直接转发给对应的pod;如果是远端pod的,那么转发到 vxlan0 再通过 VXLAN 网络发到对方节点。这过程跟上面说明的过程就差不多了,不再赘述。

3.7.2 从 pod 中访问 service

从某个 pod 中访问同一个 service。IP 包从 br0 的某个端口进入 OVS,然后执行以下流表规则:

table=30, n_packets=14212117, n_bytes=1219709382, priority=100,ip,nw_dst=172.30.0.0/16 actions=goto_table:60
table=60, n_packets=0, n_bytes=0, priority=100,ip,nw_dst=172.30.162.172,nw_frag=later actions=load:0xbe3127->NXM_NX_REG1[],load:0x2->NXM_NX_REG2[],goto_table:80
table=60, n_packets=0, n_bytes=0, priority=100,tcp,nw_dst=172.30.162.172,tp_dst=8080 actions=load:0xbe3127->NXM_NX_REG1[],load:0x2->NXM_NX_REG2[],goto_table:80 table=80, n_packets=0, n_bytes=0, priority=100,reg0=0xbe3127,reg1=0xbe3127 actions=output:NXM_NX_REG2[]

从 table60 可以看出,OVS 流表给该网络包设置的出口端口为2,即 tun0,因为要去做NAT。出去后,即开始 iptables NAT 过程,也就是 3.7.1 中的过程。最后还是要回到 OVS br0,再走到 vxlan0,通过 VXLAN 隧道发到目标pod 所在的宿主机。该过程示意图如下:

对于返回的网络包,其目的地址是源pod 宿主机上的 tun0,即左图中的 10.131.0.1/23. 数据包到达左图中的 br0 后,首先要出 tun0,因为要去做NAT:

table=30, n_packets=1214735, n_bytes=1135728626, priority=300,ip,nw_dst=10.131.0.1 actions=output:2

根据这篇文章(https://superuser.com/questions/1269859/linux-netfilter-how-does-connection-tracking-track-connections-changed-by-nat),发送阶段 iptables 在做 SNAT 时会利用 conntrack 记录这次修改(在/proc/net/nf_conntrack 中);在现在回复包返回的时候,会自动地做相反SNAT操作(类似DNAT),将包的目的IP地址(tun0的IP地址)修改为原来的源IP地址即源pod地址。

/proc/net/nf_conntrack 文件的有关记录:

ipv4     2 tcp      6 70 TIME_WAIT src=10.131.0.1 dst=10.131.1.72 sport=56862 dport=8080 src=10.131.1.72 dst=10.131.0.1 sport=8080 dport=56862 [ASSURED] mark=0 secctx=system_u:object_r:unlabeled_t:s0 zone=0 use=2

做完De-SNAT后,根据路由表,它又会回到 tun0, OVS 根据流表,会根据目的pod IP 地址对它进行转发,使得它回到原来的出发pod。

 

参考文档:

感谢您的阅读,欢迎关注我的微信公众号:

posted on 2018-12-05 15:27  SammyLiu  阅读(11938)  评论(1编辑  收藏  举报