在线编程实现!如何在Java后端通过DockerClient操作Docker生成python环境

news2024/11/23 6:57:52

目录

在线编程实现!如何在Java后端通过DockerClient操作Docker生成python环境

一、为什么要用后端程序操作Docker

二、安装Docker

1、安装Docker

2、启动Docker

三、DockerClient与CMD操作Docker的区别

四、干货!如何使用DockerClient实现在线编程

1、前置工作

①引入并安装依赖

②构建Java与Docker的链接关系

③在宿主机上安装一个基础镜像

④在宿主机上准备一个工作目录

⑤其他工作

2、生成并启动容器

3、安装python脚本所需的依赖

4、 执行脚本

5、删除容器,清空工作目录

五、总结


作者:watermelo37

涉及领域:Vue、SpingBoot、Docker、LLM、python等

---------------------------------------------------------------------

温柔地对待温柔的人,包容的三观就是最大的温柔。

---------------------------------------------------------------------

在线编程实现!如何在Java后端通过DockerClient操作Docker生成python环境

一、为什么要用后端程序操作Docker

        Docker 是现代开发和部署流程中不可或缺的一部分。它简化了应用程序的环境配置、打包和分发,使得在不同机器上运行相同的应用变得更加轻松和一致。本文将详细介绍如何使用命令行工具(CMD)操控 Docker 来配置环境。

        实现后端操作docker,可以用来实现云端IDE、一键环境搭建、多人协作环境、互动编程教学、可视化部署和管理等等功能。是Docker从服务器走向客户端的必经之路。

二、安装Docker

1、安装Docker

        我写过一份详细的博客,请移步:Docker 入门全攻略:安装、操作与常用命令指南

2、启动Docker

        安装完成后,启动 Docker Desktop,并确保其正常运行。可以在 CMD 中通过以下命令来验证:

docker --version

三、DockerClient与CMD操作Docker的区别

        说实话,我去年开始做在线编程的时候,入门就是用的DockerClient,后来又做了一个进阶项目自动化配置环境的开发,改成了用Java执行cmd指令来操控Docker,前不久看cmd指令不顺眼,又重新改成了DockerClient。

        为什么?因为DockerClient高度封装,将很多细小的指令封装成若干个参数,你看到的就只是一小块含参的链式调用,但其实相当于执行了相当多的“cmd命令”,这样带来的结果就是提升了入门难度,并且长期维护和二次开发需要对DockerClient有较高的熟练度和较深的理解,不像cmd,一行有一行的作用,一行比一行清晰,大致有一个印象就能马上知道它的含义。比如docker cp是复制,比如docker build是镜像生成,再比如docker run用来启动容器,指令后面的参数也高度语义化,非常好理解,最最最关键的是,用cmd指令的时候如果有bug,只需要在终端里面输入执行,查看返回内容以及Docker engine里面的状态,就能知道哪里有bug,非常方便。

        但是cmd的缺点也很明显,比如命令执行较散乱,要注意异步请求的时间节点控制、及时使用websocket返回流式数据等...

        “cmd是这样的,DockerClient只需要把你要执行的命令写到链式调用的参数里面就行了,用cmd要考虑的可就多了”(套公式解题就是快)来看一个例子:

    public void buildImageAndContainer(){
        try {
            // 设置第一个命令:构建Docker镜像
            ProcessBuilder buildProcessBuilder = new ProcessBuilder("docker", "build", "-t", "test0419", ".");

            // 设置工作目录为 "E:\\code\\docker\\test"
            buildProcessBuilder.directory(new File("E:\\code\\docker\\test"));

            // 启动构建镜像的命令并等待其完成
            Process buildProcess = buildProcessBuilder.start();
            buildProcess.waitFor();

            // 读取并打印出构建镜像的输出
            printProcessOutput(buildProcess);

            // 检查构建是否成功
            if (buildProcess.exitValue() == 0) {
                // 设置第二个命令:运行Docker容器
                ProcessBuilder runProcessBuilder = new ProcessBuilder("docker", "run", "-v", "E:/code/docker/test:/app", "-p", "80:80", "test0419");

                // 启动运行容器的命令
                Process runProcess = runProcessBuilder.start();

                // 读取并打印出运行容器的输出
                printProcessOutput(runProcess);

                // 可以在这里等待容器运行的进程结束,或者根据需要进行其他操作
                // runProcess.waitFor();
            } else {
                System.out.println("Docker image build failed.");
            }
        } catch (IOException | InterruptedException e) {
            System.out.println(e);
            e.printStackTrace();
        }
    }

        这一大段代码包括根据Dockerfile文件创建镜像并生成一个容器,并获取执行时的日志信息,以及错误抛出。但是如果使用DockerClient就一两行代码,区别就是这么大。

        这里有一篇基础的使用cmd调用Java后端操作Docker的博文,感兴趣请移步:干货含源码!如何用Java后端操作Docker(命令行篇)

        综上所述,如果你对Docker的原理和执行逻辑比较熟悉,并且需要较多的副产物(日志数据,错误抛出,容器复用,用户管理等),可以考虑使用cmd指令,开发反馈非常好。如果你对Docker的运作机理还不太了解,或者你对Docker已经熟悉透了,都可以使用DockerClient来开发,流程更加整体,代码简洁。

        本篇文章将带大家来看看如何使用DockerClient操作Docker生成python环境,该思路同样适用于所有在线编程的开发过程。

        其他Docker相关文章请上划到文章标题下,在专栏中查阅,希望您能找到您的开发思路,有疑问的也欢迎大家前来沟通:

