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