1. 程式人生 > >如何從外部訪問Kubernetes叢集中的應用?\

如何從外部訪問Kubernetes叢集中的應用?\

前言 我們知道,kubernetes的Cluster Network屬於私有網路,只能在cluster Network內部才能訪問部署的應用,那如何才能將Kubernetes叢集中的應用暴露到外部網路,為外部使用者提供服務呢?本文探討了從外部網路訪問kubernetes cluster中應用的幾種實現方式。

本文儘量試著寫得比較容易理解,但要做到“深入淺出”,把複雜的事情用通俗易懂的語言描述出來是非常需要功力的,個人自認尚未達到此境界,唯有不斷努力。此外,kubernetes本身是一個比較複雜的系統,無法在本文中詳細解釋涉及的所有相關概念,否則就可能脫離了文章的主題,因此假設閱讀此文之前讀者對kubernetes的基本概念如docker,container,pod已有所瞭解。

另外此文中的一些內容是自己的理解,由於個人的知識範圍有限,可能有誤,如果讀者對文章中的內容有疑問或者勘誤,歡迎大家指證。  

Pod和Service 我們首先來了解一下Kubernetes中的Pod和Service的概念。

Pod(容器組),英文中Pod是豆莢的意思,從名字的含義可以看出,Pod是一組有依賴關係的容器,Pod包含的容器都會執行在同一個host節點上,共享相同的volumes和network namespace空間。Kubernetes以Pod為基本操作單元,可以同時啟動多個相同的pod用於failover或者load balance。

Pod的生命週期是短暫的,Kubernetes根據應用的配置,會對Pod進行建立,銷燬,根據監控指標進行縮擴容。kubernetes在建立Pod時可以選擇叢集中的任何一臺空閒的Host,因此其網路地址是不固定的。由於Pod的這一特點,一般不建議直接通過Pod的地址去訪問應用。

為了解決訪問Pod不方便直接訪問的問題,Kubernetes採用了Service的概念,Service是對後端提供服務的一組Pod的抽象,Service會繫結到一個固定的虛擬IP上,該虛擬IP只在Kubernetes Cluster中可見,但其實該IP並不對應一個虛擬或者物理裝置,而只是IPtable中的規則,然後再通過IPtable將服務請求路由到後端的Pod中。通過這種方式,可以確保服務消費者可以穩定地訪問Pod提供的服務,而不用關心Pod的建立、刪除、遷移等變化以及如何用一組Pod來進行負載均衡。

Service的機制如下圖所示,Kube-proxy監聽kubernetes master增加和刪除Service以及Endpoint的訊息,對於每一個Service,kube proxy建立相應的iptables規則,將傳送到Service Cluster IP的流量轉發到Service後端提供服務的Pod的相應埠上。 

備註:雖然可以通過Service的Cluster IP和服務埠訪問到後端Pod提供的服務,但該Cluster IP是Ping不通的,原因是Cluster IP只是iptable中的規則,並不對應到一個網路裝置。

Service的型別 Service的型別(ServiceType)決定了Service如何對外提供服務,根據型別不同,服務可以只在Kubernetes cluster中可見,也可以暴露到Cluster外部。Service有三種類型,ClusterIP,NodePort和LoadBalancer。其中ClusterIP是Service的預設型別,這種型別的服務會提供一個只能在Cluster內才能訪問的虛擬IP,其實現機制如上面一節所述。

通過NodePort提供外部訪問入口 通過將Service的型別設定為NodePort,可以在Cluster中的主機上通過一個指定埠暴露服務。注意通過Cluster中每臺主機上的該指定埠都可以訪問到該服務,傳送到該主機埠的請求會被kubernetes路由到提供服務的Pod上。採用這種服務型別,可以在kubernetes cluster網路外通過主機IP:埠的方式訪問到服務。

注意:官方文件中說明了Kubernetes clusterIp的流量轉發到後端Pod有Iptable和kube proxy兩種方式。但對Nodeport如何轉發流量卻語焉不詳。該圖來自網路,從圖來看是通過kube proxy轉發的,我沒有去研究過原始碼。歡迎瞭解的同學跟帖說明。

下面是通過NodePort向外暴露服務的一個例子,注意可以指定一個nodePort,也可以不指定。在不指定的情況下,kubernetes會從可用的埠範圍內自動分配一個隨機埠。  

kind: Service
apiVersion: v1
metadata:
  name: influxdb
spec:
  type: NodePort
  ports:
    - port: 8086
      nodePort: 30000
  selector:
    name: influxdb

通過NodePort從外部訪問有下面的一些問題,自己玩玩或者進行測試時可以使用該方案,但不適宜用於生產環境。

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訪問應用了。

該方案假設客戶端可以訪問Kubernetes host所在網路。在生產環境中,客戶端和Kubernetes host網路可能是隔離的。例如客戶端可能是公網中的一個手機APP,是無法直接訪問host所在的私有網路的。

因此,需要通過一個閘道器來將外部客戶端的請求匯入到Cluster中的應用中,在kubernetes中,這個閘道器是一個4層的load balancer。

通過Load Balancer提供外部訪問入口 通過將Service的型別設定為LoadBalancer,可以為Service建立一個外部Load Balancer。Kubernetes的文件中宣告該Service型別需要雲服務提供商的支援,其實這裡只是在Kubernetes配置檔案中提出了一個要求,即為該Service建立Load Balancer,至於如何建立則是由Google Cloud或Amazon Cloud等雲服務商提供的,建立的Load Balancer不在Kubernetes Cluster的管理範圍中。kubernetes 1.6版本中,WS, Azure, CloudStack, GCE and OpenStack等雲提供商已經可以為Kubernetes提供Load Balancer.下面是一個Load balancer型別的Service例子:

kind: Service
apiVersion: v1
metadata:
  name: influxdb
spec:
  type: LoadBalancer
  ports:
    - port: 8086
  selector:
    name: influxdb

部署該Service後,我們來看一下Kubernetes建立的內容

$ kubectl get svc influxdb
NAME       CLUSTER-IP     EXTERNAL-IP     PORT(S)          AGE
influxdb   10.97.121.42   10.13.242.236   8086:30051/TCP   39s

Kubernetes首先為influxdb建立了一個叢集內部可以訪問的ClusterIP 10.97.121.42。由於沒有指定nodeport埠,kubernetes選擇了一個空閒的30051主機埠將service暴露在主機的網路上,然後通知cloud provider建立了一個load balancer,上面輸出中的EEXTERNAL-IP就是load balancer的IP。

測試使用的Cloud Provider是OpenStack,我們通過neutron lb-vip-show可以檢視建立的Load Balancer詳細資訊。  

neutron lb-vip-show 9bf2a580-2ba4-4494-93fd-9b6969c55ac3
+---------------------+--------------------------------------------------------------+
| Field               | Value                                                        |
+---------------------+--------------------------------------------------------------+
| address             | 10.13.242.236                                                |
| admin_state_up      | True                                                         |
| connection_limit    | -1                                                           |
| description         | Kubernetes external service a6ffa4dadf99711e68ea2fa163e0b082 |
| id                  | 9bf2a580-2ba4-4494-93fd-9b6969c55ac3                         |
| name                | a6ffa4dadf99711e68ea2fa163e0b082                             |
| pool_id             | 392917a6-ed61-4924-acb2-026cd4181755                         |
| port_id             | e450b80b-6da1-4b31-a008-280abdc6400b                         |
| protocol            | TCP                                                          |
| protocol_port       | 8086                                                         |
| session_persistence |                                                              |
| status              | ACTIVE                                                       |
| status_description  |                                                              |
| subnet_id           | 73f8eb91-90cf-42f4-85d0-dcff44077313                         |
| tenant_id           | 4d68886fea6e45b0bc2e05cd302cccb9                             |
+---------------------+--------------------------------------------------------------+

$ neutron lb-pool-show 392917a6-ed61-4924-acb2-026cd4181755
+------------------------+--------------------------------------+
| Field                  | Value                                |
+------------------------+--------------------------------------+
| admin_state_up         | True                                 |
| description            |                                      |
| health_monitors        |                                      |
| health_monitors_status |                                      |
| id                     | 392917a6-ed61-4924-acb2-026cd4181755 |
| lb_method              | ROUND_ROBIN                          |
| members                | d0825cc2-46a3-43bd-af82-e9d8f1f85299 |
|                        | 3f73d3bb-bc40-478d-8d0e-df05cdfb9734 |
| name                   | a6ffa4dadf99711e68ea2fa163e0b082     |
| protocol               | TCP                                  |
| provider               | haproxy                              |
| status                 | ACTIVE                               |
| status_description     |                                      |
| subnet_id              | 73f8eb91-90cf-42f4-85d0-dcff44077313 |
| tenant_id              | 4d68886fea6e45b0bc2e05cd302cccb9     |
| vip_id                 | 9bf2a580-2ba4-4494-93fd-9b6969c55ac3 |
+------------------------+--------------------------------------+

$ neutron lb-member-list
+--------------------------------------+--------------+---------------+--------+----------------+--------+
| id                                   | address      | protocol_port | weight | admin_state_up | status |
+--------------------------------------+--------------+---------------+--------+----------------+--------+
| 3f73d3bb-bc40-478d-8d0e-df05cdfb9734 | 10.13.241.89 |         30051 |      1 | True           | ACTIVE |
| d0825cc2-46a3-43bd-af82-e9d8f1f85299 | 10.13.241.10 |         30051 |      1 | True           | ACTIVE |
+--------------------------------------+--------------+---------------+--------+---------

可以看到OpenStack使用VIP 10.13.242.236在埠8086建立了一個Load Balancer,Load Balancer對應的Lb pool裡面有兩個成員10.13.241.89 和 10.13.241.10,正是Kubernetes的host節點,進入Load balancer流量被分發到這兩個節點對應的Service Nodeport 30051上。

但是如果客戶端不在Openstack Neutron的私有子網上,則還需要在load balancer的VIP上關聯一個floating IP,以使外部客戶端可以連線到load balancer。

