forked from ci4s/ci4sManagement
add jupyter get url
This commit is contained in:
parent
10770dd539
commit
69335b51a0
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.ruoyi.platform.service;
|
||||
|
||||
public interface JupyterService {
|
||||
String getJupyterServiceUrl();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
13
pom.xml
|
|
@ -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>
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue