1. 简介

本教程将展示如何在 Java 应用中使用 Kubernetes 官方提供的客户端库来调用 Kubernetes API。

2. 为什么需要调用 Kubernetes API?

如今,Kubernetes 已成为管理容器化应用的事实标准。它提供了一套功能强大的 API,允许我们部署、扩缩容和监控应用及其相关资源,比如存储、密钥和环境变量。可以这么说,这套 API 是分布式系统中的“系统调用”。

大多数情况下,我们的应用其实并不需要感知自己运行在 Kubernetes 上。这是一件好事,因为这意味着我们可以本地开发,然后通过几条命令和 YAML 配置轻松地部署到多个云平台,改动很少。

但有些场景下,我们确实需要与 Kubernetes API 交互才能实现特定功能:

✅ 启动一个外部程序执行任务,并在之后获取其完成状态
✅ 根据用户请求动态创建或修改服务
✅ 跨多个 Kubernetes 集群(甚至跨云平台)构建自定义监控仪表盘

虽然这些场景不常见,但借助 Kubernetes API,实现起来其实非常简单直接。

而且,由于 Kubernetes API 是开放规范,我们可以放心地认为代码能在任何合规的实现上无修改运行。

3. 本地开发环境准备

在开始编写应用前,我们首先需要一个可用的 Kubernetes 集群。虽然可以选择使用公有云平台,但在本地搭建轻量级集群通常更便于控制配置细节。

以下是一些适合本地开发的轻量级发行版:

K3S
Minikube
Kind

具体安装步骤这里不再赘述,无论选择哪种方式,请确保在开始开发前能正常运行 kubectl 命令。

4. Maven 依赖配置

首先,我们在项目的 pom.xml 中添加 Kubernetes Java 客户端依赖:

<dependency>
    <groupId>io.kubernetes</groupId>
    <artifactId>client-java</artifactId>
    <version>11.0.0</version>
</dependency>

最新版本的 client-java 可从 Maven Central 获取。

5. Hello, Kubernetes

接下来我们写一个简单的示例程序,列出当前集群中的节点,并打印一些基本信息。

尽管这个例子很简单,但它完整展示了连接集群并调用 API 所需的基本步骤。无论你在实际项目中调用哪个接口,这些步骤都是通用的。

5.1. 初始化 ApiClient

ApiClient 类是整个客户端库中最核心的类之一,它封装了与 Kubernetes API Server 通信的所有逻辑。推荐使用 Config 类中的静态方法来初始化这个对象。最简单的方式是使用 defaultClient() 方法:

ApiClient client = Config.defaultClient();

使用该方法可以确保我们的代码既能在本地开发环境中运行,也能部署到集群内部。它会自动按照以下顺序查找配置文件:

✅ 由 KUBECONFIG 环境变量指定的配置文件
$HOME/.kube/config 文件
✅ Pod 内部的服务账户令牌:*/var/run/secrets/kubernetes.io/serviceaccount*
✅ 直接访问本地地址:http://localhost:8080

⚠️ 第三项特别重要,它使得我们的应用可以在集群内部以 Pod 形式运行时,依然能正常访问 API。

另外,如果配置文件中定义了多个上下文(context),此方法会自动使用当前设置为默认的那个(通过 kubectl config set-context 设置)。

5.2. 创建 API Stub

有了 ApiClient 实例后,我们可以用它来创建各种 Kubernetes API 的调用桩(stub)。在这个例子中,我们将使用 CoreV1Api 类来获取节点列表:

CoreV1Api api = new CoreV1Api(client);

这里我们传入了之前创建的 ApiClient 实例。

⚠️ 虽然也提供了无参构造函数,但我们应避免使用它。因为其内部会依赖一个全局默认的 ApiClient 实例,必须提前调用 Configuration.setDefaultApiClient() 设置好,否则会导致运行时异常或维护困难。

更好的做法是使用依赖注入框架统一管理这些对象的生命周期,并按需注入到业务代码中。

