當前位置: 妍妍網 > 碼農

面試官:K8s Service 背後是怎麽工作的?

2024-05-13碼農

原文連結:https://www.cnblogs.com/daemon365/p/18181146

kube-proxy 是 Kubernetes 集群中負責服務發現和負載均衡的元件之一。它是一個網路代理,執行在每個節點上, 用於 service 資源的負載均衡。它有兩種模式: iptables ipvs

iptables

iptables 是 Linux 系統中的一個使用者空間實用程式,用於配置內核的網路包過濾和網路地址轉換(NAT)規則。它是 Linux 內核中的 netfilter 框架的一部份,並負責在網路包進入、轉發或離開電腦時進行篩選和處理。其主要功能和用途包括:

  1. 防火墻:iptables 提供了強大的防火墻功能,可以根據不同的規則來過濾和拒絕不需要的網路包。管理員可以建立自訂的規則集,允許或拒絕從特定 IP 地址、埠或協定的封包。

  2. NAT(網路地址轉換):iptables 支持 NAT 功能,可以用來將私有網路中的電腦與外部網路連線。例如,它可以在一個 NAT 路由器上將企業網路絡的多個裝置對映到單個外部 IP 地址。

  3. 埠轉發:iptables 可以將特定的埠流量從一個網路介面轉發到另一個介面或目標 IP 地址,通常用於企業網路絡的服務公開。

  4. 負載均衡:它也可以透過 DNAT(目標網路地址轉換)功能將流量轉發給多個內部伺服器,實作簡單的負載均衡。

