Kubernetes 客户端
Quarkus 包含 kubernetes-client
扩展,该扩展允许在原生模式下使用 Fabric8 Kubernetes Client,同时还使其更易于使用。
在 Quarkus 中拥有 Kubernetes 客户端扩展对于解锁 Kubernetes Operator 的强大功能非常有用。Kubernetes Operator 正在迅速成为一类新的云原生应用程序。这些应用程序本质上会监视 Kubernetes API 并对各种资源的变化做出反应,可用于管理数据库、消息系统等各种复杂系统的生命周期。能够使用原生镜像提供的极低占用空间的 Java 语言编写此类 Operator 是一个很好的匹配。
配置
配置好 Quarkus 项目后,您可以通过在项目根目录下运行以下命令,将 kubernetes-client
扩展添加到您的项目中。
quarkus extension add kubernetes-client
./mvnw quarkus:add-extension -Dextensions='kubernetes-client'
./gradlew addExtension --extensions='kubernetes-client'
这会将以下内容添加到您的构建文件中
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes-client</artifactId>
</dependency>
implementation("io.quarkus:quarkus-kubernetes-client")
用法
Quarkus 配置了一个 KubernetesClient
类型的 Bean,可以使用众所周知的 CDI 方法将其注入到应用程序代码中。可以使用各种属性配置此客户端,如下例所示
quarkus.kubernetes-client.trust-certs=false
quarkus.kubernetes-client.namespace=default
请注意,完整的属性列表可在配置参考的 Dev Services 部分 中找到。
在开发模式下以及在运行测试时,Kubernetes 的 Dev Services 会自动启动一个 Kubernetes API 服务器。
定制和覆盖
Quarkus 提供了多个集成点来影响作为 CDI Bean 提供的 Kubernetes 客户端。
Kubernetes 客户端配置定制
第一个集成点是使用 io.quarkus.kubernetes.client.KubernetesConfigCustomizer
接口。当存在此类 Bean 时,它允许对 Quarkus 创建的 io.fabric8.kubernetes.client.Config
进行任意定制(该配置会考虑 quarkus.kubernetes-client.*
属性)。
或者,应用程序代码可以通过简单地声明自定义版本的 io.fabric8.kubernetes.client.Config
或甚至 io.fabric8.kubernetes.client.KubernetesClient
Bean(通常由扩展提供)来覆盖它们。
以下代码片段展示了此示例
@Singleton
public class KubernetesClientProducer {
@Produces
public KubernetesClient kubernetesClient() {
// here you would create a custom client
return new DefaultKubernetesClient();
}
}
Kubernetes 客户端 ObjectMapper 定制
Fabric8 Kubernetes 客户端使用自己的 ObjectMapper
实例来序列化和反序列化 Kubernetes 资源。此映射器通过注入到 KubernetesClient
Bean 的 KubernetesSerialization
实例提供给客户端。
如果出于某种原因,您必须定制此扩展提供并被 Kubernetes 客户端使用的默认 ObjectMapper
Bean,您可以声明一个实现 KubernetesClientObjectMapperCustomizer
接口的 Bean 来做到这一点。
以下代码片段包含一个 KubernetesClientObjectMapperCustomizer
示例,用于设置 ObjectMapper
的区域设置
@Singleton
public static class Customizer implements KubernetesClientObjectMapperCustomizer {
@Override
public void customize(ObjectMapper objectMapper) {
objectMapper.setLocale(Locale.ROOT);
}
}
此外,如果您需要替换扩展自动创建的 Kubernetes 客户端使用的默认 ObjectMapper
Bean,您可以通过声明一个 @KubernetesClientObjectMapper
类型的 Bean 来做到这一点。以下代码片段显示了如何声明此 Bean
@Singleton
public class KubernetesObjectMapperProducer {
@KubernetesClientObjectMapper
@Singleton
@Produces
public ObjectMapper kubernetesClientObjectMapper() {
return new ObjectMapper();
}
}
已弃用静态的 io.fabric8.kubernetes.client.utils.Serialization utils 类,不应使用。应将对 Serialization.jsonMapper() 的访问替换为使用已声明的 @KubernetesClientObjectMapperCustomizer Bean。 |
测试
为了使针对模拟 Kubernetes API 的测试变得极其简单,Quarkus 提供了 WithKubernetesTestServer
注解,该注解会自动启动一个 Kubernetes API 服务器的模拟,并设置必要的环境变量,以便 Kubernetes 客户端将其自身配置为使用所述模拟。测试可以使用 @KubernetesTestServer
注解注入模拟服务器并以任何必要的方式进行设置,以满足特定测试需求。
假设我们定义了一个 REST 端点如下
@Path("/pod")
public class Pods {
private final KubernetesClient kubernetesClient;
public Pods(KubernetesClient kubernetesClient) {
this.kubernetesClient = kubernetesClient;
}
@GET
@Path("/{namespace}")
public List<Pod> pods(String namespace) {
return kubernetesClient.pods().inNamespace(namespace).list().getItems();
}
}
我们可以很容易地为该端点编写一个测试,如下所示
// you can even configure aspects like crud, https and port on this annotation
@WithKubernetesTestServer
@QuarkusTest
public class KubernetesClientTest {
@KubernetesTestServer
KubernetesServer mockServer;
@Inject
KubernetesClient client;
@BeforeEach
public void before() {
final Pod pod1 = new PodBuilder().withNewMetadata().withName("pod1").withNamespace("test").and().build();
final Pod pod2 = new PodBuilder().withNewMetadata().withName("pod2").withNamespace("test").and().build();
// Set up Kubernetes so that our "pretend" pods are created
client.pods().resource(pod1).create();
client.pods().resource(pod2).create();
}
@Test
public void testInteractionWithAPIServer() {
RestAssured.when().get("/pod/test").then()
.body("size()", is(2));
}
}
请注意,要利用这些功能,需要添加 quarkus-test-kubernetes-client
依赖项,例如
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-kubernetes-client</artifactId>
<scope>test</scope>
</dependency>
testImplementation("io.quarkus:quarkus-test-kubernetes-client")
默认情况下,模拟服务器将处于 CRUD 模式,因此您必须使用客户端在您的应用程序可以检索它之前构建其状态,但您也可以将其设置为非 CRUD 模式并模拟所有发往 Kubernetes 的 HTTP 请求
// you can even configure aspects like crud, https and port on this annotation
@WithKubernetesTestServer(crud = false)
@QuarkusTest
public class KubernetesClientTest {
@KubernetesTestServer
KubernetesServer mockServer;
@BeforeEach
public void before() {
final Pod pod1 = new PodBuilder().withNewMetadata().withName("pod1").withNamespace("test").and().build();
final Pod pod2 = new PodBuilder().withNewMetadata().withName("pod2").withNamespace("test").and().build();
// Mock any HTTP request to Kubernetes pods so that our pods are returned
mockServer.expect().get().withPath("/api/v1/namespaces/test/pods")
.andReturn(200,
new PodListBuilder().withNewMetadata().withResourceVersion("1").endMetadata().withItems(pod1, pod2)
.build())
.always();
}
@Test
public void testInteractionWithAPIServer() {
RestAssured.when().get("/pod/test").then()
.body("size()", is(2));
}
}
您还可以使用 @WithKubernetesTestServer
注解上的 setup
属性来提供一个将配置 KubernetesServer
实例的类
@WithKubernetesTestServer(setup = MyTest.Setup.class)
@QuarkusTest
public class MyTest {
public static class Setup implements Consumer<KubernetesServer> {
@Override
public void accept(KubernetesServer server) {
server.expect().get().withPath("/api/v1/namespaces/test/pods")
.andReturn(200, new PodList()).always();
}
}
// tests
}
或者,您可以创建一个 KubernetesServerTestResource
类的扩展,以确保所有启用 @QuarkusTest
的测试类都通过 QuarkusTestResource
注解共享相同的模拟服务器设置
public class CustomKubernetesMockServerTestResource extends KubernetesServerTestResource {
@Override
protected void configureServer() {
super.configureServer();
server.expect().get().withPath("/api/v1/namespaces/test/pods")
.andReturn(200, new PodList()).always();
}
}
并在您的其他测试类中如下使用
@QuarkusTestResource(CustomKubernetesMockServerTestResource.class)
@QuarkusTest
public class KubernetesClientTest {
//tests will now use the configured server...
}
关于实现或扩展泛型类型的注意事项
由于 GraalVM 施加的限制,在实现或扩展客户端提供的泛型类型时需要格外小心,如果应用程序打算在原生模式下工作。本质上,每个泛型类的实现或扩展,例如 Watcher
、ResourceHandler
或 CustomResource
,都需要在类定义时指定其关联的 Kubernetes 模型类(或者,对于 CustomResource
,是常规 Java 类型)。为了更好地理解这一点,假设我们要监视 Kubernetes Pod
资源的更改。有几种方法可以编写这样的 Watcher
,这些方法可以保证在原生模式下工作
client.pods().watch(new Watcher<Pod>() {
@Override
public void eventReceived(Action action, Pod pod) {
// do something
}
@Override
public void onClose(KubernetesClientException e) {
// do something
}
});
或
public class PodResourceWatcher implements Watcher<Pod> {
@Override
public void eventReceived(Action action, Pod pod) {
// do something
}
@Override
public void onClose(KubernetesClientException e) {
// do something
}
}
...
client.pods().watch(new PodResourceWatcher());
请注意,通过类似以下示例的类层次结构定义泛型类型也将正常工作
public abstract class MyWatcher<S> implements Watcher<S> {
}
...
client.pods().watch(new MyWatcher<Pod>() {
@Override
public void eventReceived(Action action, Pod pod) {
// do something
}
});
以下示例在原生模式下将不起作用,因为由于类和方法定义的限制,无法确定 watcher 的泛型类型,因此 Quarkus 无法正确确定需要反射注册的 Kubernetes 模型类 |
public class ResourceWatcher<T extends HasMetadata> implements Watcher<T> {
@Override
public void eventReceived(Action action, T resource) {
// do something
}
@Override
public void onClose(KubernetesClientException e) {
// do something
}
}
client.pods().watch(new ResourceWatcher<Pod>());
使用椭圆曲线密钥的注意事项
请注意,如果您想在 Kubernetes 客户端中使用椭圆曲线密钥,则需要添加 BouncyCastle PKIX 依赖项
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
</dependency>
implementation("org.bouncycastle:bcpkix-jdk18on")
请注意,如果 org.bouncycastle.jce.provider.BouncyCastleProvider
尚未注册,它将在内部注册。
您可以按照 BouncyCastle 或 BouncyCastle FIPS 部分中的描述注册此提供程序。
访问 Kubernetes API
在许多情况下,要访问 Kubernetes API 服务器,需要 ServiceAccount
、Role
和 RoleBinding
。允许列出所有 Pod 的示例可能看起来像这样
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: <applicationName>
namespace: <namespace>
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: <applicationName>
namespace: <namespace>
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: <applicationName>
namespace: <namespace>
roleRef:
kind: Role
name: <applicationName>
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: <applicationName>
namespace: <namespace>
将 <applicationName>
和 <namespace>
替换为您自己的值。有关更多信息,请参阅 配置 Pod 的服务帐户。
OpenShift 客户端
如果目标 Kubernetes 集群是 OpenShift 集群,则可以通过 openshift-client
扩展以类似的方式访问它。这利用了专用的 fabric8 OpenShift 客户端,并提供了对 OpenShift
专有对象(例如 Route
、ProjectRequest
、BuildConfig
等)的访问。
请注意,配置属性与 kubernetes-client
扩展共享。特别是,它们具有相同的 quarkus.kubernetes-client
前缀。
通过以下方式添加扩展
quarkus extension add openshift-client
./mvnw quarkus:add-extension -Dextensions='openshift-client'
./gradlew addExtension --extensions='openshift-client'
请注意,openshift-client
扩展依赖于 kubernetes-client
扩展。
要使用客户端,请注入 OpenShiftClient
而不是 KubernetesClient
@Inject
private OpenShiftClient openshiftClient;
如果您需要覆盖默认的 OpenShiftClient
,请提供一个生产者,例如
@Singleton
public class OpenShiftClientProducer {
@Produces
public OpenShiftClient openshiftClient() {
// here you would create a custom client
return new DefaultOpenShiftClient();
}
}
在上一节中解释的 @WithKubernetesTestServer
也可以以类似的方式提供模拟支持
@WithKubernetesTestServer
@QuarkusTest
public class OpenShiftClientTest {
@KubernetesTestServer
KubernetesServer mockServer;
@Inject
OpenShiftClient client;
@Test
public void testInteractionWithAPIServer() {
RestAssured.when().get("/route/test").then()
.body("size()", is(2));
}
}
要使用此功能,您必须添加对 quarkus-test-kubernetes-client
的依赖
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-kubernetes-client</artifactId>
<scope>test</scope>
</dependency>
testImplementation("io.quarkus:quarkus-test-kubernetes-client")
优化原生镜像
Kubernetes 和 OpenShift 客户端扩展旨在提供出色的开发者体验,同时使客户端能够在原生模式下工作。在构建原生镜像时,Kubernetes 客户端扩展将注册所有可访问的 Kubernetes 模型类以供反射使用。不幸的是,这可能导致原生镜像体积庞大和构建时间延长。
完成应用程序实现后,如果您想将应用程序分发和部署为原生镜像,您应该考虑通过遵循以下指南来减小其体积。
使用 Kubernetes 客户端扩展
OpenShift Client
为常见的 OpenShift 资源提供特定领域语言 (DSL) 访问器。此外,该扩展还提供了必要的项目配置,以引入 OpenShift 模型类型模块。
在 JVM 模式下,这效果很好,因为作为开发人员,您无需担心配置。但是,在原生模式下,通过依赖 OpenShift 扩展,您引入了许多应用程序可能不需要的资源,从而不必要地增加了其体积。
在这种情况下,最好只依赖您需要的,通过添加 Kubernetes 客户端扩展的依赖项和最小的 OpenShift 模型依赖项
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes-client</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>openshift-model</artifactId>
</dependency>
implementation("io.quarkus:quarkus-kubernetes-client")
implementation("io.fabric8:openshift-model")
您将无法使用特定于 OpenShift 的 DSL 访问器,因为您现在将拥有一个 KubernetesClient
类型的 Bean 而不是 OpenShiftClient
。但是,Fabric8 Kubernetes 客户端提供了通用的入口点来对任何资源执行操作
// List OpenShift Routes in any namespace
kubernetesClient
.resources(io.fabric8.openshift.api.model.Route.class)
.inAnyNamespace().list();
// Delete an OpenShift Route
kubernetesClient
.resources(io.fabric8.openshift.api.model.Route.class)
.inNamespace("default").withName("the-route").delete();
// Create or replace a new OpenShift Route
kubernetesClient
.resource(new RouteBuilder()/* ... */.build())
.inNamespace("default").createOr(NonDeletingOperation::update);
仅依赖您需要的模块
Kubernetes 客户端扩展具有对所有标准 Kubernetes API 模型类型的传递依赖项。这在 JVM 模式下非常方便,因为您不必担心配置项目。
但是,在原生模式下,这意味着为模型类型注册反射,这些模型类型很可能在您的应用程序中不会被使用。您可以通过提供更细粒度的项目配置并仅依赖于您确信应用程序使用的模型来缓解此问题。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes-client</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client-api</artifactId>
<!-- Exclude all transitive dependencies -->
<exclusions>
<exclusion>
<groupId>io.fabric8</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Include only those that make sense for your application -->
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-model-core</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-model-admissionregistration</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-model-apps</artifactId>
</dependency>
<!-- ... -->
implementation("quarkus-kubernetes-client")
implementation("io.fabric8:kubernetes-client-api") {
// Exclude all transitive dependencies
exclude group: "io.fabric8"
}
// Include only those that make sense for your application
implementation("io.fabric8:kubernetes-client")
implementation("io.fabric8:kubernetes-model-core")
implementation("io.fabric8:kubernetes-model-admissionregistration")
implementation("io.fabric8:kubernetes-model-apps")
// ...
配置参考
构建时固定的配置属性 - 所有其他配置属性都可以在运行时覆盖
配置属性 |
类型 |
默认 |
---|---|---|
如果 API 服务器提供自签名证书,客户端是否应信任该证书 环境变量: 显示更多 |
布尔值 |
|
Kubernetes API 服务器的 URL 环境变量: 显示更多 |
字符串 |
|
字符串 |
||
字符串 |
||
字符串 |
||
客户端证书文件 环境变量: 显示更多 |
字符串 |
|
客户端证书数据 环境变量: 显示更多 |
字符串 |
|
客户端密钥文件 环境变量: 显示更多 |
字符串 |
|
客户端密钥数据 环境变量: 显示更多 |
字符串 |
|
客户端密钥算法 环境变量: 显示更多 |
字符串 |
|
客户端密钥密码 环境变量: 显示更多 |
字符串 |
|
字符串 |
||
字符串 |
||
字符串 |
||
Watch 重连间隔 环境变量: 显示更多 |
||
Watch 失败情况下的最大重连尝试次数。默认情况下,重连尝试次数没有限制 环境变量: 显示更多 |
整数 |
|
等待与 API 服务器建立连接的最大时长 环境变量: 显示更多 |
||
等待 API 服务器完成请求的最大时长 环境变量: 显示更多 |
||
HTTP 代码 >= 500 的 API 请求的最大重试尝试次数 环境变量: 显示更多 |
整数 |
|
HTTP 代码 >= 500 的 API 请求的重试尝试之间的时间间隔 环境变量: 显示更多 |
||
用于访问 Kubernetes API 服务器的 HTTP 代理 环境变量: 显示更多 |
字符串 |
|
用于访问 Kubernetes API 服务器的 HTTPS 代理 环境变量: 显示更多 |
字符串 |
|
字符串 |
||
字符串 |
||
字符串列表 |
||
启用 RBAC 清单的生成。如果启用并且没有使用属性 环境变量: 显示更多 |
布尔值 |
|
类型 |
默认 |
|
是否应使用 Kubernetes 的开发服务。(默认为 true)如果为 true 并且未配置 Kubernetes 客户端,则将启动并使用 Kubernetes 集群。 环境变量: 显示更多 |
布尔值 |
|
要使用的 kubernetes api 服务器版本。如果未设置,Kubernetes 的 Dev Services 将使用给定版本的最新支持版本。请参阅 https://github.com/dajudge/kindcontainer/blob/master/k8s-versions.json 环境变量: 显示更多 |
字符串 |
|
要使用的风格(kind、k3s 或 api-only)。如果未设置,Kubernetes 的开发服务将设置为:api-only。 环境变量: 显示更多 |
|
|
默认情况下,如果找到 kubeconfig,Kubernetes 的开发服务将不会启动。将此设置为 true 可覆盖 kubeconfig 配置。 环境变量: 显示更多 |
布尔值 |
|
指示 Quarkus Dev Services 管理的 Kubernetes 集群是否共享。当共享时,Quarkus 通过基于标签的服务发现来查找正在运行的容器。如果找到匹配的容器,则使用它,因此不会启动第二个容器。否则,Dev Services for Kubernetes 将启动一个新容器。 发现使用 容器共享仅在开发模式下使用。 环境变量: 显示更多 |
布尔值 |
|
附加到已启动容器的 当您需要多个共享 Kubernetes 集群时,将使用此属性。 环境变量: 显示更多 |
字符串 |
|
传递给容器的环境变量。 环境变量: 显示更多 |
Map<String,String> |
关于 Duration 格式
要写入持续时间值,请使用标准的 您还可以使用简化的格式,以数字开头
在其他情况下,简化格式将被转换为
|