node節點的iptables是由kube-proxy生成的,具體實現可以參見kube-proxy的代碼
kube-proxy只修改了filter和nat表,它對iptables的鏈進行了擴充,自定義了KUBE-SERVICES,KUBE-NODEPORTS,KUBE-POSTROUTING,KUBE-MARK-MASQ和KUBE-MARK-DROP五個鏈,並主要通過為 KUBE-SERVICES鏈(附着在PREROUTING和OUTPUT)增加rule來配制traffic routing 規則,官方定義如下:
// the services chain kubeServicesChain utiliptables.Chain = "KUBE-SERVICES" // the external services chain kubeExternalServicesChain utiliptables.Chain = "KUBE-EXTERNAL-SERVICES" // the nodeports chain kubeNodePortsChain utiliptables.Chain = "KUBE-NODEPORTS" // the kubernetes postrouting chain kubePostroutingChain utiliptables.Chain = "KUBE-POSTROUTING" // the mark-for-masquerade chain KubeMarkMasqChain utiliptables.Chain = "KUBE-MARK-MASQ" /*對於未能匹配到跳轉規則的traffic set mark 0x8000,有此標記的數據包會在filter表drop掉*/ // the mark-for-drop chain KubeMarkDropChain utiliptables.Chain = "KUBE-MARK-DROP" /*對於符合條件的包 set mark 0x4000, 有此標記的數據包會在KUBE-POSTROUTING chain中統一做MASQUERADE*/ // the kubernetes forward chain kubeForwardChain utiliptables.Chain = "KUBE-FORWARD"
KUBE-MARK-MASQ和KUBE-MARK-DROP
這兩個規則主要用來對經過的報文打標簽,打上標簽的報文可能會做相應處理,打標簽處理如下:
(注:iptables set mark的用法可以參見
https://unix.stackexchange.com/questions/282993/how-to-add-marks-together-in-iptables-targets-mark-and-connmark
http://ipset.netfilter.org/iptables-extensions.man.html)
-A KUBE-MARK-DROP -j MARK --set-xmark 0x8000/0x8000 -A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
KUBE-MARK-DROP和KUBE-MARK-MASQ本質上就是使用了iptables的MARK命令
Chain KUBE-MARK-DROP (6 references) pkts bytes target prot opt in out source destination 0 0 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 MARK or 0x8000 Chain KUBE-MARK-MASQ (89 references) pkts bytes target prot opt in out source destination 88 5280 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 MARK or 0x4000
對於KUBE-MARK-MASQ鏈中所有規則設置了kubernetes獨有MARK標記,在KUBE-POSTROUTING鏈中對NODE節點上匹配kubernetes獨有MARK標記的數據包,當報文離開node節點時進行SNAT,MASQUERADE源IP
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
而對於KUBE-MARK-DROP設置標記的報文則會在KUBE_FIREWALL中全部丟棄
-A KUBE-FIREWALL -m comment --comment "kubernetes firewall for dropping marked packets" -m mark --mark 0x8000/0x8000 -j DROP
KUBE_SVC和KUBE-SEP
Kube-proxy接着對每個服務創建“KUBE-SVC-”鏈,並在nat表中將KUBE-SERVICES鏈中每個目標地址是service的數據包導入這個“KUBE-SVC-”鏈,如果endpoint尚未創建,KUBE-SVC-鏈中沒有規則,任何incomming packets在規則匹配失敗后會被KUBE-MARK-DROP。在iptables的filter中有如下處理,如果KUBE-SVC處理失敗會通過KUBE_FIREWALL丟棄
Chain INPUT (policy ACCEPT 209 packets, 378K bytes) pkts bytes target prot opt in out source destination 540K 1370M KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */ 540K 1370M KUBE-FIREWALL all -- * * 0.0.0.0/0 0.0.0.0/0
KUBE_FIREWALL內容如下,就是直接丟棄所有報文:
Chain KUBE-FIREWALL (2 references) pkts bytes target prot opt in out source destination 0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes firewall for dropping marked packets */ mark match 0x8000/0x8000
下面是對nexus的service的處理,可以看到該規對目的IP為172.21.12.49(Cluster IP)且目的端口為8080的報文作了特殊處理:KUBE-SVC-HVYO5BWEF5HC7MD7
-A KUBE-SERVICES -d 172.21.12.49/32 -p tcp -m comment --comment "default/sonatype-nexus: cluster IP" -m tcp --dport 8080 -j KUBE-SVC-HVYO5BWEF5HC7MD7
KUBE-SEP表示的是KUBE-SVC對應的endpoint,當接收到的 serviceInfo中包含endpoint信息時,為endpoint創建跳轉規則,如上述的KUBE-SVC-HVYO5BWEF5HC7MD7有endpoint,其iptables規則如下:
-A KUBE-SVC-HVYO5BWEF5HC7MD7 -m comment --comment "oqton-backoffice/sonatype-nexus:" -j KUBE-SEP-ESZGVIJJ5GN2KKU
KUBE-SEP-ESZGVIJJ5GN2KKU中的處理為將經過該鏈的所有tcp報文,DNAT為container 內部暴露的訪問方式172.20.5.141:8080。結合對KUBE-SVC的處理可可知,這種訪問方式就是cluster IP的訪問方式,即將目的IP是cluster IP且目的端口是service暴露的端口的報文DNAT為目的IP是container且目的端口是container暴露的端口的報文,
-A KUBE-SEP-ESZGVIJJ5GN2KKUR -p tcp -m comment --comment "oqton-backoffice/sonatype-nexus:" -m tcp -j DNAT --to-destination 172.20.5.141:8080
如果service類型為nodePort,(從LB轉發至node的數據包均屬此類)那么將KUBE-NODEPORTS鏈中每個目的地址是NODE節點端口的數據包導入這個“KUBE-SVC-”鏈;KUBE-NODEPORTS必須位於KUBE-SERVICE鏈的最后一個,可以看到iptables在處理報文時會優先處理目的IP為cluster IP的報文,匹配失敗之后再去使用NodePort方式。如下規則表明,NodePort方式下會將目的ip為node節點且端口為node節點暴露的端口的報文進行KUBE-SVC-HVYO5BWEF5HC7MD7處理,KUBE-SVC-HVYO5BWEF5HC7MD7中會對報文進行DNAT轉換。因此Custer IP和NodePort方式的唯一不同點就是KUBE-SERVICE中是根據cluster IP還是根據node port進行匹配
"-m addrtype --dst-type LOCAL"表示對目的地址是本機地址的報文執行KUBE-NODEPORTS鏈的操作
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A KUBE-NODEPORTS -p tcp -m comment --comment "oqton-backoffice/sonatype-nexus:" -m tcp --dport 32257 -j KUBE-MARK-MASQ -A KUBE-NODEPORTS -p tcp -m comment --comment "oqton-backoffice/sonatype-nexus:" -m tcp --dport 32257 -j KUBE-SVC-HVYO5BWEF5HC7MD7
如果服務用到了loadblance,此時報文是從LB inbound的,報文的outbound處理則是通過KUBE-FW實現outbound報文的負載均衡。如下對目的IP是50.1.1.1(LB公網IP)且目的端口是443(一般是https)的報文作了KUBE-FW-J4ENLV444DNEMLR3處理。(參考kubernetes ingress到pod的數據流)
-A KUBE-SERVICES -d 50.1.1.1/32 -p tcp -m comment --comment "kube-system/nginx-ingress-lb:https loadbalancer IP" -m tcp --dport 443 -j KUBE-FW-J4ENLV444DNEMLR3
如下在KUBE-FW-J4ENLV444DNEMLR3中顯示的是LB的3個endpoint(該endpoint可能是service),使用比率對報文進行了負載均衡控制
Chain KUBE-SVC-J4ENLV444DNEMLR3 (3 references) 10 600 KUBE-SEP-ZVUNFBS77WHMPNFT all -- * * 0.0.0.0/0 0.0.0.0/0 /* kube-system/nginx-ingress-lb:https */ statistic mode random probability 0.33332999982 18 1080 KUBE-SEP-Y47C2UBHCAA5SP4C all -- * * 0.0.0.0/0 0.0.0.0/0 /* kube-system/nginx-ingress-lb:https */ statistic mode random probability 0.50000000000 16 960 KUBE-SEP-QGNNICTBV4CXTTZM all -- * * 0.0.0.0/0 0.0.0.0/0 /* kube-system/nginx-ingress-lb:https */
而上述3條鏈對應的處理如下,可以看到上述的每條鏈都作了DNAT,將目的IP由LB公網IP轉換為LB的container IP
-A KUBE-SEP-ZVUNFBS77WHMPNFT -s 172.20.1.231/32 -m comment --comment "kube-system/nginx-ingress-lb:https" -j KUBE-MARK-MASQ -A KUBE-SEP-ZVUNFBS77WHMPNFT -p tcp -m comment --comment "kube-system/nginx-ingress-lb:https" -m tcp -j DNAT --to-destination 172.20.1.231:443 -A KUBE-SEP-Y47C2UBHCAA5SP4C -s 172.20.2.191/32 -m comment --comment "kube-system/nginx-ingress-lb:https" -j KUBE-MARK-MASQ -A KUBE-SEP-Y47C2UBHCAA5SP4C -p tcp -m comment --comment "kube-system/nginx-ingress-lb:https" -m tcp -j DNAT --to-destination 172.20.2.191:443 -A KUBE-SEP-QGNNICTBV4CXTTZM -s 172.20.2.3/32 -m comment --comment "kube-system/nginx-ingress-lb:https" -j KUBE-MARK-MASQ -A KUBE-SEP-QGNNICTBV4CXTTZM -p tcp -m comment --comment "kube-system/nginx-ingress-lb:https" -m tcp -j DNAT --to-destination 172.20.2.3:443
從上面可以看出,node節點上的iptables中有到達所有service的規則,service 的cluster IP並不是一個實際的IP,它的存在只是為了找出實際的endpoint地址,對達到cluster IP的報文都要進行DNAT為Pod IP(+port),不同node上的報文實際上是通過POD IP傳輸的,cluster IP只是本node節點的一個概念,用於查找並DNAT,即目的地址為clutter IP的報文只是本node發送的,其他節點不會發送(也沒有路由支持),即默認下cluster ip僅支持本node節點的service訪問,如果需要跨node節點訪問,可以使用插件實現,如flannel,它將pod ip進行了封裝
- 至此已經講完了kubernetes的容器中iptables的基本訪問方式,在分析一個應用的iptables規則時,可以從KUBE-SERVICE入手,並結合該應用關聯的服務(如ingress LB等)進行分析。
- 查看iptables表項最好結合iptables-save以及如iptables -t nat -nvL的方式,前者給出了iptables的具體內容,但比較雜亂;后者給出了iptables的結構,可以方便地看出表中的內容,但是沒有詳細信息,二者結合起來才能比較好地分析。鏈接狀態可以查看/proc/net/nf_conntrack
TIPs:
- openshift下使用如下配置創建nodeport類型的service。"nodeport"表示通過nodeport方式訪問的端口;"port"表示通過service方式訪問的端口;"targetPort"表示后端服務"app"暴露的pod端口。通過nodeport訪問集群的流程為: {dstNodeIP:dstNodePort}-->(iptables)DNAT-->{dstPodIP:dstPodPort}。創建nodeport類型的service時會在每個node上開啟一個端口號為{nodePort}的監聽socket(單獨使用docker run -p啟動nodeport進程為docker-proxy),其中一個作用方便給應用通過loopback地址訪問容器服務,刪除該進程后將無法通過host的loopback接口訪問容器服務,更多參見docker-proxy。
同時也可以通過:{service:servicePort}-->(iptables)DNAT-->{dstPodIP:dstPodPort}的方式在集群內部訪問后端服務。
apiVersion: v1 kind: Service metadata: annotations: name: app-test namespace: openshift-monitoring spec: externalTrafficPolicy: Cluster ports: - name: cluster nodePort: 33333 port: 44444 protocol: TCP targetPort: 55555 selector: app: app sessionAffinity: None type: NodePort
主要參考:https://blog.csdn.net/ebay/article/details/52798074
kube-proxy的轉發規則查看:http://www.lijiaocn.com/%E9%A1%B9%E7%9B%AE/2017/03/27/Kubernetes-kube-proxy.html