理解OpenShift(1):網絡之 Router 和 Routehtml
理解OpenShift(2):網絡之 DNS(域名服務)node
理解OpenShift(5):從 Docker Volume 到 OpenShift Persistent Volumegithub
** 本文基於 OpenShift 3.11,Kubernetes 1.11 進行測試 ***web
爲了OpenShift 集羣中 pod 之間的網絡通訊,OpenShift 以插件形式提供了三種符合Kubernetes CNI 要求的 SDN實現:docker
當使用 ansible 部署 OpenShift 時,默認會啓用ovs-subnet,可是能夠在部署完成後修改成其它兩種實現。本文中的說明都是針對 ovs-multitenant。後端
要部署一個OpenShift 生產環境,主要的網絡規劃和設計以下圖所示:api
節點角色類型:安全
網絡類型:
在PoC 或開發測試環境中,管理/SDN/存儲網絡能夠合併爲一個網絡。
節點上的主要網絡設備:
Pod 網絡整體設置流程以下(來源:OpenShift源碼簡析之pod網絡配置(上)):
簡單說明:
本部份內容主要引用自 OVS 在雲項目中的使用:
流量規則表:
備註一些經常使用的操做命令:
訪問: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 設置的出口,以完成轉發。
網絡路徑:節點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
接收端(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[]
網絡路徑: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.
由於 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 網卡發出去。
整體來講,OVS 中的OpenFlow流表根據網絡包的目的地址將其分爲四類來處理:
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 的了,由於它也須要訪問其它項目。
下圖顯示了兩個項目之間的三種網絡狀態:
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 中會用到
每當建立一個 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
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 網絡發到對方節點。這過程跟上面說明的過程就差很少了,再也不贅述。
從某個 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。
參考文檔:
感謝您的閱讀,歡迎關注個人微信公衆號: