add jupyter get url

This commit is contained in:
fans 2023-12-07 11:40:09 +08:00
parent 10770dd539
commit 69335b51a0
7 changed files with 339 additions and 1 deletions

View File

@ -68,7 +68,23 @@
<artifactId>persistence-api</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java-api</artifactId>
<version>16.0.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java-api</artifactId>
<version>12.0.1</version>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
<version>4.11.1</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,5 @@
package com.ruoyi.platform.service;
public interface JupyterService {
String getJupyterServiceUrl();
}

View File

@ -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;
}
}

View File

@ -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<String, String> 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<String, Quantity> requests = new HashMap<String, Quantity>();
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<String, String> selector = new LinkedHashMap<String, String>();
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<String, String> selector = new LinkedHashMap<String, String>();
selector.put("k8s-app",podName);
V1Service service = createService(namespace, podName + "-svc", port, selector);
return service.getSpec().getPorts().get(0).getNodePort();
}
}

13
pom.xml
View File

@ -169,6 +169,12 @@
<artifactId>ruoyi-common</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<dependency>
<groupId>io.kubernetes</groupId>
<artifactId>client-java</artifactId>
<version>12.0.1</version>
</dependency>
</dependencies>
</dependencyManagement>
@ -223,5 +229,10 @@
</snapshots>
</pluginRepository>
</pluginRepositories>
<distributionManagement>
<repository>
<id>nexus</id>
<url>http://172.20.32.181:30005/repository/maven-releases/</url>
</repository>
</distributionManagement>
</project>

View File

@ -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());
}
}

View File

@ -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