iptables 是透過鏈(chains)和表(tables)來組織規則的。每個鏈由一組規則組成,當網路封包經過時,這些規則會逐一執行。常用的表包括:

  • filter 表:用於包過濾,是最常用的表。

  • nat 表:用於網路地址轉換。

  • mangle 表:用於修改封包的 IP 層欄位。

  • raw 表:用於繞過連線跟蹤。

  • 鏈的流向為:

    所以,根據上圖,我們能夠想象出某些常用場景中,報文的流向:

    到本機某行程的報文: PREROUTING –> INPUT

    由本機轉發的報文: PREROUTING –> FORWARD –> POSTROUTING

    由本機的某行程發出報文(通常為響應報文): OUTPUT –> POSTROUTING

    盡管在某些情況下配置 iptables 規則可能復雜,但它提供了高度的靈活性和強大的功能,使其成為 Linux 網路安全的重要組成部份。

    service 負載均衡

    我啟動了一個 3 個 nginx pod,和一個對應的 service,service 的型別是 ClusterIP ,這樣 service 就會有一個虛擬 IP,這個 IP 會被 kube-proxy 代理到後端的 pod 上。


    ~ » k get pod -owide

    NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES

    nginx-deployment-59f546cb79-2k9ng 1/1 Running 2 (31m ago) 50m 10.244.0.28 minikube <none> <none>

    nginx-deployment-59f546cb79-wfw84 1/1 Running 2 (31m ago) 50m 10.244.0.30 minikube <none> <none>

    nginx-deployment-59f546cb79-zr9xm 1/1 Running 2 (31m ago) 50m 10.244.0.27 minikube <none> <none>

    ----------------------------------------------------------------------------------------------------------------------------------------------------

    ~ » k get svc nginx-service

    NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

    nginx-service ClusterIP 10.101.57.97 <none> 80/TCP 29m

    當我們在 master 使用 curl 10.101.57.97 存取 service 的時候,首先會進入 PREROUTING 鏈:


    root@minikube:/# iptables-save |grep PREROUTING

    :PREROUTING ACCEPT [0:0]

    :PREROUTING ACCEPT [34:2040]

    -A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES

    -A PREROUTING -d 192.168.49.1/32 -j DOCKER_OUTPUT

    -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER

    首先會嘗試匹配 KUBE-SERVICES 鏈,這個鏈是 kube-proxy 生成的,用於處理 service 的請求。後兩個是 docker 的鏈,用於處理 docker 的請求。


    root@minikube:/# iptables-save |grep "\-A KUBE-SERVICES"

    -A KUBE-SERVICES -d 10.96.0.1/32 -p tcp -m comment --comment "default/kubernetes:https cluster IP" -j KUBE-SVC-NPX46M4PTMTKRN6Y

    -A KUBE-SERVICES -d 10.101.57.97/32 -p tcp -m comment --comment "default/nginx-service cluster IP" -j KUBE-SVC-V2OKYYMBY3REGZOG

    -A KUBE-SERVICES -d 10.96.0.10/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -j KUBE-SVC-TCOU7JCQXEZGVUNU

    -A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp cluster IP" -j KUBE-SVC-ERIFXISQEP7F7OF4

    -A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -j KUBE-SVC-JD5MR3NA4I4DYORP

    -A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS

    我們這個 nginx-service 的 cluster IP 是 10.101.57.97 , 所以會走 KUBE-SVC-V2OKYYMBY3REGZOG 鏈:


    root@minikube:/# iptables-save |grep "\-A KUBE-SVC-V2OKYYMBY3REGZOG"

    -A KUBE-SVC-V2OKYYMBY3REGZOG ! -s 10.244.0.0/16 -d 10.101.57.97/32 -p tcp -m comment --comment "default/nginx-service cluster IP" -j KUBE-MARK-MASQ

    -A KUBE-SVC-V2OKYYMBY3REGZOG -m comment --comment "default/nginx-service -> 10.244.0.27:80" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-POZMZY2HDLRATSJV

    -A KUBE-SVC-V2OKYYMBY3REGZOG -m comment --comment "default/nginx-service -> 10.244.0.28:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-Z3HXRORN5VDCFRJU

    -A KUBE-SVC-V2OKYYMBY3REGZOG -m comment --comment "default/nginx-service -> 10.244.0.30:80" -j KUBE-SEP-S46ZL6MIFVWDY42O

    -A KUBE-SVC-V2OKYYMBY3REGZOG ! -s 10.244.0.0/16 -d 10.101.57.97/32 -p tcp -m comment --comment "default/nginx-service cluster IP" -j KUBE-MARK-MASQ 這條規則的如果源ip 不是 10.244.0.0/16 的 ip(也就是不是 pod發出來的請求),目的ip 是 service ip,jump 跳轉到 這個鏈 KUBE-MARK-MASQ


    root@minikube:/# iptables-save |grep "\-A KUBE-MARK-MASQ"

    -A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000

    這條規則的作用是給封包打上 0x4000 這個標記。這個標記會被後續的 NAT 規則辨識到,從而讓這些封包透過特定的 NAT 規則進行 IP 地址轉換。

    接下來看主要的三條規則:


    -A KUBE-SVC-V2OKYYMBY3REGZOG -m comment --comment "default/nginx-service -> 10.244.0.27:80" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-POZMZY2HDLRATSJV

    -A KUBE-SVC-V2OKYYMBY3REGZOG -m comment --comment "default/nginx-service -> 10.244.0.28:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-Z3HXRORN5VDCFRJU

    -A KUBE-SVC-V2OKYYMBY3REGZOG -m comment --comment "default/nginx-service -> 10.244.0.30:80" -j KUBE-SEP-S46ZL6MIFVWDY42O

    第一條是說有 33.33% 的機率會被轉發到 KUBE-SEP-POZMZY2HDLRATSJV , 第二條是 50% 的機率會被轉發到 KUBE-SEP-Z3HXRORN5VDCFRJU , 第三條是一定會被轉發到 KUBE-SEP-S46ZL6MIFVWDY42O
    轉到第一條的機率是 33.33%,轉到第二條的機率是 66.67%(沒有到第一條的機率) * 50% = 33.33%,第三條就是 66.67%(沒有到第一條的機率) * 50%(沒有到第二條的機率) = 33.33%。所以這三條規則的機率是一樣的。都是 33.33%。
    那麽
    KUBE-SEP-POZMZY2HDLRATSJV 又是什麽呢?


    root@minikube:/# iptables-save |grep "\-A KUBE-SEP-POZMZY2HDLRATSJV"

    -A KUBE-SEP-POZMZY2HDLRATSJV -s 10.244.0.27/32 -m comment --comment "default/nginx-service" -j KUBE-MARK-MASQ

    -A KUBE-SEP-POZMZY2HDLRATSJV -p tcp -m comment --comment "default/nginx-service" -m tcp -j DNAT --to-destination 10.244.0.27:80



    root@minikube:/# iptables-save |grep "\-A KUBE-SEP-POZMZY2HDLRATSJV"

    -A KUBE-SEP-Z3HXRORN5VDCFRJU -s 10.244.0.28/32 -m comment --comment "default/nginx-service" -j KUBE-MARK-MASQ

    -A KUBE-SEP-Z3HXRORN5VDCFRJU -p tcp -m comment --comment "default/nginx-service" -m tcp -j DNAT --to-destination 10.244.0.28:80



    root@minikube:/# iptables-save |grep "\-A KUBE-SEP-S46ZL6MIFVWDY42O"

    -A KUBE-SEP-S46ZL6MIFVWDY42O -s 10.244.0.30/32 -m comment --comment "default/nginx-service" -j KUBE-MARK-MASQ

    -A KUBE-SEP-S46ZL6MIFVWDY42O -p tcp -m comment --comment "default/nginx-service" -m tcp -j DNAT --to-destination 10.244.0.30:80

    第一條規則確保流量從 10.244.0.20 發出時被打上 MASQUERADE 標記,以便透過 NAT 機制進行 IP 偽裝。
    第二條是將流量轉發到
    10.244.0.20:80 並使用 DNAT 機制進行目標地址轉換, 轉換的 ip 10.244.0.27:80 就是 pod 的 ip 和埠。
    KUBE-SEP-Z3HXRORN5VDCFRJU KUBE-SEP-S46ZL6MIFVWDY42O 的規則和這條同理。

    ipvs

    IPVS(IP Virtual Server)是 Linux 內核中實作負載均衡功能的模組。是一種高效的負載均衡技術,可以在第 4 層(傳輸層)進行流量轉發和排程。IPVS 通常被用於構建高效能、高可用性的負載均衡集群。

    檢視 ipvs 的規則:


    root@minikube:/etc/apt# sudo ipvsadm -Ln|grep -A 4 10.101.57.97

    Prot LocalAddress:Port Scheduler Flags

    -> RemoteAddress:Port Forward Weight ActiveConn InActConn

    TCP 10.101.57.97:80 rr

    -> 10.244.0.27:80 Masq 1 0 0

    -> 10.244.0.28:80 Masq 1 0 0

    -> 10.244.0.30:80 Masq 1 0 0

    UDP 10.96.0.10:53 rr

    這個的意思是到 10.101.57.97:80 的 tcp 瀏覽會使用 rr(輪詢)的方式轉發到 10.244.0.27:80 10.244.0.28:80 10.244.0.30:80 這三個 pod 上。Masq:指示 "Masquerading",表示透過 NAT 來處理網路流量。
    所以 ipvs 的模式比 iptables 效能高的多,第一因為 ipvs 是輪詢選,iptables 是逐條百分比匹配的,這個還是可以接受的。更要命的是第二條,當 pod 頻繁變更的時候 service 對應的 endpoint 的 ENDPOINTS 就會增加或者是減少。那麽 iptables 對應 service 的所有規則的百分比都會變化,就會導致一個 service 的規則全部要重刷,當 pod 變化太頻繁時,會吃掉大量的 CPU。

    不是說開啟了 ipvs 就不會有 iptables 了,還需要 iptables 的 SNAT 規則來處理返回的封包。


    -A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING

    -A KUBE-POSTROUTING -m comment --comment "Kubernetes endpoints dst ip:port, source ip for solving hairpin purpose" -m set --match-set KUBE-LOOP-BACK dst,dst,src -j MASQUERADE

    -m set --match-set KUBE-LOOP-BACK dst,dst,src 的意思是匹配 KUBE-LOOP-BACK 這個 set,這個 set 裏面存放的是 pod 的 ip,這個規則的作用是將封包的源地址替換為本機的地址,以便封包能夠正確返回到客戶端。
    為什麽是 pod ip 而不是 service cluster ip 呢?因為已經透過 ipvs DNAT 過了,所以這裏是 pod ip。


    root@minikube:/etc/apt# ipset -L|grep -A 15 KUBE-LOOP-BACK

    Name: KUBE-LOOP-BACK

    Type: hash:ip,port,ip

    Revision: 6

    Header: family inet hashsize 1024 maxelem 65536 bucketsize 12 initval 0xe4e21451

    Size in memory: 544

    References: 1

    Number of entries: 6

    Members:

    10.244.0.28,tcp:80,10.244.0.28

    10.244.0.30,tcp:80,10.244.0.30

    10.244.0.29,tcp:9153,10.244.0.29

    10.244.0.29,tcp:53,10.244.0.29

    10.244.0.27,tcp:80,10.244.0.27

    10.244.0.29,udp:53,10.244.0.29


    很明顯我們的三個 pod 都在這個 set 裏面。

    -A KUBE-POSTROUTING -m comment --comment "Kubernetes endpoints dst ip:port, source ip for solving hairpin purpose" -m set --match-set KUBE-LOOP-BACK dst,dst,src -j MASQUERADE
    這條規則的作用是將封包的源地址替換為本機的地址,以便封包能夠正確返回到客戶端。現在我們使用 curl 發出的包目標 ip 是 pod ip了,源 ip 是 node ip。等到封包返回的時候,kernel 會根據 鏈路追蹤 中的數據記錄,會把包的源ip 的pod ip 替換為 serice cluster ip, 目標 ip 從 node ip 替換為我發起請求的 ip。這樣包就能正確返回到客戶端了。

    往期推薦


    點亮,伺服器三年不宕機