Kubernetes(十八) kube-proxy 詳解


前言

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

  • User space 模式
  • iptables 模式
  • IPVS 模式
  1. User space模式

    在這種模式下,kube-proxy通過觀察Kubernetes中service和endpoint對象的變化,當有新的service創建時,所有節點的kube-proxy在node節點上隨機選擇一個端口,在iptables中追加一條把訪問service的請求重定向到這個端口的記錄,並開始監聽這個端口的連接請求。

    比如說創建一個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的規則上,

    默認情況下,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來執行規則制定,常見術語

    解釋
    Real Server 后端請求處理服務器
    P Client IP,客戶端IP
    P Director Virtual IP,負載均衡器虛擬IP
    P Director IP,負載均衡器IP
    P 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機制對請求進行轉發了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM