要么改变世界,要么适应世界

Docker-Java-Api操控Docker,并向容器中的程序传递参数(标准输入)

2024-05-17 19:24:54
0
目录

问题描述

如果我有一个程序,运行后会从标准输入中获取参数,例如:

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int a = Integer.parseInt(scan.next());
        int b = Integer.parseInt(scan.next());
        System.out.println((a + b));
    }
}

在正常情况下,我们只需要执行以下命令:

javac Main.java
java Main

程序就会在当前的终端执行上述代码对应的字节码(字节码执行过程由JVM负责),我们输入的内容就会传递给该程序。

可是,当上述字节码文件在一个容器中,我们需要使用Java连接Docker且我们的输入也由Java提供时,该如何实现?

两种方式可以实现。

解决方案一

借助echo命令重定向:

@Test
void testDefault() throws InterruptedException {
    final String DOCKER_HOST = "192.168.1.1";
    final int DOCKER_PORT = 2375;
    String serverUrl = String.format("tcp://%s:%d", DOCKER_HOST, DOCKER_PORT);
    DockerClient dockerClient = DockerClientBuilder.getInstance(serverUrl).build();

    // 容器ID
    String containerId = "613ed37d5346";
    // 模拟输入
    String inputString = "2 99\n";
    String[] userCodeCmd = {"sh", "-c", String.format("echo \"%s\" | %s", inputString, "java Main")};
    System.out.println(Arrays.stream(userCodeCmd)
            .collect(Collectors.joining(" ")));
    ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId)
            .withAttachStdout(true)
            .withAttachStderr(true)
            .withAttachStdin(true)
            .withCmd(userCodeCmd)
            .exec();
    // 由于连接容器是一个异步操作,因此要传入回调函数
    ExecStartResultCallback execStartResultCallback = new ExecStartResultCallback() {
        @Override
        public void onNext(Frame frame) {
            if (frame.getStreamType() == StreamType.STDERR) {
                System.out.println("ERROR -> " + new String(frame.getPayload()));
            } else if (frame.getStreamType() == StreamType.STDOUT) {
                System.out.println("STDOUT -> " + new String(frame.getPayload()));
            }
            super.onNext(frame);
        }
    };
    dockerClient.execStartCmd(execCreateCmdResponse.getId())
            .exec(execStartResultCallback).awaitCompletion();
}

实际上我们向容器中传递的命令是:

sh -c echo "2 99
" | java Main

这样的方式简单粗暴,但是不是很优雅,这是因为在Linux中,命令行长度有限制,在Linux系统中,命令行更大长度的限制是由内核参数设置的,通常情况下,这个限制的默认值是4096个字符,一旦超过这个上限就会出现错误信息。

解决方案二(推荐)

更好的方式其实是使用流(stream)的方式,也就是将我们输入的字符串转为输入流,然后直接将该输入流传递给程序,但是默认的连接对象不支持流操作,因此我要换一种方式初始化连接对象:

@Test
void testHttpClient() throws URISyntaxException {
    DefaultDockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder().build();
    // 初始化连接
    URI uri = new URI("tcp://192.168.1.1:2375");
    DockerHttpClient dockerHttpClient = new ApacheDockerHttpClient.Builder()
            .dockerHost(uri)
            .maxConnections(3000)
            .build();
    DockerClient dockerClient = DockerClientBuilder.getInstance(config).withDockerHttpClient(dockerHttpClient).build();
    // 容器ID
    String containerId = "613ed37d5346";
    // 进入容器后要执行的命令
    String[] cmdArgs = new String[] { "java", "Main"};
    ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId)
            .withCmd(cmdArgs)
            .withAttachStdin(true)
            .withAttachStdout(true)
            .withAttachStderr(true)
            .exec();
    System.out.println( execCreateCmdResponse.getRawValues());
    // 由于连接容器是一个异步操作,因此要传入回调函数
    ExecStartResultCallback execStartResultCallback = new ExecStartResultCallback() {
        @Override
        public void onNext(Frame frame) {
            if (frame.getStreamType() == StreamType.STDERR) {
                System.out.println("ERROR -> " + new String(frame.getPayload()));
            } else {
                System.out.println("STDOUT -> " + new String(frame.getPayload()));
            }
            super.onNext(frame);
        }
    };
    // 模拟输入
    String inputString = "2 5\n";
    // 转为输入流
    byte[] inputBytes = inputString.getBytes(StandardCharsets.UTF_8);
    InputStream inputStream = new ByteArrayInputStream(inputBytes);
    // 传入回调函数并执行
    try {
        dockerClient.execStartCmd(execCreateCmdResponse.getId())
                .withStdIn(inputStream)
                .exec(execStartResultCallback)
                .awaitCompletion();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

参考文献

【docker-java】

历史评论
开始评论