天天看點

Kubernetes 網絡實作——Service網絡

引言

本文介紹 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作業系統核心層内部的一個資料包處理子產品,它具有如下功能:

  • 網絡位址轉換(Network Address Translate)
  • 資料包内容修改
  • 資料包過濾的防火牆功能
在 iptables 中 有兩個概念:鍊和表,iptables 中定義有5條鍊,說白了就是5個鈎子函數,它們是在資料包經過核心的過程中有五處關鍵地方,分别是 PREROUTING、INPUT、OUTPUT、FORWARD、POSTROUTING,因為每個鈎子函數中可以定義多條規則,每當資料包到達一個鈎子函數時,iptables 就會從鈎子函數中第一條規則開始檢查,看該資料包是否滿足規則所定義的條件。如果滿足,系統就會根據該條規則所定義的方法處理該資料包;否則 iptables 将繼續檢查下一條規則,如果該資料包不符合鈎子函數中任一條規則,iptables 就會根據該函數預先定義的預設政策來處理資料包。iptables 中還有一個表的概念,每個表(tables)對應了一個特定的功能,有filter表(實作包過濾)、nat表(實作網絡位址轉換)、mangle表(實作包修改)、raw表(實作資料跟蹤),不同的表中包含了不同的鈎子函數,不同表的同一鈎子函數具有一定的優先級:raw-->mangle-->nat-->filter。一條鍊上可定義不同功能的規則,檢查資料包時将根據上面的優先級順序檢查。
Kubernetes 網絡實作——Service網絡
總結一下,資料包先經過 PREOUTING,由該鍊确定資料包的走向:
  1. 目的位址是本地,則發送到 INPUT,讓INPUT決定是否接收下來送到使用者空間,流程為①--->②;
  2. 若滿足 PREROUTING 的 nat 表上的轉發規則,則發送給 FORWARD,然後再經過 POSTROUTING 發送出去,流程為: ①--->③--->④--->⑥
  3. 主機發送資料包時,流程則是⑤--->⑥
Kubernetes 網絡實作——Service網絡
此處列出一些 iptables 常用的動作,在後面分析 iptables 時您将看到 Kubernetes 對各種動作的使用:
  • 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:

  1. KUBE-SERVICES: source ip 不在 pob subnet 域并且 destination 是 service ip + service port 時,實際的通路者在哪個叢集主機上,就會使用該主機被配置設定的 pod subnet ip,即便是在 pod 中通路也會使用該 pod 所處的 host 被配置設定的 pod subnet ip(即 flannel 網卡 ip),而不是使用 pod 的 ip;
  2. KUBE-SEP-DHEI6HLVRKMGWFA5: source ip 和 destination ip 都是目标服務 pod 的 ip 時,會使用該 pod 所處 host 的 pod subnet ip(即 flannel 網卡 ip),而不是 pod 内的 ip;
  3. 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:
  • NEW建立的連接配接的包
  • ESTABLISHED已建立連接配接的後續包
  • RELATED相關連接配接的包, 如FTP控制和資料
  • INVALID沒識别出來, 或者沒有狀态, 一般DROP此類的包
conntrack: 連接配接追蹤表,記錄了 SNAT 與 DNAT 的轉換,可以通過

conntrack -L

檢視

KUBE-FORWARD 中對打了 mark 0x4000 的包,源 ip 或者目的 ip 是 pod subnet 的包(狀态:已建立連接配接或者存在相關連接配接)放行 Forward,對于其他非法狀态的包則直接丢棄。

下圖中所有黃色區域都為 Kubernetes 在 iptables 上做的改動。

Kubernetes 網絡實作——Service網絡

從上述 iptables 的分析中,我們可以知道,service network 實際上隻是存在于 iptables 的路由規則,裡面記錄了一個指定 service ip 應該将流量路由到哪個 Pod ip 位址,以及收到的包應該如何進行 SNAT。下面兩個動圖描述了,從 Pod 發包給 Service,以及 service pod 回包的情況:

Kubernetes 網絡實作——Service網絡

我們可以看到,來自 pod1 的封包以某種形式流到了主控端上(我們後面會詳細介紹這部分的實作原理),然後要從主控端的 eth0 裝置發出,這時候觸發了 iptables 的規則比對,根據 iptables 中的規則,最終将請求的目标位址改成了 Pod ip 并發送出去。在這個過程中 Linux kernel 的 conntrack 會記住此時選擇的 pod,并且在下次這個源 pod 要發送給該 service 時,仍路由到相同的 service pod。至此,iptables 就完成了叢集内的負載均衡,剩下的傳輸過程就由 flannel network 負責了。

Kubernetes 網絡實作——Service網絡

當 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           

文章說明

更多有價值的文章均收錄于

貝貝貓的文章目錄
Kubernetes 網絡實作——Service網絡

版權聲明: 本部落格所有文章除特别聲明外,均采用 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]

四層、七層負載均衡的差別