From 69335b51a00c9b8c520f57b388eaf6057ef58fff Mon Sep 17 00:00:00 2001 From: fans <1141904845@qq.com> Date: Thu, 7 Dec 2023 11:40:09 +0800 Subject: [PATCH] add jupyter get url --- management-platform/pom.xml | 16 ++ .../platform/service/JupyterService.java | 5 + .../service/impl/JupyterServiceImpl.java | 33 +++ .../ruoyi/platform/utils/K8sClientUtil.java | 235 ++++++++++++++++++ pom.xml | 13 +- .../controller/jupyter/JupyterController.java | 22 ++ .../src/main/resources/application.yml | 16 ++ 7 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 management-platform/src/main/java/com/ruoyi/platform/service/JupyterService.java create mode 100644 management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java create mode 100644 management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/web/controller/jupyter/JupyterController.java diff --git a/management-platform/pom.xml b/management-platform/pom.xml index a5e9292..bdf80d4 100644 --- a/management-platform/pom.xml +++ b/management-platform/pom.xml @@ -68,7 +68,23 @@ persistence-api 1.0.2 + + io.kubernetes + client-java-api + 16.0.3 + compile + + + io.kubernetes + client-java-api + 12.0.1 + + + io.fabric8 + kubernetes-client + 4.11.1 + \ No newline at end of file diff --git a/management-platform/src/main/java/com/ruoyi/platform/service/JupyterService.java b/management-platform/src/main/java/com/ruoyi/platform/service/JupyterService.java new file mode 100644 index 0000000..3d34a85 --- /dev/null +++ b/management-platform/src/main/java/com/ruoyi/platform/service/JupyterService.java @@ -0,0 +1,5 @@ +package com.ruoyi.platform.service; + +public interface JupyterService { + String getJupyterServiceUrl(); +} diff --git a/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java b/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java new file mode 100644 index 0000000..dfdac44 --- /dev/null +++ b/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java @@ -0,0 +1,33 @@ +package com.ruoyi.platform.service.impl; + +import com.ruoyi.common.core.domain.model.LoginUser; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.platform.service.JupyterService; +import com.ruoyi.platform.utils.K8sClientUtil; +import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim; +import org.springframework.beans.factory.annotation.Value; + +public class JupyterServiceImpl implements JupyterService { + + @Value("${jupyter.image}") + private String image; + @Value("${jupyter.port}") + private Integer port; + @Value("${jupyter.namespace}") + private String namespace; + @Value("${jupyter.mountPath}") + private String mountPath; + @Value("${jupyter.storage}") + private String storage; + @Value("${jupyter.masterIp}") + private String masterIp; + @Override + public String getJupyterServiceUrl() { + LoginUser loginUser = SecurityUtils.getLoginUser(); + String podName = loginUser.getUser().getUserName() + "pod"; + String pvcName = loginUser.getUser().getUserName() + "pvc"; + V1PersistentVolumeClaim pvc = K8sClientUtil.createPvc(namespace, pvcName, storage); + Integer podPort = K8sClientUtil.createPod(podName, namespace, port, mountPath, pvc, image); + return masterIp + ":" + podPort; + } +} diff --git a/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java b/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java new file mode 100644 index 0000000..89a6248 --- /dev/null +++ b/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java @@ -0,0 +1,235 @@ +package com.ruoyi.platform.utils; + +import io.kubernetes.client.custom.IntOrString; +import io.kubernetes.client.custom.Quantity; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.models.*; +import io.kubernetes.client.util.ClientBuilder; +import io.kubernetes.client.util.KubeConfig; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; + +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * k8s客户端 + * + * @author wanghuidong + * @date 2021/6/18 14:14 + */ +@Slf4j +public class K8sClientUtil { + + /** + * k8s-api客户端 + */ + private static ApiClient apiClient; + + /** + * 构建集群POD内通过SA访问的客户端 + * loading the in-cluster config, including: + * 1. service-account CA + * 2. service-account bearer-token + * 3. service-account namespace + * 4. master endpoints(ip, port) from pre-set environment variables + */ + public K8sClientUtil() { + try { + this.apiClient = ClientBuilder.cluster().build(); + } catch (IOException e) { + log.error("构建K8s-Client异常", e); + throw new RuntimeException("构建K8s-Client异常"); + } + } + + /** + * 构建集群外通过UA访问的客户端 + * loading the out-of-cluster config, a kubeconfig from file-system + * + * @param kubeConfigPath kube连接配置文件 + */ + public K8sClientUtil(String kubeConfigPath) { + try { + this.apiClient = ClientBuilder.kubeconfig(KubeConfig.loadKubeConfig(new FileReader(kubeConfigPath))).build(); + } catch (IOException e) { + log.error("读取kubeConfigPath异常", e); + throw new RuntimeException("读取kubeConfigPath异常"); + } catch (Exception e) { + log.error("构建K8s-Client异常", e); + throw new RuntimeException("构建K8s-Client异常"); + } + } + + /** + * 获取所有的Pod + * + * @return podList + */ + public static V1PodList getAllPodList() { + // new a CoreV1Api + CoreV1Api api = new CoreV1Api(apiClient); + + // invokes the CoreV1Api client + try { + V1PodList list = api.listPodForAllNamespaces(null, null, null, null, null, null, null, null, null, null); + return list; + } catch (ApiException e) { + log.error("获取podlist异常:" + e.getResponseBody(), e); + } + return null; + } + + /** + * 创建k8s service + * + * @param namespace 命名空间 + * @param serviceName 服务名称 + * @param port 服务端口号(和目标pod的端口号一致) + * @param selector pod标签选择器 + * @return 创建成功的service对象 + */ + public static V1Service createService(String namespace, String serviceName, Integer port, Map selector) { + //构建service的yaml对象 + V1Service svc = new V1ServiceBuilder() + .withNewMetadata() + .withName(serviceName) + .endMetadata() + .withNewSpec() + .addNewPort() + .withProtocol("TCP") + .withPort(port) + .withTargetPort(new IntOrString(port)) + .withNodePort(0) + .endPort() + .withSelector(selector) + .endSpec() + .build(); + + // Deployment and StatefulSet is defined in apps/v1, so you should use AppsV1Api instead of CoreV1API + CoreV1Api api = new CoreV1Api(apiClient); + V1Service v1Service = null; + try { + v1Service = api.createNamespacedService(namespace, svc, null, null, null); + } catch (ApiException e) { + log.error("创建service异常:" + e.getResponseBody(), e); + } catch (Exception e) { + log.error("创建service系统异常:", e); + } + return v1Service; + } + + /** + * 创建k8s PVC + * + * @param namespace 命名空间 + * @param pvcName 服务名称 + * @return 创建成功的service对象 + */ + public static V1PersistentVolumeClaim createPvc(String namespace, String pvcName ,String storage){ + CoreV1Api api = new CoreV1Api(apiClient); + + V1PersistentVolumeClaimList pvcList = null; + try { + pvcList = api.listNamespacedPersistentVolumeClaim(namespace, null,null, null, null, null,null,null, null, null, null); + } catch (ApiException e) { + log.error("获取 PVC 异常:", e); + } + + for (V1PersistentVolumeClaim pvc1 : pvcList.getItems()) { + if (StringUtils.equals(pvc1.getMetadata().getName(),pvcName)) { + // PVC 已存在 + return pvc1; + } + } + V1PersistentVolumeClaim pvc = null; + Map requests = new HashMap(); + requests.put("storage", new Quantity(storage)); + pvc = new V1PersistentVolumeClaimBuilder() + .withNewMetadata() + .withName(pvcName) + .endMetadata() + .withNewSpec() + .withAccessModes("ReadWriteOnce") + .withStorageClassName("storage-nfs") + .withResources(new V1ResourceRequirementsBuilder() + .withRequests(requests) + .build()) + .endSpec() + .build(); + + try { + pvc = api.createNamespacedPersistentVolumeClaim(namespace, pvc, null, null, null); + } catch (ApiException e) { + log.error("创建pvc异常:" + e.getResponseBody(), e); + } catch (Exception e) { + log.error("创建pvc系统异常:", e); + } + return pvc; + } + + + /** + * 创建k8s 临时POD + * @param podName pod name + * @param namespace 命名空间 + * @param port port + * @param mountPath 映射路径 + * @param pvc 存储 + * @param image 镜像 + * @return 创建成功的pod,的nodePort端口 + */ + + public static Integer createPod(String podName, String namespace, Integer port ,String mountPath, V1PersistentVolumeClaim pvc, String image){ + CoreV1Api api = new CoreV1Api(apiClient); + V1PodList v1PodList = null; + try { + v1PodList = api.listNamespacedPod(namespace, null, null, null, null, null, null, null, null, null, null); + } catch (ApiException e) { + log.error("获取 POD 异常:", e); + } + for (V1Pod pod1 : v1PodList.getItems()) { + if (StringUtils.equals(pod1.getMetadata().getName(),podName)) { + // PVC 已存在 + Map selector = new LinkedHashMap(); + selector.put("k8s-app",podName); + V1Service service = createService(namespace, podName + "-svc", port, selector); + return service.getSpec().getPorts().get(0).getNodePort(); + } + } + V1Pod pod = new V1PodBuilder() + .withNewMetadata() + .withName(podName) + .endMetadata() + .withNewSpec() + .addNewContainer() + .withName(podName) + .withImage(image) + .withPorts(new V1ContainerPort().containerPort(port).protocol("TCP")) + .withVolumeMounts(new V1VolumeMount().name("workspace").mountPath(mountPath)) + .endContainer() + .addNewVolume() + .withName("workspace").withPersistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvc.getMetadata().getName())) + .endVolume() + .endSpec() + .build(); + + + try { + pod = api.createNamespacedPod(namespace, pod, null, null, null); + } catch (ApiException e) { + log.error("创建pvc异常:" + e.getResponseBody(), e); + } catch (Exception e) { + log.error("创建pvc系统异常:", e); + } + Map selector = new LinkedHashMap(); + selector.put("k8s-app",podName); + V1Service service = createService(namespace, podName + "-svc", port, selector); + return service.getSpec().getPorts().get(0).getNodePort(); + } +} diff --git a/pom.xml b/pom.xml index 7e69408..f84e6f2 100644 --- a/pom.xml +++ b/pom.xml @@ -169,6 +169,12 @@ ruoyi-common ${ruoyi.version} + + io.kubernetes + client-java + 12.0.1 + + @@ -223,5 +229,10 @@ - + + + nexus + http://172.20.32.181:30005/repository/maven-releases/ + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jupyter/JupyterController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jupyter/JupyterController.java new file mode 100644 index 0000000..d7d3d9b --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jupyter/JupyterController.java @@ -0,0 +1,22 @@ +package com.ruoyi.web.controller.jupyter; + +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.platform.service.JupyterService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.io.IOException; + +@RestController +@RequestMapping("/jupyter") +public class JupyterController extends BaseController { + @Resource + private JupyterService jupyterService; + @GetMapping(value = "/getURL") + public AjaxResult getURL() throws IOException { + return AjaxResult.success(jupyterService.getJupyterServiceUrl()); + } +} diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 3bee084..7c589cc 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -122,6 +122,7 @@ swagger: # 防止XSS攻击 xss: + # 过滤开关 enabled: true # 排除链接(多个用逗号分隔) @@ -138,3 +139,18 @@ argo: workflowStatus: /api/v1/workflow/getWorkflow workflowTermination: /api/v1/workflow/terminate workflowLog: /api/v1/workflow/getWorkflowLog + + +# 流水线配置 +pipeline: + control_strategy: [{"type":"str","label":"超时中断","require":1,"choice":[],"default":"0","placeholder":"","describe":"组件运行时长,支持s(秒),m(分钟),h(小时),d(天),示例:3h,代表3个小时超时,0表示不限制时长","editable":1},{"type":"int","label":"重试次数","require":1,"choice":[],"default":"0","placeholder":"","describe":"组件运行失败自动重试次数","editable":1}] + + +# jupyter配置 +jupyter: + image: imageName + port: + namespace: + mountPath: + storage: 2Gi + masterIp: http://172.20.32.181