【云计算】k8sclient API 镜像操作 Java 类封装

基于 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

五、使用注意事项

  1. 版本兼容:fabric8 kubernetes-client 版本需与 k8s 集群版本兼容(建议集群版本 1.20+,客户端版本 6.0+)。

  2. 镜像格式:所有操作的镜像名称需为完整格式(仓库地址+镜像名+标签),避免使用无标签镜像(latest 标签可省略,但建议显式指定)。

  3. 资源释放:项目销毁时需调用 closeClient() 方法关闭 k8s 客户端,避免连接泄露。

  4. 私有镜像:若拉取私有仓库镜像,需先在节点上配置私有仓库认证(如 docker login),否则拉取会失败。

相关推荐
invicinble6 小时前
spring事务相关信息量的沉淀
java·后端·spring
liux35286 小时前
K8s 排坑 01:Pod 一直 Pending 怎么办?
云原生·容器·kubernetes
basketball6167 小时前
C++ 多态完全指南:同一个接口,千变万化的行为
java·开发语言·c++
KANGBboy7 小时前
java知识二(程序流程控制)
java·开发语言
Dicky-_-zhang7 小时前
JWT令牌安全实践详解
java·jvm
qq7422349847 小时前
全面深入的C#核心知识体系与编程实践精要——从语法基础到高级特性系统学习指南
java·算法·c#
运维老郭7 小时前
Kubernetes Pod 从创建到运行全流程拆解:5 个阶段 + 排错实录
运维·云原生·kubernetes
萌新小码农‍7 小时前
Python的input函数
java·前端·python
NiceCloud喜云7 小时前
AutoClaw 接入自定义 Anthropic 端点:让 Kanban 工作流跑在自己的模型路由上
java·开发语言·c++·人工智能·python·eclipse·batch