基于 kubernetes-client(fabric8)封装,涵盖镜像查询、拉取、删除、校验等核心操作,依赖 fabric8 的 kubernetes-client 依赖,需提前在 pom.xml 中引入相关依赖,所有方法均添加异常处理,可直接集成到 Spring Boot 或普通 Java 项目中。
一、依赖引入(pom.xml)
<dependencies>
<!-- Kubernetes Java Client -->
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
<version>6.7.2</version>
<!-- 建议使用稳定版,根据项目实际情况调整 -->
</dependency>
<!-- SLF4J 日志接口 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<!-- Logback 日志实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
</dependencies>
二、Java 类封装(完整可运行)
java
import io.fabric8.kubernetes.api.model.ContainerImage;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.fabric8.kubernetes.client.dsl.ExecWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* k8sclient API 镜像操作封装类
* 核心功能:镜像查询、拉取、删除、校验镜像是否存在、获取镜像详情
*/
public class K8sImageOperator {
private static final Logger logger = LoggerFactory.getLogger(K8sImageOperator.class);
private final KubernetesClient k8sClient;
/**
* 无参构造器:默认使用集群内配置(Pod 内部运行时自动读取 /var/run/secrets/kubernetes.io/serviceaccount)
*/
public K8sImageOperator() {
this.k8sClient = new KubernetesClientBuilder().build();
logger.info("K8sImageOperator 初始化完成,使用集群内默认配置");
}
/**
* 有参构造器:指定 k8s 配置文件路径(集群外本地调试时使用)
*
* @param kubeConfigPath k8s 配置文件路径(如:~/.kube/config)
*/
public K8sImageOperator(String kubeConfigPath) {
this.k8sClient = new KubernetesClientBuilder().withConfigPath(kubeConfigPath).build();
logger.info("K8sImageOperator 初始化完成,使用配置文件:{}", kubeConfigPath);
}
/**
* 1. 查询集群内所有镜像(去重)
*
* @return 镜像名称列表(格式:仓库地址/镜像名:标签,如:nginx:1.21.6)
*/
public List<String> listAllImages() {
List<String> imageList = new ArrayList<>();
try {
// 遍历所有节点,获取节点上的镜像,去重处理
k8sClient.nodes().list().getItems().forEach(node -> {
List<ContainerImage> containerImages = node.getStatus().getImages();
if (containerImages != null && !containerImages.isEmpty()) {
containerImages.forEach(image -> {
List<String> names = image.getNames();
if (names != null && !names.isEmpty()) {
// 取第一个镜像名称(通常包含完整标签),避免重复添加
String imageName = names.get(0);
if (!imageList.contains(imageName)) {
imageList.add(imageName);
}
}
});
}
});
logger.info("查询集群内所有镜像成功,共找到 {} 个镜像", imageList.size());
} catch (Exception e) {
logger.error("查询集群内镜像失败", e);
throw new RuntimeException("查询k8s镜像失败:" + e.getMessage(), e);
}
return imageList;
}
/**
* 2. 拉取镜像到指定节点
*
* @param nodeName 目标节点名称(必须是集群内存在的节点)
* @param imageName 镜像名称(完整格式,如:nginx:1.21.6、registry.cn-hangzhou.aliyuncs.com/xxx/xxx:v1.0)
* @param timeout 超时时间(单位:秒),建议设置300秒以上(大镜像拉取耗时久)
* @return 拉取结果(true:成功,false:失败)
*/
public boolean pullImageToNode(String nodeName, String imageName, long timeout) {
// 校验节点是否存在
if (!checkNodeExists(nodeName)) {
logger.error("节点 {} 不存在,无法拉取镜像", nodeName);
return false;
}
// 通过在节点上执行 docker pull 命令拉取镜像(需节点支持 docker 命令)
String[] command = {"docker", "pull", imageName};
try (ExecWatch watch = k8sClient.nodes().withName(nodeName).exec(command);
OutputStream outputStream = new ByteArrayOutputStream()) {
// 等待命令执行完成,超时则中断
boolean executeSuccess = watch.waitForCompletion(timeout, TimeUnit.SECONDS);
if (executeSuccess) {
logger.info("镜像 {} 拉取到节点 {} 成功", imageName, nodeName);
return true;
} else {
logger.error("镜像 {} 拉取到节点 {} 超时({} 秒)", imageName, nodeName, timeout);
return false;
}
} catch (IOException | InterruptedException e) {
logger.error("镜像 {} 拉取到节点 {} 失败", imageName, nodeName, e);
return false;
}
}
/**
* 3. 从指定节点删除镜像
*
* @param nodeName 目标节点名称
* @param imageName 镜像名称(完整格式或短格式均可,如:nginx:1.21.6、nginx)
* @return 删除结果(true:成功,false:失败)
*/
public boolean deleteImageFromNode(String nodeName, String imageName) {
// 校验节点是否存在
if (!checkNodeExists(nodeName)) {
logger.error("节点 {} 不存在,无法删除镜像", nodeName);
return false;
}
// 执行 docker rmi 命令删除镜像,添加 -f 强制删除(避免镜像被占用无法删除)
String[] command = {"docker", "rmi", "-f", imageName};
try (ExecWatch watch = k8sClient.nodes().withName(nodeName).exec(command)) {
// 等待命令执行完成,超时时间设为60秒(删除镜像耗时较短)
boolean executeSuccess = watch.waitForCompletion(60, TimeUnit.SECONDS);
if (executeSuccess) {
logger.info("从节点 {} 删除镜像 {} 成功", nodeName, imageName);
return true;
} else {
logger.error("从节点 {} 删除镜像 {} 超时", nodeName, imageName);
return false;
}
} catch (IOException | InterruptedException e) {
logger.error("从节点 {} 删除镜像 {} 失败", nodeName, imageName, e);
return false;
}
}
/**
* 4. 校验镜像是否已存在于指定节点
*
* @param nodeName 目标节点名称
* @param imageName 镜像名称(完整格式,如:nginx:1.21.6)
* @return 校验结果(true:存在,false:不存在)
*/
public boolean checkImageExistsOnNode(String nodeName, String imageName) {
// 校验节点是否存在
if (!checkNodeExists(nodeName)) {
logger.error("节点 {} 不存在,无法校验镜像", nodeName);
return false;
}
// 执行 docker images 命令,查询镜像是否存在
String[] command = {"docker", "images", "--format", "{{.Repository}}:{{.Tag}}", imageName};
try (ExecWatch watch = k8sClient.nodes().withName(nodeName).exec(command);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
// 捕获命令输出,判断是否包含目标镜像
watch.getOutput().transferTo(outputStream);
String output = outputStream.toString().trim();
boolean exists = output.contains(imageName);
logger.info("节点 {} 镜像 {} 存在性校验:{}", nodeName, imageName, exists);
return exists;
} catch (IOException e) {
logger.error("校验节点 {} 镜像 {} 存在性失败", nodeName, imageName, e);
return false;
}
}
/**
* 5. 获取指定镜像的详细信息(从指定节点)
*
* @param nodeName 目标节点名称
* @param imageName 镜像名称(完整格式)
* @return 镜像详细信息(docker inspect 输出的JSON字符串),失败返回null
*/
public String getImageDetails(String nodeName, String imageName) {
if (!checkNodeExists(nodeName)) {
logger.error("节点 {} 不存在,无法获取镜像详情", nodeName);
return null;
}
if (!checkImageExistsOnNode(nodeName, imageName)) {
logger.error("节点 {} 上不存在镜像 {},无法获取详情", nodeName, imageName);
return null;
}
// 执行 docker inspect 命令,获取镜像详细信息(JSON格式)
String[] command = {"docker", "inspect", imageName};
try (ExecWatch watch = k8sClient.nodes().withName(nodeName).exec(command);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
watch.getOutput().transferTo(outputStream);
String imageDetails = outputStream.toString().trim();
logger.info("获取节点 {} 镜像 {} 详情成功,长度:{} 字符", nodeName, imageName, imageDetails.length());
return imageDetails;
} catch (IOException e) {
logger.error("获取节点 {} 镜像 {} 详情失败", nodeName, imageName, e);
return null;
}
}
/**
* 辅助方法:校验节点是否存在于集群中
*
* @param nodeName 节点名称
* @return 校验结果(true:存在,false:不存在)
*/
private boolean checkNodeExists(String nodeName) {
try {
return k8sClient.nodes().withName(nodeName).exists();
} catch (Exception e) {
logger.error("校验节点 {} 存在性失败", nodeName, e);
return false;
}
}
/**
* 关闭 k8s 客户端连接(建议在项目销毁时调用,避免资源泄露)
*/
public void closeClient() {
if (k8sClient != null) {
k8sClient.close();
logger.info("K8sImageOperator 客户端连接已关闭");
}
}
/**
* 测试方法(本地调试用)
*/
public static void main(String[] args) {
// 集群外调试:指定 kubeconfig 路径
K8sImageOperator imageOperator = new K8sImageOperator("~/.kube/config");
// 1. 查询所有镜像
List<String> allImages = imageOperator.listAllImages();
System.out.println("集群内所有镜像:" + allImages);
// 2. 拉取镜像(示例:拉取nginx:1.21.6到节点node-1)
boolean pullSuccess = imageOperator.pullImageToNode("node-1", "nginx:1.21.6", 300);
System.out.println("镜像拉取结果:" + pullSuccess);
// 3. 校验镜像是否存在
boolean imageExists = imageOperator.checkImageExistsOnNode("node-1", "nginx:1.21.6");
System.out.println("镜像是否存在:" + imageExists);
// 4. 获取镜像详情
String imageDetails = imageOperator.getImageDetails("node-1", "nginx:1.21.6");
System.out.println("镜像详情:" + imageDetails);
// 5. 删除镜像(示例:删除节点node-1上的nginx:1.21.6)
// boolean deleteSuccess = imageOperator.deleteImageFromNode("node-1", "nginx:1.21.6");
// System.out.println("镜像删除结果:" + deleteSuccess);
// 关闭客户端
imageOperator.closeClient();
}
}
三、核心说明
-
客户端初始化:提供两种构造器,集群内运行(Pod)使用无参构造器,自动读取集群内默认配置;本地调试使用有参构造器,指定 kubeconfig 路径(如 ~/.kube/config)。
-
镜像操作依赖:所有镜像操作均通过在节点上执行 docker 命令实现,需确保节点已安装 docker,且 k8s 客户端有节点 exec 权限(需配置对应的 RBAC 权限)。
-
异常处理:所有方法均捕获异常并打印日志,同时抛出运行时异常(可根据项目需求调整为返回异常信息,而非抛出异常)。
-
超时设置:拉取镜像超时时间建议设置300秒以上,避免大镜像拉取超时;删除、校验等操作超时时间可适当缩短。
-
RBAC 权限配置:若运行时出现权限不足,需为当前服务账号配置节点操作权限(示例 RBAC 配置可参考下方)。
四、RBAC 权限配置(可选,集群内运行时需配置)
bash
# 镜像操作所需 RBAC 权限配置(创建 ServiceAccount、ClusterRole、ClusterRoleBinding)
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: k8s-image-operator-sa
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: k8s-image-operator-role
rules:
- apiGroups: [""]
resources: ["nodes", "nodes/exec"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete", "exec"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: k8s-image-operator-binding
subjects:
- kind: ServiceAccount
name: k8s-image-operator-sa
namespace: default
roleRef:
kind: ClusterRole
name: k8s-image-operator-role
apiGroup: rbac.authorization.k8s.io
五、使用注意事项
-
版本兼容:fabric8 kubernetes-client 版本需与 k8s 集群版本兼容(建议集群版本 1.20+,客户端版本 6.0+)。
-
镜像格式:所有操作的镜像名称需为完整格式(仓库地址+镜像名+标签),避免使用无标签镜像(latest 标签可省略,但建议显式指定)。
-
资源释放:项目销毁时需调用 closeClient() 方法关闭 k8s 客户端,避免连接泄露。
-
私有镜像:若拉取私有仓库镜像,需先在节点上配置私有仓库认证(如 docker login),否则拉取会失败。