parent
8d8ad03f30
commit
c594dca0ec
|
@ -140,7 +140,12 @@
|
|||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<version>3.2.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.docker-java</groupId>
|
||||
<artifactId>docker-java-transport-httpclient5</artifactId>
|
||||
<version>3.2.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
|
@ -216,6 +221,12 @@
|
|||
<version>3.0.8</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.6</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
|
|
@ -78,16 +78,15 @@ public class ImageController extends BaseController {
|
|||
* @param image 实体
|
||||
* @return 新增结果
|
||||
*/
|
||||
@PostMapping
|
||||
@ApiOperation("新增镜像,不包含镜像版本")
|
||||
public GenericsAjaxResult<Image> add(@RequestBody Image image) {
|
||||
return genericsSuccess(this.imageService.insert(image));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增镜像和版本
|
||||
*
|
||||
* @param imageVo 实体
|
||||
* 新增镜像和版本 @PostMapping
|
||||
* @return 新增结果
|
||||
*/
|
||||
@PostMapping("/addImageAndVersion")
|
||||
|
@ -149,5 +148,11 @@ public class ImageController extends BaseController {
|
|||
return genericsSuccess(this.imageService.uploadImageFiles(file));
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/saveImage")
|
||||
@ApiOperation(value = "保存环境为镜像", notes = "docker commit方式保存,并推送到horbor")
|
||||
public GenericsAjaxResult<String> saveImage(ImageVo imageVo){
|
||||
return genericsSuccess(this.imageService.saveImage(imageVo));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -93,6 +93,5 @@ public interface ImageService {
|
|||
Map<String, String> createImageFromNet(String imageName, String imageTag, String NetPath) throws Exception;
|
||||
Map<String, String> uploadImageFiles(MultipartFile file) throws Exception;
|
||||
|
||||
|
||||
|
||||
String saveImage(ImageVo imageVo);
|
||||
}
|
||||
|
|
|
@ -9,12 +9,14 @@ import com.ruoyi.platform.mapper.ImageVersionDao;
|
|||
import com.ruoyi.platform.service.ImageService;
|
||||
import com.ruoyi.platform.service.ImageVersionService;
|
||||
import com.ruoyi.platform.service.MinioService;
|
||||
import com.ruoyi.platform.utils.DockerClientUtil;
|
||||
import com.ruoyi.platform.utils.FileUtil;
|
||||
import com.ruoyi.platform.utils.K8sClientUtil;
|
||||
import com.ruoyi.platform.vo.ImageVo;
|
||||
import com.ruoyi.system.api.model.LoginUser;
|
||||
import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim;
|
||||
import io.kubernetes.client.openapi.models.V1Pod;
|
||||
import lombok.Synchronized;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
@ -50,6 +52,9 @@ public class ImageServiceImpl implements ImageService {
|
|||
private ImageVersionDao imageVersionDao;
|
||||
@Resource
|
||||
private K8sClientUtil k8sClientUtil;
|
||||
@Resource
|
||||
private DockerClientUtil dockerClientUtil;
|
||||
|
||||
@Resource
|
||||
private MinioService minioService;
|
||||
@Value("${harbor.bucketName}")
|
||||
|
@ -75,6 +80,8 @@ public class ImageServiceImpl implements ImageService {
|
|||
private String proxyUrl;
|
||||
@Value("${minio.pvcName}")
|
||||
private String pvcName;
|
||||
@Value("${jupyter.namespace}")
|
||||
private String namespace;
|
||||
/**
|
||||
* 通过ID查询单条数据
|
||||
*
|
||||
|
@ -350,4 +357,26 @@ public class ImageServiceImpl implements ImageService {
|
|||
String path = loginUser.getUsername()+"/"+file.getOriginalFilename();
|
||||
return minioService.uploadFile(bucketName, path, file);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Synchronized
|
||||
public String saveImage(ImageVo imageVo) {
|
||||
if(imageDao.getByName(imageVo.getName()) != null){
|
||||
throw new IllegalStateException("镜像名称已存在");
|
||||
}
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
String username = loginUser.getUsername().toLowerCase();
|
||||
String podName = username +"-editor-pod" + "-" + imageVo.getDevEnvironmentId();
|
||||
|
||||
try {
|
||||
String containerId = k8sClientUtil.getPodContainerId(podName, namespace);
|
||||
String hostIp = k8sClientUtil.getHostIp(podName, namespace);
|
||||
|
||||
dockerClientUtil.commitImage(imageVo,containerId,hostIp,username);
|
||||
dockerClientUtil.pushImageToHorbor(imageVo,hostIp);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ public class JupyterServiceImpl implements JupyterService {
|
|||
//TODO 设置镜像可配置,这里先用默认镜像启动pod
|
||||
|
||||
// 调用修改后的 createPod 方法,传入额外的参数
|
||||
Integer podPort = k8sClientUtil.createConfiguredPod(podName, namespace, port, mountPath, pvc, image, minioPvcName, datasetPath, modelPath);
|
||||
Integer podPort = k8sClientUtil.createConfiguredPod(podName, namespace, port, mountPath, pvc, devEnvironment.getImage(), minioPvcName, datasetPath, modelPath);
|
||||
String url = masterIp + ":" + podPort;
|
||||
redisService.setCacheObject(podName,masterIp + ":" + podPort);
|
||||
devEnvironment.setStatus("Pending");
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
package com.ruoyi.platform.utils;
|
||||
|
||||
import com.github.dockerjava.api.DockerClient;
|
||||
import com.github.dockerjava.api.command.CommitCmd;
|
||||
import com.github.dockerjava.api.model.AuthConfig;
|
||||
import com.github.dockerjava.core.DefaultDockerClientConfig;
|
||||
import com.github.dockerjava.core.DockerClientConfig;
|
||||
import com.github.dockerjava.core.DockerClientImpl;
|
||||
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
|
||||
import com.github.dockerjava.transport.DockerHttpClient;
|
||||
import com.ruoyi.platform.vo.ImageVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DockerClientUtil {
|
||||
|
||||
@Value("${harbor.bucketName}")
|
||||
private String bucketName;
|
||||
@Value("${harbor.repository}")
|
||||
private String repository;
|
||||
@Value("${harbor.harborUrl}")
|
||||
private String harborUrl;
|
||||
@Value("${harbor.harborUser}")
|
||||
private String harborUser;
|
||||
@Value("${harbor.harborpassword}")
|
||||
private String harborpassword;
|
||||
|
||||
public DockerClient getDockerClient(String dockerServerUrl) {
|
||||
|
||||
//创建DefaultDockerClientConfig(指定docker服务器的配置)
|
||||
DockerClientConfig config = DefaultDockerClientConfig
|
||||
.createDefaultConfigBuilder()
|
||||
.withDockerHost("tcp://"+ dockerServerUrl +":2375")
|
||||
.withDockerTlsVerify(false)
|
||||
.withApiVersion("1.40")
|
||||
// .withDockerCertPath(dcokerCertPath)
|
||||
// .withRegistryUsername(registryUser)
|
||||
// .withRegistryPassword(registryPass)
|
||||
// .withRegistryEmail(registryMail)
|
||||
// .withRegistryUrl(registryUrl)
|
||||
.build();
|
||||
|
||||
//创建DockerHttpClient
|
||||
DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
|
||||
.dockerHost(config.getDockerHost())
|
||||
.sslConfig(config.getSSLConfig())
|
||||
.maxConnections(1000)
|
||||
.connectionTimeout(Duration.ofSeconds(300))
|
||||
.responseTimeout(Duration.ofSeconds(450))
|
||||
.build();
|
||||
|
||||
//创建DockerClient
|
||||
return DockerClientImpl.getInstance(config, httpClient);
|
||||
|
||||
}
|
||||
|
||||
public String commitImage(ImageVo imageVo, String containerId, String hostIp, String userName) {
|
||||
DockerClient dockerClient = getDockerClient(hostIp);
|
||||
|
||||
// dockerClient.startContainerCmd(containerId).exec();
|
||||
// 提交容器为镜像,这里的"new_image"和"new_tag"是新镜像的名字和标签
|
||||
CommitCmd commitCmd = dockerClient.commitCmd(containerId)
|
||||
.withRepository(imageVo.getName())
|
||||
.withTag(imageVo.getTagName())
|
||||
.withAuthor(userName)
|
||||
.withMessage(imageVo.getDescription());
|
||||
String exec = commitCmd.exec();
|
||||
return exec;
|
||||
}
|
||||
|
||||
public void pushImageToHorbor(ImageVo imageVo, String hostIp) {
|
||||
|
||||
DockerClient dockerClient = getDockerClient(hostIp);
|
||||
//Harbor登录信息
|
||||
AuthConfig autoConfig = new AuthConfig().withRegistryAddress(harborUrl).withUsername(harborUser).withPassword(harborpassword);
|
||||
|
||||
|
||||
String localImageName = imageVo.getName() + ":" + imageVo.getTagName();
|
||||
String imageName = harborUrl + "/" + bucketName + "/" + imageVo.getName();
|
||||
|
||||
//给镜像打上tag
|
||||
dockerClient.tagImageCmd(localImageName, imageName, imageVo.getTagName()).exec();
|
||||
//推送镜像至镜像仓库
|
||||
try {
|
||||
dockerClient.pushImageCmd(imageName).withAuthConfig(autoConfig).start().awaitCompletion();
|
||||
//push成功后,删除本地加载的镜像
|
||||
dockerClient.removeImageCmd(localImageName).exec();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("推送镜像失败:"+e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -136,7 +136,7 @@ public class K8sClientUtil {
|
|||
if (v1ServiceList!=null) {
|
||||
for (V1Service svc : v1ServiceList.getItems()) {
|
||||
if (StringUtils.equals(svc.getMetadata().getName(), serviceName)) {
|
||||
// PVC 已存在
|
||||
// SVC 已存在
|
||||
return svc;
|
||||
}
|
||||
}
|
||||
|
@ -380,6 +380,9 @@ public class K8sClientUtil {
|
|||
Map<String, String> selector = new LinkedHashMap<>();
|
||||
selector.put("k8s-jupyter", podName);
|
||||
|
||||
Map<String, String> nodeSelector = new LinkedHashMap<>();
|
||||
nodeSelector.put("resource-type", "CPU-GPU");
|
||||
|
||||
CoreV1Api api = new CoreV1Api(apiClient);
|
||||
V1PodList v1PodList = null;
|
||||
try {
|
||||
|
@ -423,6 +426,7 @@ public class K8sClientUtil {
|
|||
.withVolumeMounts(volumeMounts)
|
||||
.endContainer()
|
||||
.withVolumes(volumes)
|
||||
.withNodeSelector(nodeSelector)
|
||||
.endSpec()
|
||||
.build();
|
||||
|
||||
|
@ -506,6 +510,28 @@ public class K8sClientUtil {
|
|||
return pod.getStatus().getPhase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Pod的名称和Namespace查询Pod的容器信息
|
||||
* @param podName Pod的名称
|
||||
* @param namespace Pod所在的Namespace
|
||||
*/
|
||||
public String getPodContainerId(String podName, String namespace) throws Exception {
|
||||
CoreV1Api api = new CoreV1Api(apiClient);
|
||||
V1Pod pod = api.readNamespacedPod(podName, namespace, null, null, null);
|
||||
|
||||
if(pod.getStatus().getContainerStatuses().size() !=1){
|
||||
throw new RuntimeException("容器错误");
|
||||
}
|
||||
String containerId = pod.getStatus().getContainerStatuses().get(0).getContainerID().split("//")[1];
|
||||
return containerId;
|
||||
}
|
||||
|
||||
public String getHostIp(String podName, String namespace) throws Exception {
|
||||
CoreV1Api api = new CoreV1Api(apiClient);
|
||||
V1Pod pod = api.readNamespacedPod(podName, namespace, null, null, null);
|
||||
return pod.getStatus().getHostIP();
|
||||
}
|
||||
|
||||
public String getPodLogs(String podName,String namespace,String container,int line) {
|
||||
CoreV1Api api = new CoreV1Api(apiClient);
|
||||
try {
|
||||
|
|
|
@ -43,7 +43,7 @@ public class ImageVo implements Serializable {
|
|||
/**
|
||||
* 镜像tag名称
|
||||
*/
|
||||
@ApiModelProperty(name = "tag_name")
|
||||
@ApiModelProperty(name = "tagName")
|
||||
private String tagName;
|
||||
|
||||
/**
|
||||
|
@ -64,6 +64,8 @@ public class ImageVo implements Serializable {
|
|||
private String path;
|
||||
|
||||
|
||||
@ApiModelProperty(value = "环境id")
|
||||
private String devEnvironmentId;
|
||||
// public Integer getId() {
|
||||
// return id;
|
||||
// }
|
||||
|
@ -147,7 +149,17 @@ public class ImageVo implements Serializable {
|
|||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String getDevEnvironmentId() {
|
||||
return devEnvironmentId;
|
||||
}
|
||||
|
||||
public void setDevEnvironmentId(String devEnvironmentId) {
|
||||
this.devEnvironmentId = devEnvironmentId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- RuoYi Common Log -->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
|
@ -77,7 +77,13 @@
|
|||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-swagger</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- RuoYi Common DataSource -->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common-datasource</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
Loading…
Reference in New Issue