文章目录


在这里插入图片描述

一、上代码

  K8sPodExample.java:

package com.example.k8s;

import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.V1DeleteOptions;
import io.kubernetes.client.openapi.models.V1Pod;
import io.kubernetes.client.openapi.models.V1PodList;
import io.kubernetes.client.util.Config;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Kubernetes Pod 操作工具类
 * 提供 Pod 删除、状态查询、日志获取等功能
 */
public class K8sPodExample {

    private CoreV1Api api;

    /**
     * 构造函数,初始化 Kubernetes API 客户端
     * 从 src/main/resources/config 文件读取配置
     */
    public K8sPodExample() throws IOException {
        ApiClient client = createClientFromResourcesConfig();
        Configuration.setDefaultApiClient(client);
        this.api = new CoreV1Api();
    }

    /**
     * 从 resources/config 文件创建 ApiClient
     * 如果文件在文件系统中存在,直接使用;否则从 resources 中提取到临时文件
     */
    private ApiClient createClientFromResourcesConfig() throws IOException {
        File configFile = null;
        
        // 首先尝试从文件系统读取(开发环境)
        String[] possiblePaths = {
            "src/main/resources/config",
            "config",
            System.getProperty("user.dir") + "/src/main/resources/config"
        };
        
        System.out.println("正在查找 Kubernetes 配置文件...");
        for (String path : possiblePaths) {
            File file = new File(path);
            System.out.println("检查路径: " + file.getAbsolutePath() + " (存在: " + file.exists() + ")");
            if (file.exists() && file.isFile()) {
                configFile = file;
                System.out.println("找到配置文件: " + configFile.getAbsolutePath());
                break;
            }
        }

        // 如果文件不存在(可能是打包后的 jar),从 resources 中提取
        if (configFile == null || !configFile.exists()) {
            System.out.println("文件系统中未找到配置文件,尝试从 resources 中读取...");
            InputStream configStream = getClass().getClassLoader().getResourceAsStream("config");
            if (configStream == null) {
                throw new IOException("无法找到配置文件: resources/config");
            }

            // 创建临时文件
            File tempFile = File.createTempFile("kubeconfig", ".tmp");
            tempFile.deleteOnExit();
            System.out.println("创建临时配置文件: " + tempFile.getAbsolutePath());

            try (FileOutputStream fos = new FileOutputStream(tempFile);
                 InputStream is = configStream) {
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = is.read(buffer)) != -1) {
                    fos.write(buffer, 0, bytesRead);
                }
            }
            configFile = tempFile;
        }

        // 使用 Config.fromConfig 读取配置文件
        System.out.println("正在加载 Kubernetes 配置: " + configFile.getAbsolutePath());
        try {
            ApiClient client = Config.fromConfig(configFile.getAbsolutePath());
            System.out.println("Kubernetes 客户端初始化成功!");
            System.out.println("API Server: " + client.getBasePath());
            return client;
        } catch (Exception e) {
            throw new IOException("加载 Kubernetes 配置失败: " + e.getMessage(), e);
        }
    }

    /**
     * 检查 Pod 是否存在
     *
     * @param namespace Pod 所在的命名空间
     * @param podName   Pod 名称
     * @return 如果 Pod 存在返回 true,否则返回 false
     */
    public boolean podExists(String namespace, String podName) {
        try {
            api.readNamespacedPod(podName, namespace, null);
            return true;
        } catch (ApiException e) {
            if (e.getCode() == 404) {
                return false;
            }
            // 其他错误也返回 false
            return false;
        }
    }

    /**
     * 删除指定命名空间中的 Pod
     *
     * @param namespace Pod 所在的命名空间
     * @param podName   Pod 名称
     * @return 被删除的 Pod 对象
     * @throws ApiException 如果删除操作失败
     */
    public V1Pod deletePod(String namespace, String podName) throws ApiException {
        return deletePod(namespace, podName, null);
    }

    /**
     * 删除指定命名空间中的 Pod(带删除选项)
     *
     * @param namespace    Pod 所在的命名空间
     * @param podName      Pod 名称
     * @param deleteOptions 删除选项(如优雅删除时间等)
     * @return 被删除的 Pod 对象
     * @throws ApiException 如果删除操作失败
     */
    public V1Pod deletePod(String namespace, String podName, V1DeleteOptions deleteOptions) throws ApiException {
        // 先检查 Pod 是否存在
        if (!podExists(namespace, podName)) {
            throw new ApiException(404, "Pod '" + podName + "' 在命名空间 '" + namespace + "' 中不存在");
        }

        if (deleteOptions == null) {
            deleteOptions = new V1DeleteOptions();
            deleteOptions.setGracePeriodSeconds(0L);  // 0 表示立即删除
        }

        V1Pod pod = api.deleteNamespacedPod(
                podName,
                namespace,
                null,                    // pretty
                null,                    // dryRun
                null,                    // gracePeriodSeconds (通过 deleteOptions 设置)
                null,                    // orphanDependents
                null,                    // propagationPolicy
                deleteOptions            // body
        );

        return pod;
    }

    /**
     * 获取 Pod 的详细信息(包括状态)
     *
     * @param namespace Pod 所在的命名空间
     * @param podName   Pod 名称
     * @return Pod 对象,包含完整的状态信息
     * @throws ApiException 如果查询操作失败
     */
    public V1Pod getPodStatus(String namespace, String podName) throws ApiException {
        return api.readNamespacedPod(podName, namespace, null);
    }

    /**
     * 获取 Pod 的状态信息(简化版)
     *
     * @param namespace Pod 所在的命名空间
     * @param podName   Pod 名称
     * @return Pod 状态信息字符串
     * @throws ApiException 如果查询操作失败
     */
    public String getPodStatusInfo(String namespace, String podName) throws ApiException {
        V1Pod pod = getPodStatus(namespace, podName);
        if (pod == null || pod.getStatus() == null) {
            return "无法获取 Pod 状态";
        }

        StringBuilder statusInfo = new StringBuilder();
        statusInfo.append("Pod 名称: ").append(podName).append("\n");
        statusInfo.append("命名空间: ").append(namespace).append("\n");
        
        if (pod.getMetadata() != null) {
            statusInfo.append("创建时间: ").append(pod.getMetadata().getCreationTimestamp()).append("\n");
            if (pod.getMetadata().getLabels() != null) {
                statusInfo.append("标签: ").append(pod.getMetadata().getLabels()).append("\n");
            }
        }

        statusInfo.append("阶段 (Phase): ").append(pod.getStatus().getPhase()).append("\n");
        
        if (pod.getStatus().getConditions() != null) {
            statusInfo.append("条件 (Conditions):\n");
            pod.getStatus().getConditions().forEach(condition -> {
                statusInfo.append("  - ").append(condition.getType())
                        .append(": ").append(condition.getStatus())
                        .append(" (").append(condition.getReason()).append(")\n");
            });
        }

        if (pod.getStatus().getContainerStatuses() != null) {
            statusInfo.append("容器状态:\n");
            pod.getStatus().getContainerStatuses().forEach(containerStatus -> {
                statusInfo.append("  - 容器: ").append(containerStatus.getName()).append("\n");
                statusInfo.append("    就绪: ").append(containerStatus.getReady()).append("\n");
                statusInfo.append("    重启次数: ").append(containerStatus.getRestartCount()).append("\n");
                
                if (containerStatus.getState() != null) {
                    if (containerStatus.getState().getRunning() != null) {
                        statusInfo.append("    状态: 运行中\n");
                        statusInfo.append("    启动时间: ").append(containerStatus.getState().getRunning().getStartedAt()).append("\n");
                    } else if (containerStatus.getState().getWaiting() != null) {
                        statusInfo.append("    状态: 等待中\n");
                        statusInfo.append("    原因: ").append(containerStatus.getState().getWaiting().getReason()).append("\n");
                        statusInfo.append("    消息: ").append(containerStatus.getState().getWaiting().getMessage()).append("\n");
                    } else if (containerStatus.getState().getTerminated() != null) {
                        statusInfo.append("    状态: 已终止\n");
                        statusInfo.append("    退出码: ").append(containerStatus.getState().getTerminated().getExitCode()).append("\n");
                        statusInfo.append("    原因: ").append(containerStatus.getState().getTerminated().getReason()).append("\n");
                        statusInfo.append("    开始时间: ").append(containerStatus.getState().getTerminated().getStartedAt()).append("\n");
                        statusInfo.append("    结束时间: ").append(containerStatus.getState().getTerminated().getFinishedAt()).append("\n");
                    }
                }
            });
        }

        return statusInfo.toString();
    }

    /**
     * 获取 Pod 的日志(指定容器)
     *
     * @param namespace Pod 所在的命名空间
     * @param podName   Pod 名称
     * @param containerName 容器名称(如果 Pod 有多个容器,需要指定)
     * @return Pod 的日志内容
     * @throws ApiException 如果获取日志失败
     */
    public String getPodLogs(String namespace, String podName, String containerName) throws ApiException {
        return getPodLogs(namespace, podName, containerName, null, null);
    }

    /**
     * 获取 Pod 的日志(完整参数)
     *
     * @param namespace Pod 所在的命名空间
     * @param podName   Pod 名称
     * @param containerName 容器名称(可选,如果 Pod 有多个容器)
     * @param tailLines 返回最后 N 行日志(可选)
     * @param sinceSeconds 返回最近 N 秒的日志(可选)
     * @return Pod 的日志内容
     * @throws ApiException 如果获取日志失败
     */
    public String getPodLogs(String namespace, String podName, String containerName, 
                            Integer tailLines, Integer sinceSeconds) throws ApiException {
        return api.readNamespacedPodLog(
                podName,
                namespace,
                containerName,      // container
                Boolean.FALSE,      // follow
                Boolean.FALSE,      // previous
                sinceSeconds,       // sinceSeconds
                tailLines != null ? String.valueOf(tailLines) : null,  // tailLines (String)
                Boolean.FALSE,      // timestamps
                null,               // limitBytes
                null,               // other params
                null                // other params
        );
    }

    /**
     * 获取 Pod 的日志并保存到文件
     *
     * @param namespace Pod 所在的命名空间
     * @param podName   Pod 名称
     * @param containerName 容器名称(可选)
     * @param filePath 保存日志的文件路径
     * @throws ApiException 如果获取日志失败
     * @throws IOException 如果写入文件失败
     */
    public void savePodLogsToFile(String namespace, String podName, 
                                   String containerName, String filePath) throws ApiException, IOException {
        String logs = getPodLogs(namespace, podName, containerName);
        try (FileOutputStream fos = new FileOutputStream(filePath)) {
            fos.write(logs.getBytes("UTF-8"));
        }
    }

    /**
     * 列出命名空间中的所有 Pod
     *
     * @param namespace 命名空间
     * @return Pod 列表
     * @throws ApiException 如果查询操作失败
     */
    public V1PodList listPods(String namespace) throws ApiException {
        return api.listNamespacedPod(namespace, null, Boolean.FALSE, null, null, null, null, null, null, null, null, null);
    }
}

  K8sPodApp.java:

