引言
本文介紹 Kubernetes 網絡中 Service 網路部分的實作方案,更多關于 Kubernetes 的介紹均收錄于
<Kubernetes系列文章>中。
Service 網絡
service network 比較特殊,每個新建立的 service 會被配置設定一個 service IP,它實際上隻是一個存在于 iptables 中的路由規則,并沒有對應的虛拟網卡。還記得我們在試玩環節建立的 kubia service 嗎?我們隻要在叢集中的任意機器檢視 iptables 就能發現端倪,這裡需要聲明的是我當時在建立 Kubernetes 叢集時指定的 PodSubnet 為
192.168.128.0/18
,serviceSubnet 為
192.168.192.0/18
。
我們知道,linux 系統中有 route,用來和其他網絡連接配接。如果沒有它,linux 就無法和外部通信了。
iptables 是一系列的規則,用來對核心中的資料進行處理。沒有它,linux 可以正常工作,有了它,linux 可以對網絡中的資料的操作更加多樣化,可見 iptables 是一個輔助角色,可以讓 linux 的網絡系統更強大。
iptables 其實不是真正的防火牆,我們可以把它了解成一個用戶端代理,使用者通過 iptables 這個代理,将使用者的安全設定執行到對應的"安全架構"中,這個"安全架構"才是真正的防火牆,這個架構的名字叫 netfilter。
netfilter 才是防火牆真正的安全架構(framework),netfilter位于核心空間。
iptables 其實是一個指令行工具,位于使用者空間,我們用這個工具操作真正的架構。
Netfilter是Linux作業系統核心層内部的一個資料包處理子產品,它具有如下功能:
在 iptables 中 有兩個概念:鍊和表,iptables 中定義有5條鍊,說白了就是5個鈎子函數,它們是在資料包經過核心的過程中有五處關鍵地方,分别是 PREROUTING、INPUT、OUTPUT、FORWARD、POSTROUTING,因為每個鈎子函數中可以定義多條規則,每當資料包到達一個鈎子函數時,iptables 就會從鈎子函數中第一條規則開始檢查,看該資料包是否滿足規則所定義的條件。如果滿足,系統就會根據該條規則所定義的方法處理該資料包;否則 iptables 将繼續檢查下一條規則,如果該資料包不符合鈎子函數中任一條規則,iptables 就會根據該函數預先定義的預設政策來處理資料包。iptables 中還有一個表的概念,每個表(tables)對應了一個特定的功能,有filter表(實作包過濾)、nat表(實作網絡位址轉換)、mangle表(實作包修改)、raw表(實作資料跟蹤),不同的表中包含了不同的鈎子函數,不同表的同一鈎子函數具有一定的優先級:raw-->mangle-->nat-->filter。一條鍊上可定義不同功能的規則,檢查資料包時将根據上面的優先級順序檢查。 總結一下,資料包先經過 PREOUTING,由該鍊确定資料包的走向:
- 網絡位址轉換(Network Address Translate)
- 資料包内容修改
- 資料包過濾的防火牆功能
此處列出一些 iptables 常用的動作,在後面分析 iptables 時您将看到 Kubernetes 對各種動作的使用:
- 目的位址是本地,則發送到 INPUT,讓INPUT決定是否接收下來送到使用者空間,流程為①--->②;
- 若滿足 PREROUTING 的 nat 表上的轉發規則,則發送給 FORWARD,然後再經過 POSTROUTING 發送出去,流程為: ①--->③--->④--->⑥
- 主機發送資料包時,流程則是⑤--->⑥
- ACCEPT:允許資料包通過。
- DROP:直接丢棄資料包,不給任何回應資訊,這時候用戶端會感覺自己的請求泥牛入海了,過了逾時時間才會有反應。
- REJECT:拒絕資料包通過,必要時會給資料發送端一個響應的資訊,用戶端剛請求就會收到拒絕的資訊。
- SNAT:源位址轉換,解決内網使用者用同一個公網位址上網的問題。
- MASQUERADE:是SNAT的一種特殊形式,适用于動态的、臨時會變的ip上。
- DNAT:目标位址轉換。
- REDIRECT:在本機做端口映射。
- MARK:打标記。
我們先回顧一下 kubia service 的端口和 service ip, 可以看到它的 service endpoint 是 192.168.199.234:8080,因為采用了 NodePort 類型的 service,是以我們也可以通過所有 pod 所處主控端的 32681 端口通路該服務。
kubectl get service -o wide
#NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
#kubia NodePort 192.168.199.234 <none> 8080:32681/TCP 17h app=kubia
然後我們先看一下 iptables 的 nat 表内容,因為它控制了網絡位址轉換
iptables -t nat -nL
#Chain PREROUTING (policy ACCEPT)
#target prot opt source destination
#KUBE-SERVICES all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
# 注入 KUBE-SERVICES 鍊
#
#Chain OUTPUT (policy ACCEPT)
#target prot opt source destination
#KUBE-SERVICES all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
# 注入 KUBE-SERVICES 鍊
在 PREROUTING 鍊和 OUTPUT 鍊中,Kubernetes 注入了自己的鍊,該鍊比對所有 ip 的包。然後,我們看下 KUBE-SERVICES 鍊都做了什麼。
#Chain KUBE-SERVICES (2 references)
#target prot opt source destination
#KUBE-MARK-MASQ tcp -- !192.168.128.0/18 192.168.199.234 /* default/kubia: cluster IP */ tcp dpt:8080
#KUBE-SVC-L5EAUEZ74VZL5GSC tcp -- 0.0.0.0/0 192.168.199.234 /* default/kubia: cluster IP */ tcp dpt:8080
#KUBE-NODEPORTS all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL
在 KUBE-SERVICES 鍊中可以看到,當我們的 destination 為 192.168.199.234 tcp 8080 時,會先判斷是不是 Pod 子網,如果不是則會先進入 KUBE-MARK-MASQ 鍊,然後才會進入 KUBE-SVC-L5EAUEZ74VZL5GSC 鍊,最後一條規則是 KUBE-NODEPORTS 比對目标位址是 local 的流量。
接下來我們分别看一看 KUBE-SERVICES 下層的三個鍊分别是幹什麼用的。
#Chain KUBE-MARK-MASQ (25 references)
#target prot opt source destination
#MARK all -- 0.0.0.0/0 0.0.0.0/0 MARK or 0x4000
KUBE-MARK-MASQ 鍊的作用,隻是打上标記
MARK or 0x4000
,該标記的作用一會兒再說。
#Chain KUBE-SVC-L5EAUEZ74VZL5GSC (2 references)
#target prot opt source destination
#KUBE-SEP-DHEI6HLVRKMGWFA5 all -- 0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.16667000018
#KUBE-SEP-U2WLSCWTWVRL43BI all -- 0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.20000000019
#KUBE-SEP-T3I6FPLS4WOL5Q3I all -- 0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.25000000000
#KUBE-SEP-SRYJEUDBHDCQM4KY all -- 0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.33332999982
#KUBE-SEP-VVYACXWHEDETJZUB all -- 0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.50000000000
#KUBE-SEP-5A4LPBRVHCEID4EO all -- 0.0.0.0/0 0.0.0.0/0
在 KUBE-SVC-L5EAUEZ74VZL5GSC 鍊中就對應了我們 kubia 服務的的 6 個 pod,可以看到他在這一層做了一個負載均衡,第一個 KUBE-SEP 配置設定了 1/6 的份額,第二個 KUBE-SEP 配置設定了剩餘份額中的 1/5 ,以此類推,最終達到的效果就是每個 KUBE-SEP 配置設定 1/Pod count 的份額,我們挑選第一個 pod 對應的 KUBE-SEP-DHEI6HLVRKMGWFA5 看一下。
#Chain KUBE-SEP-DHEI6HLVRKMGWFA5 (1 references)
#target prot opt source destination
#KUBE-MARK-MASQ all -- 192.168.131.21 0.0.0.0/0
#DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp to:192.168.131.21:8080
如果源 ip 和 pod ip 位址相同的話,則同樣打上 mark,然後将目标位址(DNAT)改為該 Pod 的 ip + 服務端口,to:192.168.131.21:8080,到這裡就完成了路由工作。
#Chain KUBE-NODEPORTS (1 references)
#target prot opt source destination
#KUBE-MARK-MASQ tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/kubia: */ tcp dpt:32681
#KUBE-SVC-L5EAUEZ74VZL5GSC tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/kubia: */ tcp dpt:32681
最後,在KUBE-NODEPORTS 鍊中,如果目标端口和 kubia service 的 nodePort 比對上的話,就會打上 mark 并進去之前的 service 鍊 KUBE-SVC-L5EAUEZ74VZL5GSC, 也就是說無論你通路叢集内的任意一台機器的 32681 端口,流量均會均勻地轉發到對應的 pod 中。
接下來我們看一下前面打的 MARK or 0x4000 都用來幹什麼了。
#Chain POSTROUTING (policy ACCEPT)
#target prot opt source destination
#KUBE-POSTROUTING all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes postrouting rules */
#RETURN all -- 192.168.128.0/18 192.168.128.0/18
#MASQUERADE all -- 192.168.128.0/18 !224.0.0.0/4
#RETURN all -- !192.168.128.0/18 192.168.132.0/24
#MASQUERADE all -- !192.168.128.0/18 192.168.128.0/18
# 這裡是在 POSTROUTING 中注入的 KUBE-POSTROUTING 鍊, 并使用 MASQUERADE 進行與 Pod subnet 相關的 SNAT,實際上這是将 service pod 的源 ip SNAT 為 service ip。後面會詳細介紹這個過程。
#
#Chain KUBE-POSTROUTING (1 references)
#target prot opt source destination
#MASQUERADE all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service traffic requiring SNAT */ mark match 0x4000/0x4000
KUBE-POSTROUTING 中比對了所有打了 mark 0x4000 的封包,并通過 MASQUERADE 位址僞裝,它是 SNAT 的一種特殊形式,可以自動化的進行源位址轉換 SNAT,這個自動進行 SNAT 轉換的過程實際是将封包的源位址轉化為 Pod Subnet 的 ip 域。
回顧一下前面哪些情況下加了 mark:
- KUBE-SERVICES: source ip 不在 pob subnet 域并且 destination 是 service ip + service port 時,實際的通路者在哪個叢集主機上,就會使用該主機被配置設定的 pod subnet ip,即便是在 pod 中通路也會使用該 pod 所處的 host 被配置設定的 pod subnet ip(即 flannel 網卡 ip),而不是使用 pod 的 ip;
- KUBE-SEP-DHEI6HLVRKMGWFA5: source ip 和 destination ip 都是目标服務 pod 的 ip 時,會使用該 pod 所處 host 的 pod subnet ip(即 flannel 網卡 ip),而不是 pod 内的 ip;
- KUBE-NODEPORTS: 如果通路了 Service 使用的 NodePort,比如 Worker1 的 32681 端口,那麼 SNAT 将改為 Worker1 的 pod subnet ip(即 flannel 網卡 ip);
另一個 mark 0x4000 的用途在 iptables 的 filter 表中,該表決定了資料封包的丢棄與放行的決策。
iptables -t filter -nL
#Chain INPUT (policy ACCEPT)
#target prot opt source destination
#KUBE-SERVICES all -- 0.0.0.0/0 0.0.0.0/0 ctstate NEW /* kubernetes service portals */
#KUBE-EXTERNAL-SERVICES all -- 0.0.0.0/0 0.0.0.0/0 ctstate NEW /* kubernetes externally-visible service portals */
# 允許建立任意 ip 位址的連接配接,因為 service 網絡是不存在與任何主控端網絡中的,但是我們需要允許以 service ip 建立連接配接
#
#Chain OUTPUT (policy ACCEPT)
#target prot opt source destination
#KUBE-SERVICES all -- 0.0.0.0/0 0.0.0.0/0 ctstate NEW /* kubernetes service portals */
# 同 INPUT,OUTPUT 鍊也放行以 service ip 相關的連接配接件建立
#Chain FORWARD (policy ACCEPT)
#ACCEPT all -- 192.168.128.0/18 0.0.0.0/0
#ACCEPT all -- 0.0.0.0/0 192.168.128.0/18
#KUBE-FORWARD all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding rules */
# 在 FORWARD 鍊中注入了 KUBE-FORWARD,放行源 ip 或者目的 ip 為 pod subnet 的包
#
#Chain KUBE-FORWARD (1 references)
#target prot opt source destination
#DROP all -- 0.0.0.0/0 0.0.0.0/0 ctstate INVALID
#ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding rules */ mark match 0x4000/0x4000
#ACCEPT all -- 192.168.128.0/18 0.0.0.0/0 /* kubernetes forwarding conntrack pod source rule */ ctstate RELATED,ESTABLISHED
#ACCEPT all -- 0.0.0.0/0 192.168.128.0/18 /* kubernetes forwarding conntrack pod destination rule */ ctstate RELATED,ESTABLISHED
ctstate description:conntrack: 連接配接追蹤表,記錄了 SNAT 與 DNAT 的轉換,可以通過
- NEW建立的連接配接的包
- ESTABLISHED已建立連接配接的後續包
- RELATED相關連接配接的包, 如FTP控制和資料
- INVALID沒識别出來, 或者沒有狀态, 一般DROP此類的包
檢視
conntrack -L
KUBE-FORWARD 中對打了 mark 0x4000 的包,源 ip 或者目的 ip 是 pod subnet 的包(狀态:已建立連接配接或者存在相關連接配接)放行 Forward,對于其他非法狀态的包則直接丢棄。
下圖中所有黃色區域都為 Kubernetes 在 iptables 上做的改動。
從上述 iptables 的分析中,我們可以知道,service network 實際上隻是存在于 iptables 的路由規則,裡面記錄了一個指定 service ip 應該将流量路由到哪個 Pod ip 位址,以及收到的包應該如何進行 SNAT。下面兩個動圖描述了,從 Pod 發包給 Service,以及 service pod 回包的情況:
我們可以看到,來自 pod1 的封包以某種形式流到了主控端上(我們後面會詳細介紹這部分的實作原理),然後要從主控端的 eth0 裝置發出,這時候觸發了 iptables 的規則比對,根據 iptables 中的規則,最終将請求的目标位址改成了 Pod ip 并發送出去。在這個過程中 Linux kernel 的 conntrack 會記住此時選擇的 pod,并且在下次這個源 pod 要發送給該 service 時,仍路由到相同的 service pod。至此,iptables 就完成了叢集内的負載均衡,剩下的傳輸過程就由 flannel network 負責了。
當 service 響應 pod 的請求後,資料包被 eth0 網卡接收到,并再次進入 iptables 的比對過程,還記得前面 iptables 中的 POSTROUTING 嗎?
# 從 service pod 發來的回包,将 SNAT 恢複為 service ip 或者虛拟機的 ip
#MASQUERADE all -- 192.168.128.0/18 !224.0.0.0/4
# 将對外部網絡的通路恢複 SNAT
#MASQUERADE all -- !192.168.128.0/18 192.168.128.0/18
這裡同樣會使用到核心的 conntrack ,通過它可以将封包的源位址從 pod 的 ip 改為 service 的 ip,這下通路 service 的完整流程就走完了。如下是一個 conntrack 例子:
conntrack -L|grep 32681
#tcp 6 9 CLOSE src=<exeternal-ip> dst=<local-eth0-ip> sport=62142 dport=32681 src=192.168.132.73 dst=192.168.128.0 sport=8080 dport=62142 mark=0 use=1
#tcp 6 9 CLOSE src=<exeternal-ip> dst=<local-eth0-ip> sport=62790 dport=32681 src=192.168.132.72 dst=192.168.128.0 sport=8080 dport=62790 mark=0 use=1
conntrack -L|grep 192.168.192
#tcp 6 86377 ESTABLISHED src=192.168.128.2 dst=192.168.192.1 sport=38742 dport=443 src=<other-vm-ip> dst=<local-eth0-ip> sport=6443 dport=28222 [ASSURED] mark=0 use=1
#tcp 6 86394 ESTABLISHED src=<exeternal-ip> dst=192.168.192.1 sport=50800 dport=443 src=<local-eth0-ip> dst=<local-eth0-ip> sport=6443 dport=50800 [ASSURED] mark=0 use=1
文章說明
更多有價值的文章均收錄于
貝貝貓的文章目錄版權聲明: 本部落格所有文章除特别聲明外,均采用 BY-NC-SA 許可協定。轉載請注明出處!
創作聲明: 本文基于下列所有參考内容進行創作,其中可能涉及複制、修改或者轉換,圖檔均來自網絡,如有侵權請聯系我,我會第一時間進行删除。
參考内容
[1]
kubernetes GitHub 倉庫[2]
Kubernetes 官方首頁[3]
Kubernetes 官方 Demo[4] 《Kubernetes in Action》
[5]
了解Kubernetes網絡之Flannel網絡[6]
Kubernetes Handbook[7]
iptables概念介紹及相關操作[8]
iptables超全詳解[9]
了解Docker容器網絡之Linux Network Namespace[10]
A Guide to the Kubernetes Networking Model[11]
Kubernetes with Flannel — Understanding the Networking[12]
四層、七層負載均衡的差別