四、干货!如何使用DockerClient实现在线编程

1、前置工作

①引入并安装依赖
        <dependency>
            <groupId>com.github.docker-java</groupId>
            <artifactId>docker-java</artifactId>
            <version>3.2.5</version> 
        </dependency>
②构建Java与Docker的链接关系

        首先你需要让你的java拥有Docker的访问权限,如果是linux系统比较简单,但如果是windows就需要先做好Docker的配置,配置代码如下(我稍后会写一篇博文介绍如何在各种系统上正确的使用Java连接Docker,敬请期待,如果我忘了请踢我的屁股):

package edu.njnu.opengms.r2.config;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientImpl;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import com.github.dockerjava.transport.DockerHttpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;

@Configuration
public class dockerConfig {

    @Value("${docker.clientHost}")
    private String clientHost;

    @Value("${docker.clientPort}")
    private String clientPort;

    @Bean(name = "dockerClient")
    DockerClient dockerClient() {
        return connect();
//        return DockerClientBuilder.getInstance().build();
    }

    //    连接docker
    private DockerClient connect() {
        String host = "tcp://" + clientHost + ":" + clientPort;
        DefaultDockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder()
                .withDockerHost(host)
                .build();
        DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
                .dockerHost(config.getDockerHost())
                .maxConnections(100)
                .connectionTimeout(Duration.ofSeconds(30))
                .responseTimeout(Duration.ofSeconds(180))
                .build();
        DockerClient client = DockerClientImpl.getInstance(config, httpClient);
        return client;
//        log.info("docker initialize successfully");
    }
}

        在需要使用到DockerClient的位置(比如Controller或者Service)注入依赖

    @Autowired
    DockerClient dockerClient;
③在宿主机上安装一个基础镜像

        随便安装一个你需要的基础镜像,比如python:3.9,但如果是从github上拉取的话,有魔法会更快一些,不然有time out 的风险

docker pull python:3.9
④在宿主机上准备一个工作目录

        新建一个docker专用的文件夹,记录路径,在Java中定义为常量,比如:

private static final String WORKING_DIRECTORY = "E:\\code\\docker\\workDirectory\\";

        这一步是为了做卷挂载,卷挂载后容器内指定文件夹的内容会与宿主机上指定文件夹的内容完全一致

⑤其他工作

        写好api、返回数据结构等。

