K8s & K3s 對外服務暴露分析


背景簡介

K8s & K3s 將外部流量引入群集有哪些不同方式,在何種場景選用那種方式,我們進行如下探討:

  • NodePort NodePort在集群中的主機節點上為Service提供一個代理端口,以允許從主機網絡上對Service進行訪問。 NodePort的流量轉發機制和Cluster IP的iptables模式類似,唯一不同之處是在主機網絡上開了一個“NodePort”來接受外部流量。從上面的規則也可以看出,在創建Nodeport時,Kube-proxy也會同時為Service創建Cluster IP相關的iptables規則。

NodePort提供了一種從外部網絡訪問Kubernetes集群內部Service的方法,但該方法存在下面一些限制,導致這種方式主要適用於程序開發,不適合用於產品部署。

Kubernetes cluster host的IP必須是一個well-known IP,即客戶端必須知道該IP。但Cluster中的host是被作為資源池看待的,可以增加刪除,每個host的IP一般也是動態分配的,因此並不能認為host IP對客戶端而言是well-known IP。 客戶端訪問某一個固定的host IP的方式存在單點故障。假如一台host宕機了,kubernetes cluster會把應用 reload到另一節點上,但客戶端就無法通過該host的nodeport訪問應用了。 通過一個主機節點作為網絡入口,在網絡流量較大時存在性能瓶頸。

  • LoadBalancer Kubernetes提供了LoadBalancer。通過將Service定義為LoadBalancer類型,Kubernetes在主機節點的NodePort前提供了一個四層的負載均衡器。該四層負載均衡器負責將外部網絡流量分發到后面的多個節點的NodePort端口上。 下圖展示了Kubernetes如何通過LoadBalancer方式對外提供流量入口,圖中LoadBalancer后面接入了兩個主機節點上的NodePort,后端部署了三個Pod提供服務。根據集群的規模,可以在LoadBalancer后面可以接入更多的主機節點,以進行負荷分擔。

備注:LoadBalancer類型需要雲服務提供商的支持,Service中的定義只是在Kubernetes配置文件中提出了一個要求,即為該Service創建Load Balancer,至於如何創建則是由Google Cloud或Amazon Cloud等雲服務商提供的,創建的Load Balancer的過程不在Kubernetes Cluster的管理范圍中。

目前WS, Azure, CloudStack, GCE 和 OpenStack 等主流的公有雲和私有雲提供商都可以為Kubernetes提供Load Balancer。一般來說,公有雲提供商還會為Load Balancer提供一個External IP,以提供Internet接入。如果你的產品沒有使用雲提供商,而是自建Kubernetes Cluster,則需要自己提供LoadBalancer。

  • Ingress LoadBalancer類型的Service提供的是四層負載均衡器,當只需要向外暴露一個服務的時候,采用這種方式是沒有問題的。但當一個應用需要對外提供多個服務時,采用該方式則要求為每一個四層服務(IP+Port)都創建一個外部load balancer。

一般來說,同一個應用的多個服務/資源會放在同一個域名下,在這種情況下,創建多個Load balancer是完全沒有必要的,反而帶來了額外的開銷和管理成本。另外直接將服務暴露給外部用戶也會導致了前端和后端的耦合,影響了后端架構的靈活性,如果以后由於業務需求對服務進行調整會直接影響到客戶端。為了解決該問題,可以通過使用Kubernetes Ingress來作為網絡入口。 Kubernetes Ingress聲明了一個應用層(OSI七層)的負載均衡器,可以根據HTTP請求的內容將來自同一個TCP端口的請求分發到不同的Kubernetes Service,其功能包括:

  1. 按HTTP請求的URL進行路由 同一個TCP端口進來的流量可以根據URL路由到Cluster中的不同服務,如下圖所示:

  2. 按HTTP請求的Host進行路由 同一個IP進來的流量可以根據HTTP請求的Host路由到Cluster中的不同服務,如下圖所示:

