Spring Cloud Kubernetes 移除 Eureka 中間件
Kubernetes 通過(guò) Kube-proxy 組件、Service 對(duì)象實(shí)現(xiàn)了 Pod 的服務(wù)發(fā)現(xiàn)、負(fù)載均衡問(wèn)題,在 Spring Cloud 體系中是通過(guò) Eureka、Nacos 等中間件來(lái)實(shí)現(xiàn)的,既然我們的微服務(wù)是基于 Kubernetes 來(lái)部署的,那這部分功能就可以下沉到基礎(chǔ)設(shè)施層,由 Kubernetes 來(lái)提供。
在 Spring-Cloud-Dependencies 中已經(jīng)引入了 Kubernetes 客戶端操作的相關(guān)包,來(lái)解決微服務(wù)在 K8s 體系中服務(wù)發(fā)現(xiàn) Discovery(Service) 和配置中心 Config(ConfigMap) 的問(wèn)題。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-dependencies</artifactId>
<version>${spring-cloud-kubernetes.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
下面還以 https://github.com/14032/cloud 這個(gè) Demo 程序?yàn)槔瑏?lái)看下服務(wù)中如何使用 K8s 的服務(wù)發(fā)現(xiàn)功能來(lái)替代掉 Eureka。
在 Kubernetes 的實(shí)現(xiàn)版本中,首先去除掉 Eureka、Ribbon 客戶端的依賴(lài)。
引入 Spring Cloud Kubernetes 相關(guān)依賴(lài)做適配,Spring Cloud Kubernetes 本身引入了 Fabbric8 的 Kubernetes Client 作為客戶端來(lái)操作 Kubernetes API Server。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes</artifactId>
</dependency>
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-loadbalancer</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId>
</dependency>
上面提供了兩種負(fù)載均衡實(shí)現(xiàn),ribbon 和 loadbalancer 選擇一個(gè)即可,再看下配置文件如下:
spring:
cloud:
kubernetes:
discovery:
service-name: ${spring.application.name}
all-namespaces: true
ribbon:
enabled: true
mode: service
cluster-domain: cluster.local
spring.cloud.kubernetes.ribbon.mode 提供了兩種模式:service 和 pod。
這兩種模式,其實(shí)也就對(duì)應(yīng)了 Kubernetes 的兩個(gè) API 接口:
/api/v1/namespaces/cloud/endpoints/api/v1/namespaces/cloud/services
@Bean
@ConditionalOnMissingBean
public ServerList<?> ribbonServerList(KubernetesClient client, IClientConfig config,...) {
KubernetesServerList serverList;
if (properties.getMode() == KubernetesRibbonMode.SERVICE) {
serverList = new KubernetesServicesServerList(client, properties);
}
else {
serverList = new KubernetesEndpointsServerList(client, properties);
}
return serverList;
}
如果選用 service 模式,Ribbon 的客戶端負(fù)載均衡也就不在有效,而是使用 Kubernetes Service 本身具有的基于 DNS 的負(fù)載均衡功能。例如:auth-server.cloud.svc.cluster.local:9096,這種方式也不會(huì)再出現(xiàn)使用Eureka (服務(wù)端緩存、客戶端心跳、客戶端更新頻率)時(shí)因服務(wù)更新而導(dǎo)致的服務(wù)間短暫調(diào)用失敗問(wèn)題。
因?yàn)橹挥刑幱诰途w狀態(tài)(readliness)的服務(wù)才會(huì)出現(xiàn)在 Service 的 Endpoints 站點(diǎn)列表中。pod 模式,就是去獲取 Service 代理的 Endpoints 站點(diǎn),由 Ribbon 來(lái)提供負(fù)載均衡功能。
下面再看下 Spring Cloud Kubernetes 是如何獲取 K8s 集群服務(wù)列表的?答案就是:Fabbric8。
Fabbric8 通過(guò)默認(rèn)的 Kubernetes API Server 的代理 Service 域名來(lái)訪問(wèn) API Server。
當(dāng)你從 Pod 中訪問(wèn) API 時(shí),定位和驗(yàn)證 apiserver 會(huì)有些許不同。
在 Pod 中定位 apiserver 的推薦方式是通過(guò)kubernetes.default.svc這個(gè) DNS 名稱(chēng),該名稱(chēng)將會(huì)解析為服務(wù) IP,然后服務(wù) IP 將會(huì)路由到 apiserver。
向 apiserver 進(jìn)行身份驗(yàn)證的推薦方法是使用 服務(wù)帳戶 憑據(jù)。 通過(guò) kube-system,Pod 與服務(wù)帳戶相關(guān)聯(lián),并且該服務(wù)帳戶的憑證(token) 被放置在該 Pod 中每個(gè)容器的文件系統(tǒng)中,位于/var/run/secrets/kubernetes.io/serviceaccount/token。
DEFAULT_MASTER_URL = "https://kubernetes.default.svc"

