Quarkus 和 Kubernetes 的 Java 微服务高级教程(四)
十三、构建 Kubernetized 微服务
介绍
第八章讨论了如何将数据驱动设计(DDD)应用到单体应用中。我们打破了在分析项目时揭示的边界上下文之间的关系。在 Stan4J 中,最终的代码结构如下所示:
在本章中,您将实现三个微服务— Product、Order和Customer。这些包依赖于commons包,因此,您需要在实现三个微服务之前实现它。
创建公共图书馆
commons JAR 库将包装commons包的内容。
我们将生成一个简单的 Maven 项目,其中我们将复制commons包的内容。
mvn archetype:generate -DgroupId=com.targa.labs.commons -DartifactId=quarkushop-commons \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.4 -DinteractiveMode=false
该命令生成一个包含以下内容的简单项目:
project
|-- pom.xml
`-- src
|-- main/java
| `-- com.targa.labs.commons
| `-- App.java
`-- test/java
`-- com.targa.labs.commons
`-- AppTest.java
我们将删除
App.java和AppTest.java,因为我们不再需要它们。
然后,我们将commons包的内容从单体应用复制/粘贴到我们的quarkushop-commons项目中。
不要害怕!当您粘贴复制的类时,您会看到许多错误和警告,但是下一步是添加缺少的依赖项以使 IDE 满意。
让我们打开pom.xml文件并开始进行更改:
-
首先将
maven.compiler.source和maven.compiler.target从1.7改为11。 -
如下定义依赖关系:
<dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>2.0.1.Final</version> </dependency> <dependency> <groupId>org.eclipse.microprofile.openapi</groupId> <artifactId>microprofile-openapi-api</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>org.jboss.spec.javax.ws.rs</groupId> <artifactId>jboss-jaxrs-api_2.1_spec</artifactId> <version>2.0.1.Final</version> </dependency> <dependency> <groupId>jakarta.persistence</groupId> <artifactId>jakarta.persistence-api</artifactId> <version>2.2.3</version> </dependency> <dependency> <groupId>jakarta.enterprise</groupId> <artifactId>jakarta.enterprise.cdi-api</artifactId> <version>2.0.2</version> </dependency> <dependency> <groupId>org.eclipse.microprofile.health</groupId> <artifactId>microprofile-health-api</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>org.eclipse.microprofile.config</groupId> <artifactId>microprofile-config-api</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>org.eclipse.microprofile.metrics</groupId> <artifactId>microprofile-metrics-api</artifactId> <version>2.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.11.2</version> </dependency> <dependency> <groupId>io.quarkus.security</groupId> <artifactId>quarkus-security</artifactId> <version>1.1.2.Final</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.16</version> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <version>1.15.3</version> </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-test-common</artifactId> <version>1.13.3.Final</version> </dependency> </dependencies>
哇哦!这些依赖来自哪里?我敢肯定你和我一样,不喜欢写自己不懂的东西。不过不用担心,这些依赖项都是来自 Quarkus 框架。我使用 IDE 添加了缺少的依赖项:
将出现 Maven 工件搜索窗口,如下所示:
那么,您应该选择给定依赖项的哪个版本呢?您可以使用 IntelliJ 来确定在单体应用中使用哪些外部库:
展开该部分并滚动以找到所需的库。版本将显示在groupId和artifactId之后,如下所示:
在这里,您可以看到单体应用正在使用Lombok v1.18.12,因此在Commons项目中,您需要选择相同的版本。
为了避免冲突,请确保
quarkus-test-common依赖项与微服务具有相同的 Quarkus 版本。
最后,您需要使用mvn clean install构建 Maven 项目。这个命令将构建 JAR,并使它在本地.m2目录中可用。这使您能够在以后的步骤中将它用作依赖项。
等等!你还没说完呢!你需要考虑一下测试!您需要将utils包从测试类复制到quarkushop-commons项目的主类中。
为了能够在quarkushop-commons库之外重用这些类,您需要将它们放在主目录中,就像任何其他普通类一样。属于测试目录的类只是用于测试目的,它们不打算被重用。
实施产品微服务
在这一部分,您开始做严肃的工作:创建Product微服务。让我们从代码中生成一个名为quarkushop-product的新 Quarkus 应用。夸库斯。io :
以下是选定的扩展:
-
RESTEasy JAX-RS
-
塞西·JSON-b
-
SmallRye OpenAPI
-
冬眠的奥姆
-
Hibernate 验证程序
-
JDBC 驱动程序- PostgreSQL
-
候鸟迁徙所经的路径
-
spring 数据 jpa api 的 quartus 扩展
-
小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj
-
SmallRye 健康
-
忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈
-
库比涅斯配置
-
容器图像悬臂
通过单击 Generate Your Application 下载生成的应用 skull。
然后将代码导入您的 IDE。打开pom.xml文件,向其中添加 Lombok 和 TestContainers 依赖项:
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.15.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.15.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
接下来,添加最重要的依赖项——quarkushop-commons:
<dependency>
<groupId>com.targa.labs</groupId>
<artifactId>quarkushop-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
将代码从product包复制到quarkushop-product微服务。
不要忘记将 banner.txt 文件从单体应用复制到
Product微服务的src/main/resources目录中。
下一步是填充application.properties:
1 # Datasource config properties
2 quarkus.datasource.db-kind=postgresql
3 quarkus.datasource.username=developer
4 quarkus.datasource.password=p4SSW0rd
5 quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/product
6 # Flyway minimal config properties
7 quarkus.flyway.migrate-at-start=true
8 # HTTP config properties
9 quarkus.http.root-path=/api
10 quarkus.http.access-log.enabled=true
11 %prod.quarkus.http.access-log.enabled=false
12 # Swagger UI
13 quarkus.swagger-ui.always-include=true
14 # Datasource config properties
15 %test.quarkus.datasource.db-kind=postgresql
16 # Flyway minimal config properties
17 %test.quarkus.flyway.migrate-at-start=true
18 # Define the custom banner
19 quarkus.banner.path=banner.txt
20 ### Security
21 quarkus.http.cors=true
22 quarkus.smallrye-jwt.enabled=true
23 # Keycloak Configuration
24 keycloak.credentials.client-id=quarkushop
25 # MP-JWT Config
26 mp.jwt.verify.publickey.location=http://localhost:9080/auth/realms/quarkushop-realm/protocol/openid-connect/certs
27 mp.jwt.verify.issuer=http://localhost:9080/auth/realms/quarkushop-realm
28 ### Health Check
29 quarkus.smallrye-health.ui.always-include=true
30 # Kubernetes ConfigMaps
31 quarkus.kubernetes-config.enabled=true
32 quarkus.kubernetes-config.config-maps=quarkushop-product-config
这些属性与单体应用几乎相同,这是合乎逻辑的,因为微服务是从单体应用上切下的一片。
我更改了数据库的名称(第 5 行)和
ConfigMap(第 34 行)。
回想一下,我们更改了ConfigMap,所以现在我们需要创建它。见清单 13-1 。
1 apiVersion: v1
2 kind: ConfigMap
3 metadata:
4 name: quarkushop-product-config
5 data:
6 application.properties: |-
7 quarkus.datasource.jdbc.url=jdbc:postgresql://postgres:5432/product
8 mp.jwt.verify.publickey.location=http://keycloak-http.keycloak/auth/realms/quarkushop-realm/protocol/openid-connect/certs
9 mp.jwt.verify.issuer=http://keycloak-http.keycloak/auth/realms/quarkushop-realm
Listing 13-1quarkushop-product-config.yml
我还在这个
ConfigMap中更改了数据库的名称(第 7 行)。
我更改了数据库名称,因为正如您在第九章中了解到的,每个微服务拥有一个数据库是明智的。这就是我为每个微服务创建专用模式的原因:
因为我们在数据库上下文中,我们需要将 Flyway 脚本V1.0__Init_app.sql和V1.1__Insert_samples.sql从单体应用复制到Product微服务的src/main/resources/db/migration目录中。我们还需要清理 SQL 脚本,只保留产品绑定的上下文相关对象和样本数据。
确保正确清理脚本,否则部署将在应用引导过程中失败。
接下来,有一个非常重要的任务要做:将quarkushop-commons项目标识到quarkushop-product的 Quarkus 索引。
What is the Quarkus Index?
Quarkus 自动索引当前模块。但是,当您拥有包含 CDI beans、实体和序列化为 JSON 的对象的外部模块时,您需要显式地对它们进行索引。
可以通过多种方式建立索引:
-
使用 Jandex Maven 插件
-
添加一个空的
META-INF/beans.xml文件 -
使用 Quarkus 索引依赖属性,这是我最喜欢的选择
可以使用application.properties值完成该索引:
quarkus.index-dependency.commons.group-id=com.targa.labs
quarkus.index-dependency.commons.artifact-id=quarkushop-commons
没有这个
index-dependency配置,您就无法构建应用的本机二进制文件。
在构建项目之前,我们需要将相关测试从 monolith 复制到quarkushop-product微服务:
-
CategoryResourceIT -
CategoryResourceTest -
ProductResourceIT -
ProductResourceTest -
ReviewResourceIT -
ReviewResourceTest
我们还需要将 Keycloak Docker 文件从src/main/docker复制到quarkushop-product微服务:
-
keycloak-test.yml文件 -
realms目录
为了能够执行测试,我们需要在test环境/概要文件中禁用 Kubernetes 支持:
%test.quarkus.kubernetes-config.enabled=false
quarkus.test.native-image-profile=test
我们将原生映像测试配置文件定义为
test,以便禁用 Kubernetes 对原生映像测试的支持。
接下来,我们需要执行测试,构建并推送quarkushop-product映像:
mvn clean install -Pnative \
-Dquarkus.native.container-build=true \
-Dquarkus.container-image.build=true
然后,我们将quarkushop-product图像推送到容器注册表,如下所示:
docker push nebrass/quarkushop-product:1.0.0-SNAPSHOT
我们现在创建quarkushop-product-config ConfigMap:
kubectl apply -f quarkushop-product/quarkushop-product-config.yml
并将Product微服务部署到 Kubernetes 集群:
kubectl apply -f quarkushop-product/target/kubernetes/kubernetes.json
太棒了!现在我们可以列出 pod 了:
$ kubectl get pods
NAME READY STATUS
postgres-69c47c748-pnbbf 1/1 Running
quarkushop-product-7748f9f74c-dqnqk 1/1 Running
我们可以在quarkushop-product上使用port-forward来测试应用:
$ kubectl port-forward quarkushop-product-7748f9f74c-dqnqk 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Handling connection for 8080
Handling connection for 8080
然后一个curl命令可以计算存储在数据库中的产品数量:
$ curl -X GET "http://localhost:8080/api/products/count"
4
很好!对数据库的访问工作正常。我们还可以通过使用
curl -X GET "http://localhost:8080/api/health"命令来运行健康检查,以确保正确到达键盘锁:
{
"status": "UP",
"checks": [
{
"name": "Keycloak connection health check",
"status": "UP"
},
{
"name": "Database connections health check",
"status": "UP"
}
]
}
太棒了!一切都在按预期工作!
我们现在可以转移到
Order微服务。
实施订单微服务
在本节中,我们将生成与Product微服务具有相同扩展的Order微服务,并增加一个扩展:REST 客户端扩展。
我们知道Order微服务对Product微服务有通信依赖。这种通信可以作为从Order微服务到Product微服务的 REST API 调用来实现。这就是为什么我们在选择的依赖项中包含 REST 客户端扩展。
生成项目后,我们将运行与使用Product微服务时相同的任务:
-
将代码从单体应用中的
order包复制到新的Order微服务中 -
添加 Lombok、AssertJ 和 TestContainers 依赖项
-
添加
quarkushop-commons依赖项 -
从 monolith 复制
banner.txt文件 -
添加
application.properties并更改数据库和ConfigMap名称 -
创建
quarkushop-product-config ConfigMap文件 -
复制 Flyway 脚本并清理不相关的对象和数据
-
在
application.properties中添加quarkushop-commons的 Quarkus 索引依赖关系
此时,我们需要修复代码,因为我们在OrderItemService类中仍然有一个ProductRepository引用。
OrderItemService使用ProductRepository查找使用给定 ID 的产品。这个编程调用将被对Product微服务的 REST API 调用所取代。为此,我们需要创建一个ProductRestClient类,它将使用给定的 ID 获取产品数据:
-
①
ProductRestClient将指向/productsURI。 -
②
@RegisterRestClient允许 Quarkus 知道这个接口可以作为 REST 客户端用于 CDI 注入。 -
③
findById()方法将在/productsURI 上进行 HTTP GET。
@Path("/products") ①
@RegisterRestClient ②
public interface ProductRestClient {
@GET
@Path("/{id}")
ProductDto findById(@PathParam Long id); ③
}
但是Product微服务 API 的基 URL 是什么?为了正常工作,需要对ProductRestClient进行配置。可以使用这些属性完成配置:
-
①
ProductRestClient的基本 URL 配置。 -
②将
ProductRestClientbean 的范围定义为Singleton。
1 product-service.url=http://quarkushop-product:8080/api
2 com.targa.labs.quarkushop.order.client.ProductRestClient/mp-rest/url=${product-service.url} ①
3 com.targa.labs.quarkushop.order.client.ProductRestClient/mp-rest/scope=javax.inject.Singleton ②
我们将重构OrderItemService类来改变:
@Inject
ProductRepository productRepository;
敬新的:
@RestClient
ProductRestClient productRestClient;
@RestClient用于注入一个 REST 客户端。
然后,我们将productRepository.getOne()调用更改为productRestClient.findById()。JPA 存储库获取已经被 REST API 调用所取代。
因为我们对Product微服务有一个外部依赖,所以我们需要创建一个健康检查来验证Product微服务是否可达,就像我们对 PostgreSQL 和 Keycloak 所做的一样。ProductServiceHealthCheck看起来是这样的:
@Slf4j
@Liveness
@ApplicationScoped
public class ProductServiceHealthCheck implements HealthCheck {
@ConfigProperty(name = "product-service.url", defaultValue = "false")
Provider<String> productServiceUrl;
@Override
public HealthCheckResponse call() {
HealthCheckResponseBuilder responseBuilder =
HealthCheckResponse.named("Product Service connection health check");
try {
productServiceConnectionVerification();
responseBuilder.up();
} catch (IllegalStateException e) {
responseBuilder.down().withData("error", e.getMessage());
}
return responseBuilder.build();
}
private void productServiceConnectionVerification() {
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofMillis(3000))
.build();
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create(productServiceUrl.get() + "/health"))
.build();
HttpResponse<String> response = null;
try {
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException e) {
log.error("IOException", e);
} catch (InterruptedException e) {
log.error("InterruptedException", e);
Thread.currentThread().interrupt();
}
if (response == null || response.statusCode() != 200) {
throw new IllegalStateException("Cannot contact Product Service");
}
}
}
我们需要将这些相关测试从 monolith 复制到quarkushop-order微服务:
-
AddressServiceUnitTest -
CartResourceIT -
CartResourceTest -
OrderItemResourceIT -
OrderItemResourceTest -
OrderResourceIT -
OrderResourceTest
我们还需要将 Keycloak Docker 文件从src/main/docker复制到quarkushop-order微服务:
-
keycloak-test.yml文件 -
realms目录
然后我们添加执行测试所需的属性:
%test.quarkus.kubernetes-config.enabled=false
quarkus.test.native-image-profile=test
quarkushop-product不依赖任何其他微服务,所以我们为quarkushop-order所做的修改对quarkushop-product来说已经足够了。然而,quarkushop-product微服务上的quarkushop-order回复意味着测试也依赖于quarkushop-product微服务。
我们有许多解决方案,以下是我的两个选择:
-
嘲笑
RestClient -
添加一个
quarkushop-product的测试实例
我将为quarkushop-order使用第二种选择,并为quarkushop-customer模拟RestClient,因为它在另一个微服务上回复。
回想一下,我们使用 TestContainers 框架为集成测试提供了一个 Keycloak 实例。该供应是使用一个docker-compose文件进行的,其中创建了一个keycloak服务。我们可以使用相同的方法来提供一个quarkushop-product实例,使用相同的docker-compose文件。但是我们必须首先创建一个新的QuarkusTestResourceLifecycleManager类,而不是使用KeycloakRealmResource,因为它只提供 Keycloak。
让我们将src/main/docker/keycloak-test.yml文件重命名为src/main/docker/context-test.yml。然后我们可以添加两个服务:quarkushop-product和postgresql-db。
我们为什么要增加
postgresql-db服务?答案很简单;这是
quarkushop-product所需要的(我们的微服务需要数据库存储和一个 Keycloak 租户来工作)。
src/main/docker/ context-test.yml的内容如下:
version: '3'
services:
keycloak:
image: jboss/keycloak:latest
command:
[
'-b','0.0.0.0',
'-Dkeycloak.migration.action=import',
'-Dkeycloak.migration.provider=dir',
'-Dkeycloak.migration.dir=/opt/jboss/keycloak/realms',
'-Dkeycloak.migration.strategy=OVERWRITE_EXISTING',
'-Djboss.socket.binding.port-offset=1000',
'-Dkeycloak.profile.feature.upload_scripts=enabled',
]
volumes:
- ./realms-test:/opt/jboss/keycloak/realms
environment:
- KEYCLOAK_USER=admin
- KEYCLOAK_PASSWORD=admin
- DB_VENDOR=h2
ports:
- 9080:9080
- 9443:9443
- 10990:10990
quarkushop-product:
image: nebrass/quarkushop-product:1.0.0-SNAPSHOT
environment:
- QUARKUS_PROFILE=test
- QUARKUS_DATASOURCE_JDBC_URL=jdbc:postgresql://postgresql-db:5432/product
- MP_JWT_VERIFY_PUBLICKEY_LOCATION=http://keycloak:9080/auth/realms/quarkushop-realm/protocol/openid-connect/certs
- MP_JWT_VERIFY_ISSUER=http://keycloak:9080/auth/realms/quarkushop-realm
depends_on:
- postgresql-db
- keycloak
ports:
- 8080:8080
postgresql-db:
image: postgres:13
volumes:
- /opt/postgres-volume:/var/lib/postgresql/data
environment:
- POSTGRES_USER=developer
- POSTGRES_PASSWORD=p4SSW0rd
- POSTGRES_DB=product
- POSTGRES_HOST_AUTH_METHOD=trust
ports:
- 5432:5432
注意,通过
quarkushop-product的环境变量使用了可用的 Keycloak 实例。
太棒了!现在,TestContainers将提供 Keycloak/PostgreSQL/ quarkushop-product实例,这是quarkushop-order在这些集成测试中所需要的。
接下来,我们需要创建一个新的QuarkusTestResourceLifecycleManager类,名为ContextTestResource。这个类将提供 Keycloak 和quarkushop-product,并将它们的属性传递给应用。见清单 13-2 。
-
①定义提供的服务。
-
②定义 Keycloak 和
quarkushop-product需要的属性。
public class ContextTestResource implements QuarkusTestResourceLifecycleManager {
@ClassRule
public static DockerComposeContainer ECOSYSTEM = new DockerComposeContainer(
new File("src/main/docker/context-test.yml"))
.withExposedService("quarkushop-product_1", 8080, ①
Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(30)))
.withExposedService("keycloak_1", 9080, ①
Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(30)));
@Override
public Map<String, String> start() {
ECOSYSTEM.start();
String jwtIssuerUrl = String.format("http://%s:%s/auth/realms/quarkus-realm",
ECOSYSTEM.getServiceHost("keycloak_1", 9080),
ECOSYSTEM.getServicePort("keycloak_1", 9080)
);
TokenService tokenService = new TokenService();
Map<String, String> config = new HashMap<>();
try {
String adminAccessToken = tokenService.getAccessToken(jwtIssuerUrl,
"admin", "test", "quarkus-client", "mysecret");
String testAccessToken = tokenService.getAccessToken(jwtIssuerUrl, "test", "test", "quarkus-client", "mysecret");
config.put("quarkus-admin-access-token", adminAccessToken);
config.put("quarkus-test-access-token", testAccessToken);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
config.put("mp.jwt.verify.publickey.location", jwtIssuerUrl
+ "/protocol/openid-connect/certs"); ②
config.put("mp.jwt.verify.issuer", jwtIssuerUrl); ②
String productServiceUrl = String.format("http://%s:%s/api",
ECOSYSTEM.getServiceHost("quarkushop-product_1", 8080),
ECOSYSTEM.getServicePort("quarkushop-product_1", 8080)
);
config.put("product-service.url", productServiceUrl); ②
return config;
}
@Override
public void stop() {
ECOSYSTEM.stop();
}
}
Listing 13-2src/test/java/com/targa/labs/quarkushop/order/util/ContextTestResource.java
然后我们重构测试以包含ContextTestResource:
@DisabledOnNativeImage
@QuarkusTest
@QuarkusTestResource(TestContainerResource.class)
@QuarkusTestResource(ContextTestResource.class)
class CartResourceTest {
...
}
当运行重构步骤时,我删除了客户创建。我用的是随机生成的客户 ID。
让我们仔细看看微服务的创建过程:
-
构建
quarkushop-order映像并将其推送到容器注册表中: -
创建
quarkushop-order-config ConfigMap文件:
$ mvn clean install -Pnative \
-Dquarkus.native.container-build=true \
-Dquarkus.container-image.build=true
$ docker push nebrass/quarkushop-order:1.0.0-SNAPSHOT
- 在 Kubernetes 中创建
quarkushop-order-config ConfigMap:
1 apiVersion: v1
2 kind: ConfigMap
3 metadata:
4 name: quarkushop-order-config
5 data:
6 application.properties: |-
7 quarkus.datasource.jdbc.url=jdbc:postgresql://postgres:5432/order
8 mp.jwt.verify.publickey.location=http://keycloak-http.keycloak/auth/realms/quarkushop-realm/protocol/openid-connect/certs
9 mp.jwt.verify.issuer=http://keycloak-http.keycloak/auth/realms/quarkushop-realm
- 将
quarkushop-order应用部署到 Kubernetes:
kubectl apply -f quarkushop-order/quarkushop-order-config.yml
- 使用健康检查、一个
port-forward和一个简单的curlGET 命令对/healthAPI 检查quarkushop-orderpod:
kubectl apply -f quarkushop-order/target/kubernetes/kubernetes.json
{
"status": "UP",
"checks": [
{
"name": "Keycloak connection health check",
"status": "UP"
},
{
"name": "Product Service connection health check",
"status": "UP"
},
{
"name": "Database connections health check",
"status": "UP"
}
]
}
很好!Order微服务工作正常。接下来,我们对Customer微服务做同样的事情。
实施客户微服务
为了实现Customer微服务,我们将应用与Order微服务相同的步骤。同样,我们将:
-
将代码从单体应用中的
customer包复制到新的Customer微服务中 -
添加 Lombok、AssertJ 和 TestContainers 依赖项
-
添加
quarkushop-commons依赖项 -
从 monolith 复制
banner.txt文件 -
添加
application.properties并更改数据库和ConfigMap名称 -
复制 Flyway 脚本并清理不相关的对象和数据
-
在
application.properties中添加quarkushop-commons的 Quarkus 索引依赖关系
然后,我们需要将这些相关测试从 monolith 复制到quarkushop-customer微服务:
-
CustomerResourceIT -
CustomerResourceTest -
PaymentResourceIT -
PaymentResourceTest
我们还需要将 Keycloak Docker 文件从src/main/docker复制到quarkushop-order微服务:
-
keycloak-test.yml文件 -
realms目录
然后我们添加执行测试所需的属性:
%test.quarkus.kubernetes-config.enabled=false
quarkus.test.native-image-profile=test
接下来,我们嘲弄一下OrderRestClient。我们创建一个名为MockOrderRestClient的新类,如清单 13-3 所示。
-
①用于模拟测试中注入的 beans 的注释。
-
②
Mock类实现了RestClient接口。 -
③实现了
Mock方法,以返回适合测试的结果。
@Mock ①
@ApplicationScoped
@RestClient
public class MockOrderRestClient implements OrderRestClient { ②
@Override
public Optional<OrderDto> findById(Long id) { ③
OrderDto order = new OrderDto();
order.setId(id);
order.setTotalPrice(BigDecimal.valueOf(1000));
return Optional.of(order);
}
@Override
public Optional<OrderDto> findByPaymentId(Long id) {
OrderDto order = new OrderDto();
order.setId(5L);
return Optional.of(order);
}
@Override
public OrderDto save(OrderDto order) {
return order;
}
}
Listing 13-3src/test/java/com/targa/labs/quarkushop/customer/utils/MockOrderRestClient.java
仅此而已!模仿是非常容易的,被模仿的组件会被自动注入到测试中。微服务创建流程如下:
-
构建
quarkushop-customer映像并将其推送到容器注册表中 -
创建
quarkushop-customer-config ConfigMap文件 -
在 Kubernetes 中创建
quarkushop-customer-config ConfigMap -
使用健康检查、
port-forward和简单的curlGET 命令对/healthAPI 检查quarkushop-customerpod
你会在本书的
GitHub 资源库中找到所有的代码和资源。
实施用户微服务
什么,额外的微服务?我知道我在这一章的开始没有提到这一点。
别担心,这不是一个巨大的微服务。它用于身份验证,并将保存 Quarkushop Swagger UI monolith 中列出的user部分 REST APIs:
让我们用这些扩展生成一个新的 Quarkus 应用:
-
RESTEasy JAX-RS
-
塞西·JSON-b
-
SmallRye OpenAPI
-
小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj 小 jj
-
SmallRye 健康
-
忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈
-
库比涅斯配置
-
容器图像悬臂
没有与持久性相关的扩展,因为在这个微服务中你不会与数据库交互。
实施User微服务的步骤如下:
-
复制用户相关内容。这个微服务非常少,所以只会装
UserResource级。 -
添加 Lombok 依赖项。
-
添加
quarkushop-commons依赖项。 -
从 monolith 复制
banner.txt文件。 -
添加
application.properties,更改ConfigMap名称,并删除所有数据持久性属性(Flyway、JPA 等。). -
在
application.properties中添加quarkushop-commons的 Quarkus 索引依赖关系。 -
构建
quarkushop-user映像并将其推送到容器注册中心。 -
创建
quarkushop-user-config ConfigMap文件。 -
在 Kubernetes 中创建
quarkushop-user-config ConfigMap。 -
使用健康检查、
port-forward和简单的curlGET 命令对/healthAPI 检查quarkushop-userpod。
你会在本书的
GitHub 资源库中找到所有的代码和资源。
在部署了quarkushop-user微服务之后,您可以使用它来获得一个access_token,并与其他三个微服务的安全 API 进行通信:
很好!微服务已正确部署并正常工作!
结论
在本章中,您学习了如何创建微服务并将它们部署到 Kubernetes。在这个繁重的任务中,您实现了在第九章中介绍的模式。但是您没有实现最需要的模式。这是你在下一章要做的。
十四、与 Quarkus 和 Kubernetes 一起飞遍天空
介绍
从第九章开始,您学习了云原生模式,甚至实现了其中的一些模式:
-
服务发现和注册:使用 Kubernetes
Deployment和Service对象完成 -
外部化配置:使用 Kubernetes
ConfigMap和Secret对象制作 -
每个服务的数据库:在使用 DDD 概念分割整体代码库时创建的
-
应用度量:使用 SmallRye Metrics Quarkus 扩展实现
-
健康检查 API :使用 SmallRye Health Quarkus 扩展实现
-
服务间的安全性:使用 SmallRye JWT 夸库扩展和 Keycloak 实现
在本章中,您将学习如何实现更流行的模式:
-
断路器
-
日志聚合
-
分布式跟踪
-
应用编程接口网关
实现断路器模式
断路器模式对于使用错误通信协议(如 HTTP)的弹性微服务非常有用。该模式的思想是处理微服务之间的任何通信问题。
断路器模式的这种实现只会影响
Order和Customer微服务,其中我们使用 REST 客户端进行外部调用。
在基于 Quarkus 的应用中实现这种模式非常容易。第一步是将这个依赖项添加到pom.xml文件中:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-fault-tolerance</artifactId>
</dependency>
现在,我们在 REST 客户端组件上启用断路器特性。假设我们希望 REST 客户机停止调用 API 15 秒钟,如果我们在最近 10 次请求中有 50%的请求失败。这可以使用下面这行代码来完成:
@CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 15000)
在Order微服务中:
@Path("/products")
@RegisterRestClient
public interface ProductRestClient {
@GET
@Path("/{id}")
@CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 15000)
ProductDto findById(@PathParam Long id);
}
@CircuitBreaker有许多属性:
-
failOn:应被视为失败的异常类型列表;默认值为{Throwable.class}。由带注释的方法抛出的继承自Throwable的所有异常都被认为是失败的。 -
skipOn:不应被视为失败的异常类型列表;默认值为{}。 -
delay:开路转变为半开状态的延迟时间;默认值为5000。 -
delayUnit:延时的单位。默认值为ChronoUnit.MILLIS。 -
requestVolumeThreshold:滚动窗口中连续请求的数量。默认值为20。 -
failureRatio:滚动窗口内使电路跳闸断开的故障比率。默认值为0.50。 -
successThreshold:半开电路再次闭合前成功执行的次数。默认值为1。
断路器模式有三种状态:
-
所有的请求都是正常的
-
Half-open:进行验证以检查问题是否仍然出现的过渡状态 -
Open:所有请求都被禁用,直到延迟到期
在Customer微服务中:
@Path("/orders")
@RegisterRestClient
public interface OrderRestClient {
@GET
@Path("/{id}")
@CircuitBreaker(requestVolumeThreshold = 10, delay = 15000)
Optional<OrderDto> findById(@PathParam Long id);
@GET
@Path("/payment/{id}")
@CircuitBreaker(requestVolumeThreshold = 10, delay = 15000)
Optional<OrderDto> findByPaymentId(Long id);
@POST
@CircuitBreaker(requestVolumeThreshold = 10, delay = 15000)
OrderDto save(OrderDto order);
}
SmallRye 容错扩展提供了许多其他有用的选项来处理故障,以便成为具有强大弹性的微服务。例如,我们有这些机制:
- 重试机制:用于调用失败时重试的次数;
@Path("/products")
@RegisterRestClient
public interface ProductRestClient {
@GET
@Path("/{id}")
@CircuitBreaker(requestVolumeThreshold = 10, delay = 15000)
@Retry(maxRetries = 4)
ProductDto findById(@PathParam Long id);
}
如果调用失败,
@Retry(maxRetries = 4)将运行最多四次重试。
- 超时机制:用于定义方法执行超时。这很容易实现:
@Path("/products")
@RegisterRestClient
public interface ProductRestClient {
@GET
@Path("/{id}")
@CircuitBreaker(requestVolumeThreshold = 10, delay = 15000)
@Retry(maxRetries = 4)
@Timeout(500)
ProductDto findById(@PathParam Long id);
}
如果findById()调用超过 500 毫秒,那么
@Timeout(500)会让应用抛出一个TimeoutException。
- 回退机制:用于在主方法失败时调用回退(或备份)方法。这里有一个注释来完成这项工作:
public class SomeClass {
@Inject
@RestClient
ProductRestClient productRestClient;
@Fallback(fallbackMethod = "fallbackFetchProduct")
List<ProductDto> findProductsByCategory(String category){
return productRestClient.findProductsByCategory(category);
}
public List<ProductDto> fallbackFetchProduct(String category) {
return Collections.emptyList();
}
}
如果
productRestClient.findProductsByCategory()失败,您将从fallbackFetchProduct()方法而不是findProductsByCategory()获得响应。您可以进一步调整这个强大的机制。例如,您可以将其配置为在定义异常或特定超时后切换到回退方法。
请注意,断路器模式和容错模式在 Quarkus 框架中得到了完美的实现。
实现日志聚合模式
对于日志聚合模式,我们使用著名的 ELK(弹性搜索、日志存储和 Kibana)堆栈。这是三个开源项目:
-
Elasticsearch 是一个搜索和分析引擎。
-
Logstash 是一个服务器端数据处理管道,它同时从多个来源获取数据,对其进行转换,然后将其发送到 Elasticsearch 这样的“stash”。
-
Kibana 允许用户在 Elasticsearch 中用图表和图形可视化数据。
总之,这些工具最常用于集中和分析分布式系统中的日志。ELK 堆栈很受欢迎,因为它满足了日志分析领域的需求。
本章中 ELK 堆栈的使用案例如下:
所有的微服务都会将各自的日志推送到 Logstash,Logstash 会使用 Elasticsearch 对它们进行索引。索引日志可以在以后被 Kibana 使用。
Quarkus 有一个很棒的扩展叫做quarkus-logging-gelf,它被描述为“使用 Graylog 扩展日志格式的日志,并将您的日志集中在埃尔克或 EFK。”
What is the Graylog Extended log Format?
基于 Graylog.org 网站:“Graylog 扩展日志格式(GELF)是一种独特方便的日志格式,旨在解决传统普通系统日志的所有缺点。这一企业功能允许您从任何地方收集结构化事件,然后在眨眼之间压缩并分块它们。”
很好!Logstash 本身支持 Graylog 扩展日志格式。你只需要在配置过程中激活它。
步骤 1:将 ELK 栈部署到 Kubernetes
如何在 Kubernetes 集群中安装 ELK 堆栈?这是个大问题!
这是一个极其简单的任务:正如我们对 Keycloak 所做的那样,我们将使用 Helm 来安装 ELK 堆栈。
首先将官方 ELK Helm 图表库添加到我们的 Helm 客户端:
helm repo add elastic https://helm.elastic.co
接下来,我们需要更新引用:
helm repo update
如果你像我一样在 Minikube 上,你需要创建一个elasticsearch-values.yaml文件,你将使用它来定制helm install。见清单 14-1 。
# Permit co-located instances for solitary minikube virtual machines.
antiAffinity: "soft"
# Shrink default JVM heap.
esJavaOpts: "-Xmx128m -Xms128m"
# Allocate smaller chunks of memory per pod.
resources:
requests:
cpu: "100m"
memory: "512M"
limits:
cpu: "1000m"
memory: "512M"
# Request smaller persistent volumes.
volumeClaimTemplate:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "standard"
resources:
requests:
storage: 100M
Listing 14-1elasticsearch-values.yaml
该配置文件是在 Minikube 上安装 Elasticsearch 时推荐使用的配置:
https://github.com/elastic/helm-charts/blob/master/elasticsearch/examples/minikube/values.yaml
现在我们安装 Elasticsearch:
helm install elasticsearch elastic/elasticsearch -f ./elasticsearch-values.yaml
我们可以看到使用该命令创建了什么:
$ kubectl get all -l release=elasticsearch
NAME READY STATUS RESTARTS
pod/elasticsearch-master-0 1/1 Running 0
pod/elasticsearch-master-1 1/1 Running 0
pod/elasticsearch-master-2 1/1 Running 0
NAME TYPE CLUSTER-IP PORT(S)
service/elasticsearch-master ClusterIP 10.103.91.46 9200/TCP,9300/TCP
service/elasticsearch-master-headless ClusterIP None 9200/TCP,9300/TCP
NAME READY
statefulset.apps/elasticsearch-master 3/3
这三个主机是为了实现高可用性。我知道我们现在没有问题,但是想想黑色星期五!
pod 处于运行状态,因此我们可以测试 Elasticsearch 9200 端口。我们可以做一个port-forward和一个curl:
$ kubectl port-forward service/elasticsearch-master 9200
Forwarding from 127.0.0.1:9200 -> 9200
Forwarding from [::1]:9200 -> 9200
$ curl localhost:9200
{
"name" : "elasticsearch-master-1",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "UkYbL4KsSeK4boVr4rOe2w",
"version" : {
"number" : "7.9.2",
...
"lucene_version" : "8.6.2",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
太棒了!我们可以部署基巴纳:
helm install kibana elastic/kibana --set fullnameOverride=quarkushop-kibana
我们可以看到使用该命令创建了什么:
$ kubectl get all -l release=kibana
NAME READY STATUS
pod/quarkushop-kibana-696f869668-5tcvz 1/1 Running
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
service/quarkushop-kibana ClusterIP 10.107.223.6 <none> 5601/TCP
NAME READY UP-TO-DATE AVAILABLE
deployment.apps/quarkushop-kibana 1/1 1 1
NAME DESIRED CURRENT READY
replicaset.apps/quarkushop-kibana-696f869668 1 1 1
我们将在 Kibana 服务上做一个port-forward:
kubectl port-forward service/quarkushop-kibana 5601
然后打开 Kibana UI,检查一切是否正常工作:
很好!我们需要安装 Logstash,但是我们需要使用 Helm logstash-values.yaml文件定制安装;见清单 14-2 。
logstashConfig:
logstash.yml: |
http.host: "0.0.0.0"
xpack.monitoring.elasticsearch.hosts: [ "http://elasticsearch-master:9200" ]
xpack.monitoring.enabled: true
pipelines.yml: |
- pipeline.id: custom
path.config: "/usr/share/logstash/pipeline/logstash.conf"
logstashPipeline:
logstash.conf: |
input {
gelf {
port => 12201
type => gelf
}
}
output {
stdout {}
elasticsearch {
hosts => ["http://elasticsearch-master:9200"]
index => "logstash-%{+YYYY-MM-dd}"
}
}
service:
annotations: {}
type: ClusterIP
ports:
- name: filebeat
port: 5000
protocol: TCP
targetPort: 5000
- name: api
port: 9600
protocol: TCP
targetPort: 9600
- name: gelf
port: 12201
protocol: UDP
targetPort: 12201
Listing 14-2logstash-values.yaml
该values.yaml文件用于配置:
-
Logstash 管道。启用
gelf插件,公开默认的 12201 端口,并定义 Logstash 输出模式和到 Elasticsearch 实例的流 -
Logstash 服务定义和公开的端口
现在,让我们安装 Logstash:
helm install -f ./logstash-values.yaml logstash elastic/logstash \
--set fullnameOverride=quarkushop-logstash
要列出创建的对象,请运行以下命令:
$ k get all -l chart=logstash
NAME READY STATUS RESTARTS
pod/quarkushop-logstash-0 1/1 Running 0
NAME TYPE CLUSTER-IP PORT(S)
service/quarkushop-logstash ClusterIP 10.107.204.49 5000/TCP,9600/TCP,12201/UDP
service/quarkushop-logstash-headless ClusterIP None 5000/TCP,9600/TCP,12201/UDP
NAME READY
statefulset.apps/quarkushop-logstash 1/1
太棒了!现在,ELK 堆栈已正确部署。下一步是配置微服务以登录到 ELK 堆栈。
步骤 2:配置微服务以登录 ELK 堆栈
我们将在此步骤中进行的修改适用于:
-
quarkushop-product -
quarkushop-order -
quarkushop-customer -
quarkushop-user
让我们给pom.xml文件添加扩展名:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-logging-gelf</artifactId>
</dependency>
我们需要为每个微服务ConfigMap文件定义 Logstash 服务器属性,如清单 14-3 所示。
-
①Logstash 主机是公开 log stash 的 Kubernetes 服务。
-
②Logstash 端口在 Logstash Kubernetes 服务中定义。
apiVersion: v1
kind: ConfigMap
metadata:
name: quarkushop-order-config
data:
application.properties: |-
quarkus.datasource.jdbc.url=jdbc:postgresql://postgres:5432/order
mp.jwt.verify.publickey.location=http://keycloak-http.keycloak/auth/realms/quarkushop-realm/protocol/openid-connect/certs
mp.jwt.verify.issuer=http://keycloak-http.keycloak/auth/realms/quarkushop-realm
quarkus.log.handler.gelf.enabled=true
quarkus.log.handler.gelf.host=quarkushop-logstash ①
quarkus.log.handler.gelf.port=12201 ②
Listing 14-3quarkushop-order-config.yml
让我们构建、推送容器,并再次部署到我们的 Kubernetes 集群。我们需要再次导入ConfigMaps来更新它们。
步骤 3:收集日志
一旦部署和配置好一切,我们需要访问 Kibana UI 来解析收集到的日志:
kubectl port-forward service/quarkushop-kibana 5601
转到管理➤堆栈管理➤指数管理➤基巴纳➤指数模式:
从这里,单击创建索引模式以创建新的索引模式。将出现一个新屏幕:
用logstash-*填充索引模式名称字段,并点击下一步:
在下一个屏幕上,为时间字段选择@timestamp,并点击创建索引模式:
将创建一个新的索引模式,并出现一个确认屏幕:
现在,如果您单击 Kibana 部分中的 Discover 菜单,您将看到日志列表:
太棒了。现在,我们可以密切关注所有微服务中生成的所有日志。我们可以享受 ELK stack 的强大功能。例如,我们可以创建一个自定义查询来监控日志流中特定种类的错误。
实现分布式跟踪模式
分布式跟踪模式在 Quarkus 中有一个名为quarkus-smallrye-opentracing的专用扩展。就像日志聚合模式一样,我们需要一个分布式跟踪模式的分布式跟踪系统。
分布式跟踪系统用于收集和存储微服务架构中监控通信请求所需的定时数据,以便检测延迟问题。市面上有很多分布式追踪系统,比如 Zipkin 和 Jaeger。在本书中,我们将使用 Jaeger,因为它是由quarkus-smallrye-opentracing扩展支持的默认跟踪器。
我们需要安装 Jaeger 并配置微服务来支持它,以便收集请求跟踪。
在开始安装之前,以下是 Jaeger 生态系统的组成部分:
-
Jaeger Client :包含用于分布式跟踪的 OpenTracing API 的特定语言实现。
-
Jaeger Agent :监听 UDP 上发送的 SPANS 的网络守护程序。
-
Jaeger Collector :接收跨度并将其放入队列进行处理。这允许收集器立即返回到客户端/代理,而不是等待 SPAN 进入存储。
-
查询:从存储中检索踪迹的服务。
-
Jaeger 控制台:一个用户界面,让你可视化你的分布式追踪数据。
Jaeger 组件架构如下所示:
步骤 1:将 Jaeger 一体机部署到 Kubernetes
我们首先用清单 14-4 中显示的内容创建jaeger-deployment.yml文件。
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: jaeger
app.kubernetes.io/component: all-in-one
app.kubernetes.io/name: jaeger
name: jaeger
spec:
progressDeadlineSeconds: 2147483647
replicas: 1
revisionHistoryLimit: 2147483647
selector:
matchLabels:
app: jaeger
app.kubernetes.io/component: all-in-one
app.kubernetes.io/name: jaeger
strategy:
type: Recreate
template:
metadata:
annotations:
prometheus.io/port: "16686"
prometheus.io/scrape: "true"
labels:
app: jaeger
app.kubernetes.io/component: all-in-one
app.kubernetes.io/name: jaeger
spec:
containers:
- env:
- name: COLLECTOR_ZIPKIN_HTTP_PORT
value: "9411"
image: jaegertracing/all-in-one
imagePullPolicy: Always
name: jaeger
ports:
- containerPort: 5775
protocol: UDP
- containerPort: 6831
protocol: UDP
- containerPort: 6832
protocol: UDP
- containerPort: 5778
protocol: TCP
- containerPort: 16686
protocol: TCP
- containerPort: 9411
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /
port: 14269
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
Listing 14-4jaeger/jaeger-deployment.yml
接下来,将该文件导入 Kubernetes 集群:
kubectl apply -f jaeger/jaeger-deployment.yml
这个Deployment资源将在一个容器中部署所有 Jaeger 后端组件和 UI。
我们现在需要创建一个名为jaeger-query的负载平衡的 Kubernetes 服务对象,如清单 14-5 所示。
apiVersion: v1
kind: Service
metadata:
name: jaeger-query
labels:
app: jaeger
app.kubernetes.io/name: jaeger
app.kubernetes.io/component: query
spec:
ports:
- name: query-http
port: 80
protocol: TCP
targetPort: 16686
selector:
app.kubernetes.io/name: jaeger
app.kubernetes.io/component: all-in-one
type: LoadBalancer
Listing 14-5jaeger/jaeger-query-service.yml
我们还需要创建另一个名为jaeger-collector的服务,如清单 14-6 所示。
apiVersion: v1
kind: Service
metadata:
name: jaeger-collector
labels:
app: jaeger
app.kubernetes.io/name: jaeger
app.kubernetes.io/component: collector
spec:
ports:
- name: jaeger-collector-tchannel
port: 14267
protocol: TCP
targetPort: 14267
- name: jaeger-collector-http
port: 14268
protocol: TCP
targetPort: 14268
- name: jaeger-collector-zipkin
port: 9411
protocol: TCP
targetPort: 9411
selector:
app.kubernetes.io/name: jaeger
app.kubernetes.io/component: all-in-one
type: ClusterIP
Listing 14-6jaeger/jaeger-collector-service.yml
清单 14-7 显示了我们需要创建的最后一个,名为jaeger-agent。
apiVersion: v1
kind: Service
metadata:
name: jaeger-agent
labels:
app: jaeger
app.kubernetes.io/name: jaeger
app.kubernetes.io/component: agent
spec:
ports:
- name: agent-zipkin-thrift
port: 5775
protocol: UDP
targetPort: 5775
- name: agent-compact
port: 6831
protocol: UDP
targetPort: 6831
- name: agent-binary
port: 6832
protocol: UDP
targetPort: 6832
- name: agent-configs
port: 5778
protocol: TCP
targetPort: 5778
clusterIP: None
selector:
app.kubernetes.io/name: jaeger
app.kubernetes.io/component: all-in-one
Listing 14-7jaeger/jaeger-agent-service.yml
我们通过 TCP 和 UDP 协议为 Jaeger 代理公开了许多端口。UDP 上的 6831 消耗跨度。这是我们用来和杰格特工联系的。
接下来,让我们创建 Kubernetes 对象:
kubectl apply -f jaeger/jaeger-query-service.yml
kubectl apply -f jaeger/jaeger-collector-service.yml
kubectl apply -f jaeger/jaeger-agent-service.yml
现在很好,让我们检查一下 Jaeger 是否安装正确。我们需要对jaeger-query服务执行port-forward:
kubectl port-forward service/jaeger-query 8888:80
然后打开localhost:8888,如截图所示:
太棒了。我们现在可以转到微服务配置。
步骤 2:在我们的微服务中启用 Jaeger 支持
第一步是将quarkus-smallrye-opentracing依赖性添加到我们的微服务中:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-opentracing</artifactId>
</dependency>
然后,我们需要定义每个微服务的 Jaeger 配置:
1 apiVersion: v1
2 kind: ConfigMap
3 metadata:
4 name: quarkushop-user-config
5 data:
6 application.properties: |-
7 mp.jwt.verify.publickey.location=http://keycloak-http.keycloak/auth/realms/quarkushop-realm/protocol/openid-connect/certs
8 mp.jwt.verify.issuer=http://keycloak-http.keycloak/auth/realms/quarkushop-realm
9 quarkus.log.handler.gelf.enabled=true
10 quarkus.log.handler.gelf.host=quarkushop-logstash
11 quarkus.log.handler.gelf.port=12201
12 quarkus.jaeger.service-name=quarkushop-user
13 quarkus.jaeger.sampler-type=const
14 quarkus.jaeger.sampler-param=1
15 quarkus.log.console.format=%d{HH:mm:ss} %-5p traceId=%X{traceId}, spanId=%X{spanId}, sampled=%X{sampled} [%c{2.}] (%t) %s%e%n
16 quarkus.jaeger.agent-host-port=jaeger-agent:6831
新的 Jaeger 特性如下:
-
quarkus.jaeger.service-name:服务名,是微服务用来向 Jaeger 服务器呈现自己的名称。 -
quarkus.jaeger.sampler-type:示例中的采样器类型为const。我们将不断发送quarkus.jaeger.sampler-param中定义的配额。 -
quarkus.jaeger.sampler-param:在 0 和 1 之间定义的样本配额,其中 1 表示 100%的请求。 -
quarkus.log.console.format:在日志消息中添加跟踪 id。 -
quarkus.jaeger.agent-host-port:通过 UDP 与 Jaeger 代理通信的主机名和端口。我们将它指向作为主机的jaeger-agent和作为端口的 6831。
很好。让我们构建、推送容器,并再次将它们部署到我们的 Kubernetes 集群。我们还需要再次导入ConfigMaps来更新它们。
步骤 3:收集痕迹
部署 Jaeger 服务器和更新微服务后,我们需要发出一些请求来生成踪迹。然后我们就能看到耶格在抓什么了。
例如,我们可以使用quarkushop-user向 Keycloak 请求一个access_token:
kubectl port-forward service/quarkushop-user 8080
然后运行一个curl向quarkushop-user微服务请求access_token:
curl -X POST "http://localhost:8080/api/user/access-token?password=password&username=nebrass"
很好!让我们做一个port-forward并访问 Jaeger,看看那里发生了什么:
kubectl port-forward service/jaeger-query 8888:80
然后打开localhost:8888,如截图所示:
正如您在服务部分看到的,有三个元素。只需选择quarkushop-user并点击查找痕迹:
显示了一个跨度:
如果您点按它,会出现更多详细信息:
您可以看到此处显示了请求的所有详细信息:
-
网址
-
HTTP 动词
-
持续时间
-
诸如此类;一切都在这里
搞定了。我们以一种非常有效和简单的方式在 Quarkus 中实现了分布式跟踪模式!我真的很开心!
实现 API 网关模式
一个 API 网关是一个位于 API 前面的编程门面,充当一组定义的微服务的单一入口点。
为了实现 Kubernetes,一个入口管理对集群中服务的外部访问,通常是facadeHTTP。Ingress 可以提供负载平衡、SSL 终止和基于名称的虚拟主机。
一个入口是允许入站连接到达集群服务的规则集合。它可以被配置为向服务提供外部可达的 URL、负载平衡的流量、终止 SSL、基于名称的虚拟主机等等。
一个入口控制器负责实现入口,通常带有一个负载平衡器,尽管它也可以配置您的边缘路由器或附加前端,以帮助以高可用性的方式处理流量。
让我们将 API 网关模式引入 Kubernetes。
步骤 1:在 Minikube 中启用入口支持
第一步是在 Minikube 中启用入口支持。对于那些使用真正的 Kubernetes 集群的人(幸运的人)来说,这一步是不需要的。
要在 Minikube 中启用入口支持,只需启动您的minikube实例,然后运行以下命令:
minikube addons enable ingress
Ingress 是 Minikube 的附加产品。
我们需要一个入口域名;让我们使用quarkushop.io域名。我们通过键入以下命令获得 Minikube 的 IP 地址:
$ minikube ip
192.168.39.243
然后,我们需要在这个 IP 地址的/etc/hosts文件中添加一个新条目,以便将其用于我们的自定义域quarkushop.io:
192.168.39.243 quarkushop.io
该自定义内部 DNS 条目将对目标192.168.39.243进行任何调用。
步骤 2:创建 API 网关入口
Ingress 将指向我们的四种微服务:
-
quarkushop-product -
quarkushop-order -
quarkushop-customer -
quarkushop-user
入口描述符如清单 14-8 所示。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-gateway
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
rules:
- http:
paths:
- path: /user
pathType: Prefix
backend:
service:
name: quarkushop-user
port:
number: 8080
- path: /product
pathType: Prefix
backend:
service:
name: quarkushop-product
port:
number: 8080
- path: /order
pathType: Prefix
backend:
service:
name: quarkushop-order
port:
number: 8080
- path: /customer
pathType: Prefix
backend:
service:
name: quarkushop-customer
port:
number: 8080
Listing 14-8api-gateway-ingress.yml
只需将该内容保存到api-gateway-ingress.yml并使用以下命令创建资源:
kubectl create -f api-gateway-ingress.yml
入口已成功创建!让我们检查一下:
$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
api-gateway <none> quarkushop.io 192.168.39.243 80 7m46s
太棒了!如你所见,ADDRESS与 Minikube IP 相同。
步骤 3:测试入口
现在我们可以享受我们的入口了。我们可以用它向quarkushop-user微服务请求一个access_token:
$ curl -X POST "http://quarkushop.io/user/api/user/access-token?password=password&username=nebrass"
eyJhbGciOiJSUzI1NiIsIn...
万岁!我们收到了access_token请求! Ingress 非常好用!
结论
在这一章中,我们使用不同的 Quarkus 扩展和 Kubernetes 对象实现了许多模式。这个任务非常简单,特别是因为我们将许多任务委托给了 Kubernetes。
Hakuna matata!我们成功实施了云原生微服务。我对可用的扩展和提供的优秀文档感到非常高兴。