天天看點

kubernetes service 原了解析

為什麼需要 service

在 kubernetes 中,當建立帶有多個副本的 deployment 時,kubernetes 會建立出多個 pod,此時即一個服務後端有多個容器,那麼在 kubernetes 中負載均衡怎麼做,容器漂移後 ip 也會發生變化,如何做服務發現以及會話保持?這就是 service 的作用,service 是一組具有相同 label pod 集合的抽象,叢集内外的各個服務可以通過 service 進行互相通信,當建立一個 service 對象時也會對應建立一個 endpoint 對象,endpoint 是用來做容器發現的,service 隻是将多個 pod 進行關聯,實際的路由轉發都是由 kubernetes 中的 kube-proxy 元件來實作,是以,service 必須結合 kube-proxy 使用,kube-proxy 元件可以運作在 kubernetes 叢集中的每一個節點上也可以隻運作在單獨的幾個節點上,其會根據 service 和 endpoints 的變動來改變節點上 iptables 或者 ipvs 中儲存的路由規則。

service 的工作原理

kubernetes service 原了解析

endpoints controller 是負責生成和維護所有 endpoints 對象的控制器,監聽 service 和對應 pod 的變化,更新對應 service 的 endpoints 對象。當使用者建立 service 後 endpoints controller 會監聽 pod 的狀态,當 pod 處于 running 且準備就緒時,endpoints controller 會将 pod ip 記錄到 endpoints 對象中,是以,service 的容器發現是通過 endpoints 來實作的。而 kube-proxy 會監聽 service 和 endpoints 的更新并調用其代理子產品在主機上重新整理路由轉發規則。

service 的負載均衡

上文已經提到 service 實際的路由轉發都是由 kube-proxy 元件來實作的,service 僅以一種 VIP(ClusterIP) 的形式存在,kube-proxy 主要實作了叢集内部從 pod 到 service 和叢集外部從 nodePort 到 service 的通路,kube-proxy 的路由轉發規則是通過其後端的代理子產品實作的,kube-proxy 的代理子產品目前有四種實作方案,userspace、iptables、ipvs、kernelspace,其發展曆程如下所示:

  • kubernetes v1.0:services 僅是一個“4層”代理,代理子產品隻有 userspace
  • kubernetes v1.1:Ingress API 出現,其代理“7層”服務,并且增加了 iptables 代理子產品
  • kubernetes v1.2:iptables 成為預設代理模式
  • kubernetes v1.8:引入 ipvs 代理子產品
  • kubernetes v1.9:ipvs 代理子產品成為 beta 版本
  • kubernetes v1.11:ipvs 代理模式 GA

在每種模式下都有自己的負載均衡政策,下文會詳解介紹。

userspace 模式

在 userspace 模式下,通路服務的請求到達節點後首先進入核心 iptables,然後回到使用者空間,由 kube-proxy 轉發到後端的 pod,這樣流量從使用者空間進出核心帶來的性能損耗是不可接受的,是以也就有了 iptables 模式。

為什麼 userspace 模式要建立 iptables 規則,因為 kube-proxy 監聽的端口在使用者空間,這個端口不是服務的通路端口也不是服務的 nodePort,是以需要一層 iptables 把通路服務的連接配接重定向給 kube-proxy 服務。

kubernetes service 原了解析

iptables 模式

iptables 模式是目前預設的代理方式,基于 netfilter 實作。當用戶端請求 service 的 ClusterIP 時,根據 iptables 規則路由到各 pod 上,iptables 使用 DNAT 來完成轉發,其采用了随機數實作負載均衡。

iptables 模式與 userspace 模式最大的差別在于,iptables 子產品使用 DNAT 子產品實作了 service 入口位址到 pod 實際位址的轉換,免去了一次核心态到使用者态的切換,另一個與 userspace 代理模式不同的是,如果 iptables 代理最初選擇的那個 pod 沒有響應,它不會自動重試其他 pod。

iptables 模式最主要的問題是在 service 數量大的時候會産生太多的 iptables 規則,使用非增量式更新會引入一定的時延,大規模情況下有明顯的性能問題。

kubernetes service 原了解析

ipvs 模式

當叢集規模比較大時,iptables 規則重新整理會非常慢,難以支援大規模叢集,因其底層路由表的實作是連結清單,對路由規則的增删改查都要涉及周遊一次連結清單,ipvs 的問世正是解決此問題的,ipvs 是 LVS 的負載均衡子產品,與 iptables 比較像的是,ipvs 的實作雖然也基于 netfilter 的鈎子函數,但是它卻使用哈希表作為底層的資料結構并且工作在核心态,也就是說 ipvs 在重定向流量和同步代理規則有着更好的性能,幾乎允許無限的規模擴張。

ipvs 支援三種負載均衡模式:DR模式(Direct Routing)、NAT 模式(Network Address Translation)、Tunneling(也稱 ipip 模式)。三種模式中隻有 NAT 支援端口映射,是以 ipvs 使用 NAT 模式。linux 核心原生的 ipvs 隻支援 DNAT,當在資料包過濾,SNAT 和支援 NodePort 類型的服務這幾個場景中ipvs 還是會使用 iptables。

此外,ipvs 也支援更多的負載均衡算法,例如:

  • rr:round-robin/輪詢
  • lc:least connection/最少連接配接
  • dh:destination hashing/目标哈希
  • sh:source hashing/源哈希
  • sed:shortest expected delay/預計延遲時間最短
  • nq:never queue/從不排隊

