Browse Source

feat:开发环境数据集模型可配置一阶段

pull/56/head
西大锐 1 year ago
parent
commit
c25e811376
5 changed files with 230 additions and 8 deletions
  1. +32
    -3
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/jupyter/JupyterController.java
  2. +6
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/JupyterService.java
  3. +11
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java
  4. +65
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java
  5. +116
    -5
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java

+ 32
- 3
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/controller/jupyter/JupyterController.java View File

@@ -4,11 +4,11 @@ import com.ruoyi.common.core.web.controller.BaseController;
import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.core.web.domain.GenericsAjaxResult; import com.ruoyi.common.core.web.domain.GenericsAjaxResult;
import com.ruoyi.platform.service.JupyterService; import com.ruoyi.platform.service.JupyterService;
import com.ruoyi.platform.vo.FrameLogPathVo;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import org.springframework.web.bind.annotation.*;


import javax.annotation.Resource; import javax.annotation.Resource;
import java.io.File; import java.io.File;
@@ -28,6 +28,35 @@ public class JupyterController extends BaseController {
return genericsSuccess(jupyterService.getJupyterServiceUrl()); return genericsSuccess(jupyterService.getJupyterServiceUrl());
} }



/**
* 启动jupyter容器接口
*
* @param id 开发环境配置id
* @return url
*/
@PostMapping("/run/{id}")
@ApiOperation("根据开发环境id启动jupyter pod")
@ApiResponse
public GenericsAjaxResult<String> runJupyter(@PathVariable("id") Integer id) throws Exception {
return genericsSuccess(this.jupyterService.runJupyterService(id));
}


/**
* 停止jupyter容器接口
*
* @param id 开发环境配置id
* @return 操作结果
*/
@DeleteMapping("/stop/{id}")
@ApiOperation("根据开发环境id停止jupyter pod")
@ApiResponse
public GenericsAjaxResult<String> stopJupyter(@PathVariable("id") Integer id) throws Exception {
return genericsSuccess(this.jupyterService.stopJupyterService(id));
}