Ingress 規則定義了對七層網關的要求,包括URL分發規則,基於不同域名的虛擬主機,SSL證書等。Kubernetes使用Ingress Controller 來監控Ingress規則,並通過一個七層網關來實現這些要求,一般可以使用Nginx,HAProxy,Envoy等。

Ingress配合NodePort和LoadBalancer提供對外流量入口

雖然Ingress Controller通過七層網關為后端的多個Service提供了統一的入口,但由於其部署在集群中,因此並不能直接對外提供服務。實際上Ingress需要配合NodePort和LoadBalancer才能提供對外的流量入口,如下圖所示:

上圖描述了如何采用Ingress配合NodePort和Load Balancer為集群提供外部流量入口,從該拓撲圖中可以看到該架構的伸縮性非常好,在NodePort,Ingress,Pod等不同的接入層面都可以對系統進行水平擴展,以應對不同的外部流量要求。

上圖只展示了邏輯架構,下面的圖展示了具體的實現原理:

流量從外部網絡到達Pod的完整路徑如下:

  1. 外部請求先通過四層Load Balancer進入內部網絡
  2. Load Balancer將流量分發到后端多個主機節點上的NodePort (userspace轉發)
  3. 請求從NodePort進入到Ingress Controller (iptabes規則,Ingress Controller本身是一個NodePort類型的Service)
  4. Ingress Controller根據Ingress rule進行七層分發,根據HTTP的URL和Host將請求分發給不同的Service (userspace轉發)
  5. Service將請求最終導入到后端提供服務的Pod中 (iptabes規則)

從前面的介紹可以看到,K8S Ingress提供了一個基礎的七層網關功能的抽象定義,其作用是對外提供一個七層服務的統一入口,並根據URL/HOST將請求路由到集群內部不同的服務上。

場景應用分析

ClusterIP

ClusterIP 服務是 Kubernetes 的默認服務。它給你一個集群內的服務,集群內的其它應用都可以訪問該服務。集群外部無法訪問它。

ClusterIP 服務的 YAML 文件類似如下:

apiVersion: v1

kind: Service
metadata:  
name: my-internal-service
selector:    
app: my-app
spec:
type: ClusterIP
ports:  
- name: http
port: 80
targetPort: 80
protocol: TC

從Internet 沒法訪問 ClusterIP 服務,那么為什么要討論它呢?那是因為我們可以通過 Kubernetes 的 proxy 模式來訪問該服務!

# 啟動 Kubernetes proxy 模式:
kubectl proxy --port=8080
# 通過Kubernetes API,使用如下模式來訪問這個服務:
http://localhost:8080/api/v1/proxy/namespaces/<NAMESPACE>/services/<SERVICE-NAME>:<PORT-NAME>
# 要訪問上面定義的服務,可以使用如下地址:
http://localhost:8080/api/v1/proxy/namespaces/default/services/my-internal-service:http/
何時使用這種方式?

有一些場景下,你得使用 Kubernetes 的 proxy 模式來訪問你的服務: 由於某些原因,你需要調試你的服務,或者需要直接通過筆記本電腦去訪問它們。 容許內部通信,展示內部儀表盤等。

這種方式要求我們運行 kubectl 作為一個未認證的用戶,因此我們不能用這種方式把服務暴露到 internet 或者在生產環境使用。

NodePort

NodePort 服務是引導外部流量到你的服務的最原始方式。NodePort,正如這個名字所示,在所有節點(虛擬機)上開放一個特定端口,任何發送到該端口的流量都被轉發到對應服務。

NodePort 服務的 YAML 文件類似如下:

apiVersion: v1

kind: Service
metadata:  
name: my-nodeport-service
selector:    
app: my-app
spec:
type: NodePort
ports:  
- name: http
port: 80
targetPort: 80
nodePort: 30036
protocol: TC

