標籤:

kubernetes的網路數據包流程

關於路由

很多像我這樣的網路小白,對於整個路由過程比較模糊。下面以一個簡單的PING示例解釋一下路由的整個過程。

有兩個原則需要記住:

二層網路是通過MAC地址進行交換

三層路由通過IP選路。

一個ICMP的乙太網格式報文:

|目的MAC|源MAC|目的IP|源IP|ICMP包|

主機A發送一個ICMP包到主機B, 此時目的IP為10.10.0.2,源IP為192.168.0.2。 主機A發現,這個IP和我不在一個網段,那就需要進行IP選路了。此時它就只能把包發往直接的網關了(192.168.0.1) ,主機A和網關在同一個網段,通過MAC地址進行交換,所以目的MAC就是MACRA, 源Mac就是MACHOSTA。

因此從主機A發出的一個乙太網報文格式為:

|MACRA|MACHOSTA|10.10.0.2|192.168.0.2|ICMP包

路由器在介面A處收到乙太網包進行分析,選出目的IP,根據自身的路由表發現,這個數據包要經過介面B發出啊。於是就在路由器介面B封裝數據包

|MACHOSTB|MACRB|10.10.0.2|192.168.0.2|ICMP包

主機B在二層收到從路由器介面B發生的乙太網進行解析發現目的IP是自己的,然後開始回復。首先發現目的IP是192.168.0.2.這貨和我不是一個網段的啊,那我就把數據包先發給網關,讓網關幫我郵遞。於是封裝出下面的乙太網包:

|MACRB|MACHOSTB|192.168.0.2|10.10.0.2|ICMP

路由器從介面B收到數據包之後,同樣進行分析之後知道自己需要通過介面A發出。於是封裝的乙太網包:

|MACHOSA|MACRA|192.168.0.2|10.10.0.2|ICMP

終於回復的數據包被主機A收到,整個ICMP過程完成。

有一個非常重要的原則要記住:在數據跳轉的過程中,改變的是MAC,IP是始終是沒有改變的。

關於NAT

NAT是網路地址轉換的意思,什麼時候會用到它呢, 一個簡單的例子就是我們平時上網用到路由器,

上圖是我們家裡常用的路由器連接示例,HOSTA和HOSTB連接到路由器的LAN口,當主機A訪問8.8.8.8時,根據上面講的路由過程,我們知道到達路由器的乙太網包為:

|MACRA|HOSTA|8.8.8.8|192.168.1.2|ICMP包

此時路由器通過WAN口將報文發送到公網上去,按這常理它的數據包格式應該是:

|下一跳路由MAC|路由器A的WAN口的MAC|8.8.8.8|192.168.1.2|ICMP包

我們在深入思考一下,假設這樣的一個數據包發送到公網上之後,確定還能按原路找回來嗎? 大家的路由器網關都是192.168.1.1 ,數據如果回來之後根本就不知道發送到哪一個路由器了。

這個時候就出現了NAT的技術,如果想讓回復的數據包能夠返回,那麼我就需要更改源IP地址啊,於是路由器在從WAN口路由出去之後更改源IP為WAN口的IP,同時記錄NAT轉換的信息,保證回復的數據能夠投遞到指定的目標。這種技術叫做SNAT。同理還有一個叫做DNAT,也就是更改目的地址IP。 後邊會在介紹k8s路由過程中解釋。 只需要記住SNAT是路由之後進行的,DNAT是路由之前進行的即可。

Iptables原理

在介紹k8s的網路之前,還需要介紹一下iptables的原理,因為k8s中用到了大量iptables的知識,iptables博大精深,本人也只是略懂一二。

Iptables是Linux內核模塊netfilter的應用程序,通過iptables這個應用程序來控制netfilter內核的行為。Linux的iptables默認是4表5鏈,當然也可以自定義自己的規則鏈。

4表: nat, manage, raw, filter,

5鏈: prerouting, input, output, forward, postrouting

一般情況下我們只需要關心nat和filter這個兩個表。Nat表可以控制非本地的網路包,filter可以控制路由到本地的數據包。

不廢話,先上圖:(所有圖均是自己繪製的,心疼自己幾十秒)