@GetMapping(value = "/upload") @GetMapping(value = "/upload")
public AjaxResult upload() throws Exception { public AjaxResult upload() throws Exception {
File file = new File("D://nexus-deploy.yaml"); File file = new File("D://nexus-deploy.yaml");


+ 6
- 0
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/JupyterService.java View File

@@ -1,5 +1,7 @@
package com.ruoyi.platform.service; package com.ruoyi.platform.service;


import com.ruoyi.platform.vo.FrameLogPathVo;

import java.io.InputStream; import java.io.InputStream;


public interface JupyterService { public interface JupyterService {
@@ -8,4 +10,8 @@ public interface JupyterService {
void upload(InputStream inputStream); void upload(InputStream inputStream);


void mlflow(); void mlflow();

String runJupyterService(Integer id);

String stopJupyterService(Integer id) throws Exception;
} }

+ 11
- 0
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DevEnvironmentServiceImpl.java View File

@@ -4,7 +4,10 @@ import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.platform.domain.DevEnvironment; import com.ruoyi.platform.domain.DevEnvironment;
import com.ruoyi.platform.mapper.DevEnvironmentDao; import com.ruoyi.platform.mapper.DevEnvironmentDao;
import com.ruoyi.platform.service.DevEnvironmentService; import com.ruoyi.platform.service.DevEnvironmentService;
import com.ruoyi.platform.service.JupyterService;
import com.ruoyi.platform.utils.JacksonUtil;
import com.ruoyi.system.api.model.LoginUser; import com.ruoyi.system.api.model.LoginUser;
import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@@ -13,6 +16,7 @@ import org.springframework.data.domain.PageRequest;


import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.Date; import java.util.Date;
import java.util.Map;


/** /**
* (DevEnvironment)表服务实现类 * (DevEnvironment)表服务实现类
@@ -25,6 +29,11 @@ public class DevEnvironmentServiceImpl implements DevEnvironmentService {
@Resource @Resource
private DevEnvironmentDao devEnvironmentDao; private DevEnvironmentDao devEnvironmentDao;



@Resource
private JupyterService jupyterService;


/** /**
* 通过ID查询单条数据 * 通过ID查询单条数据
* *
@@ -111,4 +120,6 @@ public class DevEnvironmentServiceImpl implements DevEnvironmentService {
devEnvironment.setState(0); devEnvironment.setState(0);
return this.devEnvironmentDao.update(devEnvironment)>0?"删除成功":"删除失败"; return this.devEnvironmentDao.update(devEnvironment)>0?"删除成功":"删除失败";
} }


} }

+ 65
- 0
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/JupyterServiceImpl.java View File

@@ -1,7 +1,12 @@
package com.ruoyi.platform.service.impl; package com.ruoyi.platform.service.impl;


import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.platform.domain.DevEnvironment;
import com.ruoyi.platform.mapper.DevEnvironmentDao;
import com.ruoyi.platform.service.DevEnvironmentService;
import com.ruoyi.platform.service.JupyterService; import com.ruoyi.platform.service.JupyterService;
import com.ruoyi.platform.utils.JacksonUtil;
import com.ruoyi.platform.utils.K8sClientUtil; import com.ruoyi.platform.utils.K8sClientUtil;
import com.ruoyi.platform.utils.MinioUtil; import com.ruoyi.platform.utils.MinioUtil;
import com.ruoyi.platform.utils.MlflowUtil; import com.ruoyi.platform.utils.MlflowUtil;
@@ -13,6 +18,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.io.InputStream; import java.io.InputStream;
import java.util.List; import java.util.List;
import java.util.Map;


@Service @Service
public class JupyterServiceImpl implements JupyterService { public class JupyterServiceImpl implements JupyterService {
@@ -39,6 +45,15 @@ public class JupyterServiceImpl implements JupyterService {
@Resource @Resource
private MlflowUtil mlflowUtil; private MlflowUtil mlflowUtil;


@Resource
private DevEnvironmentDao devEnvironmentDao;

@Resource
private DevEnvironmentService devEnvironmentService;

@Resource
private RedisService redisService;

public JupyterServiceImpl(MinioUtil minioUtil) { public JupyterServiceImpl(MinioUtil minioUtil) {
this.minioUtil = minioUtil; this.minioUtil = minioUtil;
} }
@@ -53,6 +68,54 @@ public class JupyterServiceImpl implements JupyterService {
return masterIp + ":" + podPort; return masterIp + ":" + podPort;
} }


@Override
public String runJupyterService(Integer id) {
DevEnvironment devEnvironment = this.devEnvironmentDao.queryById(id);
String envName = devEnvironment.getName();
//TODO 设置环境变量

// 提取数据集,模型信息,得到数据集模型的path
Map<String, Object> dataset = JacksonUtil.parseJSONStr2Map(devEnvironment.getDataset());
String datasetPath = (String) dataset.get("path");
Map<String, Object> model = JacksonUtil.parseJSONStr2Map(devEnvironment.getModel());
String modelPath = (String) model.get("path");

LoginUser loginUser = SecurityUtils.getLoginUser();
String podName = loginUser.getUsername().toLowerCase() + "-editor-pod";
String pvcName = loginUser.getUsername().toLowerCase() + "-editor-pvc";
V1PersistentVolumeClaim pvc = k8sClientUtil.createPvc(namespace, pvcName, storage, storageClassName);

//TODO 设置镜像可配置,这里先用默认镜像启动pod

// 调用修改后的 createPod 方法,传入额外的参数
Integer podPort = k8sClientUtil.createConfiguredPod(podName, namespace, port, mountPath, pvc, image, datasetPath, modelPath);
return masterIp + ":" + podPort;


}

@Override
public String stopJupyterService(Integer id) throws Exception {
DevEnvironment devEnvironment = this.devEnvironmentDao.queryById(id);
if (devEnvironment==null){
throw new Exception("开发环境配置不存在");
}

LoginUser loginUser = SecurityUtils.getLoginUser();
String podName = loginUser.getUsername().toLowerCase() + "-editor-pod";

// 使用 Kubernetes API 删除 Pod
String deleteResult = k8sClientUtil.deletePod(podName, namespace);

// 检查 Pod 是否存在
boolean exists = k8sClientUtil.checkPodExists(podName, namespace);
if (exists) {
throw new Exception("Pod " + podName + " 删除失败");
}
return deleteResult + ",编辑器已停止";
}


@Override @Override
public void upload(InputStream inputStream) { public void upload(InputStream inputStream) {
try { try {
@@ -71,4 +134,6 @@ public class JupyterServiceImpl implements JupyterService {
} }






} }

+ 116
- 5
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/K8sClientUtil.java View File

@@ -22,9 +22,7 @@ import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.*;


/** /**
* k8s客户端 * k8s客户端
@@ -282,13 +280,13 @@ public class K8sClientUtil {
.endSpec() .endSpec()
.build(); .build();



try { try {
pod = api.createNamespacedPod(namespace, pod, null, null, null); pod = api.createNamespacedPod(namespace, pod, null, null, null);
} catch (ApiException e) { } catch (ApiException e) {
log.error("创建pod异常:" + e.getResponseBody(), e); log.error("创建pod异常:" + e.getResponseBody(), e);
} catch (Exception e) { } catch (Exception e) {
log.error("创建pod系统异常:", e); log.error("创建pod系统异常:", e);

} }


V1Service service = createService(namespace, podName + "-svc", port, selector); V1Service service = createService(namespace, podName + "-svc", port, selector);
@@ -324,7 +322,6 @@ public class K8sClientUtil {
for (V1Pod pod1 : v1PodList.getItems()) { for (V1Pod pod1 : v1PodList.getItems()) {
if (StringUtils.equals(pod1.getMetadata().getName(), podName)) { if (StringUtils.equals(pod1.getMetadata().getName(), podName)) {
// PVC 已存在 // PVC 已存在

V1Service service = createService(namespace, podName + "-svc", port, selector); V1Service service = createService(namespace, podName + "-svc", port, selector);
if (service != null) { if (service != null) {
return service.getSpec().getPorts().get(0).getNodePort(); return service.getSpec().getPorts().get(0).getNodePort();
@@ -378,6 +375,73 @@ public class K8sClientUtil {
return service.getSpec().getPorts().get(0).getNodePort(); return service.getSpec().getPorts().get(0).getNodePort();
} }



public Integer createConfiguredPod(String podName, String namespace, Integer port, String mountPath, V1PersistentVolumeClaim pvc, String image, String datasetPath, String modelPath) {
Map<String, String> selector = new LinkedHashMap<>();
selector.put("k8s-jupyter", podName);

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);
}
if (v1PodList != null) {
for (V1Pod pod1 : v1PodList.getItems()) {
// PVC 已存在
if (StringUtils.equals(pod1.getMetadata().getName(), podName)) {
V1Service service = createService(namespace, podName + "-svc", port, selector);
if (service != null) {
return service.getSpec().getPorts().get(0).getNodePort();
}
}
}
}

// 配置卷和卷挂载
List<V1VolumeMount> volumeMounts = new ArrayList<>();
volumeMounts.add(new V1VolumeMount().name("workspace").mountPath(mountPath));
volumeMounts.add(new V1VolumeMount().name("dataset").mountPath("/datasets").subPath(datasetPath).readOnly(true));
volumeMounts.add(new V1VolumeMount().name("model").mountPath("/model").subPath(modelPath).readOnly(true));

List<V1Volume> volumes = new ArrayList<>();
volumes.add(new V1Volume().name("workspace").persistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvc.getMetadata().getName())));
volumes.add(new V1Volume().name("dataset").persistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvc.getMetadata().getName())));
volumes.add(new V1Volume().name("model").persistentVolumeClaim(new V1PersistentVolumeClaimVolumeSource().claimName(pvc.getMetadata().getName())));

V1Pod pod = new V1PodBuilder()
.withNewMetadata()
.withName(podName)
.withLabels(selector)
.endMetadata()
.withNewSpec()
.addNewContainer()
.withName(podName)
.withImage(image)
.withPorts(new V1ContainerPort().containerPort(port).protocol("TCP"))
.withVolumeMounts(volumeMounts)
.endContainer()
.withVolumes(volumes)
.withTerminationGracePeriodSeconds(14400L)
.endSpec()
.build();

try {
pod = api.createNamespacedPod(namespace, pod, null, null, null);
} catch (ApiException e) {
log.error("创建pod异常:" + e.getResponseBody(), e);
} catch (Exception e) {
log.error("创建pod系统异常:", e);
}

V1Service service = createService(namespace, podName + "-svc", port, selector);
return service.getSpec().getPorts().get(0).getNodePort();
}




/** /**
* 根据获取namespace,deploymentName的Pod Name * 根据获取namespace,deploymentName的Pod Name
* *
@@ -495,4 +559,51 @@ public class K8sClientUtil {
} }
return pod; return pod;
} }

/**
* 删除 Pod
*
* @param podName Pod 名称
* @param namespace 命名空间
* @throws ApiException 异常
*/
public String deletePod(String podName, String namespace) throws ApiException {
CoreV1Api api = new CoreV1Api(apiClient);
try {
V1Pod pod = api.deleteNamespacedPod(podName, namespace, null, null, null, null, null, null);
return "Pod " + podName + " 删除请求已发送";
} catch (ApiException e) {
if (e.getCode() == 404) {
return "Pod " + podName + " 不存在";
} else {
log.error("删除pod异常:" + e.getResponseBody(), e);
throw e;
}
}
}

/**
* 检查 Pod 是否存在
*
* @param podName Pod 名称
* @param namespace 命名空间
* @return 是否存在
* @throws ApiException 异常
*/
public boolean checkPodExists(String podName, String namespace) throws ApiException {
CoreV1Api api = new CoreV1Api(apiClient);
try {
api.readNamespacedPod(podName, namespace, null,false,false);
return true;
} catch (ApiException e) {
if (e.getCode() == 404) {
return false;
} else {
log.error("检查pod存在性异常:" + e.getResponseBody(), e);
throw e;
}
}
}


} }

Loading…
Cancel
Save