Kubernetes kube-proxy 詳解

前言

在kubernets集羣的每一個節點上都運行着kube-proxy進程,負責實現Kubernetes中service組件的虛擬IP服務。目前kube-proxy有以下三種工做模式:node

  • User space 模式
  • iptables 模式
  • IPVS 模式
  1. 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階段執行後端

    執行過程:緩存

    • 當有請求訪問service時,在PREROUTING階段,將請求jumpKUBE-PORTALS-CONTAINER
    • KUBE-PORTALS-CONTAINER中的這條記錄起做用,把請求重定向到kube-proxy剛監聽的端口32890上,數據包進入kube-proxy進程內,
    • 而後kube-proxy會從這個service對應的endpoint中根據SessionAffinity來選擇一個做爲真實服務響應請求。

    這種模式的缺點就是,請求數據須要到kube-proxy中,就是用戶空間中,才能決定真正要轉發的實際服務地址,性能會有損耗。而且在應用執行過程當中,kube-proxy的可用性也會影響系統的穩定服務器

  2. 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階段執行

    執行過程:
    • 當請求訪問service時,iptables在prerouting階段,將講求jump到KUBE-SERVICES,
    • 在KUBE-SERVICES 中匹配到上面的第一條準則,繼續jump到KUBE-SVC-XXXXXXXXXXXXXXXX,
    • 根據這條準則jump到KUBE-SEP-XXXXXXXXXXXXXXXX,
    • 在KUBE-SEP-XXXXXXXXXXXXXXXX規則中通過DNAT動作,訪問真正的pod地址10.1.0.8:8080。

    在這種邏輯下,數據轉發都在系統內核層面作,提高了性能,而且即使kube-proxy不工做了,已經建立好的服務還能正常工做 。可是在這種模式下,若是選中的第一個pod不能響應,請求就會失敗,不能像userspace模式,請求失敗後kube-proxy還能對其餘endpoint進行重試。

    這就要求咱們的應用(pod)要提供readiness probes功能,來驗證後端服務是否能正常提供服務,kube-proxy只會將readiness probes測試經過的pod寫入到iptables規則中,以此來避免將請求轉發到不正常的後端服務中。

  3. IPVS 模式
    1. 介紹
      因爲在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在對後端服務的選擇上也提供了更多靈活的算法:
      • rr: round-robin
      • lc: least connection (最少鏈接數算法)
      • dh: destination hashing(目的hash算法)
      • sh: source hashing(原地址hash算法)
      • sed: shortest expected delay(最短延遲算法)
      • nq: never queue

kube-proxy啓用IPVS

因爲IPVS的優點,因此儘量的採用IPVS做爲kube-proxy的工做模式,經過如下步驟在建立集羣時啓用IPVS

  1. 安裝依賴工具包

    apt install -y ipvsadm ipset
    • ipset是IPVS工做時會用刀的工具包
    • ipvsadm 是一個客戶端工具,可讓咱們和ipvs表的數據進行交互
  2. 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
  3. 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

  1. 介紹
    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
  2. 工做原理

    1. 由於IPVS是hook到netfilter的input鏈中執行的,因此須要先了解下數據在netfilter中的流轉過程

      正常流程:

      • 數據進入內核空間,到達PreRouting
      • 進行路由決策
      • 請求是發往本機的進入input
      • 進入用戶空間處理
      • 處理完進入output
      • 進入postrouting
      • 發送出去
      • 請求不是本機的,進入forward
      • 進入postrouting
      • 發送出去
    2. 加入IPVS後的簡化邏輯圖

      • 請求到達負載均衡器的內核空間時,到達PREROUTING鏈
      • 當內核根據路由決策發現地址是本機時,將數據包送往INPUT鏈
      • 數據包到達INPUT鏈時,首先會被IPVS檢查,若是請求的目的地址、端口信息不在本身的hash表中時,數據正常發往用戶空間處理
      • 若是hash表中的規則匹配到請求記錄,依據IPVS的工做模式,對請求頭中的信息進行修改,以後直接交由POSTROUTING鏈執行
      • POSTROUTING根據IPVS修改後的請求頭信息(此時目的地址是真實的服務地址),經過路由表,用正確的設備將請求轉發出去

      能夠看到,當IPVS模塊工做後,在input鏈上會再次對請求作匹配,匹配到的直接作postrouting,再也不進入用戶空間處理,而是把請求發送到IPVS選中的提供真實服務的服務器

    3. 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比對發現目標爲本身的IP,開始構建響應報文。 此時報文的源IP爲RIP,目標IP爲CIP 。
      • 由於Real Server的網關指向DIP,而且一般狀況下CIP和RIP不在同一個網段內,在這種狀況下,Real Server會先將數據發送給Director Server(網關),這樣數據就能夠沿原路返回。

      • Director Server在響應客戶端前,此時會將源IP地址修改成本身的VIP地址,而後響應給客戶端。 此時報文的源IP爲VIP,目標IP爲CIP。

      • 在此過程當中CIP一直是不發生變化的

kube-proxy如何使用IPVS

  1. 從IPVS的工做原理上能夠知道,kube-proxy想使用IPVS,須要完成如下幾個工做

    • 須要路由決策判斷要訪問的service的VIP屬於本機,不然,數據不通過input,直接forward出去,IPVS就沒有機會工做了
    • iptables中的規則須要容許對service請求經過,不然數據被過濾掉也不會通過IPVS
    • IPVS要可以判斷出訪問的service服務是屬於集羣服務
    • IPVS要可以根據service服務對應的VIP,找到真實的服務,以後修改請求頭,向真實的服務發送請求
    • 因爲在kubernetes下工做的IPVS,不必定知足經典的NAT模式的特性,因此數據返回路徑不必定和請求路徑一致,會出現Real Server直接返回數據給客戶端的狀況
  2. 爲了讓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這個設備下面

  3. 爲了讓iptables中的規則容許對Servcie的請求經過,kube-proxy在iptables中追加了以下幾條主要規則(iptables.masqueradeAll=false;clusterCIDR=172.30.0.0/16,clusterCIDR就是集羣中的pod能分配的IP地址段):

    1. 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

      有兩點須要解釋下

      1. 關於規則

        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
      2. 關於匹配語法 -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
        • 對應的類型信息:Type: hash:ip,port
        • 結合前面的規則

          ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            match-set KUBE-CLUSTER-IP dst,dst

          表述的意思是請求的目的地址IP、目的端口和KUBE-CLUSTER-IP中的Members有匹配時,就接收請求

    2. 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的規則表固定大小,實現大規模服務集羣中保持性能的穩定

  4. 請求經過了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機制對請求進行轉發了。

相關文章
相關標籤/搜索