上圖表示了一個數據包在Linux的netfilter內核模塊中流轉的流程。可以說Linux的強大的網路功能讓我非常震驚。驚呆了。

K8s的網路流程

終於,終於到正題了,大家看看就行了,吾生也有涯,而知也無涯。以有涯隨無涯,怠矣。

當你使用kubernets的時候,可能會感到奇怪,為何我訪問的集群的任意一個IP均可以訪問到指定的應用。 為何在集群的機器上訪問一個不存在的IP(clusterIP)卻能夠幫我路由到指定的pod,而且還是等概率的訪問一個節點。

上面是一張k8s的架構圖(這張不是我畫的),我們只看proxy部分,kube-proxy是k8s的一個核心組件。每台機器上都運行一個kube-proxy服務,它監聽API server中service和endpoint的變化情況,並通過iptables等來為服務配置負載均衡。 所以接下來我們通過例子結合iptable表中的規則來梳理整個流程。

環境:

master01: 172.24.6.153

master02: 172.24.6.154

master03: 172.24.6.145

我們以kdcos-keystone這個應用為例來分析訪問流程。

從圖中可以看出, kdcos-keystone的clusterIP為10.0.0.10, 容器埠是8080, 集群埠是30808.

Keystonde的pod分布:

master01的路由表:

現在我們先不看iptable的規則,自己去考慮一下,如果我想在master01 上訪問10.0.0.80(隨便找一個不衝突的IP都行) 最終能夠訪問到master02上的keystone(10.254.59.219)應該怎麼做呢?

1. 首先可以確定的是肯定是在iptable的nat表上進行添加規則。

2. 我們在集群內部訪問的clusterIP是一個虛擬IP,最終要訪問的IP是10.254.59.219. 所有肯定是要進行DNAT轉換的。(這樣數據包應該是從本地協議發出 會經過nat的OUTPUT和POSTROUTING鏈。看上圖的iptable流程圖即可明白)

3. 我們通過在集群外部訪問nodeport,最終如果要訪問到10.254.59.219,需要進行DNAT轉換,這個和第二步一樣,另外,在上面講解NAT的時候說到,如果要想讓消息能返回客戶端,那就需要進行SNAT轉換。如果實在覺得理解困難,可以把master01當做一個路由器,客戶端就是一個主機。

# 在集群中訪問虛擬IP最終訪問到keystone

iptables -t nat -I OUTPUT -p tcp -mcomment --comment "this is clusterip demo" -d 10.0.0.80/32 --dport 8080 -j DNAT --to-destination 10.254.59.219:8080

# 讓外部客戶端訪問nodeport最終訪問到keystone

iptables -t nat -I POSTROUTING -p tcp -d10.254.59.219 --dport 8080 -j MASQUERADEiptables -t nat -I PREROUTING -p tcp -mcomment --comment "this is nodeport demo" --dport 40808 -j DNAT --to-destination 10.254.59.219:8080

這個時候 在集群的master01上訪問10.0.0.80:8080 或者在外部訪問172.24.6.153:408080,就會發現驚訝的發現原來k8s還能這樣操作。是不是很驚喜。無圖無真相:

下圖是在master01上訪問10.0.0.80:8080 時設置的iptable規則。

下圖是讓外部能夠通過nodeport訪問keystone設置的規則。

在自己的電腦上訪問nodeport:

留個問題:

上面的iptable規則可以保證在master01上通過虛擬ip(10.0.0.80)訪問到keystone,可以保證在外部訪問172.24.6.153:8080訪問到keystone,但是如果你嘗試在master01(172.24.6.153)上通過訪問172.24.6.153:40808到達keystone確是不行的。 不信?我截圖給你看?

那你覺得應該如何設置才能在master01上通過自身IP也能訪問到keystone呢?

另:

MASQUERADE和SNAT的功能類似,只是SNAT需要明確指明源IP的的值,MASQUERADE會根據網卡IP自動更改,所以更實用一些。

所以說,如果在開發過程中,想外部訪問某個應用(比如redis),但是呢碰巧這個應用的svc又沒有開啟nodeport,那你就可以模仿我剛才設置的iptable規則,從而達到不修改SVC也能通過外部應用訪問。

