如何從外部訪問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