在kubernets集羣的每一個節點上都運行着kube-proxy進程,負責實現Kubernetes中service組件的虛擬IP服務。目前kube-proxy有以下三種工做模式:node
User space模式git
在這種模式下,kube-proxy經過觀察Kubernetes中service和endpoint對象的變化,當有新的service建立時,全部節點的kube-proxy在node節點上隨機選擇一個端口,在iptables中追加一條把訪問service的請求重定向到這個端口的記錄,並開始監聽這個端口的鏈接請求。github
好比說建立一個service,對應的IP:1.2.3.4,port:8888,kube-proxy隨機選擇的端口是32890,則會在iptables中追加算法
-A PREROUTING -j KUBE-PORTALS-CONTAINER -A KUBE-PORTALS-CONTAINER -d 1.2.3.4/32 -p tcp --dport 8888 -j REDIRECT --to-ports 32890
KUBE-PORTALS-CONTAINER 是kube-proxy在iptable中建立的規則鏈,在PREROUTING階段執行後端
執行過程:緩存
這種模式的缺點就是,請求數據須要到kube-proxy中,就是用戶空間中,才能決定真正要轉發的實際服務地址,性能會有損耗。而且在應用執行過程當中,kube-proxy的可用性也會影響系統的穩定服務器
iptables 模式(目前kube-proxy默認的工做模式)網絡
在這種模式下,kube-proxy經過觀察Kubernetes中service和endpoint對象的變化,當有servcie建立時,kube-proxy在iptables中追加新的規則。對於service的每個endpoint,會在iptables中追加一條規則,設定動做爲DNAT,將目的地址設置成真正提供服務的pod地址;再爲servcie追加規則,設定動做爲跳轉到對應的endpoint的規則上,session
默認狀況下,kube-proxy隨機選擇一個後端的服務,能夠經過iptables中的 -m recent 模塊實現session affinity功能,經過 -m statistic 模塊實現負載均衡時的權重功能數據結構
好比說建立了一個service,對應的IP:1.2.3.4,port:8888,對應一個後端地址:10.1.0.8:8080,則會在iptables中追加(主要規則):
-A PREROUTING -j KUBE-SERVICES -A KUBE-SERVICES -d 1.2.3.4/32 -p tcp –dport 8888 -j KUBE-SVC-XXXXXXXXXXXXXXXX -A KUBE-SVC-XXXXXXXXXXXXXXXX -j KUBE-SEP-XXXXXXXXXXXXXXXX -A KUBE-SEP-XXXXXXXXXXXXXXXX -p tcp -j DNAT –to-destination 10.1.0.8:8080
執行過程:KUBE-SERVICES 是kube-proxy在iptable中建立的規則鏈,在PREROUTING階段執行
在這種邏輯下,數據轉發都在系統內核層面作,提高了性能,而且即使kube-proxy不工做了,已經建立好的服務還能正常工做 。可是在這種模式下,若是選中的第一個pod不能響應,請求就會失敗,不能像userspace模式,請求失敗後kube-proxy還能對其餘endpoint進行重試。
這就要求咱們的應用(pod)要提供readiness probes功能,來驗證後端服務是否能正常提供服務,kube-proxy只會將readiness probes測試經過的pod寫入到iptables規則中,以此來避免將請求轉發到不正常的後端服務中。
介紹
因爲在iptables模式中,kube-proxy須要爲每個服務,每個endpoint都生成相應的iptables規則,當服務規模很大時,性能也將顯著降低,所以kubernetes在1.8引入了IPVS模式,1.9版本中變成beta,在1.11版本中成爲GA。
在IPVS模式下,kube-proxy觀察Kubernetes中service和endpoint對象的變化,經過調用netlink接口建立相應的IPVS規則,並週期性的對Kubernetes的service、endpoint和IPVS規則進行同步,當訪問一個service時,IPVS負責選擇一個真實的後端提供服務。
IPVS模式也是基於netfilter的hook功能(INPUT階段),這點和iptables模式是同樣的,可是用的是內核工做空間的hash表做爲存儲的數據結構,在這種模式下,iptables可經過ipset來驗證具體請求是否知足某條規則。在service變成時,只用更新ipset中的記錄,不用改變iptables的規則鏈,所以能夠保證iptables中的規則不會隨着服務的增長變多,在超大規模服務集羣的狀況下提供一致的性能效果。
在對規則進行同步時的性能也要高於iptables(只用對特定的一個hash表進行更新,而不是像iptables模式下對整個規則表進行操做),因此能提供更高的網絡流量。
IPVS在對後端服務的選擇上也提供了更多靈活的算法:因爲IPVS的優點,因此儘量的採用IPVS做爲kube-proxy的工做模式,經過如下步驟在建立集羣時啓用IPVS
安裝依賴工具包
apt install -y ipvsadm ipset
kube-proxy啓用IPVS時依賴模塊:
ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack
可經過以下命令檢查系統是否啓用了這些模塊
$ lsmod | grep -e ip_vs -e nf_conntrack
若是沒有啓用,經過以下命令啓用
$ modprobe -- ip_vs $ modprobe -- ip_vs_rr $ modprobe -- ip_vs_wrr $ modprobe -- ip_vs_sh $ modprobe -- nf_conntrack
kube-proxy配置文件中追加IPVS相關配置
proxy-mode: "ipvs" ipvs-min-sync-period: ipvs 規則刷新的最小時間間隔 ipvs-scheduler: ipvs的負載算法(rr、lc等) ipvs-sync-period: ipvs規則刷新的最大時間間隔(30s)
採用用IPVS模式時,在啓動kube-proxy前必需要確保IPVS模塊在服務上存在,若是kube-proxy啓動時,通過驗證發現IPVS模塊不可用,kube-proxy自動採用iptables模式工做,參考代碼:server_others.go
在介紹kube-proxy如何使用IPVS實現對service的請求前,先看下IPVS的簡單介紹及工做原理
介紹
IPVS是Linux服務器中用來實現LVS(Linux virtual service)的一個模塊,工做在內核空間是一種基於虛擬IP的負載均衡技術,經過用戶空間的ipvsadm來執行規則制定,常見術語
名稱 | 解釋 |
---|---|
RS | Real Server 後端請求處理服務器 |
CIP | Client IP,客戶端IP |
VIP | Director Virtual IP,負載均衡器虛擬IP |
DIP | Director IP,負載均衡器IP |
RIP | Real Server IP,後端處理請求的真實服務器IP |
工做原理
由於IPVS是hook到netfilter的input鏈中執行的,因此須要先了解下數據在netfilter中的流轉過程
正常流程:
加入IPVS後的簡化邏輯圖
能夠看到,當IPVS模塊工做後,在input鏈上會再次對請求作匹配,匹配到的直接作postrouting,再也不進入用戶空間處理,而是把請求發送到IPVS選中的提供真實服務的服務器
IPVS有三種工做模式NAT(Masq)、DR、TUN,由於kube-proxy採用的是NAT模式,因此下面只對NAT模式進行分析
NAT:Network Address Transition(網絡地址轉換),工做在此模式下的IPVS,首先根據scheduler策略選擇一個真實的後端服務,接着將請求頭中的目的地址IP、端口轉換成真實服務的IP和端口,以後進入POSTROUTING,向真實的服務器發起請求。當響應數據返回時,再經過masqreade,將返回數據的源地址修改爲虛擬服務器的地址,具備有以下特性:
Real Server應該使用私有ip地址,和client IP不在一個網段中(若是在一個網段中,real Server能夠將數據直接返回客戶端,不會再通過Director Server返回)
通常Real Server的網關應該指向DIP,否則的話沒法保證響應報文通過Director Server(IP 協議,數據發送給不在同一個網段的服務器時,會把數據發送給網關)
RIP要和DIP應該在同一網段內
進出的報文,不管請求仍是響應都要通過Directory server(知足上面三點,這個是天然而然的)
支持端口映射
Real Server可使用任意系統,只要端口對應便可
其流程以下:
當用戶請求到達DirectorServer,此時請求的數據報文會先到內核空間的PREROUTING鏈。 此時報文的源IP爲CIP,目標IP爲VIP 。
PREROUTING檢查發現數據包的目標IP是本機,將數據包送至INPUT鏈。
IPVS比對數據包請求的服務是否爲集羣服務,如果,修改數據包的目標IP地址爲後端服務器IP,而後將數據包發至POSTROUTING鏈。 此時報文的源IP爲CIP,目標IP爲RIP ,在這個過程完成了目標IP的轉換。
POSTROUTING鏈經過選路,將數據包發送給Real Server。
由於Real Server的網關指向DIP,而且一般狀況下CIP和RIP不在同一個網段內,在這種狀況下,Real Server會先將數據發送給Director Server(網關),這樣數據就能夠沿原路返回。
Director Server在響應客戶端前,此時會將源IP地址修改成本身的VIP地址,而後響應給客戶端。 此時報文的源IP爲VIP,目標IP爲CIP。
在此過程當中CIP一直是不發生變化的
從IPVS的工做原理上能夠知道,kube-proxy想使用IPVS,須要完成如下幾個工做
爲了讓node節點識別對應的VIP,kube-proxy啓動時建立了一個虛擬網絡設備kube-ipvs0,類型是dummy,並把service的VIP都設置到這個設備下面。建立這個設備,以及向設備中追加VIP的邏輯都在代碼proxier.go中
$ ip addr 27: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default link/ether ba:6b:cb:b9:61:89 brd ff:ff:ff:ff:ff:ff inet 10.254.0.1/32 brd 10.254.0.1 scope global kube-ipvs0 valid_lft forever preferred_lft forever inet 10.254.0.2/32 brd 10.254.0.2 scope global kube-ipvs0 valid_lft forever preferred_lft forever inet 10.254.199.160/32 brd 10.254.199.160 scope global kube-ipvs0 valid_lft forever preferred_lft forever
對應的service
$ kubectl get svc -A NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE default clientip ClusterIP 10.254.199.160 <none> 8080/TCP 23h default kubernetes ClusterIP 10.254.0.1 <none> 443/TCP 25d kube-system kube-dns ClusterIP 10.254.0.2 <none> 53/UDP,53/TCP,9153/TCP 23d kube-system kubelet ClusterIP None <none> 10250/TCP 18d
能夠看到全部的VIP都在kube-ipvs0這個設備下面
爲了讓iptables中的規則容許對Servcie的請求經過,kube-proxy在iptables中追加了以下幾條主要規則(iptables.masqueradeAll=false;clusterCIDR=172.30.0.0/16,clusterCIDR就是集羣中的pod能分配的IP地址段):
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 */ 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 */ 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 */ Chain KUBE-MARK-MASQ (3 references) target prot opt source destination MARK all -- 0.0.0.0/0 0.0.0.0/0 MARK or 0x4000 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 MASQUERADE all -- 0.0.0.0/0 0.0.0.0/0 match-set KUBE-LOOP-BACK dst,dst,src Chain KUBE-SERVICES (2 references) target prot opt source destination KUBE-MARK-MASQ all -- !172.30.0.0/16 0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst
有兩點須要解釋下
關於規則
KUBE-MARK-MASQ all -- !172.30.0.0/16 0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst
意思是對於非集羣內的pod訪問集羣的cluster IP時會執行masquerade,這是由於在kube-proxy的啓動條件設置成了iptables.masqueradeAll=false;clusterCIDR=172.30.0.0/16才這樣
若是設置成iptables.masqueradeAll=false;clusterCIDR=,即不設置CIDR規則會變成
KUBE-MARK-MASQ all -- 0.0.0.0/0 0.0.0.0/0 match-set KUBE-CLUSTER-IP src,dst
效果應該是對本身訪問本身的請求作masquerade,其餘請求都不作
若是iptables.masqueradeAll=true,規則變成
KUBE-MARK-MASQ all -- 0.0.0.0/0 0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst效果對全部請求都作masquerade
關於匹配語法 -m set --match-set 。。。 表明用set模塊的對請求進行匹配
語法以下:
-m set --match-set name flags
具體傳入的flags要依據 name指定的set的類型來傳入,具體ipset的用法可經過以下命令查看
ipset --help
這裏以KUBE-CLUSTER-IP爲例
$ ipset --list KUBE-CLUSTER-IP Name: KUBE-CLUSTER-IP Type: hash:ip,port Revision: 5 Header: family inet hashsize 1024 maxelem 65536 Size in memory: 408 References: 2 Number of entries: 5 Members: 10.254.0.2,udp:53 10.254.0.1,tcp:443 10.254.0.2,tcp:9153 10.254.199.160,tcp:8080 10.254.0.2,tcp:53
結合前面的規則
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 match-set KUBE-CLUSTER-IP dst,dst
表述的意思是請求的目的地址IP、目的端口和KUBE-CLUSTER-IP中的Members有匹配時,就接收請求
filter表中
$ iptables -nL -t filter Chain FORWARD (policy ACCEPT) target prot opt source destination KUBE-FORWARD all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding rules */ Chain OUTPUT (policy ACCEPT) target prot opt source destination KUBE-FIREWALL all -- 0.0.0.0/0 0.0.0.0/0 Chain KUBE-FIREWALL (2 references) target prot opt source destination DROP all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes firewall for dropping marked packets */ mark match 0x8000/0x8000 Chain KUBE-FORWARD (1 references) target prot opt source destination ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding rules */ mark match 0x4000/0x4000 ACCEPT all -- 172.30.0.0/16 0.0.0.0/0 /* kubernetes forwarding conntrack pod source rule */ ctstate RELATED,ESTABLISHED ACCEPT all -- 0.0.0.0/0 172.30.0.0/16 /* kubernetes forwarding conntrack pod destination rule */ ctstate RELATED,ESTABLISHED
經過分析kube-proxy追加的這些規則,能夠看到訪問service提供的服務時,iptables中的規則最終都會容許請求經過,而且經過 -m set --match-set 的規則匹配機制,kube-proxy不用隨着servcie的建立和銷燬來修改iptables中的規則,只用修改對應的ipset中相應的Members就可以達到效果,保證iptables的規則表固定大小,實現大規模服務集羣中保持性能的穩定
請求經過了iptables中的規則後,在INPUT階段,須要IPVS來對請求進行判斷,看請求的服務是否屬於集羣服務,是的話還要能找出正確的真實服務地址,這個kube-proxy如何實現呢
kube-proxy經過監聽API server,當有service建立時,經過調用系統的netlink 接口,將service和對應的endpoint插入到IPVS對應的hash表中,對應代碼在proxier.go中,用戶能夠經過ipvsadm工具查看ipvs hash表中的數據
$ ipvsadm --list IP Virtual Server version 1.2.1 (size=4096) Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConn TCP promote.cache-dns.local:http rr -> master:6443 Masq 1 1 0 TCP promote.cache-dns.local:doma rr -> 172.30.78.2:domain Masq 1 0 0 TCP promote.cache-dns.local:9153 rr -> 172.30.78.2:9153 Masq 1 0 0 TCP promote.cache-dns.local:http rr -> 172.30.22.4:http-alt Masq 1 0 0 -> 172.30.78.4:http-alt Masq 1 0 0 UDP promote.cache-dns.local:doma rr -> 172.30.78.2:domain Masq 1 0 0
由於啓用了DNS的cache功能,因此這個地方看到的service信息是DNS緩存信息
經過上述幾個步驟後,kube-proxy就把須要使用IPVS的依賴條件都實現,就能夠在請求到來時利用IPVS機制對請求進行轉發了。