到這裡,也許你大概明白了k8s的kube-proxy的工作原理了,總結起來就是通過更改iptable的規則讓我們可以訪問一個虛擬IP從而能夠到達後端pod。如果看到這裡還不是很了解的話,建議還是從頭再看一下,因為下面分析的k8s生成的iptable鏈要比這個會更複雜一下。

現在我們分析k8s的iptable的流程:

分析1: 集群內部通過clusterIP訪問到pod。

上面我們分析過,首先這個數據包是通過本地協議發出的,然後需要更改NAT表,那看來k8s只能在OUTPUT這個鏈上來動手了。

看下圖:

到達OUTPUT鏈的包要過KUBE-SERVICES這個k8s自定義的鏈

上圖就是KUBE-SERVICES 鏈,訪問10.0.0.10:8080 可以匹配到上圖的第一條紅線的規則,注意第二條紅線,下面會說到。

匹配到10.0.0.10:8080的時候會跳轉到KUBE-SVC-VZEERQ5BHBSZ5PRL這個鏈,那這個鏈又做了啥子呢?

看到上圖的--mode random --probability 0.33332999982 了嗎?它就是訪問SVC時能夠隨機訪問到後端POD的原因。

因為我們後面有三個pod,所以會有三條鏈, 我們只分析其中一條。

最終這個鏈做了兩件事,跳轉到KUBE-MARK-MASQ,我就不跳了,直接說結果就是給這個包打上標籤,這個標籤的用處,一會說。

然後我們終於看到了對這個包進行了一次DNAT轉換。 轉到了10.254.59.212:8080上面了。

這個OUTPUT走完之後,它就該到POSTROUTING鏈了。

跳轉到KUBE-POSTROUTING鏈,這個鏈只做了一個事,就是對打上了標籤的包進行SNAT轉換。這就引入了一個問題,在一個pod訪問一個clusterIP的時候,如果這個clusterIP的DNAT目標是自己的話,看到的IP卻是自己宿主機的IP。還有一點大家可能會有疑問,這個--mark 0x4000/0x4000是什麼鬼,就是對匹配這個標籤的數據包進行SNAT,上面說到的KUBE-MARK-MASQ就是給數據包打上了這樣的一個標籤。

這樣一個訪問clusterIP最終訪問到pod的流程就算走完了。

分析2: 下面我們來分析通過外部nodeport訪問後端pod的流程。

根據iptable的流程我們知道,首先我們應該對nat表的PREROUTING鏈動手腳。看看k8s做了啥?

對,你沒看錯,這貨又把它跳到了KUBE-SERVICES這個鏈上去了,但是上面我們知道,這回我是通過nodeport訪問的,你根本匹配不上啊,不錯確實匹配不上,還記得我說的第二根紅線嗎?忘了?在貼下上面的圖。

那既然你們匹配不上那就給我跳轉到KUBE-NODEPORTS鏈上來。

在這個KUBE-NODEPORTS最後還是調到之前的KUBE-SVC-VZEERQ5BHBSZ5PRL上,打上標籤,DNAT轉換,SNAT轉換。完成。

另外:

在最開始我提到了一個問題,在集群上使用nodeport卻訪問不了後端pod, 那是因為我們沒有在OUTPUT鏈設置匹配nodeport的包進行DNAT轉換呀,那k8s做了嗎? 當然了,反正不管你是PREROUTING還是OUTPUT鏈,我都給你跳到KUBE-SERVICE, 到了KUBE-SERVICE匹配不上我就給你跳到KUBE-NODEPORTS,這就解決了不管是那種情況,無論是訪問clusterIP,還是nodeport最終都能訪問到了後端pod

總之: Linux的網路功能複雜且強大,且學且珍惜!


推薦閱讀:

DaoCloud是一家什麼樣的公司?
docker swarm mode的服務發現和LB詳解
乾貨 | 解密監控寶Docker監控實現原理
DaoCloud和雲雀到底誰家的技術比較強一些?VMware和微軟系的比較?
Docker 可以用於生產環境了嗎?

TAG:Docker | Kubernetes |