2、生成并启动容器

        有了基础镜像就可以开始生成容器了,这里是接收一个id,然后根据id生成对应的容器和映射文件夹。如果你的在线编程开发不需要涉及多用户功能,就可以舍去获取id、生成文件夹、检查文件夹是否存在这些步骤。

        这里有个细节是指令:.withCmd("tail", "-f", "/dev/null")

        这个指令的作用是让容器持续的运行下去,这样一个容器可以反复调用不同的脚本,而不是运行完某一个脚本立刻停止。

    @PostMapping("/createContainer")
    public JsonResult createContainer(@RequestParam("scenarioId") String scenarioId,@RequestParam("env") String image) {
        // 检查WORKING_DIRECTORY+"\\scenarioId"这个文件夹是否存在,如果不存在就创建一个
        String fullDirectoryPath = WORKING_DIRECTORY + File.separator + scenarioId;
        File directory = new File(fullDirectoryPath);
        if (!directory.exists()) {
            boolean isCreated = directory.mkdirs();
            if (!isCreated) {
                return ResultUtils.error("Failed to create directory");
            }
        }
        // 检查data子文件夹是否存在,如果不存在就创建一个
        File dataDirectory = new File(fullDirectoryPath + File.separator + "data");
        if (!dataDirectory.exists()) {
            boolean isCreated = dataDirectory.mkdirs();
            if (!isCreated) {
                return ResultUtils.error("Failed to create data directory");
            }
        }


        // 生成容器,并绑定卷挂载目录
        try {
            CreateContainerResponse container = dockerClient.createContainerCmd(image)
                    // 容器持久化运行,这样可以多次使用某个容器调用不同的python脚本
                    .withCmd("tail", "-f", "/dev/null")
                    // 卷挂载,将宿主机文件夹与容器文件夹绑定起来
                    .withHostConfig(new HostConfig().withBinds(new Bind(fullDirectoryPath, new Volume("/app"))))
                    .exec();
            // 启动容器
            dockerClient.startContainerCmd(container.getId()).exec();
            return ResultUtils.success(container.getId());
        }catch(DockerException | DockerClientException e){
            System.out.println(e.getMessage());
            return ResultUtils.error("Error occurred while creating or starting the container: " + e.getMessage());
        }
    }

3、安装python脚本所需的依赖

    @PostMapping("/installRequires")
    public JsonResult installRequires(@RequestParam("containerId") String containerId){
        try {
            ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId)
                    .withCmd("pip", "install", "-r", "/app/requirements.txt")
                    .withAttachStdout(true)
                    .withAttachStderr(true)
                    .exec();

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            dockerClient.execStartCmd(execCreateCmdResponse.getId())
                    .exec(new ExecStartResultCallback(outputStream, System.err))
                    .awaitCompletion();

            return ResultUtils.success(outputStream.toString());
        } catch (InterruptedException e) {
            e.printStackTrace();
            return ResultUtils.error("Error installing dependencies: " + e.getMessage());
        }
    }

        执行这一步骤前需要将requirements.txt文件放入宿主机的对应文件夹中(在该案例中是WORKING_DIRECTORY + File.separator + scenarioId;),我写了一个文件上传的api和python代码解析的api,这两种方式都可以生成requirements.txt文件,如果不涉及用户操作,可以直接手动把requirements.txt文件放入对应文件夹中。

        requirements.txt文件里面是需要装的依赖库的安装别名,可以指定版本,内容就是这样:

pandas
scikit-learn
matplotlib
numpy

4、 执行脚本

        这个api可以多次执行,容器执行完毕后不会立刻停止。

    @PostMapping("/executeScript")
    public JsonResult executeScript(@RequestParam("containerId") String containerId,@RequestParam("scriptName") String scriptName,@RequestParam("scenarioId") String scenarioId) throws IOException {
        try {
            ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId)
                    .withCmd("python", "/app/" + scriptName)
                    .withAttachStdout(true)
                    .withAttachStderr(true)
                    .exec();

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            dockerClient.execStartCmd(execCreateCmdResponse.getId())
                    .exec(new ExecStartResultCallback(outputStream, System.err))
                    .awaitCompletion();

            return ResultUtils.success(outputStream);
        } catch (InterruptedException | IOException e) {
            e.printStackTrace();
            return ResultUtils.success(outputStream);
        }
    }