userspace、iptables、ipvs 三種模式中預設的負載均衡政策都是通過 round-robin 算法來選擇後端 pod 的,在 service 中可以通過設定

service.spec.sessionAffinity

的值實作基于用戶端 ip 的會話親和性,

service.spec.sessionAffinity

的值預設為"None",可以設定為 "ClientIP",此外也可以使用

service.spec.sessionAffinityConfig.clientIP.timeoutSeconds

設定會話保持時間。kernelspace 主要是在 windows 下使用的,本文暫且不談。

service 的類型

service 支援的類型也就是 kubernetes 中服務暴露的方式,預設有四種 ClusterIP、NodePort、LoadBalancer、ExternelName,此外還有 Ingress,下面會詳細介紹每種類型 service 的具體使用場景。

ClusterIP

ClusterIP 類型的 service 是 kubernetes 叢集預設的服務暴露方式,它隻能用于叢集内部通信,可以被各 pod 通路,其通路方式為:

pod ---> ClusterIP:ServicePort --> (iptables)DNAT --> PodIP:containePort           

ClusterIP Service 類型的結構如下圖所示:

kubernetes service 原了解析

NodePort

如果你想要在叢集外通路叢集内部的服務,可以使用這種類型的 service,NodePort 類型的 service 會在叢集内部署了 kube-proxy 的節點打開一個指定的端口,之後所有的流量直接發送到這個端口,然後會被轉發到 service 後端真實的服務進行通路。Nodeport 建構在 ClusterIP 上,其通路鍊路如下所示:

client ---> NodeIP:NodePort ---> ClusterIP:ServicePort ---> (iptables)DNAT ---> PodIP:containePort           

其對應具體的 iptables 規則會在後文進行講解。

NodePort service 類型的結構如下圖所示:

kubernetes service 原了解析

LoadBalancer

LoadBalancer 類型的 service 通常和雲廠商的 LB 結合一起使用,用于将叢集内部的服務暴露到外網,雲廠商的 LoadBalancer 會給使用者配置設定一個 IP,之後通過該 IP 的流量會轉發到你的 service 上。

LoadBalancer service 類型的結構如下圖所示:

kubernetes service 原了解析

ExternelName

通過 CNAME 将 service 與 externalName 的值(比如:foo.bar.example.com)映射起來,這種方式用的比較少。

Ingress

Ingress 其實不是 service 的一個類型,但是它可以作用于多個 service,被稱為 service 的 service,作為叢集内部服務的入口,Ingress 作用在七層,可以根據不同的 url,将請求轉發到不同的 service 上。

Ingress 的結構如下圖所示:

kubernetes service 原了解析

service 的服務發現

雖然 service 的 endpoints 解決了容器發現問題,但不提前知道 service 的 Cluster IP,怎麼發現 service 服務呢?service 目前支援兩種類型的服務發現機制,一種是通過環境變量,另一種是通過 DNS。在這兩種方案中,建議使用後者。

環境變量

當一個 pod 建立完成之後,kubelet 會在該 pod 中注冊該叢集已經建立的所有 service 相關的環境變量,但是需要注意的是,在 service 建立之前的所有 pod 是不會注冊該環境變量的,是以在平時使用時,建議通過 DNS 的方式進行 service 之間的服務發現。

DNS

可以在叢集中部署

CoreDNS

服務(舊版本的 kubernetes 群使用的是 kubeDNS), 來達到叢集内部的 pod 通過DNS 的方式進行叢集内部各個服務之間的通訊。

目前 kubernetes 叢集預設使用 CoreDNS 作為預設的 DNS 服務,主要原因是 CoreDNS 是基于 Plugin 的方式進行擴充的,簡單,靈活,并且不完全被Kubernetes所捆綁。

service 的使用

ClusterIP 方式

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
spec:
  clusterIP: 10.105.146.177
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app: my-nginx
  sessionAffinity: None
  type: ClusterIP           

NodePort 方式

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
spec:
  ports:
  - nodePort: 30090
    port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app: my-nginx
  sessionAffinity: None
  type: NodePort           

其中

nodeport

字段表示通過 nodeport 方式通路的端口,

port

表示通過 service 方式通路的端口,

targetPort

表示 container port。

Headless service(就是沒有 Cluster IP 的 service )

當不需要負載均衡以及單獨的 ClusterIP 時,可以通過指定

spec.clusterIP

的值為

None

來建立 Headless service,它會給一個叢集内部的每個成員提供一個唯一的 DNS 域名來作為每個成員的網絡辨別,叢集内部成員之間使用域名通信。

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
spec:
  clusterIP: None
  ports:
  - nodePort: 30090
    port: 80
    protocol: TCP
    targetPort: 8080
  selector:
    app: my-nginx           

總結

本文主要講了 kubernetes 中 service 的原理、實作以及使用方式,service 目前主要有 5 種服務暴露方式,service 的容器發現是通過 endpoints 來實作的,其服務發現主要是通過 DNS 實作的,其負載均衡以及流量轉發是通過 kube-proxy 實作的。在後面的文章我會繼續介紹 kube-proxy 的設計及實作。

參考:

https://www.cnblogs.com/xzkzzz/p/9559362.html https://xigang.github.io/2019/07/21/kubernetes-service/