package com.example.k8s;

import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.models.V1Pod;

import java.io.IOException;

/**
 * K8sPodKiller 功能演示示例
 * 演示 Pod 状态查询、日志获取等功能
 */
public class K8sPodApp {

    public static void main(String[] args) {
        String namespace = "task-xiaoqiang";
        String podName = "task-xiaoqiang-a55bedc8-j5q6n";

        try {
            // 创建 K8sPodKiller 实例
            K8sPodExample operator = new K8sPodExample();

            // ========== 1. 检查 Pod 是否存在 ==========
            System.out.println("\n========== 1. 检查 Pod 是否存在 ==========");
            System.out.println("Pod: " + podName + " (命名空间: " + namespace + ")");
            if (operator.podExists(namespace, podName)) {
                System.out.println("✓ Pod 存在");
            } else {
                System.out.println("✗ Pod 不存在");
                System.out.println("请确认 Pod 名称和命名空间是否正确");
                return;
            }

            // ========== 2. 查询 Pod 状态 ==========
            System.out.println("\n========== 2. 查询 Pod 状态 ==========");
            try {
                // 方式1: 获取完整的 Pod 对象
                V1Pod pod = operator.getPodStatus(namespace, podName);
                System.out.println("Pod 阶段 (Phase): " + pod.getStatus().getPhase());
                System.out.println("Pod IP: " + pod.getStatus().getPodIP());
                System.out.println("节点名称: " + pod.getSpec().getNodeName());

                // 方式2: 获取格式化的状态信息
                System.out.println("\n详细状态信息:");
                String statusInfo = operator.getPodStatusInfo(namespace, podName);
                System.out.println(statusInfo);
            } catch (ApiException e) {
                System.err.println("查询 Pod 状态失败:");
                System.err.println("HTTP 状态码: " + e.getCode());
                System.err.println("错误信息: " + e.getMessage());
            }

            // ========== 3. 保存日志到文件 ==========
//            System.out.println("\n========== 3. 保存日志到文件 ==========");
//            try {
//                String logFilePath = "pod-logs-" + podName + ".txt";
//                operator.savePodLogsToFile(namespace, podName, null, logFilePath);
//                System.out.println("日志已保存到文件: " + logFilePath);
//            } catch (ApiException | IOException e) {
//                System.err.println("保存日志失败: " + e.getMessage());
//            }
//
//            // ========== 4. 删除 Pod(可选) ==========
//             System.out.println("\n========== 4. 删除 Pod ==========");
//             try {
//                 V1Pod deletedPod = operator.deletePod(namespace, podName);
//                 System.out.println("删除成功!");
//                 if (deletedPod != null && deletedPod.getMetadata() != null) {
//                     System.out.println("Pod 名称: " + deletedPod.getMetadata().getName());
//                 }
//             } catch (ApiException e) {
//                 System.err.println("删除失败:");
//                 System.err.println("HTTP 状态码: " + e.getCode());
//                 System.err.println("错误信息: " + e.getMessage());
//             }

        } catch (IOException e) {
            System.err.println("无法初始化 Kubernetes 客户端: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

  K8sJobSubmitter.java:

package com.example.k8s;

import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.apis.BatchV1Api;
import io.kubernetes.client.openapi.models.V1Container;
import io.kubernetes.client.openapi.models.V1Job;
import io.kubernetes.client.openapi.models.V1JobSpec;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.kubernetes.client.openapi.models.V1PodSpec;
import io.kubernetes.client.openapi.models.V1PodTemplateSpec;
import io.kubernetes.client.openapi.models.V1ResourceRequirements;
import io.kubernetes.client.openapi.models.V1Volume;
import io.kubernetes.client.openapi.models.V1VolumeMount;
import io.kubernetes.client.util.Config;
import io.kubernetes.client.custom.Quantity;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Kubernetes Job 提交工具类
 * 提供 Job 创建和提交功能
 */
public class K8sJobSubmitter {

    private BatchV1Api batchApi;

    /**
     * 构造函数,初始化 Kubernetes API 客户端
     * 从 src/main/resources/config 文件读取配置
     */
    public K8sJobSubmitter() throws IOException {
        ApiClient client = createClientFromResourcesConfig();
        Configuration.setDefaultApiClient(client);
        this.batchApi = new BatchV1Api();
    }

    /**
     * 从 resources/config 文件创建 ApiClient
     */
    private ApiClient createClientFromResourcesConfig() throws IOException {
        File configFile = null;
        
        // 首先尝试从文件系统读取(开发环境)
        String[] possiblePaths = {
            "src/main/resources/config",
            "config",
            System.getProperty("user.dir") + "/src/main/resources/config"
        };
        
        for (String path : possiblePaths) {
            File file = new File(path);
            if (file.exists() && file.isFile()) {
                configFile = file;
                break;
            }
        }

        // 如果文件不存在(可能是打包后的 jar),从 resources 中提取
        if (configFile == null || !configFile.exists()) {
            InputStream configStream = getClass().getClassLoader().getResourceAsStream("config");
            if (configStream == null) {
                throw new IOException("无法找到配置文件: resources/config");
            }

            // 创建临时文件
            File tempFile = File.createTempFile("kubeconfig", ".tmp");
            tempFile.deleteOnExit();

            try (FileOutputStream fos = new FileOutputStream(tempFile);
                 InputStream is = configStream) {
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = is.read(buffer)) != -1) {
                    fos.write(buffer, 0, bytesRead);
                }
            }
            configFile = tempFile;
        }

        // 使用 Config.fromConfig 读取配置文件
        return Config.fromConfig(configFile.getAbsolutePath());
    }

    /**
     * 提交 Job(使用默认配置)
     *
     * @param namespace 命名空间
     * @param jobName   Job 名称
     * @param image     容器镜像
     * @param command   执行命令(数组形式)
     * @return 创建的 Job 对象
     * @throws ApiException 如果提交失败
     */
    public V1Job submitJob(String namespace, String jobName, String image, List<String> command) throws ApiException {
        return submitJob(namespace, jobName, image, command, null, null, null, null, null, null, null);
    }

    /**
     * 提交 Job(完整参数)
     *
     * @param namespace           命名空间
     * @param jobName             Job 名称
     * @param image               容器镜像
     * @param command             执行命令(数组形式)
     * @param workingDir          工作目录(可选)
     * @param pvcName             PVC 名称(可选,用于数据卷)
     * @param cpuRequest          CPU 请求量(可选,例如 "0.1")
     * @param memoryRequest       内存请求量(可选,例如 "10Gi")
     * @param cpuLimit            CPU 限制(可选,例如 "20")
     * @param memoryLimit         内存限制(可选,例如 "100Gi")
     * @param ttlSecondsAfterFinished 完成后 TTL(秒,可选,默认 30)
     * @return 创建的 Job 对象
     * @throws ApiException 如果提交失败
     */
    public V1Job submitJob(String namespace, String jobName, String image, List<String> command,
                          String workingDir, String pvcName, String cpuRequest, String memoryRequest,
                          String cpuLimit, String memoryLimit, Integer ttlSecondsAfterFinished) throws ApiException {
        
        // 创建 Job 对象
        V1Job job = new V1Job();
        
        // 设置元数据
        V1ObjectMeta metadata = new V1ObjectMeta();
        metadata.setName(jobName);
        job.setMetadata(metadata);
        
        // 设置 Job Spec
        V1JobSpec jobSpec = new V1JobSpec();
        
        // 设置 TTLSecondsAfterFinished(默认 30 秒)
        if (ttlSecondsAfterFinished == null) {
            ttlSecondsAfterFinished = 30;
        }
        jobSpec.setTtlSecondsAfterFinished(ttlSecondsAfterFinished);
        
        // 创建 Pod Template Spec
        V1PodTemplateSpec podTemplateSpec = new V1PodTemplateSpec();
        V1PodSpec podSpec = new V1PodSpec();
        
        // 设置 RestartPolicy: Never
        podSpec.setRestartPolicy("Never");
        
        // 创建容器
        V1Container container = new V1Container();
        container.setName("data-xiaoqiang");
        container.setImage(image);
        container.setImagePullPolicy("IfNotPresent");
        
        // 设置命令
        if (command != null && !command.isEmpty()) {
            container.setCommand(command);
        }
        
        // 设置工作目录
        if (workingDir != null && !workingDir.isEmpty()) {
            container.setWorkingDir(workingDir);
        } else {
            container.setWorkingDir("/mnt/");
        }
        
        // 设置资源限制
        if (cpuRequest != null || memoryRequest != null || cpuLimit != null || memoryLimit != null) {
            V1ResourceRequirements resources = new V1ResourceRequirements();
            Map<String, Quantity> requests = new HashMap<>();
            Map<String, Quantity> limits = new HashMap<>();
            
            if (cpuRequest != null) {
                requests.put("cpu", Quantity.fromString(cpuRequest));
            }
            if (memoryRequest != null) {
                requests.put("memory", Quantity.fromString(memoryRequest));
            }
            if (cpuLimit != null) {
                limits.put("cpu", Quantity.fromString(cpuLimit));
            }
            if (memoryLimit != null) {
                limits.put("memory", Quantity.fromString(memoryLimit));
            }
            
            if (!requests.isEmpty()) {
                resources.setRequests(requests);
            }
            if (!limits.isEmpty()) {
                resources.setLimits(limits);
            }
            
            container.setResources(resources);
        } else {
            // 使用默认资源限制(参考 Go 代码)
            V1ResourceRequirements resources = new V1ResourceRequirements();
            Map<String, Quantity> requests = new HashMap<>();
            Map<String, Quantity> limits = new HashMap<>();
            
            requests.put("cpu", Quantity.fromString("0.1"));
            requests.put("memory", Quantity.fromString("10Gi"));
            limits.put("cpu", Quantity.fromString("20"));
            limits.put("memory", Quantity.fromString("100Gi"));
            
            resources.setRequests(requests);
            resources.setLimits(limits);
            container.setResources(resources);
        }
        
        // 设置 Volume Mounts
        if (pvcName != null && !pvcName.isEmpty()) {
            List<V1VolumeMount> volumeMounts = new ArrayList<>();
            V1VolumeMount volumeMount = new V1VolumeMount();
            volumeMount.setName("data");
            volumeMount.setMountPath("/mnt");
            volumeMounts.add(volumeMount);
            container.setVolumeMounts(volumeMounts);
            
            // 设置 Volumes
            List<V1Volume> volumes = new ArrayList<>();
            V1Volume volume = new V1Volume();
            volume.setName("data");
            
            io.kubernetes.client.openapi.models.V1PersistentVolumeClaimVolumeSource pvcSource = 
                new io.kubernetes.client.openapi.models.V1PersistentVolumeClaimVolumeSource();
            pvcSource.setClaimName(pvcName);
            volume.setPersistentVolumeClaim(pvcSource);
            
            volumes.add(volume);
            podSpec.setVolumes(volumes);
        } else {
            // 使用默认 PVC(参考 Go 代码)
            List<V1VolumeMount> volumeMounts = new ArrayList<>();
            V1VolumeMount volumeMount = new V1VolumeMount();
            volumeMount.setName("data");
            volumeMount.setMountPath("/mnt");
            volumeMounts.add(volumeMount);
            container.setVolumeMounts(volumeMounts);
            
            List<V1Volume> volumes = new ArrayList<>();
            V1Volume volume = new V1Volume();
            volume.setName("data");
            
            io.kubernetes.client.openapi.models.V1PersistentVolumeClaimVolumeSource pvcSource = 
                new io.kubernetes.client.openapi.models.V1PersistentVolumeClaimVolumeSource();
            pvcSource.setClaimName("task-xiaoqiang-nfs-pvc");
            
            volume.setPersistentVolumeClaim(pvcSource);
            
            volumes.add(volume);
            podSpec.setVolumes(volumes);
        }
        
        // 将容器添加到 Pod Spec
        List<V1Container> containers = new ArrayList<>();
        containers.add(container);
        podSpec.setContainers(containers);
        
        // 设置 Pod Template Spec
        podTemplateSpec.setSpec(podSpec);
        jobSpec.setTemplate(podTemplateSpec);
        
        // 设置 Job Spec
        job.setSpec(jobSpec);
        
        // 提交 Job
        return batchApi.createNamespacedJob(namespace, job, null, null, null, null);
    }

    /**
     * 提交 Job(简化版,使用默认配置)
     * 参考 Go 代码示例的配置
     *
     * @param namespace 命名空间
     * @param jobName   Job 名称
     * @param image     容器镜像(默认: repo.xiaoqiang.net/task/basic:v0.5)
     * @param command   执行命令(数组形式)
     * @return 创建的 Job 对象
     * @throws ApiException 如果提交失败
     */
    public V1Job submitDataAnalysisJob(String namespace, String jobName, List<String> command) throws ApiException {
        return submitJob(
            namespace,
            jobName,
            "repo.xiaoqiang.net/task/basic:v0.5",  // 默认镜像
            command,
            "/mnt/",                                  // 工作目录
            "task-xiaoqiang-nfs-pvc",                  // 默认 PVC
            "0.1",                                    // CPU 请求
            "10Gi",                                   // 内存请求
            "20",                                     // CPU 限制
            "100Gi",                                  // 内存限制
            30                                        // TTL 30 秒
        );
    }

    /**
     * 获取 Job 状态
     *
     * @param namespace 命名空间
     * @param jobName   Job 名称
     * @return Job 对象
     * @throws ApiException 如果查询失败
     */
    public V1Job getJobStatus(String namespace, String jobName) throws ApiException {
        return batchApi.readNamespacedJob(jobName, namespace, null);
    }

    /**
     * 删除 Job
     *
     * @param namespace 命名空间
     * @param jobName   Job 名称
     * @throws ApiException 如果删除失败
     */
    public void deleteJob(String namespace, String jobName) throws ApiException {
        batchApi.deleteNamespacedJob(
            jobName,
            namespace,
            null,  // pretty
            null,  // dryRun
            null,  // gracePeriodSeconds
            null,  // orphanDependents
            null,  // propagationPolicy
            null   // body
        );
    }
}

  K8sJobSubmitterExample.java:

package com.example.k8s;

import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.models.V1Job;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

/**
 * K8sJobSubmitter 使用示例
 * 演示如何提交 Kubernetes Job
 */
public class K8sJobSubmitterExample {

    public static void main(String[] args) {
        String namespace = "task-xiaoqiang";

        try {
            // 创建 K8sJobSubmitter 实例
            K8sJobSubmitter submitter = new K8sJobSubmitter();

            // ========== 示例 1: 使用自定义配置提交 Job ==========
            System.out.println("\n========== 示例 1: 使用自定义配置提交 Job ==========");
            try {
                String customJobName = "custom-job-" + System.currentTimeMillis();
                String commandBuild = "python3 main.py -dt 2026-02-28";
                List<String> command = Arrays.asList(commandBuild.split(" "));

                System.out.println("提交自定义 Job:");
                System.out.println("  名称: " + customJobName);
                System.out.println("  镜像: repo.xiaoqiang.net/task/basic:v0.5");
                System.out.println("  命令: " + String.join(" ", command));

                V1Job job = submitter.submitJob(
                    namespace,
                    customJobName,
                    "repo.xiaoqiang.net/task/basic:v0.5",
                    command,
                    "/mnt/",                    // 工作目录
                    "task-xiaoqiang-nfs-pvc",     // PVC 名称
                    "0.1",                      // CPU 请求
                    "10Gi",                     // 内存请求
                    "20",                       // CPU 限制
                    "100Gi",                    // 内存限制
                    30                          // TTL 30 秒
                );

                System.out.println("✓ Job 提交成功!");
                System.out.println("Job UID: " + job.getMetadata().getUid());

            } catch (ApiException e) {
                System.err.println("提交 Job 失败:");
                System.err.println("HTTP 状态码: " + e.getCode());
                System.err.println("错误信息: " + e.getMessage());
            }
//
//            // ========== 示例 2: 查询 Job 状态 ==========
//            System.out.println("\n========== 示例 2: 查询 Job 状态 ==========");
//            try {
//                V1Job job = submitter.getJobStatus(namespace, jobName);
//                if (job != null && job.getStatus() != null) {
//                    System.out.println("Job 状态:");
//                    System.out.println("  名称: " + job.getMetadata().getName());
//                    System.out.println("  活动 Pods: " +
//                        (job.getStatus().getActive() != null ? job.getStatus().getActive() : 0));
//                    System.out.println("  成功 Pods: " +
//                        (job.getStatus().getSucceeded() != null ? job.getStatus().getSucceeded() : 0));
//                    System.out.println("  失败 Pods: " +
//                        (job.getStatus().getFailed() != null ? job.getStatus().getFailed() : 0));
//                }
//            } catch (ApiException e) {
//                System.err.println("查询 Job 状态失败:");
//                System.err.println("HTTP 状态码: " + e.getCode());
//                System.err.println("错误信息: " + e.getMessage());
//            }

            // ========== 示例 3: 删除 Job(可选) ==========
//             System.out.println("\n========== 示例 3: 删除 Job ==========");
//             try {
////                 submitter.deleteJob(namespace, jobName);
//                 System.out.println("✓ Job 删除成功!");
//             } catch (ApiException e) {
//                 System.err.println("删除 Job 失败:");
//                 System.err.println("HTTP 状态码: " + e.getCode());
//                 System.err.println("错误信息: " + e.getMessage());
//             }

        } catch (IOException e) {
            System.err.println("无法初始化 Kubernetes 客户端: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

  config:

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: xxx
    server: https://xxx.xxx.xxx.xxx:110
  name: cluster1
contexts:
- context:
    cluster: cluster1
    user: admin
  name: xiaoqiang-cluster1
current-context: xiaoqiang-cluster1
kind: Config
preferences: {}
users:
- name: admin
  user:
    client-certificate-data: xxx
    client-key-data: xxx

注:K8s 认证信息可在服务器上查看 cat ~/.kube/config

更多推荐