依赖引入

本文例子基于 k8s 1.29.5 版本

java 调 k8s 的接口,需要用到 k8s 官方提供的依赖

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

sdk 与 k8s 版本兼容性可以在这查看:https://github.com/kubernetes-client/java/wiki/2.-Versioning-and-Compatibility

其他注意事项:

  • jdk 版本兼容性:如果你使用的是 jdk8 开发,那么最高只能使用 20.0.1 版本的 client-java,从 21.x 版本开始,最低要求 jdk11

  • sdk 中方法入参方式:从 20.0.0 版本开始,各个方法入参方式都和老版本入参方式不同,按官方的说法,如果你想使用老版本的入参方式,可以使用带有 "-legacy" 后缀的版本,比如 "23.0.0-legacy",这一点在 github 中有相应的描述 https://github.com/kubernetes-client/java

创建 ServiceAccount、角色、关联 Pod(必须)

ServiceAccount 是 k8s 的一个身份标识,k8s 通过 rbac 方式进行权限管控,要想通过接口方式操作 k8s 集群,需要先创建角色,给角色分配对应的权限,然后创建 ServiceAccount 并与角色关联

角色也分为具体命名空间范围的角色(Role)和整个集群范围的角色(ClusterRole)

例子

1、在 kk-test 命名空间下创建一个名为 kk-test-service-account 的身份标识

2、给他分配 kk-test-pod 这个命名空间的一个名为 kk-test-role 的角色

3、最终效果是 kk-test-service-account 这个身份标识可以在 kk-test-pod 这个命名空间下对 Pod 进行增删改查操作

# 定义ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
  name: kk-test-service-account
  namespace: kk-test

---
# 定义Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: kk-test-role
  namespace: kk-test-pod
rules:
    # 指定允许使用哪些api组
  - apiGroups: [""]
    # 指定允许操作哪些资源
    resources: ["pods"]
    # 指定允许对资源进行哪些操作
    verbs: ["create", "watch", "get", "list", "delete", "update", "patch"]

---
# 将Role绑定到ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: kk-test-service-account-rolebinding
  # Role所在的命名空间
  namespace: kk-test-pod
roleRef:
  kind: Role
  name: kk-test-role
  apiGroup: rbac.authorization.k8s.io
subjects:
  - kind: ServiceAccount
    name: kk-test-service-account
    # ServiceAccount所在命名空间
    namespace: kk-test

如果是想创建一个集群范围的角色(ClusterRole),那就将定义 Role 的部分改成下面内容

# 定义Role
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: kk-test-role
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create", "watch", "get", "list", "delete", "update", "patch"]

核心参数解释

  • apiGroups:指定可以访问的 api 组,常见 api 组如下

作用

空字符串 ""

核心 api 组,可以操作 k8s 的核心对象,比如 Pod、Service、ConfigMap、Secret

apps

应用程序部署相关的对象,比如 Deployment、StatefulSet、DaemonSet、ReplicaSet

batch

批处理作业相关的对象,比如 Job、CronJob

networking.k8s.io

网络相关的对象,比如 Ingress、NetworkPolicy

storage.k8s.io

存储相关的对象,比如 StorageClass、PersistentVolume、PersistentVolumeClaim

rbac.authorization.k8s.io

RBAC 相关的对象,比如 Role、ClusterRole、RoleBinding、ClusterRoleBinding

autoscaling

自动缩放相关的对象,HorizontalPodAutoscaler

events.k8s.io

Event 对象

  • resources:指定可以操作的资源类型,可以用这条命令看到所有可以操作的资源以及所属的 api 组 kubectl api-resources​

  • verbs:指定允许对资源操作的动作(每种资源可以操作的动作不一样),如果填 * ,那就是允许所有操作。常见的动作有:get、list、watch、create、update、patch、delete、deletecollection、proxy、connect、redirect、portforward

情况一:调 k8s 的服务也在 k8s 集群中(推荐)

这种方式,需要将这个服务所在的 Pod 绑定上面创建的 ServiceAccount,这样,才能让 Pod 里的程序能够有权限操作 k8s 集群

例如用 Deployment 方式启动 Pod,则在 spec.template.spec.serviceAccountName 填上对应的 ServiceAccount 名即可:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: k8s-client-service
  namespace: kk-test
  labels:
    app: k8s-client-service
spec:
  replicas: 1
  selector:
    matchLabels:
      app: k8s-client-service
  strategy: {}
  template:
    metadata:
      labels:
        app: k8s-client-service
    spec:
      # 这里指定将哪个ServiceAccount绑定到Pod中
      serviceAccountName: kk-test-service-account
      containers:
        - name: k8s-client-service-container
          image: test-client:v1.0.0
          imagePullPolicy: IfNotPresent

