当前位置: 首页 > 科技观察

使用Node.js来运行Docker并不是使用Docker来容器化Node.js服务!

时间:2023-03-15 09:21:59 科技观察

最近因为工作原因,需要通过Node.js对Docker进行一系列的操作,比如创建和删除容器,下发指令获取结果等。看了一圈,网上大部分资源都是关于如何将Node.jsApp容器化,而不是通过Node.js操作Docker,而且Docker官方也没有提供Node.js的sdk,所以这篇文章就简单介绍一下如何通过Node.js相对高效地直接向Docker守护进程发出指令。Docker与容器技术简介容器化的目的是将软件代码以更轻、更标准、更快速的方式打包和分发。与传统虚拟机相比,容器化技术占用的系统资源更少,应用启动速度更快。DockerEngine类似于Client-server模式。用户通过DockerCLI向Dockerdaemon发送run、ps、rm等指令,由daemon执行相应的操作。Docker还提供了一系列http协议接口,直接向守护进程下发指令。参考:https://docs.docker.com/engine/api/v1.41/#section/Versioning注意:Dockerdaemon在本机使用Unix-socket,常用的axios不支持。有兴趣在这里提供几种方案的同学可以自己动手:让Docker服务监听Tcp端口。使用Node.js原生的http模块或者其他npm包,比如gotusingDockerode,Node.js上的第三方Dockersdk如何通过Node.js向Dockerdaemon发出指令普通的cli指令使用exec和spawninchild_process模块??函数通过子进程执行Docker提供的cli指令。如下:const{exec}=require('child_process')//列出容器infoexec('dockerps-a',(err,stdout,stderr)=>{if(err){console.error(`execerror:${err}`);return;}console.log(`stdout:${stdout}`);//打印所有存在的容器console.error(`stderr:${stderr}`);});const{spawn}=require('child_process')const{Readable}=require('stream')//使用终端传入命令constcontainer=spawn('docker',['run','-it','bash']);process.stdin.pipe(container.stdin);//connectparentstdintochildstdin//-itflag:i是开启容器stdin,t是附加一个伪tty,参考docker官方参考//通过stream的方式传入指令//constcontainer=spawn('docker',['run','-i','bash']);//constbuffer=newReadable();//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(`子进程以代码${code}`退出);});以上两种方式都可以用来向Dockerdaemon下发指令,但是每次操作都需要创建一个新的子进程并维护进程,所以开销会很大,不是Node.js的优势,所以会结合第三方docker-nodesdkDockerode和DockerhttpApi来实现上面的目标Dockerode=Docker+Node。js(https://www.npmjs.com/package/dockerode)Dockerode是一个基于Docker-modem的sdk,解决了所有的网络问题(端口,协议),封装了DockerApi。Dockerode中的所有函数都提供了两种写法,callback和promise。官网大多提供了callback的写法,这里主要使用promise结合async/await的写法。下面简单介绍一下基本用法:constDocker=require('dockerode');constdocker=newDocker();asyncfunctionwrapper(){constopts={Image:'bash',AttachStdin:true,AttachStdout:true,AttachStderr:true,Tty:true,//如果不使用进程标准输入,tty设置为falseOpenStdin:true,StdinOnce:true,//AutoRemove:true,};constcontainer_opts={stream:true,stdin:true,stdout:true,stderr:true,//hijack:true,!!如果不使用processstdin则必须在此处设置为true};constcontainer=awaitdocker.createContainer(opts);conststream=awaitcontainer.attach(container_opts);//通过终端传入指令process.stdin.pipe(stream);stream.pipe(process.stdout);//通过缓冲区传入的指令//constd=newDuplex();//d._write=()=>{};//避免小错误//d.pipe(stream);//stream.pipe(d);//stream.on('data',(data)=>{////在这里处理结果//});//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请求向Dockerdaemon发出指令,干净高效。Dockerode中调用函数的参数配置与Docker官方文档一致。只是网上介绍Dockerode的文章不多,使用的时候需要注意一些配置坑。