daemon 是docker的后台守护进程。事实上,Docker守护程序是在主机操作系统上运行的服务。它目前只能在Linux上运行,因为它依赖于许多Linux内核功能,但是有一些方法可以在MacOS和Windows上运行Docker。
Docker守护程序本身公开了一个REST API。从这里开始,许多不同的工具可以通过此API与守护进程通信。
Docker CLI是一个命令行工具,可让您与Docker守护程序通信。安装Docker时,可以同时获得Docker守护程序和Docker CLI工具。
下图表明了 daemon,rest API 和 docker CLI 三者的相互关系。
,还有一些十分有用的 docker 可视化管理工具,我使用Portainer,它方便docker 的管理。官网地址:https://www.portainer.io/
我希望用nodeJS 编写一个简单的docker 管理工具。嵌入式到自己的应用中。首先,我们大致了解一下docker CLI和docker API。
Docker CLI
docker CLI 命令非常多,我们列出一些常用的命令
镜像管理
列出镜像
-docker image ls
-docker images
保存镜像(save)
格式: docker image save [OPTIONS] IMAGE [IMAGE...]
实例: 将容器busybox 生成镜像,并保存为busybox.tar
docker save busybox > busybox.tar
docker save --output busybox.tar busybox
载入镜像(load)
格式:docker load [OPTIONS]
实例:
docker load -i ubuntu.tar
docker load < ubuntu.tar
注意:
使用save 保存的镜像,使用load 载入。load和save 是对应的命令。而import 对应export。两者不能搞混。save 和export 命令的差别是 :export 的镜像再导入时会丢失镜像所有的历史 。而save的镜像再导入时不会丢失镜像所有的历史。
删除镜像
格式
实例:
docker rmi test1
容器管理
列出容器
docker ps
新建容器
格式:-docker create [OPTIONS] IMAGE [COMMAND] [ARG...]
实例
:
使用docker镜像nginx:latest创建一个容器,并将容器命名为myrunoob
docker create --name myrunoob nginx:latest
启动和停止容器
格式
:
docker start [OPTIONS] CONTAINER [CONTAINER...]
docker stop [OPTIONS] CONTAINER [CONTAINER...]
docker restart [OPTIONS] CONTAINER [CONTAINER...]
实例:启动已被停止的容器myrunoob
docker start myrunoob
停止运行中的容器myrunoob
docker stop myrunoob
删除容器
格式:docker container rm [OPTIONS] CONTAINER [CONTAINER...]
实例:删除容器nginx01,并删除容器挂载的数据卷
docker rm -v nginx01
杀掉一个容器
格式:docker kill [OPTIONS] CONTAINER [CONTAINER...]
实例:杀掉运行中的容器mynginx
docker
kill my_container
注意 rm 和kill 的差别
docker kill 杀死一个容器
docker rm 清除一个终止的容器
docker API
docker daemon 提供了一套web RESTFull API 。下面我们列出部分常用的API:
Docker Daemon IP地址与端口
Docker Daemon作为守护进程运行在后台,和Docker Client通过Socket方式通信,Docker Daemon监听Socker有三种方式:unix、tcp、fd 。
默认情况下,Docker Daemon和Docker Client在同一台主机上时使用本地Socket通信,Docker Daemon的配置为unix:///var/run/docker.sock
当Docker Daemon和Docker Client运行在不同主机上是,需要在Docker Daemon中开启TCP Socket,如果没有启动–tls选项,则Docker Daemon和Docker Client间的通信没有任何认证加密,这个时候就可以通过-H设置Docker Daemon在某个IP上监听
当主机有多个网卡时,通过docker daemon -H tcp://0.0.0.0:2375使得Docker Daemon在主机的所有IP上监听,端口为2375;使用docker daemon -H tcp://10.35.21.8:2375则使得Docker Daemon只在某个IP上监听,这时Docker Client使用docker -H 10.35.21.8:2375 info向Docker Daemon发送命令成功,而向另一个IP发送命令则会失败。
对于单机系统而言,使用UNIX Socket 更合适。
常用的API
容器列表
GET /containers/json
返回:例如容器的json 数组。
建立容器
POST /containers/create
请求的数据体为json数据,最简单的json 体是
{
Image:“ubuntu”
}
启动容器
POST /containers/{id}/start
id 是容器的id。
没有json body。
停止容器
POST /containers/{id}/stop
id 是容器的id
载入镜像
POST /images/load
数据体是image.tar 文件
删除容器
DELETE /containers/{id}
id 是容器的id
删除镜像
DELETE /images/{name}
使用nodeJS http request 访问daemon API
编程的事情唯有代码更能帮助理解文档。我们编写了nodeJS 下的一个程序。来调用docker daemon API。网络上这样的例子并不多。github上的hodeJS container 管理软件,比如:nodeode。又好像过于复杂。我坚持采用nodeJS 的HTTP request 实现docker daemon API管理。期间还是遇到一些问题,在分享给大家。
下面是四个函数:
listContainer
uploadImage
createContainer
startContainer
function listContainer(callBack)
{
const http = require('http');
const options = {
socketPath: '/var/run/docker.sock',
path: '/containers/json\?all\=1',
method: "GET",
};
const callback = res => {
console.log(`STATUS: ${res.statusCode}`);
res.setEncoding('utf8');
res.on('data', data => {
console.log(data);
callBack(data,false);
});
res.on('error', data =>{
console.log(data);
callBack(data,true);
});
};
const clientRequest = http.request(options, callback);
clientRequest.end();
}
function uploadImage(filepath,callBack)
{
const http = require('http');
const options = {
socketPath: '/var/run/docker.sock',
path: "/images/load",
method: "POST",
headers: {
'Content-Type': 'application/octet-stream',
}
}
const callback = res => {
console.log(`STATUS: ${res.statusCode}`);
res.setEncoding('utf8');
res.on('data', data => {
// console.log(data);
callBack(data);
});
res.on('error', data =>console.log(data));
};
const clientRequest = http.request(options, callback);
var readStream = fs.createReadStream(filepath);
//clientRequest.pipe(fs.createWriteStream(filepath));
readStream.pipe(clientRequest);
//clientRequest.end();
}
function createContainer(imageName,callBack)
{
const http = require('http');
const options = {
socketPath: '/var/run/docker.sock',
path: "/containers/create",
method: "POST",
headers: {
'Content-Type': 'application/json',
},
}
const jsonbody={
'Image':imageName
};
const callback = res => {
console.log(`STATUS: ${res.statusCode}`);
res.setEncoding('utf8');
res.on('data', data => {
// console.log(data);
callBack(data);
});
res.on('error', data =>console.log(data));
};
const clientRequest = http.request(options, callback);
clientRequest.write(JSON.stringify(jsonbody));
clientRequest.end();
}
function startContainer(containerID,callBack)
{
const http = require('http');
const options = {
socketPath: '/var/run/docker.sock',
path: "/containers/"+containerID+"/start",
method: "POST",
headers: {
'Content-Type': 'application/json',
},
}
const callback = res => {
console.log(`STATUS: ${res.statusCode}`);
callBack({status:res.statusCode});
};
const clientRequest = http.request(options, callback);
clientRequest.end();
}
主程序
var express = require('express');
var bodyParser = require('body-parser');
var jsonParser= bodyParser.json();
var app = express();
const fs = require('fs');
app.use("/css", express.static(__dirname + '/css'));
app.use("/js", express.static(__dirname + '/js'));
app.use("/images", express.static(__dirname + '/images'));
app.use(bodyParser.json({limit:'1mb'}));
app.use(bodyParser.urlencoded({limit: "50mb", extended: true }));
app.get('/', function(req, res){
res.sendFile( __dirname + '/views/index.html');
});
var jsonParser = bodyParser.json()
var urlencodedParser = bodyParser.urlencoded({ extended: false })
app.get('/list', function(req, res) {
console.log("/list");
res.setHeader('Content-Type', 'application/json');
listContainer(function(data,err){
if (!err)
{
var result=new Array();
var clist=JSON.parse(data);
console.log(clist);
clist.forEach(function(item){
result.push({'name':item.Names[0],'id':item.Id,'image':item.Image,'state':item.State})
});
} else
{
result=[];
}
// console.log(result);
res.end(JSON.stringify(result));
})
})
app.post('/install',function(req, res) {
var filepath="./dockermanagement:latest.tar";
uploadImage(filepath,function result(data){
console.log(data);
createContainer("dockermanagement:latest",function result(data){
console.log(data);
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(data));
});
});
})
app.post('/start',function(req, res) {
var id=req.body.id;
console.log("/start container ID="+id);
startContainer(id,function result(data){
console.log(data);
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(data));
})
})
const port = 3000;
app.listen(port, function() {
console.log('Express server listening on port ' + port);
});
容器建立时的参数
使用docker save 保存的映像文件再次load 和create 是需要有一些额外的配置信息.希望这些信息随同映像文件一同上传。由容器管理程序自动配置。
我们的具体做法是,对应于每一个App 包括了三个文件。它们分别是:
package.json –包描述json
themnail.png –logo 文件
image.tar –映像文件
包描述
package={
container_name:”gpio”
image_name:”gpio.tar”
ExposedPorts:{
"22/tcp": { }
}
"PortBindings:{
“22/tcp”:[
{
“HostPort”:”11022”
}
]
}
Volumes:{
“volumes/data": { }
}
hardware_dependent:{
io:[“3243”,”33”]
mainboard:[“86”]
}
上述三个文件 通过zip 工具打包成 image_release.zip 文件。我们称之为发行包。