使用例子:

public static void main(String[] args) throws Exception {
	// 1、创建api client
	ApiClient client = Config.defaultClient();
	Configuration.setDefaultApiClient(client);

	// 2、pod相关操作都在 CoreV1Api 中
	CoreV1Api v1Api = new CoreV1Api();
	try {
        // 3、获取某个pod的信息,如果pod不存在,会抛出 ApiException 异常,并且 code=404
		V1Pod pod = v1Api.readNamespacedPod("pod001", "namespace001").execute();

		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.registerModule(new JavaTimeModule());

		// 打印pod信息
		System.out.println(objectMapper.writeValueAsString(pod));
	} catch (ApiException e) {
		if (e.getCode() == 404) {
			System.out.println("Pod不存在");
		}
		throw e;
	}
}

上面这个例子里,在创建 api client 这一步可以看到,我们并没有手动填写 k8s api 服务相关的信息(比如 ip、端口、认证信息)

这是因为 k8s 提供了一种机制,k8s 会将访问 k8s 集群的相关配置信息(api 服务地址、认证信息等)自动传入 Pod 中,那么这个 Pod 里面的 java 程序创建 api client 时,会自动获取 Pod 中的 k8s 连接信息,不需要再手动配置

如果你恰好有一个正在运行的 Pod(就算没有在配置文件中指定关联的 ServiceAccount 也可以),可以登陆进容器里面看看

kubectl exec -it <pod-name> bash -n <namespace>

可以看到容器里会有这一个文件夹 /var/run/secrets/kubernetes.io/serviceaccount​,文件夹里有 ca.crt、namespace、token 这三个文件

咱们再 echo 一下两个环境变量 KUBERNETES_SERVICE_HOST​、KUBERNETES_SERVICE_PORT​,其实里面填的正是 k8s 接口服务的 ip 和端口

这种方式完全依赖于 k8s 内部机制,使用起来也是最简便,推荐!!

情况二:请求 k8s 的服务不在 k8s 集群中(外部服务)

这种情况下,只能手动的配置连接信息了,并且要保证访问 k8s 接口服务的网络链路是通的

要配置连接信息,也有几种方式:

  • 使用 KubeConfig 配置文件

  • 使用 ServiceAccount Token 和 CA 证书

下面针对每种方式展开讲解

使用 KubeConfig 配置文件

KubeConfig 配置文件是一个 yaml 格式的文件,也是 k8s 命令行工具 kubectl 使用的配置文件,可以在 master 节点用 kubectl 将配置导出,但是这种方式拿到的配置,是最高权限的配置,在权限管控严格的情况下不适用

kubectl config view --raw

生成 KubeConfig 配置文件

直接使用 kubectl 的配置文件

自己在开发环境测试时,可以直接使用 kubectl 的配置文件,用下面这条命令获取

kubectl config view --raw

给某个 ServiceAccount 生成配置文件

kubectl 没有相应的命令来生产 ServiceAccount 的 KubeConfig 配置文件,需要我们手动拼装

ServiceAccount 的 KubeConfig 的内容格式如下:

apiVersion: v1
kind: Config
# 集群信息
clusters:
  # 集群名,随便填,在当前文件中唯一即可
- name: my-cluster
  cluster:
    # CA证书数据再进行base64编码后的字符串
    certificate-authority-data: <base64-encoded-certificate>
    # k8s api服务的地址和端口
    server: "https://192.168.10.11:443"

# 定义用户认证信息
users:
  # 定义一个用户名称,随便填,在当前文件中唯一即可
- name: kk-test-service-account-username
  user:
    # 需要绑定的ServiceAccount对应的token
    token: <service-account-token>

# 定义关联上下文
contexts:
  # 上下文名称,随便填,当前文件中唯一即可
- name: kk-test-service-account-context
  context:
    # 上面定义的集群的name
    cluster: my-cluster
    # 上面定义的用户的name
    user: kk-test-service-account-username

# 上面定义的context的name,指定使用哪个上下文
current-context: kk-test-service-account-context

配置文件中有几个值是需要从 ServiceAccount 信息中获取的:

  • clusters.cluster.certificate-authority-data:ServiceAccount 访问 api 服务时的 CA 证书数据再进行 base64 后的字符串

  • users.user.token:需要绑定的 ServiceAccount 对应的 token 再进行 base64 编码后的字符串

上面这几个值怎么获取,请查看下面【使用 ServiceAccount Token 和 CA 证书】这一节

指定配置文件路径