default 空間下,名稱(chēng)為 kubernetes 的 Service 的 Endpoints 即為 Master 節(jié)點(diǎn)上 6443 端口的服務(wù),下圖可以看到 6443 端口既是 kube-apiserver 組件。

經(jīng)過(guò)上面的改造后,我們就可以重新構(gòu)建鏡像將微服務(wù)部署到 Kubernetes 集群中,當(dāng)服務(wù)啟動(dòng),相互訪問(wèn)的時(shí)候,會(huì)出現(xiàn)如下錯(cuò)誤,Message: Forbidden!Configured service account doesn't have access Pod 內(nèi)沒(méi)有權(quán)限去訪問(wèn) apiserver,如果你了解過(guò) Kubernetes 基于角色的權(quán)限控制 (RBAC)的功能,你會(huì)立即想到要給 Pod 重新綁定一個(gè) ServiceAccount 來(lái)授權(quán)操作 kube-apiserver 接口。
2021-07-25 17:42:21.000 ERROR 1 [ scheduling-1] [o.s.c.k.d.KubernetesCatalogWatch ] - Error watching Kubernetes Services
io.fabric8.kubernetes.client.KubernetesClientException: Failure executing: GET at: https://10.96.0.1/api/v1/endpoints. Message: Forbidden!Configured service account doesn't have access. Service account may have been revoked. endpoints is forbidden: User "system:serviceaccount:cloud:default" cannot list resource "endpoints" in API group "" at the cluster scope.
at io.fabric8.kubernetes.client.dsl.base.OperationSupport.requestFailure(OperationSupport.java:589)
at io.fabric8.kubernetes.client.dsl.base.OperationSupport.assertResponseCode(OperationSupport.java:526)
at io.fabric8.kubernetes.client.dsl.base.OperationSupport.handleResponse(OperationSupport.java:492)
參考文檔:spring-cloud-kubernetes/docs/current/reference/html/#service-account
我這里直接給了 Kubernetes 默認(rèn)的集群只讀角色 view,更細(xì)粒度的可參看官方文檔。
apiVersion: v1
kind: ServiceAccount
metadata:
name: cloud-account
namespace: cloud
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: cloud-account
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: view
subjects:
- kind: ServiceAccount
name: cloud-account
namespace: cloud
然后再為微服務(wù)的每個(gè) Pod 綁定此賬號(hào),這樣容器里的應(yīng)用就可以使用這個(gè) ServiceAccount 來(lái)訪問(wèn) API Server 了。
spec:
serviceAccountName: cloud-account
這樣基于 kubernetes 平臺(tái)部署的微服務(wù),就不再需要引入 Eureka 中間件來(lái)解決服務(wù)發(fā)現(xiàn)/注冊(cè)的功能,而是在基礎(chǔ)設(shè)施層替代原有的應(yīng)用層面的技術(shù)組件。
同樣的,配置中心也可以下沉到基礎(chǔ)設(shè)施層,由 kubernetes 中的 ConfigMap 對(duì)象來(lái)提供。
網(wǎng)關(guān)層,前面有介紹,Ingress 并不能勝任 API 網(wǎng)關(guān)的角色。
如果引入 Istio 服務(wù)網(wǎng)格,Istio 將會(huì)接管 Kube-proxy 的代理能力,以及 Kubernetes 中 Service 服務(wù)發(fā)現(xiàn)的能力。Istio 控制平面會(huì)和 Kubernetes 的 API 對(duì)接,將集群內(nèi)部所有的服務(wù)、站點(diǎn)信息下發(fā)到每一個(gè) Sidecar 代理中。
也即 Pod 中的所有請(qǐng)求被 Envoy 代理攔截后,直接根據(jù)本地的服務(wù)列表信息進(jìn)行路由負(fù)載轉(zhuǎn)發(fā)。
這時(shí) Spring Cloud Feign、Discovery、Ribbon 等都可以移除,服務(wù)之間直接以服務(wù)名稱(chēng)(svc 域名)進(jìn)行訪問(wèn)。
~ END ~