本篇已加入《 .NET Core on K8S學習實踐系列文章索引
》,可以點選檢視更多容器化技術相關系列文章。
前面幾篇文章我們都是使用的ClusterIP供叢集内部通路,每個Pod都有一個自己的IP位址,那麼問題來了:當控制器使用新的Pod替代發生故障的Pod時又或者增加新的副本Pod時,新Pod會配置設定到新的IP位址,那麼想要對外提供服務時,用戶端如何找到并通路這個服務?沒關系,别摳腦殼了,本文介紹的Service就是解決方案。
一、認識Service
1.1 什麼是Service?
Service是一個抽象概念,它定義了邏輯集合下通路Pod組的政策。通過使用Service,我們就可以不用關心這個服務下面的Pod的增加和減少、故障重新開機等,隻需通過Service就能夠通路到對應服務的容器,即通過Service來暴露Pod的資源。
這樣說可能還是有點難懂,舉個例子,假設我們的一個服務Service A下面有3個Pod,我們都知道Pod的IP都不是持久化的,重新開機之後就會有變化。那麼Service B想要通路Service A的Pod,它隻需跟綁定了這3個Pod的Service A打交道就可以了,無須關心下面的3個Pod的IP和端口等資訊的變化。換句話說,就像一個Service Discovery服務發現的元件,你無須關心具體服務的URL,隻需知道它們在服務發現中注冊的Key就可以通過類似Consul、Eureka之類的服務發現元件中擷取它們的URL一樣,還是實作了負載均衡效果的URL。
1.2 Service的幾種類型
(1)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: TCP
那麼,如果需要從外部通路呢(比如我們在開發模式下總得調試吧)?可以啟用K8S的代理模式:
$ kubectl proxy --port=8080
如此一來,便可以通過K8S的API來通路了,例如下面這個URL就可以通路在yaml中定義的這個my-internal-service了:
http://localhost:8080/api/v1/proxy/namespaces/default/services/my-internal-service:http/
_PS:_ClusterIP是一個虛拟IP,由K8S節點上的iptables規則管理的。iptables會将通路Service的流量轉發到後端Pod,而且使用類似于輪詢的負載均衡政策轉發的。
(2)NodePort
除了隻在内部通路的服務,我們總有很多是需要暴露出來公開通路的服務吧。在ClusterIP基礎上為Service在每台機器上綁定一個端口,這樣就可以通過:NodePort來通路這些服務。例如,下面這個yaml中定義了服務為NodePort類型:
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: TCP
_PS_:這種方式顧名思義需要一個額外的端口來進行暴露,且端口範圍隻能是 30000-32767,如果節點/VM 的 IP 位址發生變化,你需要能處理這種情況。
(3)LoadBalancer
LoadBalancer 服務是暴露服務到 internet 的标準方式,它借助Cloud Provider建立一個外部的負載均衡器,并将請求轉發到:NodePort(向節點導流)。
例如下面這個yaml中:
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
clusterIP: 10.0.171.239
loadBalancerIP: 78.11.24.19
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: 146.148.47.155
_PS_:每一個用 LoadBalancer 暴露的服務都會有它自己的 IP 位址,每個用到的 LoadBalancer 都需要付費,這将是比較昂貴的花費。
二、Service的建立與運作
2.1 建立Deployment
這裡仍然以我們的一個ASP.NET Core WebAPI項目為例,準備一個Deployment的YAML檔案:
apiVersion: apps/v1
kind: Deployment
metadata:
name: edc-webapi-deployment
namespace: aspnetcore
spec:
replicas: 2
selector:
matchLabels:
name: edc-webapi
template:
metadata:
labels:
name: edc-webapi
spec:
containers:
- name: edc-webapi-container
image: edisonsaonian/k8s-demo
ports:
- containerPort: 80
imagePullPolicy: IfNotPresent
這裡我們需要注意的就是給該Deployment标注selector的matchLabels以及template中的labels,這是一個Key/Value對,用于後續Service來比對要挑選哪些Pod作為Service的後端,即需要給哪些Pod提供服務發現以及負載均衡的效果。
同樣,通過kubectl建立資源:
kubectl apply -f edc-api.yaml
然後,通過curl指令驗證一下:(這裡的兩個IP位址是ClusterIP,它們分别位于我的兩個K8S Node節點上)
curl 10.244.1.40/api/values
curl 10.244.2.31/api/values
可以看到,我們的ASP.NET Core WebAPI正常的傳回了JSON資料。
2.2 建立Service
接下來我們就為上面的兩個Pod建立一個Service:
apiVersion: v1
kind: Service
metadata:
name: edc-webapi-service
namespace: aspnetcore
spec:
ports:
- port: 8080
targetPort: 80
selector:
name: edc-webapi
這裡需要注意的幾個點:
(1)port : 8080 => 指将Service的8080端口映射到Pod的對應端口上,這裡Pod的對應端口由 targetPort 指定。
(2)selector => 指将具有 name: edc-webapi 這個label的Pod作為我們這個Service的後端,為這些Pod提供統一IP和端口。
這裡我們來進行驗證一下:
kubectl get service -n aspnetcore
curl 10.1.59.71:8080/api/values
可以看到,預設情況下Service的類型時ClusterIP,隻能提供叢集内部的服務通路。如果想要為外部提供通路,那麼需要改為NodePort。
2.3 使用NodePort
下面為Service增加NodePort通路方式:
apiVersion: v1
kind: Service
metadata:
name: edc-webapi-service
namespace: aspnetcore
spec:
type: NodePort
ports:
- port: 8080
targetPort: 80
selector:
name: edc-webapi
再次進行建立,會覆寫已有配置:
kubectl apply -f edc-api-service.yaml
再次進行驗證,會發現已經改為了NodePort方式:
這裡的PORT已經變為了8080:32413,意味着它将Service中的8080端口映射到了Node節點的32413端口,我們可以通過通路Node節點的32413端口擷取ASP.NET Core WebAPI傳回的接口資料了。
通路k8s-node1:
通路k8s-node2:
2.4 指定特定端口
剛剛的NodePort預設情況下是随機選擇一個端口(30000-32767範圍内),不過我們可以使用nodePort屬性指定一個特定端口:
apiVersion: v1
kind: Service
metadata:
name: edc-webapi-service
namespace: aspnetcore
spec:
type: NodePort
ports:
- nodePort: 31000
port: 8080
targetPort: 80
selector:
name: edc-webapi
這裡我們自己指定了一個外部通路端口:31000,通過kubectl覆寫之後,我們再次驗證一下:
通路k8s-node2:
最後,再次總結一下三個端口配置:
(1)nodePort => Node節點上監聽的端口,也就是外部通路的Service端口
(2)port => ClusterIP上監聽的端口,即内部通路的Service端口
(3)targetPort => Pod上監聽的端口,即容器内部的端口
三、DNS通路Service
Kubernetes預設安裝了一個dns元件coredns,它位于kube-system命名空間中:
每當有新的Service被建立時,coredns會添加該Service的DNS記錄,于是在Cluster中的Pod便可以通過servicename.namespacename來通路Service了,進而可以做到一個服務發現的效果。
這裡我們來驗證一下,通過臨時建立一個busybox Pod來通路edc-webapi-service.aspnetcore:8080:
kubectl run busybox --rm -ti --image=busybox /bin/sh
可以看到,coredns元件為剛剛建立的Service edc-webapi-service建立了DNS記錄,在Cluster中的Pod無須知道edc-webapi-service的IP位址隻需要知道ServiceName即可通路到該Service。
四、小結
本文介紹了K8S中Service的基本概念及常用類型,然後通過一個具體的例子示範了如何建立Service和使用NodePort的方式對外提供通路,最後介紹了如何通過DNS的方式通路Service進而實作服務發現的效果。當然,筆者也是初學,很多東西沒有介紹到,也請大家多多參考其他資料更加深入了解。
參考資料
(1)CloudMan,《
每天5分鐘玩轉Kubernetes》
(2)李振良,《
一天入門Kubernets教程(3)馬哥(馬永亮),《
Kubernetes快速入門(4)小黑老,《
K8S中Service的了解