通過(guò) .NET Core + Kubernetes:Deployment 文章的介紹,我們可以通過(guò) Deployment 控制器快速創(chuàng)建一組 Pod 來(lái)提供服務(wù),每個(gè) Pod 都會(huì)被分配一個(gè)集群內(nèi)可見(jiàn)的虛擬 IP 地址,然后通過(guò)一個(gè)獨(dú)立的 Endpoint(Pod IP + ContainerPort)進(jìn)行訪問(wèn)。但在提供服務(wù)時(shí),并不能依賴(lài) Pod 的 Endpoint,首先 Pod IP 會(huì)隨著 Pod 的重建而變化,另外同一組 Pod 更希望是以整體對(duì)外提供高可用服務(wù),組內(nèi)的 Pod 在進(jìn)行動(dòng)態(tài)伸縮、滾動(dòng)更新等操作后并不能影響服務(wù)穩(wěn)定性。
因此,Kubernetes 中的 Service 對(duì)象就是解決此問(wèn)題的核心,Service 代理 Pod 集合對(duì)外表現(xiàn)是為一個(gè)訪問(wèn)入口,它提供了一個(gè)虛擬的 IP 地址(ClusterIP)和端口,來(lái)自 ClusterIP + 端口 的請(qǐng)求將被負(fù)載均衡器 (kube-proxy)轉(zhuǎn)發(fā)到后端某個(gè) Pod 中的容器。所以借助 Service 的能力,非常方便的實(shí)現(xiàn)了服務(wù)發(fā)現(xiàn)與負(fù)載均衡。
本文將主要介紹 Kubernetes 中各 Service 類(lèi)型的使用,目前有以下四種類(lèi)型:
ClusterIP:默認(rèn)類(lèi)型,自動(dòng)分配一個(gè)僅集群內(nèi)部可以訪問(wèn)的虛擬 IP,也可使用 ClusterIP 字段指定固定 IP,選擇此類(lèi)型意味著只想這個(gè)服務(wù)在集群內(nèi)部才可以被訪問(wèn)
NodePort:在 ClusterIP 基礎(chǔ)上,在集群的每一個(gè)節(jié)點(diǎn)綁定一個(gè)端口,這樣就可以通過(guò)任意的 <NodeIP>:NodePort 來(lái)訪問(wèn)服務(wù)
LoadBalancer:在 NodePort 的基礎(chǔ)上,通過(guò)創(chuàng)建一個(gè)外部的負(fù)載均衡器,將流量轉(zhuǎn)發(fā)到每個(gè)節(jié)點(diǎn) <NodeIP>:NodePort。因?yàn)槿绻獠克锌蛻舳硕荚L問(wèn)一個(gè) NodeIP,該節(jié)點(diǎn)的壓力將會(huì)很大,LoadBalancer 則可解決此問(wèn)題
ExternalName:把集群外部的服務(wù)引入到集群內(nèi)部來(lái),在集群內(nèi)部直接使用。沒(méi)有任何類(lèi)型代理被創(chuàng)建,這只有 kubernetes 1.7 或更高版本的 kube-dns 才支持
下面分別對(duì)這幾種類(lèi)型的使用方式進(jìn)行介紹,在創(chuàng)建 Service 之前,還是先通過(guò) Deployment 控制器創(chuàng)建一組 Pod,配置文件 k8sdemo-deployment.yaml 如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8sdemo-deployment
spec:
replicas: 3
selector:
matchLabels:
name: k8sdemo
template:
metadata:
labels:
name: k8sdemo
spec:
containers:
- name: k8sdemo
image: beckjin/k8sdemo:1.0.0
ports:
- containerPort: 80
imagePullPolicy: IfNotPresent
ClusterIP 類(lèi)型
創(chuàng)建 k8sdemo-service.yaml 文件,配置如下,主要是 ports 和 selector 字段的設(shè)置,指定了 80 端口(port) 映射到 Pod ContainerPort (targetPort) 端口,最終將通過(guò) ClusterIP:80 訪問(wèn)服務(wù)。selector 字段指定將 label 含 name:k8sdemo 的 Pod 作為這個(gè) Service 指向的目標(biāo)服務(wù)。
apiVersion: v1
kind: Service
metadata:
name: k8sdemo-service
spec:
ports:
- port: 80 # Service Port
targetPort: 80 # Pod ContainerPort
selector:
name: k8sdemo
# clusterIP: 10.1.19.92 # 取消注釋指定IP
執(zhí)行命令 kubectl apply -f k8sdemo-service.yaml 創(chuàng)建 Service,然后通過(guò) kubectl get service 查看服務(wù)狀態(tài):

從上圖可以看出 k8sdemo-service 被分配的 ClusterIP 是 10.1.19.96,所以可以在服務(wù)器上通過(guò) curl http://10.1.19.96/WeatherForecast 來(lái)訪問(wèn)接口。