部署Load balancer後,應用的拓撲結構如下圖所示(注:本圖假設Kubernetes Cluster部署在Openstack私有云上)。 

備註:如果kubernetes環境在Public Cloud上,Loadbalancer型別的Service創建出的外部Load Balancer已經帶有公網IP地址,是可以直接從外部網路進行訪問的,不需要繫結floating IP這個步驟。例如在AWS上建立的Elastic Load Balancing (ELB),有興趣可以看一下這篇文章:Expose Services on your AWS Quick Start Kubernetes cluster。

如果Kubernetes Cluster是在不支援LoadBalancer特性的cloud provider或者裸機上建立的,可以實現LoadBalancer型別的Service嗎?應該也是可以的。Kubernetes本身並不直接支援Loadbalancer,但我們可以通過對Kubernetes進行擴充套件來實現,可以監聽kubernetes Master的service建立訊息,並根據訊息部署相應的Load Balancer(如Nginx或者HAProxy),來實現Load balancer型別的Service。

通過設定Service型別提供的是四層Load Balancer,當只需要向外暴露一個服務的時候,可以直接採用這種方式。但在一個應用需要對外提供多個服務時,採用該方式會為每一個服務(IP+Port)都建立一個外部load balancer。如下圖所示    一般來說,同一個應用的多個服務/資源會放在同一個域名下,在這種情況下,建立多個Load balancer是完全沒有必要的,反而帶來了額外的開銷和管理成本。直接將服務暴露給外部使用者也會導致了前端和後端的耦合,影響了後端架構的靈活性,如果以後由於業務需求對服務進行調整會直接影響到客戶端。可以通過使用Kubernetes Ingress進行L7 load balancing來解決該問題。

採用Ingress作為七層load balancer 首先看一下引入Ingress後的應用拓撲示意圖(注:本圖假設Kubernetes Cluster部署在Openstack私有云上)。    這裡Ingress起到了七層負載均衡器和Http方向代理的作用,可以根據不同的url把入口流量分發到不同的後端Service。外部客戶端只看到foo.bar.com這個伺服器,遮蔽了內部多個Service的實現方式。採用這種方式,簡化了客戶端的訪問,並增加了後端實現和部署的靈活性,可以在不影響客戶端的情況下對後端的服務部署進行調整。

下面是Kubernetes Ingress配置檔案的示例,在虛擬主機foot.bar.com下面定義了兩個Path,其中/foo被分發到後端服務s1,/bar被分發到後端服務s2。  

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test
  annotations:
    ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - path: /foo
        backend:
          serviceName: s1
          servicePort: 80
      - path: /bar
        backend:
          serviceName: s2
          servicePort: 80

注意這裡Ingress只描述了一個虛擬主機路徑分發的要求,可以定義多個Ingress,描述不同的7層分發要求,而這些要求需要由一個Ingress Controller來實現。Ingress Contorller會監聽Kubernetes Master得到Ingress的定義,並根據Ingress的定義對一個7層代理進行相應的配置,以實現Ingress定義中要求的虛擬主機和路徑分發規則。Ingress Controller有多種實現,Kubernetes提供了一個基於Nginx的Ingress Controller。需要注意的是,在部署Kubernetes叢集時並不會預設部署Ingress Controller,需要我們自行

apiVersion: v1
kind: Service
metadata:
  name: nginx-ingress
spec:
  type: LoadBalancer
  ports:
    - port: 80
      name: http
    - port: 443
      name: https
  selector:
    k8s-app: nginx-ingress-lb
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx-ingress-controller
spec:
  replicas: 2
  revisionHistoryLimit: 3
  template:
    metadata:
      labels:
        k8s-app: nginx-ingress-lb
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: nginx-ingress-controller
          image: gcr.io/google_containers/nginx-ingress-controller:0.8.3
          imagePullPolicy: Always
    //----omitted for brevity----

部署。

下面是部署Nginx Ingress Controller的配置檔案示例,注意這裡為Nginx Ingress Controller定義了一個LoadBalancer型別的Service,以為Ingress Controller提供一個外部可以訪問的公網IP。

備註:Google Cloud直接支援Ingress資源,如果應用部署在Google Cloud中,Google Cloud會自動為Ingress資源建立一個7層load balancer,併為之分配一個外部IP,不需要自行部署Ingress Controller。

結論 採用Ingress加上Load balancer的方式可以將Kubernetes Cluster中的應用服務暴露給外部客戶端。這種方式比較靈活,基本可以滿足大部分應用的需要。但如果需要在入口處提供更強大的功能,如有更高的效率要求,需求進行安全認證,日誌記錄,或者需要一些應用的定製邏輯,則需要考慮採用微服務架構中的API Gateway模式,採用一個更強大的API Gateway來作為應用的流量入口。

參考 Accessing Kubernetes Pods from Outside of the Cluster

Kubernetes nginx-ingress-controller

Using Kubernetes external load balancer feature

Expose Services on your AWS Quick Start Kubernetes cluster