5.3. 调用 Kubernetes API

最后一步就是真正发起 API 请求,获取节点信息。CoreV1Api 提供了 listNode 方法,正好满足需求:

V1NodeList nodeList = api.listNode(null, null, null, null, null, null, null, null, 10, false);
nodeList.getItems()
  .stream()
  .forEach((node) -> System.out.println(node));

在这个例子中,我们对大部分参数传了 null,因为它们是可选的。最后两个参数适用于所有 listXXX 类型的方法,分别表示请求超时时间和是否开启 Watch 模式。

下面是 listNode 的完整签名:

public V1NodeList listNode(
  String pretty,
  Boolean allowWatchBookmarks,
  String _continue,
  String fieldSelector,
  String labelSelector,
  Integer limit,
  String resourceVersion,
  String resourceVersionMatch,
  Integer timeoutSeconds,
  Boolean watch) {
    // ... method implementation
}

本次入门仅介绍基础使用,暂时忽略分页、过滤和 Watch 相关参数。返回值是一个 POJO 对象,对应 API 返回的数据结构。对于节点列表,返回的是 V1Node 对象列表,每个对象都包含大量节点信息。下面是典型的输出示例:

class V1Node {
    metadata: class V1ObjectMeta {
        labels: {
            beta.kubernetes.io/arch=amd64,
            beta.kubernetes.io/instance-type=k3s,
            // ... 其他标签省略
        }
        name: rancher-template
        resourceVersion: 29218
        selfLink: null
        uid: ac21e09b-e3be-49c3-9e3a-a9567b5c2836
    }
    // ... 多个字段省略
    status: class V1NodeStatus {
        addresses: [class V1NodeAddress {
            address: 192.168.71.134
            type: InternalIP
        }, class V1NodeAddress {
            address: rancher-template
            type: Hostname
        }]
        allocatable: {
            cpu=Quantity{number=1, format=DECIMAL_SI},
            ephemeral-storage=Quantity{number=18945365592, format=DECIMAL_SI},
            hugepages-1Gi=Quantity{number=0, format=DECIMAL_SI},
            hugepages-2Mi=Quantity{number=0, format=DECIMAL_SI},
            memory=Quantity{number=8340054016, format=BINARY_SI}, 
            pods=Quantity{number=110, format=DECIMAL_SI}
        }
        capacity: {
            cpu=Quantity{number=1, format=DECIMAL_SI},
            ephemeral-storage=Quantity{number=19942490112, format=BINARY_SI}, 
            hugepages-1Gi=Quantity{number=0, format=DECIMAL_SI}, 
            hugepages-2Mi=Quantity{number=0, format=DECIMAL_SI}, 
            memory=Quantity{number=8340054016, format=BINARY_SI}, 
            pods=Quantity{number=110, format=DECIMAL_SI}}
        conditions: [
            // ... 节点状态省略
        ]
        nodeInfo: class V1NodeSystemInfo {
            architecture: amd64
            kernelVersion: 4.15.0-135-generic
            kubeProxyVersion: v1.20.2+k3s1
            kubeletVersion: v1.20.2+k3s1
            operatingSystem: linux
            osImage: Ubuntu 18.04.5 LTS
            // ... 更多字段省略
        }
    }
}

可以看到,返回的信息非常丰富。作为对比,下面是等效的 kubectl 命令输出:

root@rancher-template:~# kubectl get nodes
NAME               STATUS   ROLES                  AGE   VERSION
rancher-template   Ready    control-plane,master   24h   v1.20.2+k3s1

6. 总结

本文带你快速入门了 Kubernetes Java 客户端的基本使用。后续我们会进一步深入探讨:

✅ 不同 API 调用方式的区别
✅ 如何使用 Watch 实时监听集群事件
✅ 如何利用分页高效获取大量数据

一如既往,本文的完整示例代码可以在 GitHub 找到。


原始标题:A Quick Intro to the Kubernetes Java Client