NodePort 服務主要有兩點區別於普通的“ClusterIP”服務。第一,它的類型是“NodePort”。有一個額外的端口,稱為 nodePort,它指定節點上開放的端口值 。如果你不指定這個端口,系統將選擇一個隨機端口。大多數時候我們應該讓 Kubernetes 來選擇端口,因為如評論中 thockin 所說,用戶自己來選擇可用端口代價太大。

何時使用這種方式?

這種方法有許多缺點: 每個端口只能是一種服務 端口范圍只能是 30000-32767 如果節點/VM 的 IP 地址發生變化,你需要能處理這種情況。

基於以上原因,我不建議在生產環境上用這種方式暴露服務。如果你運行的服務不要求一直可用,或者對成本比較敏感,你可以使用這種方法。這樣的應用的最佳例子是 demo 應用,或者某些臨時應用。

LoadBalancer

LoadBalancer 服務是暴露服務到 internet 的標准方式。在 GKE 上,這種方式會啟動一個 Network Load Balancer,它將給你一個單獨的 IP 地址,轉發所有流量到你的服務。

何時使用這種方式?

如果你想要直接暴露服務,這就是默認方式。所有通往你指定的端口的流量都會被轉發到對應的服務。它沒有過濾條件,沒有路由等。這意味着你幾乎可以發送任何種類的流量到該服務,像 HTTP,TCP,UDP,Websocket,gRPC 或其它任意種類。

這個方式的最大缺點是每一個用 LoadBalancer 暴露的服務都會有它自己的 IP 地址,每個用到的 LoadBalancer 都需要付費,這將是非常昂貴的。

Ingress

有別於以上所有例子,Ingress 事實上不是一種服務類型。相反,它處於多個服務的前端,扮演着“智能路由”或者集群入口的角色。

你可以用 Ingress 來做許多不同的事情,各種不同類型的 Ingress 控制器也有不同的能力。

GKE 上的默認 ingress 控制器是啟動一個 HTTP(S) Load Balancer。它允許你基於路徑或者子域名來路由流量到后端服務。例如,你可以將任何發往域名 foo.yourdomain.com 的流量轉到 foo 服務,將路徑 yourdomain.com/bar/path 的流量轉到 bar 服務。

GKE 上用 L7 HTTP Load Balancer 生成的 Ingress 對象的 YAML 文件類似如下:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: my-ingress
spec:
backend:
serviceName: other
servicePort: 8080
rules:
- host: foo.mydomain.com
http:
  paths:
  - backend:
      serviceName: foo
      servicePort: 8080
- host: mydomain.com
http:
  paths:
  - path: /bar/*
    backend:
      serviceName: bar
      servicePort: 8080

何時使用這種方式?

Ingress 可能是暴露服務的最強大方式,但同時也是最復雜的。Ingress 控制器有各種類型,包括 Google Cloud Load Balancer, Nginx,Contour,Istio,等等。它還有各種插件,比如 cert-manager,它可以為你的服務自動提供 SSL 證書。

如果你想要使用同一個 IP 暴露多個服務,這些服務都是使用相同的七層協議(典型如 HTTP),那么Ingress 就是最有用的。如果你使用本地的 GCP 集成,你只需要為一個負載均衡器付費,且由於 Ingress是“智能”的,你還可以獲取各種開箱即用的特性(比如 SSL,認證,路由,等等)。

拓展閱讀

針對網絡流量的負載均衡,在實際生產工作中都是機其重要的角色的,我們幾乎所有的流量通信服務都是需要對外暴露,這是第一步,之后才會開始考慮鑒權,負載,限流等諸多問題的,所以如何安全合適的暴露我們的流量是一個值得探討的話題,尤其在微服務領域,當開發人員的所有開發能力永遠和 IP+Port 時,技術人員也應該反思下自己的能力了,流量分析的問題是一個很大的面,也是從初級開發人員邁向高級/資深工程師的很關鍵的一步。


免責聲明!

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



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