随笔

Node.js 调度 Docker 执行任务

最近在更新时 https://camera.lilonghe.net/ 这个网站时,想把读取照片的 EXIF 信息功能加上,找到一个可以直接使用的库,但是发现只能读取普通的 PNG 或者 JPG 文件,不能读取 RAW 格式的照片,而且一些厂商私有的属性也没有适配。

所以又找到了一个工具,但是这个工具只提供了 Windows 和 Mac 的安装包,Linux 只提供了源码需要自己编译安装,但这样就需要去宿主机编译就不方便进行移植了,所以便想到了调度 Docker 来执行任务。

一次性执行容器

从容器的创建,启动,执行,最后销毁来走完成一次执行流程。

比如有个名为 exiftool 的镜像,暴露了 ENTRYPOINT,这里 Cmd 直接提供文件路径即可。

const docker = new Docker({ socketPath: "/var/run/docker.sock" });

// 创建一个容器
const container = await docker.createContainer({
  Image: "exiftool",
  Cmd: [fileName],
  Tty: false,
  HostConfig: {
    Binds: [path.resolve(__dirname) + ":/app"],
  },
});

// 启动容器
await container.start();

// 等待容器完成任务
const stream = await container.attach({
  stream: true,
  stdout: true,
  stderr: true,
});

stream.on("data", (data) => {
  resolve(data.toString());
});

await container.exec({
  Cmd: [fileName],
});

// 等待容器停止
await container.wait();

// 移除容器
await container.remove();
console.log("Container removed");

task result

很顺利的就拿到了结果,但是因为服务器性能过低,跟树莓派都差不多了,所以肯定不能直接这么用,每执行一次任务就创建一个容器过于奢侈了,所以需要启动一个容器后保持住,然后在容器里面开启进程去执行任务。

const targetContainer = docker.getContainer(containerId);

// 创建 exec 实例
const exec = await targetContainer.exec({
  Cmd: command,
  AttachStdout: true,
  AttachStderr: true,
});

// 启动 exec 实例并附加到输出流
const stream = await exec.start({
  Tty: false,
});

核心就是使用 exec 去开启进程执行任务,因为开启了新的进程,所以也不用担心多任务同时执行输出流可能错乱的问题。

除此之外就是需要判断容器的状态,比如容器是否已经创建,是否在运行,然后做相应的逻辑处理。

如果机器性能比较强,其实可以直接重新创建,然后还可以通过 listContainers 去获取当前运行中的容器,更容易配置并发的管理限制,用 exec 就得自己再去处理并发状态之类的东西。

以上,业务系统通过调动 Docker 来执行任务的需求就完成啦。

本文链接:https://note.lilonghe.net/post/execute-external-task-by-docker.html

-- EOF --