有了配置文件之后,就可以上代码了,很简单,将配置文件的路径传进去即可,这里也有三种方式:

  • 代码中指定配置文件路径

  • 将配置文件路径写入环境变量 KUBECONFIG​,Config.defaultClient() 方法会自动读取

  • 将配置文件放入家目录下的 .kube 文件夹下,并且文件名为 config(如:/home/user001/.kube/config),Config.defaultClient() 方法会自动读取,但如果设置了 KUBECONFIG​ ​这个环境变量,则不会再读取家目录下的配置文件

例子:代码中指定配置文件路径

public static void main(String[] args) throws Exception {
	// 1、创建api client
	ApiClient client = Config.fromConfig("./kubeconfig.yaml");
	Configuration.setDefaultApiClient(client);

	// 2、pod相关操作都在 CoreV1Api 中
	CoreV1Api v1Api = new CoreV1Api();
	try {
        // 3、获取某个pod的信息,如果pod不存在,会抛出 ApiException 异常,并且 code=404
		V1Pod pod = v1Api.readNamespacedPod("pod001", "namespace001").execute();

		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.registerModule(new JavaTimeModule());

		// 打印pod信息
		System.out.println(objectMapper.writeValueAsString(pod));
	} catch (ApiException e) {
		if (e.getCode() == 404) {
			System.out.println("Pod不存在");
		}
		throw e;
	}
}

使用 ServiceAccount Token 和 CA 证书

token 和 ca 证书都属于 ServiceAccount 的认证信息,是访问接口时必传的参数

获取 Token 和 CA 证书

获取 token 和 ca 证书,需要从 ServiceAccount 绑定的 Secret 中获取,而在 k8s 1.24 版本之前,是会自动给 ServiceAccount 创建一个 Secret 的,而在 1.24 及之后的版本,就需要用户手动创建 Secret 并关联给 ServiceAccount

情况一:ServiceAccount 有自动关联的 secret(v1.24 之前版本)

执行下面指令(这种方式只能看到 k8s 自动为 ServiceAccount 创建的 secret)

kubectl get serviceaccount kk-test-service-account -n kk-test -o yaml

找到 secret 字段(如果找不到这个字段,那就属于情况二,直接看情况二的处理方式吧),得到 secret 的名称,然后在执行

kubectl get secret <secret-name> -n kk-test -o yaml

data.token:ServiceAccount 的 token(base64 编码后的,解码后可以看到真实的 token)

data.ca.crt:CA 证书(base64 编码后的,解码后可以看到真实的 CA 证书)

情况二:ServiceAccount 没有自动关联的 secret(v1.24 及之后版本)

k8s 1.24 及之后版本是不会自动给 ServiceAccount 创建 secret 的,需要我们手动创建并关联上

定义 Secret

apiVersion: v1
kind: Secret
metadata:
  name: kk-test-service-account-secret
  # 要关联的ServiceAccount的所在命名空间
  namespace: kk-test
  annotations:
    # 指定关联的ServiceAccount
    kubernetes.io/service-account.name: "kk-test-service-account"
type: kubernetes.io/service-account-token

按道理这个配置文件还需要填写 data 字段内容的,但 k8s 1.24 及以上版本可以不手动填,k8s 会自动填上 data 字段

注意了:1.24 版本开始,kubectl get serviceaccount 命令已经看不到关联的 Secret 了,要看 ServiceAccount 关联的上面方式创建的 Secret,需要使用 kubeclt 的字段选择器,再配合 grep 来查找

kubectl get secrets -A -o yaml --field-selector type=kubernetes.io/service-account-token | grep -A 100 "kubernetes.io/service-account.name: kk-test-service-account"

执行下面命令查看 Secret 的 token 和 ca 证书

kubectl get secret kk-test-service-account-secret -n kk-test -o yaml

拿到的是 base64 编码后的,使用前记得解码

代码中使用

折腾了这么久,终于可以上代码了

public static void main(String[] args) throws Exception {
	// 1、创建api client
	ApiClient client = new ClientBuilder()
                .setBasePath("https://<api-server-address>:<port>")
                .setApiKeyPrefix("Bearer")
                .setApiKey("ServiceAccount的token")
                .setCertificateAuthority("CA证书文件路径")
                .build();
	Configuration.setDefaultApiClient(client);

	// 2、pod相关操作都在 CoreV1Api 中
	CoreV1Api v1Api = new CoreV1Api();
	try {
        // 3、获取某个pod的信息,如果pod不存在,会抛出 ApiException 异常,并且 code=404
		V1Pod pod = v1Api.readNamespacedPod("pod001", "namespace001").execute();

		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.registerModule(new JavaTimeModule());

		// 打印pod信息
		System.out.println(objectMapper.writeValueAsString(pod));
	} catch (ApiException e) {
		if (e.getCode() == 404) {
			System.out.println("Pod不存在");
		}
		throw e;
	}
}