使用 Node.js 操作 Docker,不是使用 Dcoker 容器化 Node.js 服務哦!
?最近因為工作,需要通過 Node.js 對 Docker 進行一系列操作如,創建刪除容器以及下發指令獲取結果等。找了一圈網上大部分資源都是如何容器化 Node.js App 而非通過 Node.js 操作 Docker,Docker 官方也并未提供針對 Node.js 的 sdk,所以這篇文章就簡單帶大家了解一下如何通過 Node.js 相對高效的向 Docker daemon 直接下發指令。
Docker 及容器技術簡單介紹
容器化出現的目的是以一種更加輕量、標準、快速的方式對軟件代碼進行打包以及分發。相比于傳統 VM,容器化技術使用更少的系統資源占用率且擁有更快的應用啟動速度。
Docker Engine 類似 Client-sever 模式。用戶通過 Docker CLI 如 run、ps、rm 等將指令下發給 Docker daemon 再由 daemon 去執行對應操作Docker 官方同時也提供了一系列 http 協議的接口也可以對 daemon 直接下發指令。
參考:https://docs.docker.com/engine/api/v1.41/#section/Versioning
注意: Docker daemon 在本機上使用 Unix-socket,常用的 Axios 并不支持。
在這提供幾種解決方式有興趣的同學可以動手操作看看:
- 讓 Docker 服務監聽 Tcp 端口。
- 使用 Node.js 原生的 http 模組或者其他 npm 包,如 got
- 使用 Dockerode,第三方 Docker sdk on Node.js
如何通過 Node.js 向 Docker daemon 下發指令
普通 cli 指令
使用 child_process 模組中的 exec、spawn 函數,通過子進程執行 Docker 提供的 cli 指令。如下所示:
const { exec } = require('child_process')
// list containers info
exec('docker ps -a', (err, stdout, stderr) => {
if (err) {
console.error(`exec error: ${err}`);
return;
}
console.log(`stdout: ${stdout}`); // print all existing containers
console.error(`stderr: ${stderr}`);
});
const { spawn } = require('child_process')
const { Readable} = require('stream')
// 使用terminal傳入指令
const container = spawn('docker', ['run', '-it', 'bash']);
process.stdin.pipe(container.stdin); // connect parent stdin to child stdin
// -it flag: i是開啟容器stdin,t是attach一個pseudo-tty,具體參考docker官方reference
// 通過stream的的方式傳入指令
// const container = spawn('docker', ['run', '-i', 'bash']);
// const buffer = new Readable();
// buffer.pipe(container.stdin);
// buffer.push('ls');
// buffer.push(null);
container.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
container.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
container.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
通過上面兩種方式可以實現向 Docker daemon 下發指令,但是對于每次操作都需要創建并維護一個新的子進程,因此開銷會很大,而且也不是 Node.js 的優勢所在,因此接下來會結合第三方 docker-node sdk Dockerode 和 Docker http Api,通過 http 請求的方式實現上面的目標。
Dockerode = Docker + Node.js
(https://www.npmjs.com/package/dockerode)
Dockerode 是基于 Docker-modem 在已經解決了所有網絡問題(端口、協議)的基礎上將 Docker Api 封裝而成的 sdk。Dockerode 中所有函數都提供了兩種寫法,callback 和 promise 的寫法。官網提供的大多是 callback 的寫法,在這里我們主要會使用 promise 結合 async/await 的寫法。下面將簡單介紹基本使用:
const Docker = require('dockerode');
const docker = new Docker();
async function wrapper() {
const opts = {
Image: 'bash',
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
Tty: true, // tty is set false if not using process stdin
OpenStdin: true,
StdinOnce: true,
// AutoRemove: true,
};
const container_opts = {
stream: true,
stdin: true,
stdout: true,
stderr: true,
// hijack: true, !! must be set true here if not using process stdin
};
const container = await docker.createContainer(opts);
const stream = await container.attach(container_opts);
// 通過terminal傳入指令
process.stdin.pipe(stream);
stream.pipe(process.stdout);
// 通過buffer傳入指令
// const d = new Duplex();
// d._write = () => {}; // avoid trivial error
// d.pipe(stream);
// stream.pipe(d);
// stream.on('data', (data) => {
// // do some work on result here
// });
// d.push('ls');
// d.push(null);
container.start();
}
wrapper();
以上介紹兩種使用 Dockerode 替代 cli 命令的寫法。請注意作為區別于 cli 方式,使用 stream 將指令傳入的方式,務必將 tty 設定成 false,在 container_opts 中添加 hijack:true參考:https://github.com/apocas/dockerode/issues/455#issuecomment-489436370
總結
Dockerode 使用 Node.js 最擅長的方式通過 http 請求對 Docker daemon 下發指令,干凈且高效。調用Dockerode 中函數的參數配置同 Docker 的官方文案。只是網上關于 Dockerode 文章不多,且使用時,有些配置有坑需要注意。