ASP.NET Core on K8S深入學習(4)你必須知道的Service

  • 2019 年 10 月 3 日
  • 筆記

本篇已加入《.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在每台機器上綁定一個埠,這樣就可以通過<NodeIP>: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創建一個外部的負載均衡器,並將請求轉發到<NodeIP>: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-node1:

  

   訪問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的理解