集成 Kubernetes 来构建微服务-spring cloud 入门教程
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 入门教程