Java操作Kubernetes
起因:公司准备将应用容器化部署,先使用了华为云的
Kubernetes
服务,后面又使用阿里云的Kubernetes
服务。并短期一个月内无法判断走哪个云商。而作为一个在公司内部用于应用发布,部署的应用。在对接完华为云的Kubernetes
服务Api
后。再对接阿里云发现阿里云并没用像华为云一样对Kubernetes
的Api
做简易的封装。其两者的区别是华为云可以通过ak
,sk
再加Kubernetes
Api
获取数据。可以理解为华为云多了一层代理。
添加依赖
本篇使用的是官方维护的
Kubernetes Java Client
包 。有兴趣的可以了解下面的社区维护版
建议使用最新版本 maven中央仓库
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java</artifactId>
<version>13.0.0</version>
</dependency>
准备 kubeconfig 文件
用于配置集群访问的文件称为
kubeconfig
文件。默认情况下,kubectl
在$HOME/.kube
目录下查找名为config
的文件。什么是 kubeconfig
阿里云
容器服务 - Kubernetes 》
进入集群 》
集群信息 》
连接信息。复制内容
华为云
云容器引擎 》
资源管理 》
集群管理 》
进入集群 》
基本信息右边 kubectl 》
下载kubectl 配置文件
可以使用 lens 工具管理
Kubernetes
集群
Java 连接 Kubernetes
首先将这个内容文件重命名为 config 。
使用上:官方例子获取所有 NameSpaces 下的 Pod。
package io.kubernetes.client.examples;
import io.kubernetes.client.ApiClient;
import io.kubernetes.client.ApiException;
import io.kubernetes.client.Configuration;
import io.kubernetes.client.apis.CoreV1Api;
import io.kubernetes.client.models.V1Pod;
import io.kubernetes.client.models.V1PodList;
import io.kubernetes.client.util.ClientBuilder;
import io.kubernetes.client.util.KubeConfig;
import java.io.FileReader;
import java.io.IOException;
/**
* A simple example of how to use the Java API from an application outside a kubernetes cluster
*
* <p>Easiest way to run this: mvn exec:java
* -Dexec.mainClass="io.kubernetes.client.examples.KubeConfigFileClientExample"
*
*/
public class KubeConfigFileClientExample {
public static void main(String[] args) throws IOException, ApiException {
// file path to your KubeConfig (看你自己config文件放哪)
String kubeConfigPath = "~/.kube/config";
// loading the out-of-cluster config, a kubeconfig from file-system
ApiClient client =
ClientBuilder.kubeconfig(KubeConfig.loadKubeConfig(new FileReader(kubeConfigPath))).build();
// set the global default api-client to the in-cluster one from above
Configuration.setDefaultApiClient(client);
// the CoreV1Api loads default api-client from global configuration.
CoreV1Api api = new CoreV1Api();
// invokes the CoreV1Api client
V1PodList list = api.listPodForAllNamespaces(null, null, null, null, null, null, null, null, null);
System.out.println("Listing all pods: ");
for (V1Pod item : list.getItems()) {
System.out.println(item.getMetadata().getName());
}
}
}
但我不想使用第三方云存储如阿里云的 OSS 或者在 Jar 包的相对路径下放置一份 config 文件。所以我会把这个文件放在 Jar 包内。也就是我放在 resources 下的自建目录 kubernetes 下,所以改动如下。
// import sun.security.util.Resources;
BufferedReader reader = new BufferedReader(new InputStreamReader(Resources.class.getResourceAsStream("/kubernetes/config")));
ApiClient client = ClientBuilder.kubeconfig(KubeConfig.loadKubeConfig(reader)).build();
因为当打成 Jar 包后,文件就在包内部了。
而
new FileReader(path)
实例化内部底层会new File(path)
在获取InputStream
。而File
这个路径是相对于包的相对路径,并不是指向包内的文件。如果继续使用会出现idea
跑项目时没有问题,打包部署时出错
初始化 ApiClient
因为有四个环境,不能像官方例子使用
Configuration.setDefaultApiClient()
设置默认client
public class KubernetesUtil {
public final static Map<String,ApiClient> alyClusterMap = new HashMap<String, ApiClient>(){
{
try {
BufferedReader reader = IoUtil.getReader(new ClassPathResource("kubernetes/aly-dev-config").getInputStream(), "utf-8");
put("DEV",ClientBuilder.kubeconfig(KubeConfig.loadKubeConfig(reader)).build());
put("TEST",null);
put("PRE",null);
put("PROD",null);
} catch (IOException e) {
e.printStackTrace();
}
}
};
}
例:获取pod信息
首先通过 Client 获取 CoreV1Api 对象用于调用相关接口
这里要提一句:Java Kubernetes SKD 操 Api 不仅只有 CoreV1Api 这个类。如操作 Deployment 时用的是 AppsV1Api 。使用什么我是先到 Kubernetes Api下找。然后将请求路径在 SDK 源码中查。如果有更好的方法,还请多多提点下(●'◡'●)
public static List<JSONObject> getPodsByAppName(String appName,String env) {
// 按需获取所需要的 Client
CoreV1Api api = new CoreV1Api(KubernetesUtil.alyClusterMap.get(env));
V1PodList list = null;
try {
list = api.listPodForAllNamespaces(null,null, null, "name="+appName+"-pod", null, null, null, null, null, null);
} catch (ApiException e) {
e.printStackTrace();
}
// JSONbject 是 Map 对象的实现类。来源是 Hutool 工具
List<JSONObject> jsonObjectList = list.getItems().parallelStream().map(m -> {
JSONObject jsonObject = new JSONObject();
jsonObject.putOnce("podName", m.getMetadata().getName());
jsonObject.putOnce("podIp", m.getStatus().getPodIP());
V1Container v1Container = m.getSpec().getContainers().get(0);
String[] images = v1Container.getImage().split(":");
jsonObject.putOnce("imageVersion", images[images.length - 1]);
// 1048576 = 1024 * 1024 因为单位为Byte 要转换为 MB。 因为获取到的是字节,需要转成 Mb
jsonObject.putOnce("specification", String.format("CPU:%s | MEM:%s",
v1Container.getResources().getLimits().get("cpu").getNumber(),
v1Container.getResources().getLimits().get("memory").getNumber().divide(new BigDecimal(1048576))));
jsonObject.putOnce("podStatus", m.getStatus().getPhase());
Optional.ofNullable(m.getStatus().getStartTime()).ifPresent(p -> {
// 时间是 UTC 的零时区。不是东八区
jsonObject.putOnce("startTime", p.plusHours(8).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
});
return jsonObject;
}).collect(Collectors.toList());
return jsonObjectList;
}
例:获取 pod 内 Java 启动日志
因为业务要求日志是从上往下看的。所以日志这块 Kubernetes
的参数有点不搭,所以就用 sinceSeconds
。意思是距离现在最近的多少秒日志
public static StartLogVO getAppServerLogs(int startDate,String env,String podName,String endLog){
CoreV1Api api = new CoreV1Api(KubernetesUtil.alyClusterMap.get(env));
StartLogVO startLogVO = new StartLogVO("",0,null);
try {
int now = (int)LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
startLogVO.setStartDate(now +"");
// 加5秒防止日志丢失
int i = now - startDate + 5;
String logs = api.readNamespacedPodLog(podName, "default", null, null, null, null, "true", null, i, null, null);
startLogVO.setLineNum(logs.substring(logs.length()-20));
// 去除重复日志
if(StrUtil.isNotBlank(endLog)){
int end = logs.indexOf(endLog);
if(end!=-1){
logs = StrUtil.removePrefix(logs.substring(end),endLog);
}
}
startLogVO.setLogs(Optional.ofNullable(logs).orElse(""));
} catch (ApiException e) {
e.printStackTrace();
}
return startLogVO;
}
总结
因为华为云通过 ak
,sk
再加 Kubernetes Api
请求地址。我这边仅需发起 HTTPS 请求就行。以至于对接阿里云还需要使用 Kubernetes
的 SDK
有些抗拒。还要载入 kubeconfig
文件。最后不想学阿里云例子通过调用系统命令 curl
。所以还是使用了,结果第一次使用就报 NoSuchMethodError kotlin.collections.ArraysKt.copyInto([B[BIII)[B
这个错误,一看报没方法错误,赶紧看下 Mavne
引入的依赖版本。原来是公司父级定义了旧版本,导致引入的最新 SDK
版本内的 Kotlin
版本被覆盖了。于是在最外层 pom.xml
下重新定义了版本约束。之后就是开发的小细节问题了。
感觉 Kubernetes
的 SDK
包还是很厉害的。下次也不知道有没有机会搞个 WebShell
连下 Pod
。