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管理面板SimpleDocker docker远程管理api_json

,还有一些十分有用的 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 文件。我们称之为发行包。

云原生之部署Docker管理面板SimpleDocker docker远程管理api_Docker_02