<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.vertx.howtos</groupId>
<artifactId>clustering-kubernetes-frontend</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<verticle>io.vertx.howtos.cluster.FrontendVerticle</verticle>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-stack-depchain</artifactId>
<version>5.0.0.CR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-launcher-application</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-infinispan</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-health-check</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>11</release>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<systemProperties>
<systemProperty>
<key>java.net.preferIPv4Stack</key>
<value>true</value>
</systemProperty>
<systemProperty>
<key>vertx.jgroups.config</key>
<value>default-configs/default-jgroups-udp.xml</value>
</systemProperty>
</systemProperties>
<mainClass>${verticle}</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>2.4.0</version>
<configuration>
<to>
<image>clustering-kubernetes/frontend</image>
</to>
<container>
<mainClass>io.vertx.launcher.application.VertxApplication</mainClass>
<args>
<arg>${verticle}</arg>
<arg>-cluster</arg>
</args>
<ports>
<port>8080</port>
<port>7800</port>
</ports>
</container>
</configuration>
</plugin>
</plugins>
</build>
</project>
在 Kubernetes 上使用 Infinispan 部署 Vert.x 集群应用
本文档将向您展示如何在 Kubernetes 上使用 Infinispan 部署 Vert.x 集群应用。
您将构建什么
您将构建一个 Vert.x 集群应用程序,该程序将
-
监听对
/hello
URI 的 HTTP 请求 -
提取 HTTP 查询参数
name
-
回复一个问候语,例如
"Hello <name> from <pod>
,其中-
<name>
是查询参数的值 -
<pod>
是生成问候语的 Kubernetes pod 的名称
-
它由两个部分(或*微服务*)组成,它们通过 Vert.x *事件总线*进行通信。
*前端*处理 HTTP 请求。它提取 name
参数,向总线上的 greetings
地址发送请求,并将回复转发给客户端。
*后端*消费发送到 greetings
地址的消息,生成问候语并回复给*前端*。
创建项目
*前端*和*后端*项目的代码包含功能等效的 Maven 和 Gradle 构建文件。
依赖项
两个项目都依赖于
Vert.x Infinispan 是 Vert.x 的一个集群管理器,它基于 Infinispan 内存键/值数据存储。在 Vert.x 中,集群管理器用于各种功能。特别是,它提供集群节点的发现/成员管理,并存储*事件总线*订阅数据。
Vert.x Web 是一组构建模块,可轻松创建 HTTP 应用程序。
Vert.x Health Check 是一个组件,它标准化了检查系统不同部分、推导状态并暴露状态的过程。
容器化
为了创建容器,我们将使用 Jib,因为它
-
它为依赖项、资源和类创建了不同的层,从而节省了构建时间和部署时间
-
它支持 Maven 和 Gradle
-
它不需要 Docker 也不需要 Podman
使用 Maven
以下是您应该用于*前端*的 pom.xml
文件的内容
pom.xml
对于*后端*,内容类似
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.vertx.howtos</groupId>
<artifactId>clustering-kubernetes-backend</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<verticle>io.vertx.howtos.cluster.BackendVerticle</verticle>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-stack-depchain</artifactId>
<version>5.0.0.CR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-launcher-application</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-infinispan</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-health-check</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>11</release>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<systemProperties>
<systemProperty>
<key>java.net.preferIPv4Stack</key>
<value>true</value>
</systemProperty>
<systemProperty>
<key>vertx.jgroups.config</key>
<value>default-configs/default-jgroups-udp.xml</value>
</systemProperty>
</systemProperties>
<mainClass>${verticle}</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>2.4.0</version>
<configuration>
<to>
<image>clustering-kubernetes/backend</image>
</to>
<container>
<mainClass>io.vertx.launcher.application.VertxApplication</mainClass>
<args>
<arg>${verticle}</arg>
<arg>-cluster</arg>
</args>
<ports>
<port>8080</port>
<port>7800</port>
</ports>
</container>
</configuration>
</plugin>
</plugins>
</build>
</project>
使用 Gradle
假设您使用带有 Kotlin DSL 的 Gradle,以下是您的*前端* build.gradle.kts
文件应有的样子
build.gradle.kts
plugins {
java
application
id("com.google.cloud.tools.jib") version "2.4.0"
}
repositories {
mavenCentral()
}
val vertxVersion = "5.0.0.CR2"
val verticle = "io.vertx.howtos.cluster.FrontendVerticle"
dependencies {
implementation("io.vertx:vertx-launcher-application:${vertxVersion}")
implementation("io.vertx:vertx-web:${vertxVersion}")
implementation("io.vertx:vertx-infinispan:${vertxVersion}")
implementation("io.vertx:vertx-health-check:${vertxVersion}")
implementation("ch.qos.logback:logback-classic:1.5.12")
}
application {
applicationDefaultJvmArgs =
listOf("-Djava.net.preferIPv4Stack=true", "-Dvertx.jgroups.config=default-configs/default-jgroups-udp.xml")
mainClass = verticle
}
jib {
to {
image = "clustering-kubernetes/frontend"
}
container {
mainClass = "io.vertx.launcher.application.VertxApplication"
args = listOf(verticle, "-cluster")
ports = listOf("8080", "7800")
}
}
对于*后端*,内容类似
build.gradle.kts
plugins {
java
application
id("com.google.cloud.tools.jib") version "2.4.0"
}
repositories {
mavenCentral()
}
val vertxVersion = "5.0.0.CR2"
val verticle = "io.vertx.howtos.cluster.BackendVerticle"
dependencies {
implementation("io.vertx:vertx-launcher-application:${vertxVersion}")
implementation("io.vertx:vertx-web:${vertxVersion}")
implementation("io.vertx:vertx-infinispan:${vertxVersion}")
implementation("io.vertx:vertx-health-check:${vertxVersion}")
implementation("ch.qos.logback:logback-classic:1.5.12")
}
application {
applicationDefaultJvmArgs =
listOf("-Djava.net.preferIPv4Stack=true", "-Dvertx.jgroups.config=default-configs/default-jgroups-udp.xml")
mainClass = verticle
}
jib {
to {
image = "clustering-kubernetes/backend"
}
container {
mainClass = "io.vertx.launcher.application.VertxApplication"
args = listOf(verticle, "-cluster")
ports = listOf("8080", "7800")
}
}
实现服务
让我们从*后端*服务开始。我们将继续处理*前端*,然后在开发机器上测试它们。
*后端*服务
*后端*服务封装在 BackendVerticle
类中。
它通过环境变量配置
backend/src/main/java/io/vertx/howtos/cluster/BackendVerticle.java
private static final int HTTP_PORT = Integer.parseInt(System.getenv().getOrDefault("HTTP_PORT", "0"));
private static final String POD_NAME = System.getenv().getOrDefault("POD_NAME", "unknown");
当 Verticle 启动时,它会注册一个*事件总线*消费者,设置一个 Vert.x Web Router
并绑定一个 HTTP 服务器
backend/src/main/java/io/vertx/howtos/cluster/BackendVerticle.java
@Override
public Future<?> start() {
Future<Void> registration = registerConsumer();
Router router = setupRouter();
Future<HttpServer> httpServer = vertx.createHttpServer()
.requestHandler(router)
.listen(HTTP_PORT)
.onSuccess(server -> log.info("Server started and listening on port {}", server.actualPort()));
return Future.join(registration, httpServer);
}
*事件总线*消费者接收发送到 greetings
地址的消息并格式化回复
backend/src/main/java/io/vertx/howtos/cluster/BackendVerticle.java
private Future<Void> registerConsumer() {
return vertx.eventBus().<String>consumer("greetings", msg -> {
msg.reply(String.format("Hello %s from %s", msg.body(), POD_NAME));
}).completion();
}
Router
通过 HTTP 暴露健康和就绪检查
backend/src/main/java/io/vertx/howtos/cluster/BackendVerticle.java
private Router setupRouter() {
Router router = Router.router(vertx);
router.get("/health").handler(rc -> rc.response().end("OK"));
Handler<Promise<Status>> procedure = ClusterHealthCheck.createProcedure(vertx, false);
HealthChecks checks = HealthChecks.create(vertx).register("cluster-health", procedure);
router.get("/readiness").handler(HealthCheckHandler.createWithHealthChecks(checks));
return router;
}
Vert.x Infinispan 提供开箱即用的集群健康检查。io.vertx.ext.cluster.infinispan.ClusterHealthCheck 验证底层 Infinispan 集群状态。 |
对于本地测试,main
方法是从 IDE 启动 Verticle 的一种简单方式
backend/src/main/java/io/vertx/howtos/cluster/BackendVerticle.java
public static void main(String[] args) {
Vertx.clusteredVertx(new VertxOptions())
.compose(vertx -> vertx.deployVerticle(new BackendVerticle()))
.await();
}
启动时,Vert.x Infinispan 使用默认的网络堆栈,该堆栈结合了用于发现的 IP 多播和用于组消息的 TCP 连接。此网络堆栈非常适合在我们的开发机器上进行测试。稍后我们将看到如何切换到适合部署到 Kubernetes 的堆栈。
*前端*服务
*前端*服务封装在 FrontendVerticle
类中。
它通过一个环境变量配置
frontend/src/main/java/io/vertx/howtos/cluster/FrontendVerticle.java
private static final int HTTP_PORT = Integer.parseInt(System.getenv().getOrDefault("HTTP_PORT", "8080"));
当 Verticle 启动时,它会设置一个 Vert.x Web Router
并绑定一个 HTTP 服务器
frontend/src/main/java/io/vertx/howtos/cluster/FrontendVerticle.java
@Override
public Future<?> start() {
Router router = Router.router(vertx);
setupRouter(router);
return vertx.createHttpServer()
.requestHandler(router)
.listen(HTTP_PORT)
.onSuccess(server -> log.info("Server started and listening on port {}", server.actualPort()));
}
Router
为 /hello
URI 定义了一个 *GET* 处理程序,此外它还通过 HTTP 暴露健康和就绪检查
frontend/src/main/java/io/vertx/howtos/cluster/FrontendVerticle.java
private void setupRouter(Router router) {
router.get("/hello").handler(this::handleHelloRequest);
router.get("/health").handler(rc -> rc.response().end("OK"));
Handler<Promise<Status>> procedure = ClusterHealthCheck.createProcedure(vertx, false);
HealthChecks checks = HealthChecks.create(vertx).register("cluster-health", procedure);
router.get("/readiness").handler(HealthCheckHandler.createWithHealthChecks(checks));
}
用于 /hello
URI 的 HTTP 请求处理程序提取 name
参数,通过*事件总线*发送请求并将回复转发给客户端
frontend/src/main/java/io/vertx/howtos/cluster/FrontendVerticle.java
private void handleHelloRequest(RoutingContext rc) {
vertx.eventBus().<String>request("greetings", rc.queryParams().get("name"))
.map(Message::body)
.onSuccess(reply -> rc.response().end(reply))
.onFailure(rc::fail);
}
对于本地测试,main
方法是从 IDE 启动 Verticle 的一种简单方式
frontend/src/main/java/io/vertx/howtos/cluster/FrontendVerticle.java
public static void main(String[] args) {
Vertx.clusteredVertx(new VertxOptions())
.compose(vertx -> vertx.deployVerticle(new FrontendVerticle()))
.await();
}
本地测试
您可以启动每个服务
-
直接从您的 IDE 中,或
-
使用 Maven:
mvn compile exec:java
,或者 -
使用 Gradle:
./gradlew run
(Linux, macOS) 或gradlew run
(Windows)。
*前端*服务输出应打印类似于以下内容的消息
2020-07-16 16:29:39,478 [vert.x-eventloop-thread-2] INFO i.v.howtos.cluster.FrontendVerticle - Server started and listening on port 8080
*后端*
2020-07-16 16:29:40,770 [vert.x-eventloop-thread-2] INFO i.v.howtos.cluster.BackendVerticle - Server started and listening on port 38621
记下 backend HTTP 服务器端口。默认情况下,它使用随机端口以避免与 frontend HTTP 服务器冲突。 |
首先,让我们向*前端*的 /hello
URI 发送一个请求,并将 name
查询参数设置为 Vert.x Clustering
http :8080/hello name=="Vert.x Clustering"
您应该看到类似如下内容
HTTP/1.1 200 OK content-length: 36 Hello Vert.x Clustering from unknown
当 POD_NAME 环境变量未定义时,unknown 是*后端*使用的默认 pod 名称。 |
我们还可以验证*前端*的就绪状态
http :8080/readiness HTTP/1.1 200 OK content-length: 65 content-type: application/json;charset=UTF-8 { "checks": [ { "id": "cluster-health", "status": "UP" } ], "outcome": "UP" }
以及*后端*
http :38621/readiness HTTP/1.1 200 OK content-length: 65 content-type: application/json;charset=UTF-8 { "checks": [ { "id": "cluster-health", "status": "UP" } ], "outcome": "UP"
部署到 Kubernetes
首先,通过 minikube status
确保 Minikube 已启动。
如果您不使用 Minikube,请验证 kubectl 是否已连接到您的集群。 |
推送容器镜像
有多种方法可以将容器镜像推送到 Minikube。
本文档中,我们将直接推送到集群内的 Docker 守护进程。为此,我们必须将 shell 指向 Minikube 的 Docker 守护进程
eval $(minikube -p minikube docker-env)
然后,在同一个 shell 中,我们可以使用 Jib 构建镜像
-
使用 Maven:
mvn compile jib:dockerBuild
,或 -
使用 Gradle:
./gradlew jibDockerBuild
(Linux, macOS) 或gradlew jibDockerBuild
(Windows)。
Jib 不会使用 Docker 守护进程来构建镜像,而只用于推送它。 |
如果您不使用 Minikube,请参阅 Jib Maven 或 Jib Gradle 插件文档,了解将镜像推送到注册表时如何配置它们。 |
集群应用无头服务
在 Kubernetes 上,Infinispan 不应使用默认的网络堆栈,因为 IP 多播通常不可用。
相反,我们将配置它使用一个依赖于无头服务查找进行发现以及 TCP 连接进行组消息的网络堆栈。
让我们创建一个 clustered-app
无头服务,它选择带有标签 cluster:clustered-app
的成员 Pod
headless-service.yml
apiVersion: v1
kind: Service
metadata:
name: clustered-app
spec:
selector:
cluster: clustered-app
ports:
- name: jgroups
port: 7800
protocol: TCP
publishNotReadyAddresses: true
clusterIP: None
无头服务必须考虑即使尚未就绪的 Pod(publishNotReadyAddresses 设置为 true )。 |
应用此配置
kubectl apply -f headless-service.yml
然后验证是否成功
kubectl get services clustered-app
您应该看到类似如下内容
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE clustered-app ClusterIP None <none> 7800/TCP 63m
*前端*部署和服务
现在让我们部署*前端*服务。
我们至少需要两个副本以实现高可用性。
要配置 Vert.x Infinispan,我们需要使用一些系统属性启动 JVM
-
java.net.preferIPv4Stack
-
vertx.jgroups.config
:网络堆栈配置文件,设置为default-configs/default-jgroups-kubernetes.xml
-
jgroups.dns.query
:我们刚创建的无头服务的 DNS 名称
然后 Kubernetes 需要知道活跃度、就绪性和启动探针的 URI。
启动探针可以指向就绪 URI,并具有不同的超时设置。 |
frontend/deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-deployment
labels:
app: frontend
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
cluster: clustered-app
spec:
containers:
- name: frontend
image: clustering-kubernetes/frontend:latest
imagePullPolicy: IfNotPresent
env:
- name: JAVA_TOOL_OPTIONS
value: "-Djava.net.preferIPv4Stack=true -Dvertx.jgroups.config=default-configs/default-jgroups-kubernetes.xml -Djgroups.dns.query=clustered-app.default.svc.cluster.local"
- name: HTTP_PORT
value: "8080"
ports:
- containerPort: 8080
- containerPort: 7800
livenessProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 1
periodSeconds: 10
readinessProbe:
httpGet:
path: /readiness
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
startupProbe:
httpGet:
path: /readiness
port: 8080
failureThreshold: 30
periodSeconds: 10
应用此配置
kubectl apply -f frontend/deployment.yml
验证 Pods 是否已成功启动
kubectl get pods
您应该看到类似如下内容
NAME READY STATUS RESTARTS AGE frontend-deployment-8cfd4d966-lpvsb 1/1 Running 0 4m58s frontend-deployment-8cfd4d966-tctgv 1/1 Running 0 4m58s
我们还需要一个服务来负载均衡 HTTP 流量。Pods 将通过部署中定义的标签 app:frontend
进行选择
frontend/service.yml
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer
应用此配置
kubectl apply -f frontend/service.yml
验证服务是否已成功创建
kubectl get services frontend
您应该看到类似如下内容
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE frontend LoadBalancer 10.106.16.88 <pending> 80:30729/TCP 62s
如果您使用 Minikube,请打开另一个终端窗口并运行
minikube tunnel
Minikube tunnel 作为单独的进程运行,并将服务暴露给主机操作系统。
如果您再次运行 kubectl get services frontend
,则外部 IP 应该已设置
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE frontend LoadBalancer 10.100.254.64 10.100.254.64 80:30660/TCP 30m
记下外部 IP。 |
Minikube tunnel 需要权限提升。如果您未被授予此权限,您仍然可以通过 *NodePort* 使用 |
如果您不使用 Minikube 并且您的服务未分配外部 IP,请参阅您的集群文档。 |
*后端*部署
*后端*服务的部署与*前端*服务类似。
请注意,在这种情况下
-
应创建 3 个副本
-
容器中将设置
POD_NAME
环境变量
backend/deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-deployment
labels:
app: backend
spec:
replicas: 3
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
cluster: clustered-app
spec:
containers:
- name: backend
image: clustering-kubernetes/backend:latest
imagePullPolicy: IfNotPresent
env:
- name: JAVA_TOOL_OPTIONS
value: "-Djava.net.preferIPv4Stack=true -Dvertx.jgroups.config=default-configs/default-jgroups-kubernetes.xml -Djgroups.dns.query=clustered-app.default.svc.cluster.local"
- name: HTTP_PORT
value: "8080"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
ports:
- containerPort: 8080
- containerPort: 7800
livenessProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 1
periodSeconds: 10
readinessProbe:
httpGet:
path: /readiness
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
startupProbe:
httpGet:
path: /readiness
port: 8080
failureThreshold: 30
periodSeconds: 10
应用此配置
kubectl apply -f backend/deployment.yml
验证 Pods 是否已成功启动
kubectl get pods
您应该看到类似如下内容
NAME READY STATUS RESTARTS AGE backend-deployment-74d7f45c67-h7h9c 1/1 Running 0 63s backend-deployment-74d7f45c67-r45bc 1/1 Running 0 63s backend-deployment-74d7f45c67-r75ht 1/1 Running 0 63s frontend-deployment-8cfd4d966-lpvsb 1/1 Running 0 15m frontend-deployment-8cfd4d966-tctgv 1/1 Running 0 15m
远程测试
现在我们可以向*前端*的 /hello
URI 发送请求,并将 name
查询参数设置为 Vert.x Clustering
http 10.100.254.64/hello name=="Vert.x Clustering"
您应该看到类似如下内容
HTTP/1.1 200 OK content-length: 64 Hello Vert.x Clustering from backend-deployment-74d7f45c67-6r2g2
请注意,我们现在可以看到 Pod 的名称而不是默认值(unknown
)。
此外,如果您重复发送请求,您将看到 backend
服务以循环方式接收*事件总线*请求。
总结
本文档涵盖了
-
在 Kubernetes 上使用 Infinispan 部署 Vert.x 集群应用所需的依赖项
-
使用 Jib 对 Vert.x 服务进行容器化
-
Vert.x Infinispan 集群管理器在本地测试和 Kubernetes 部署的配置