# Java 操作 Kubernetes
起因:公司准备将应用容器化部署,先使用了华为云的
Kubernetes
服务,后面又使用阿里云的Kubernetes
服务。并短期一个月内无法判断走哪个云商。而作为一个在公司内部用于应用发布,部署的应用。在对接完华为云的Kubernetes
服务Api
后。再对接阿里云发现阿里云并没用像华为云一样对Kubernetes
的Api
做简易的封装。其两者的区别是华为云可以通过ak
,sk
再加Kubernetes
Api
获取数据。可以理解为华为云多了一层代理。
# 添加依赖
本篇使用的是官方维护的
Kubernetes Java Client
包 。有兴趣的可以了解下面的社区维护版
官方 SDK
官方 JAVA SDK GitHub
建议使用最新版本 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
。