Spring Cloud和Kubernetes是目前Java平台下微服務應用的使用得最多的產品。然而,當談到微服務架構時,它們有時被描述為具有競爭力的解決方案。它們都在微服務架構中實現流行的模式,如服務發現、分布式配置、負載平衡或斷路。當然,他們的做法不同。
Kubernetes 是一個用於運行、擴展和管理容器化應用程序的平台。Kubernetes 最重要的組件之一是etcd。該高度可用的鍵值存儲負責存儲所有集群數據,包括服務注冊表和應用程序配置。我們不能用任何其他工具代替它。可以使用Istio或 Linkerd等第三方組件來實現更高級的路由和負載均衡策略。要在 Kubernetes 上部署和運行應用程序,我們無需在源代碼中添加任何內容。編排和配置是在應用程序之外實現的——在平台上。
Spring Cloud 提出了一種不同的方法。所有組件都必須在應用程序端包含和配置。它為我們提供了許多與用於雲原生開發的各種工具和框架集成的可能性。但是,一開始 Spring Cloud 是圍繞 Eureka、Ribbon等 Netflix OSS 組件構建的、Hystrix 或 Zuul。它為我們提供了一種機制,可以輕松地將它們包含到我們基於微服務的架構中,並將它們與其他雲原生組件集成。一段時間后,必須重新考慮這種方法。今天,我們有很多由 Spring Cloud 開發的組件,比如 Spring Cloud Gateway(Zuul 替代品)、Spring Cloud Load Balancer(Ribbon 替代品)、Spring Cloud Circuit Breaker(Hystrix 替代品)。還有一個相對較新的與Kubernetes集成的項目——Spring Cloud Kubernetes。
為什么選擇 Spring Cloud Kubernetes?
在我們將微服務遷移到 OpenShift 時,Spring Cloud Kubernetes 項目正處於孵化階段。由於我們沒有任何其他有趣的從 Spring Cloud 遷移到 OpenShift 的選擇,包括從 Spring Boot 應用程序中刪除用於發現(Eureka 客戶端)和配置(Spring Cloud Config 客戶端)的組件。當然,我們仍然可以使用其他 Spring Cloud 組件,如 OpenFeign、Ribbon(通過 Kubernetes 服務)或 Sleuth。那么,問題是我們真的需要 Spring Cloud Kubernetes 嗎?哪些功能對我們來說會很有趣。
首先,讓我們看看在 Spring Cloud Kubernetes 文檔站點上構建一個新框架的動機。
Spring Cloud Kubernetes 提供使用 Kubernetes 原生服務的 Spring Cloud 通用接口實現。此存儲庫中提供的項目的主要目標是促進在 Kubernetes 內運行的 Spring Cloud 和 Spring Boot 應用程序的集成。
簡單來說,Spring Cloud Kubernetes 提供了與Kubernetes Master API 的集成,以允許以 Spring Cloud 的方式使用發現、配置和負載平衡。
在本文中,我將介紹 Spring Cloud Kubernetes 的以下有用功能:
- 使用 DiscoveryClient 支持在所有命名空間中擴展發現
- 在 Spring Cloud Kubernetes Config 中使用 ConfigMap 和 Secrets 作為 Spring Boot 屬性源
- 使用 Spring Cloud Kubernetes pod 健康指標實現健康檢查
啟用 Spring Cloud Kubernetes
假設我們將使用 Spring Cloud Kubernetes 提供的更多功能,我們應該將以下依賴項包含到我們的 Maven 中pom.xml
。它包含用於發現、配置和功能區負載平衡的模塊。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-all</artifactId>
</dependency>
跨所有命名空間的發現
Spring Cloud Kubernetes 通過提供DiscoveryClient
. 我們還可以利用與 Ribbon 客戶端的內置集成,在不使用 Kubernetes 服務的情況下直接與 Pod 通信。Ribbon 客戶端可以被更高級別的 HTTP 客戶端——OpenFeign 所利用。要實現這樣的模型,我們必須啟用發現客戶端、Feign 客戶端和 Mongo 存儲庫,因為我們使用 Mongo 數據庫作為后端存儲。
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableMongoRepositories
public class DepartmentApplication {
public static void main(String[] args) {
SpringApplication.run(DepartmentApplication.class, args);
}
}
讓我們考慮一下我們有三個微服務的場景,每個微服務都部署在不同的命名空間中。划分命名空間只是一個邏輯分組,例如我們有三個不同的團隊負責每個微服務,我們希望只將命名空間的權限授予負責給定應用程序的團隊。在位於不同命名空間的應用程序之間的通信中,我們必須在調用 URL 上包含一個命名空間名稱作為前綴。我們還需要設置一個可能因應用程序而異的端口號。在這種情況下,Spring Cloud Kubernetes 發現會提供幫助。由於 Spring Cloud Kubernetes 與主 API 集成,因此能夠獲取為同一應用程序創建的所有 pod 的 IP。這是說明我們場景的圖表。
要啟用跨所有命名空間的發現,我們只需要使用以下屬性。
spring:
cloud:
kubernetes:
discovery:
all-namespaces: true
現在,我們可以實現負責消費目標端點的 Feign 客戶端接口。這是來自部門服務的示例客戶端,專門用於與員工服務進行通信。
@FeignClient(name = "employee")
public interface EmployeeClient {
@GetMapping("/department/{departmentId}")
List<Employee> findByDepartment(@PathVariable("departmentId") String departmentId);
}
Spring Cloud Kubernetes 需要訪問 Kubernetes API,以便能夠檢索為單個服務運行的 pod 的地址列表。使用 Minikube 時最簡單的方法是ClusterRoleBinding
使用cluster-admin
特權創建默認值。運行以下命令后,您可以確保每個 Pod 都有足夠的權限與 Kubernetes API 通信。
$ kubectl create clusterrolebinding admin --clusterrole=cluster-admin --serviceaccount=default:default
使用 Kubernetes PropertySource 進行配置
Spring Cloud KubernetesPropertySource
實現允許我們直接在應用程序中使用ConfigMap
和使用,Secret
而無需將它們注入Deployment
. 默認行為基於metadata.name
inside ConfigMap
or Secret
,它必須與應用程序名稱相同(由其spring.application.name
屬性定義)。您還可以使用更高級的行為,您可以為配置注入定義命名空間和對象的自定義名稱。您甚至可以使用多個ConfigMap
或Secret
實例。但是,我們使用默認行為,因此假設我們有以下內容bootstrap.yml
:
spring:
application:
name: employee
我們將定義以下內容ConfigMap
:
kind: ConfigMap
apiVersion: v1
metadata:
name: employee
data:
logging.pattern.console: "%d{HH:mm:ss} ${LOG_LEVEL_PATTERN:-%5p} %m%n"
spring.cloud.kubernetes.discovery.all-namespaces: "true"
spring.data.mongodb.database: "admin"
spring.data.mongodb.host: "mongodb.default"
或者,您可以在ConfigMap
.
apiVersion: v1
kind: ConfigMap
metadata:
name: employee
data:
application.yaml: |-
logging.pattern.console: "%d{HH:mm:ss} ${LOG_LEVEL_PATTERN:-%5p} %m%n"
spring.cloud.kubernetes.discovery.all-namespaces: true
spring:
data:
mongodb:
database: admin
host: mongodb.default
在配置映射中,我們定義了 Mongo 位置、日志模式和負責允許多命名空間發現的屬性。Mongo 憑據應在Secret
對象內部定義。規則與配置映射相同。
apiVersion: v1
kind: Secret
metadata:
name: employee
type: Opaque
data:
spring.data.mongodb.username: UGlvdF8xMjM=
spring.data.mongodb.password: cGlvdHI=
值得注意的是,出於安全原因,默認情況下不啟用通過 API 使用機密。但是,我們已經設置了默認cluster-admin
角色,所以我們不必擔心。我們唯一需要做的就是通過 Spring Cloud Kubernetes 的 API 啟用使用機密,默認情況下該 API 是禁用的。為此,我們必須在bootstrap.yml
.
spring:
cloud:
kubernetes:
secrets:
enableApi: true
在 Minikube 上部署 Spring Cloud 應用程序
首先,讓我們使用kubectl create namespace
命令創建所需的命名空間。下面是創建命名空間的命令a
,b
,c
和d
。
然后,讓我們通過執行 Mavenmvn clean install
命令來構建代碼。
我們還需要設置cluster-admin
新創建的命名空間,以允許在這些命名空間內運行的 Pod 讀取主 API。
現在,讓我們看看我們的 Kubernetes 部署清單。它非常簡單,因為它沒有從ConfigMap
和注入任何屬性Secret
。它已經由 Spring Cloud Kubernetes Config 執行。這是employee-service的部署 YAML 文件。
apiVersion: apps/v1
kind: Deployment
metadata:
name: employee
labels:
app: employee
spec:
replicas: 1
selector:
matchLabels:
app: employee
template:
metadata:
labels:
app: employee
spec:
containers:
- name: employee
image: piomin/employee:1.1
ports:
- containerPort: 8080
最后,我們可以在 Kubernetes 上部署我們的應用程序。每個微服務有ConfigMap
,Secret
,Deployment
和Service
對象。YAML 清單在/kubernetes
目錄內的 Git 存儲庫中可用。我們使用kubectl apply
如下所示的命令依次應用它們。
出於測試目的,您可以通過定義NodePort
類型在節點外公開示例應用程序。
apiVersion: v1
kind: Service
metadata:
name: department
labels:
app: department
spec:
ports:
- port: 8080
protocol: TCP
selector:
app: department
type: NodePort
公開有關 Pod 的信息
如果你定義了你Service
,NodePort
你可以在 Minikube 之外輕松訪問它。要檢索目標端口,只需執行kubectl get svc
如下所示。現在,您可以使用 address 調用它http://192.168.99.100:31119
。
使用 Spring Cloud Kubernetes,每個 Spring Boot 應用程序都會公開有關 pod ip、pod 名稱和命名空間名稱的信息。要輸入它,您需要調用/info
端點,如下所示。
這是部署所有示例微服務和網關后分布在所有命名空間之間的 pod 列表。
還有一個部署列表。
運行網關
我們架構中的最后一個元素是網關。我們使用 Spring Cloud Netflix Zuul,它通過 Ribbon 客戶端與 Kubernetes 發現集成。它公開了分布在多個命名空間中的所有示例微服務的 Swagger 文檔。這是所需依賴項的列表。
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-all</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
</dependencies>
路由的配置非常簡單。我們只需要使用 Spring Cloud Kubernetes 發現功能。
apiVersion: v1
kind: ConfigMap
metadata:
name: gateway
data:
logging.pattern.console: "%d{HH:mm:ss} ${LOG_LEVEL_PATTERN:-%5p} %m%n"
spring.cloud.kubernetes.discovery.all-namespaces: "true"
zuul.routes.department.path: "/department/**"
zuul.routes.employee.path: "/employee/**"
zuul.routes.organization.path: "/organization/**"
雖然 Zuul 代理與DiscoveryClient
我們自動集成,但我們可以輕松配置微服務公開的動態解析 Swagger 端點。
@Configuration
public class GatewayApi {
@Autowired
ZuulProperties properties;
@Primary
@Bean
public SwaggerResourcesProvider swaggerResourcesProvider() {
return () -> {
List<SwaggerResource> resources = new ArrayList<>();
properties.getRoutes().values().stream()
.forEach(route -> resources.add(createResource(route.getId(), "2.0")));
return resources;
};
}
private SwaggerResource createResource(String location, String version) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(location);
swaggerResource.setLocation("/" + location + "/v2/api-docs");
swaggerResource.setSwaggerVersion(version);
return swaggerResource;
}
}
通常,我們必須配置 KubernetesIngress
才能訪問網關。使用 Minikube,我們只需要創建一個類型為 的服務NodePort
。最后,我們可以開始使用在網關上公開的 Swagger UI 來測試我們的應用程序。但是在這里,我們得到了一個意想不到的驚喜……跨所有命名空間的發現不適用於 Ribbon 客戶端。它僅適用於DiscoveryClient
. 我認為 Ribbon 自動配置應該尊重 property spring.cloud.kubernetes.discovery.all-namespaces
,但在這種情況下,除了准備解決方法之外我們別無選擇。我們的解決方法是覆蓋 Spring Cloud Kubernetes 中提供的 Ribbon 客戶端自動配置。我們DiscoveryClient
直接使用它,如下所示。
public class RibbonConfiguration {
@Autowired
private DiscoveryClient discoveryClient;
private String serviceId = "client";
protected static final String VALUE_NOT_SET = "__not__set__";
protected static final String DEFAULT_NAMESPACE = "ribbon";
public RibbonConfiguration () {
}
public RibbonConfiguration (String serviceId) {
this.serviceId = serviceId;
}
@Bean
@ConditionalOnMissingBean
public ServerList<?> ribbonServerList(IClientConfig config) {
Server[] servers = discoveryClient.getInstances(config.getClientName()).stream()
.map(i -> new Server(i.getHost(), i.getPort()))
.toArray(Server[]::new);
return new StaticServerList(servers);
}
}
Ribbon 配置類需要在主類上設置。
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
@EnableSwagger2
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = RibbonConfiguration.class)
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
現在,我們終於可以利用多命名空間發現和負載平衡,並使用網關上公開的 Swagger UI 輕松測試它。
使用 Zuul、Ribbon、Feign、Eureka 和 Sleuth、Zipkin 創建簡單spring cloud微服務用例-spring cloud 入門教程
微服務集成SPRING CLOUD SLEUTH、ELK 和 ZIPKIN 進行監控-spring cloud 入門教程
使用Hystrix 、Feign 和 Ribbon構建微服務-spring cloud 入門教程
使用 Spring Boot Admin 監控微服務-spring cloud 入門教程