5、删除容器,清空工作目录

        容器使用完毕后,可以删除容器释放资源。

    @PostMapping("/destroyContainer")
    public JsonResult destroyContainer(@RequestParam("containerId") String containerId){
        // 关闭并删除容器
        String containerState = "";
        try{
            dockerClient.stopContainerCmd(containerId).exec();
            dockerClient.removeContainerCmd(containerId).exec();
            containerState = "容器已成功移除";
        }catch (Exception e){
            containerState = "容器删除失败,错误原因: " +e.getMessage();
        }


        //清空工作目录
        String filesState = pythonEnvironmentalService.cleanWorkingDirectory();
        return ResultUtils.success(containerState+filesState);
    }

        清除工作目录的Service层函数

    // 清除工作目录内容
    public String cleanWorkingDirectory(){
        Path workingDirectoryPath = Paths.get(WORKING_DIRECTORY);
        try {
            deleteDirectoryRecursively(workingDirectoryPath);
            return "工作目录及其所有内容已被删除: " + WORKING_DIRECTORY;
        } catch (IOException e) {
            return e.getMessage();
        }
    }

    // 清空文件夹中非文件夹的方法,同时通过递归清除文件夹
    private static void deleteDirectoryRecursively(Path path) throws IOException {
        if (Files.notExists(path)) {
            // 如果路径不存在,则不需要删除
            return;
        }
        if (Files.isDirectory(path)) {
            // 如果是目录,则获取目录中的所有条目
            Files.list(path).forEach(child -> {
                try {
                    // 对每个条目递归调用此方法
                    deleteDirectoryRecursively(child);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }
        // 删除当前文件或目录(此时它应该是空的)
        if (!path.equals(Paths.get(WORKING_DIRECTORY))&&!path.equals(Paths.get(WORKING_DIRECTORY+"\\data"))) {
            Files.delete(path);
        }
    }

五、总结

        以上内容是一个简单的实现在Java后端中通过DockerClient操作Docker生成python环境并执行代码,最后销毁的案例全过程,也是实现一个简单的在线编程后端API的完整流程,你可以在此基础上添加额外的辅助功能,比如上传文件、编辑文件、查阅文件、自定义安装等功能。

        只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

        更多优质内容,请关注:

        你真的会使用Vue3的onMounted钩子函数吗?Vue3中onMounted的用法详解

        通过array.filter()实现数组的数据筛选、数据清洗和链式调用

        极致的灵活度满足工程美学:用Vue Flow绘制一个完美流程图

        el-table实现动态数据的实时排序,一篇文章讲清楚elementui的表格排序功能

        干货含源码!如何用Java后端操作Docker(命令行篇)

        JavaScript中闭包详解+举例,闭包的各种实践场景:高级技巧与实用指南

        PDF预览:利用vue3-pdf-app实现前端PDF在线展示

        Docker 入门全攻略:安装、操作与常用命令指南

        shpfile转GeoJSON且控制转化精度;如何获取GeoJSON?GeoJson结构详解

        巧用Array.forEach:简化循环与增强代码可读性

        通过array.reduce()实现数据汇总、条件筛选和映射、对象属性的扁平化、转换数据格式等

        Mapbox添加行政区矢量图层、分级设色图层、自定义鼠标悬浮框、添加天地图底图等

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2118717.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

通过信息架构提升商业智能:实现数据驱动创新的策略与方法

拥抱数据驱动时代 在当前的数字化转型浪潮中&#xff0c;数据已成为企业最重要的资产之一。全球范围内的网络巨头通过精细的数据管理和分析&#xff0c;已成功颠覆了传统行业。这些企业的成功不仅源于他们掌握了大量数据&#xff0c;还因为他们能有效地利用这些数据来做出精准…

研一小白读论文记录,计算机视觉,Transformer

论文是IEEE收录的一篇论文《CrossFormer: A Versatile Vision Transformer Hinging on Cross-Scale Attention》&#xff08;《基于跨尺度自注意力机制的多功能视觉Transformer》&#xff09; 泛读完之后最大的感悟有以下几点&#xff1a; 1、文章在实验的结果中呈现了大量的…

Getty Images推AI训练样本:3750张高质量照片免费开放

近日&#xff0c;全球领先的商业图库Getty Images发布了一项重大决策&#xff0c;宣布将为AI开发者提供一份包含3750张高分辨率照片的免费训练数据集。这份详尽的数据集覆盖了商业、教育、医疗健康、运动健身、物品物体、插图图标等15个不同领域&#xff0c;目的在于助力AI技术…

unity UGUI高性能飘字解决方案(对象池+合并网格)

本方案仅供参考 从需求出发 游戏类型&#xff1a;微信小游戏 帧数限定&#xff1a;60 已知的几种方案&#xff1a; 1:场上只存在一个mesh&#xff0c;每帧把所有字绘制到一个mesh。 优点&#xff1a;每帧都重绘&#xff0c;高度定制化&#xff0c;可以随意添加、删除。 …

HTTP状态码解析:在Haskell中判断响应成功与否

在互联网的世界里&#xff0c;HTTP状态码是服务器与客户端之间通信的一种语言。它们告诉我们请求是否成功&#xff0c;或者遇到了什么问题。在进行网络编程时&#xff0c;正确地解析和处理这些状态码是至关重要的。本文将探讨HTTP状态码的基本概念&#xff0c;并展示如何在Hask…

KUKA中级学习4:修改软件中机器人名字,纠正示教器时间,下载备份文件进示教器

这里写目录标题 一、修改机器人名字1.1、程序安装下载二、示教器时间修改2.1、时间修改&#xff0c;示教器全英文显示三、下载备份文件 一、修改机器人名字 1.1、程序安装下载 选下面这个 二、示教器时间修改 2.1、时间修改&#xff0c;示教器全英文显示 三、下载备份文件 …

FancyVideo环境搭建推理

引子 很少关注360开源的代码&#xff0c;最近360AI团队开源了最新视频模型FancyVideo&#xff0c;据说RTX3090可跑。可以在消费级显卡 (如 GeForce RTX 3090) 上生成任意分辨率、任意宽高比、不同风格、不同运动幅度的视频&#xff0c;其衍生模型还能够完成视频扩展、视频回溯…

springboot+vue+mybatis计算机毕业设计网上购物系统+PPT+论文+讲解+售后

本文首先实现了网上购物系统设计与实现管理技术的发展随后依照传统的软件开发流程&#xff0c;最先为系统挑选适用的言语和软件开发平台&#xff0c;依据需求分析开展控制模块制做和数据库查询构造设计&#xff0c;随后依据系统整体功能模块的设计&#xff0c;制作系统的功能模…

《JavaEE进阶》----13.<Spring Boot【配置文件】>

本篇博客讲解 1.SpringBoot配置文件的格式以及对应的语法 2.了解两个配置文件格式的差异、优缺点。 我们这里只做简单的介绍。看会&#xff0c;了解&#xff0c;学会读取就行了。 因为配置文件实在太多了&#xff0c;这里只做基础的介绍。 一、配置文件的作用 前言 计算机中有许…

E5053A 微波下变频器

_XLT新利通_ E5053A 微波下变频器 E5052B SSA 专用的微波下变频器 Keysight E5053A 是一款与 E5052B 信号源分析仪&#xff08;SSA&#xff09;相关的微波下变频器。 如果您需要设计和测试微波或毫米波频率的信号源&#xff0c;E5053A 支持您扩展该分析仪的频率范围。 从…

阿卡迈 Akamai 逆向分析2

在Lzo这个url中点击第一个 进入以后有个HPH是我们需要破解的参数 我们搜索所有的HPH 大概有10个地方&#xff0c;我们需要全部打上断点(部分HPH用不到) 大约这5个地方的HPH需要破解 第一步 清除cookie f5进行刷新 需要破解K1H&#xff0c; 58位的数组其中下标 1 3 15 25 53需…

C# winforms 窗口延迟初始化 splash 定时器

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

微课录制技巧|高效录制微课的方法,如何高效录制微课?

在教育领域&#xff0c;微课作为一种新兴的教学方式&#xff0c;越来越受到教师和学生的欢迎。本文将为您详细介绍如何高效录制微课&#xff0c;以及如何利用各种资源来提升备课和教学的质量。 微课录制技巧&#xff1a; 录制前的准备 在开始录制前&#xff0c;确保您已经明确…

客服知识库与员工培训:打造专业客服团队的秘密武器

在竞争激烈的商业环境中&#xff0c;优质的客户服务已成为企业脱颖而出的关键要素之一。而构建一个高效、专业的客服团队&#xff0c;则离不开一个全面、精准的客服知识库。客服知识库不仅是信息的宝库&#xff0c;更是员工培训与技能提升的秘密武器&#xff0c;它在新员工入职…

Adobe Illustrator非矢量图片的交集利用剪切蒙版实现

AI不支持对于非矢量图片的交集处理&#xff0c;但是可以通过剪切蒙版类似地实现需求。 如下图&#xff0c;字母F是一张PNG图片&#xff0c;为位图文件&#xff08;非矢量&#xff09;。 现在我需要将这种图片与黑色的矩形求交&#xff1a; 将两个目标全部选中&#xff0c;鼠标…

AI 浪潮中的一体化数据库|外滩大会之OceanBase实录

2024 年 9 月 5 日至 7 日&#xff0c;在上海黄浦世博园区&#xff0c;“2024 Inclusion 外滩大会”盛大举行。期间&#xff0c;9月6日&#xff0c;由OceanBase携手赛迪顾问共同策划并主办了 “AI浪潮中的分布式数据库&#xff1a;探索行业增长新动能与关键业务负载实践”。本…

优橙240419期就业榜来啦!就业班平均就业薪资8,333.3元!梦想不会发光,发光的是追梦的你!

有多坚定的信念&#xff0c;就有多勇毅的行动&#xff0c;就能开辟多光明的未来。时隔3个月&#xff0c;优橙240419就业喜报已送达&#xff01; 就业班平均就业薪资8,333.3元&#xff0c;就业学员即将奔赴祖国各地。 行百里者半九十。人类的美好理想&#xff0c;都不可能唾手而…

构建Web3社交平台:DeBox式DApp开发全攻略

要仿照DeBox构建一款Web3社交平台系统&#xff0c;首先需要理解DeBox的核心功能和技术架构&#xff0c;并根据自己的目标和用户需求进行调整和创新。以下是一个基本的开发步骤指南&#xff0c;帮助你从概念到实践&#xff0c;逐步构建一个类似的Web3社交平台。 1. 明确项目目标…

vue3 +百度地图 实现 地点检索,输入联想,经纬度,逆地理编码,创建标记,label等

由于百度地图文档确实有点欠缺&#xff0c;在这里记录一下 vue3 百度地图&#xff08;js api 3.0&#xff09;实现效果如下实现方式注意事项 vue3 百度地图&#xff08;js api 3.0&#xff09; 需求&#xff1a; 地图弹框组件&#xff0c;可以搜索地图点&#xff0c;输入联想…

算法-双指针技巧

文章目录 算法概述奇偶数字归位寻找重复数字接雨水救生艇问题 算法概述 设置两个指针的技巧&#xff0c;其实这种说法很宽泛&#xff0c;似乎没什么可总结的 有时候所谓的双指针技巧&#xff0c;就单纯是代码过程用双指针的形式表达出来而已。 没有单调性(贪心)方面的考虑有时…