使用 Node.js 操作 Docker,不是使用 Dcoker 容器化 Node.js 服務(wù)哦!
?最近因?yàn)楣ぷ?,需要通過 Node.js 對(duì) Docker 進(jìn)行一系列操作如,創(chuàng)建刪除容器以及下發(fā)指令獲取結(jié)果等。找了一圈網(wǎng)上大部分資源都是如何容器化 Node.js App 而非通過 Node.js 操作 Docker,Docker 官方也并未提供針對(duì) Node.js 的 sdk,所以這篇文章就簡(jiǎn)單帶大家了解一下如何通過 Node.js 相對(duì)高效的向 Docker daemon 直接下發(fā)指令。
Docker 及容器技術(shù)簡(jiǎn)單介紹
容器化出現(xiàn)的目的是以一種更加輕量、標(biāo)準(zhǔn)、快速的方式對(duì)軟件代碼進(jìn)行打包以及分發(fā)。相比于傳統(tǒng) VM,容器化技術(shù)使用更少的系統(tǒng)資源占用率且擁有更快的應(yīng)用啟動(dòng)速度。
Docker Engine 類似 Client-sever 模式。用戶通過 Docker CLI 如 run、ps、rm 等將指令下發(fā)給 Docker daemon 再由 daemon 去執(zhí)行對(duì)應(yīng)操作Docker 官方同時(shí)也提供了一系列 http 協(xié)議的接口也可以對(duì) daemon 直接下發(fā)指令。
參考:https://docs.docker.com/engine/api/v1.41/#section/Versioning
注意: Docker daemon 在本機(jī)上使用 Unix-socket,常用的 Axios 并不支持。
在這提供幾種解決方式有興趣的同學(xué)可以動(dòng)手操作看看:
- 讓 Docker 服務(wù)監(jiān)聽 Tcp 端口。
- 使用 Node.js 原生的 http 模組或者其他 npm 包,如 got
- 使用 Dockerode,第三方 Docker sdk on Node.js
如何通過 Node.js 向 Docker daemon 下發(fā)指令
普通 cli 指令
使用 child_process 模組中的 exec、spawn 函數(shù),通過子進(jìn)程執(zhí)行 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一個(gè)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}`);
});
通過上面兩種方式可以實(shí)現(xiàn)向 Docker daemon 下發(fā)指令,但是對(duì)于每次操作都需要?jiǎng)?chuàng)建并維護(hù)一個(gè)新的子進(jìn)程,因此開銷會(huì)很大,而且也不是 Node.js 的優(yōu)勢(shì)所在,因此接下來會(huì)結(jié)合第三方 docker-node sdk Dockerode 和 Docker http Api,通過 http 請(qǐng)求的方式實(shí)現(xiàn)上面的目標(biāo)。
Dockerode = Docker + Node.js
(https://www.npmjs.com/package/dockerode)
Dockerode 是基于 Docker-modem 在已經(jīng)解決了所有網(wǎng)絡(luò)問題(端口、協(xié)議)的基礎(chǔ)上將 Docker Api 封裝而成的 sdk。Dockerode 中所有函數(shù)都提供了兩種寫法,callback 和 promise 的寫法。官網(wǎng)提供的大多是 callback 的寫法,在這里我們主要會(huì)使用 promise 結(jié)合 async/await 的寫法。下面將簡(jiǎn)單介紹基本使用:
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 命令的寫法。請(qǐng)注意作為區(qū)別于 cli 方式,使用 stream 將指令傳入的方式,務(wù)必將 tty 設(shè)定成 false,在 container_opts 中添加 hijack:true參考:https://github.com/apocas/dockerode/issues/455#issuecomment-489436370
總結(jié)
Dockerode 使用 Node.js 最擅長(zhǎng)的方式通過 http 請(qǐng)求對(duì) Docker daemon 下發(fā)指令,干凈且高效。調(diào)用Dockerode 中函數(shù)的參數(shù)配置同 Docker 的官方文案。只是網(wǎng)上關(guān)于 Dockerode 文章不多,且使用時(shí),有些配置有坑需要注意。