因?yàn)?ClusterIP 默認(rèn)是自動(dòng)分配的,所以每次重建會(huì)變化,如果需要固定,可通過(guò) ClusterIP 字段設(shè)置。對(duì)于只需在集群內(nèi)部提供的服務(wù),ClusterIP 類(lèi)型已足夠。
NodePort 類(lèi)型
基于 ClusterIP 類(lèi)型中使用的配置文件 k8sdemo-service.yaml,增加 type: NodePort 配置,重新創(chuàng)建 Service,如下:
apiVersion: v1
kind: Service
metadata:
name: k8sdemo-service
spec:
ports:
- port: 80
targetPort: 80
# nodePort: 30080 # 取消注釋指定端口
selector:
name: k8sdemo
type: NodePort

從上圖可以看出,除了類(lèi)型變了, PORT(S)也變成了 80:30231/TCP,即將 Service 的 80 端口與集群中各節(jié)點(diǎn)的 30231 端口進(jìn)行映射,所以最終可以通過(guò)集群內(nèi)任意的 NodeIP:30231 來(lái)訪問(wèn),整個(gè)過(guò)程為:Client > NodeIP:NodePort > ClusterIP:ServicePort > PodIP:ContainerPort。NodePort 默認(rèn)分配的是 30000-32767 范圍內(nèi)隨機(jī)選擇的一個(gè)端口,實(shí)際使用時(shí)可以通過(guò) nodePort 字段指定。請(qǐng)求結(jié)果如下:

注:192.168.124.10 是集群內(nèi)某一臺(tái)的 IP
LoadBalancer 類(lèi)型
通過(guò) NodePort 類(lèi)型的使用介紹,已經(jīng)了解可以通過(guò) NodeIP:NodePort 方式來(lái)服務(wù)訪問(wèn),而且 NodeIP 可以是集群內(nèi)任意任何一臺(tái)的 IP。而 LoadBalancer 則是在外層附加的負(fù)載均衡器,使請(qǐng)求能分?jǐn)偟郊簝?nèi)各個(gè)節(jié)點(diǎn)上。
MetalLB 搭建
要使用 LoadBalancer 類(lèi)型會(huì)稍微復(fù)雜一些,并不能只單純的修改配置文件,因?yàn)橐话阕越ǖ?Kubernetes 集群默認(rèn)并不支持 LoadBalancer,所以它需要借助外部的負(fù)載均衡器來(lái)實(shí)現(xiàn),這里將使用 MetalLB (v0.9.3),安裝請(qǐng)參考 Installation By Manifest ,步驟不復(fù)雜,但需要確保依賴(lài)鏡像下載順利,完成后查看 Pod 狀態(tài):

另外需要為 Metallb 設(shè)置地址池以及協(xié)議相關(guān)配置,Metallb 會(huì)監(jiān)控服務(wù)對(duì)象的變化,當(dāng)有新的 LoadBalancer 服務(wù)運(yùn)行,但沒(méi)有可申請(qǐng)的負(fù)載均衡器時(shí),就會(huì)從配置的地址池中分配一個(gè)給該服務(wù)。這里以 Metallb Layer2 工作模式為例(Metallb 支持 Layer2/BGP 兩種工作模式),創(chuàng)建一個(gè)資源類(lèi)型為 ConfigMap 的配置文件 metallb-layer2-config.yaml,內(nèi)容如下:
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- 192.168.124.200-192.168.124.210 # IP 地址范圍需與自己的集群環(huán)境對(duì)應(yīng)
Layer2 工作模式原理圖:

配置修改
有了以上的準(zhǔn)備工作后,只需要在 Service 配置文件將 type 修改為 LoadBalancer ,然后重新創(chuàng)建 Service,如下:
apiVersion: v1
kind: Service
metadata:
name: k8sdemo-service
spec:
ports:
- port: 80
targetPort: 80
selector:
name: k8sdemo
type: LoadBalancer

從上圖可以看出,TYPE 已是 LoadBalancer,另外 EXTERNAL-IP 被分配為地址池中的 192.168.124.200,接下來(lái)就可以通過(guò)這個(gè) IP 進(jìn)行訪問(wèn)了,結(jié)果如下:

ExternalName
ExternalName 類(lèi)型比較特殊,它沒(méi)有 selector,也沒(méi)有定義任何的端口, 對(duì)于運(yùn)行在集群外部的服務(wù),它通過(guò)返回該外部服務(wù)的別名這種方式來(lái)提供服務(wù),如下:
apiVersion: v1
kind: Service
metadata:
name: k8sdemo-external-service
spec:
type: ExternalName
externalName: mingdao.com
當(dāng)訪問(wèn) k8sdemo-external-service.default.svc.cluster.local 時(shí),集群的 DNS 服務(wù)將返回值為 mingdao.com 的 CNAME 記錄,訪問(wèn)這種類(lèi)型的服務(wù)與其它的唯一不同的是重定向發(fā)生在 DNS 層,而且不會(huì)進(jìn)行代理或轉(zhuǎn)發(fā)。
進(jìn)入 Kubernetes 集群的任意一個(gè) Pod 中(必須是集群內(nèi)部才可訪問(wèn)),如: kubectl exec -it k8sdemo-deployment-68cb864ff6-fzzdq -- /bin/bash,執(zhí)行 curl -L http://k8sdemo-external-service.default.svc.cluster.local/ 即會(huì)重定向請(qǐng)求到 mingdao.com,結(jié)果如下:
