您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
Docker Client的启动与命令执行
 
作者 shlallen的博客,火龙果软件    发布于 2014-10-22
   次浏览      
 

在上文Docker源码分析之——Docker Daemon的启动 中,介绍了Docker Daemon进程的启动。Docker Daemon可以认为是一个Docker作为Server的运行载体,而真正发送关于docker container操作的请求的载体,在于Docker Client。本文从Docker源码的角度,分析Docker Client启动与执行请求的过程。

Docker Client启动的流程与Docker Daemon启动的过程相仿。首先执行reexec.Init();随后解析flag参数;由于没有指定-v,-D参数,因此*flVersion与*flDebug均为false,不执行相应的代码块;接着程序执行flHosts信息的获取与验证;又由于没有指定-d参数,故*flDeamon为false,不会执行mainDaemon()方法所在的代码块;最后,接着执行的都是Docker Client启动以及执行的工作。

首先,验证flHosts参数中是带有多个Host的信息,如tcp://host:port,unix://path_to_socket或者fd://socketfd,当flHosts的长度大于1,即Host信息不止一个时,通过log的形式通知用户最好指定一个Host。然后,默认将flHosts信息中的第一个对象作为Host,进行协议与host地址的解析,代码如下:

if len(flHosts) > 1 {  
log.Fatal("Please specify only one -H")
}
protoAddrParts := strings.SplitN(flHosts[0], "://", 2)

接着,创建一个Docker Client的cli以及与安全传输层协议TLS相关的对象tlsConfig。安全传输层协议(TLS)用于两个通信应用程序之间保密性与数据完整性,该协议有两层组成:TLS记录协议和TLS握手协议。代码如下:

var (  
cli *client.DockerCli
tlsConfig tls.Config
)
tlsConfig.InsecureSkipVerify = true

随后,如果执行docker命令的时候指定了-tlsverify参数,则*flTlsVerify为true,说明Docker Client需要加载一个受信的ca,用以验证 Docker Server,执行一下代码块:

// If we should verify the server, we need to load a trusted ca  
if *flTlsVerify {
*flTls = true
certPool := x509.NewCertPool()
file, err := ioutil.ReadFile(*flCa)
if err != nil {
log.Fatalf("Couldn't read ca cert %s: %s", *flCa, err)
}
certPool.AppendCertsFromPEM(file)
tlsConfig.RootCAs = certPool
tlsConfig.InsecureSkipVerify = false
}

可以看到,最要的就是*flCa参数,而该参数在./docker/flag.go中的init()方法中初始化时被赋值,如下:

flCa = flag.String([]string{"-tlscacert"}, filepath.Join(dockerCertPath, defaultCaFile),
 "Trust only remotes providing a certificate signed by the CA given here")  

var (  
dockerCertPath = os.Getenv("DOCKER_CERT_PATH")
)

const (  
defaultCaFile = "ca.pem"
defaultKeyFile = "key.pem"
defaultCertFile = "cert.pem"
)

因此,可以通过以上信息,获取*flCa,并读取相应的ca.pem文件,最终添加至tlsConfig对象的属性RootCAs和InsecureSkipVerify中。

验证完server,添加了受信ca后,Docker Client需要配置认证信息,以便之后的发送。主要还是给tlsConfig对象中添加属性Certificates,代码如下:

// If tls is enabled, try to load and send client certificates  
if *flTls || *flTlsVerify {
_, errCert := os.Stat(*flCert)
_, errKey := os.Stat(*flKey)
if errCert == nil && errKey == nil {
*flTls = true
cert, err := tls.LoadX509KeyPair(*flCert, *flKey)
if err != nil {
log.Fatalf("Couldn't load X509 key pair: %s. Key encrypted?", err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
}

以下真正给Docker Client的cli对象初始化,主要的区别在于是否需要使用安全传输层协议,即只要*flTls和*flTlsVerify中有一个为true,则说明需要使用安全传输层协议;反之,不使用安全传输层协议。

if *flTls || *flTlsVerify {  
cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, protoAddrParts[0], protoAddrParts[1], &tlsConfig)
} else {
cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, protoAddrParts[0], protoAddrParts[1], nil)
}

client.NewDockerCli()的实现位于./api/client/cli.go,代码如下:

func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string, tlsConfig *tls.Config) *DockerCli {  
var (
isTerminal = false
terminalFd uintptr
scheme = "http"
)

if tlsConfig != nil {
scheme = "https"
}

if in != nil {
if file, ok := out.(*os.File); ok {
terminalFd = file.Fd()
isTerminal = term.IsTerminal(terminalFd)
}
}

if err == nil {
err = out
}
return &DockerCli{
proto: proto,
addr: addr,
in: in,
out: out,
err: err,
isTerminal: isTerminal,
terminalFd: terminalFd,
tlsConfig: tlsConfig,
scheme: scheme,
}
}

总体而言,创建DockerCli对象较为简单,较为重要的DockerCli的属性有proto:传输协议;addr:host的目标地址,tlsConfig:安全传输层协议的配置。若tlsConfig为有内容,则说明需要使用安全传输层协议,DockerCli对象的scheme设置为“https”,另外还有关于输入,输出以及错误显示的配置,最终返回该对象。

在./docker/docker.go中,创建完给cli对象初始化之后,执行的就是用户所指定的命令了,代码如下:

if err := cli.Cmd(flag.Args()...); err != nil {  
if sterr, ok := err.(*utils.StatusError); ok {
if sterr.Status != "" {
log.Println(sterr.Status)
}
os.Exit(sterr.StatusCode)
}
log.Fatal(err)
}

在该过程中,真正的执行载体为cli.Cmd(flag.Args()...),该代码解析并执行真正如“docker pull xxx”, “docker search xxx”等的指令。

进入./api/client/cli.go中的Cmd方法,代码如下:

// Cmd executes the specified command  
func (cli *DockerCli) Cmd(args ...string) error {
if len(args) > 0 {
method, exists := cli.getMethod(args[0])
if !exists {
fmt.Println("Error: Command not found:", args[0])
return cli.CmdHelp(args[1:]...)
}
return method(args[1:]...)
}
return cli.CmdHelp(args...)
}

首先Cmd方法通过第一个参数来执行getMethod方法,若该method没有找到的话,则调用CmdHelp方法,若method找到,则执行method方法,传入参数为第一个开始往后所有的参数,代码如下:

func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) {  
if len(name) == 0 {
return nil, false
}
methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
method := reflect.ValueOf(cli).MethodByName(methodName)
if !method.IsValid() {
return nil, false
}
return method.Interface().(func(...string) error), true
}

以下以命令“docker pull xxx”为例,分析Docker Client的Cmd的运行。首先,载Cmd方法中传入的参数为“pull”,“xxx”;则getMethod方法传入参数只有一个,为“pull”;getMethod方法返回CmdPull;则在Cmd方法中执行CmdPull(args[1:]...)。而CmdPull方法的实现位于./api/client/command.go中的CmdPull。

在具体的CmdXxx方法中,一般都需要通过cli于Docker Server进行通信,如CmdPull方法实现中有cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{ "X-Registry-Auth": registryAuthHeader,}) ,而cli的stream方法实现,以及类似的call方法实现,都位于./api/client/utils.go中。utils.go中主要实现了cli如何一个HTTPClient的形式给server发送请求,以及如果需要的话,如何在整个发送过程使用安全传输协议。

当cli请求发送完毕,且接收到相应的相应之后,则对于Docker Client而言,执行一次的生命周期就结束了。当你需要另外再执行一个命令时,则需要以上的流程再走一遍。

以上便是从Docker源码的角度,分析Docker Client的启动与命令执行。

   
次浏览       
 
相关文章

云计算的架构
对云计算服务模型
云计算核心技术剖析
了解云计算的漏洞
 
相关文档

云计算简介
云计算简介与云安全
下一代网络计算--云计算
软浅析云计算
 
相关课程

云计算原理与应用
云计算应用与开发
CMMI体系与实践
基于CMMI标准的软件质量保证
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]

专家视角看IT与架构
软件架构设计
面向服务体系架构和业务组件的思考
人人网移动开发架构
架构腐化之谜
谈平台即服务PaaS
更多...   
相关培训课程

云计算原理与应用
Windows Azure 云计算应用

摩托罗拉 云平台的构建与应用
通用公司GE Docker原理与实践
某研发中心 Openstack实践
知名电子公司 云平台架构与应用
某电力行业 基于云平台构建云服务
云计算与Windows Azure培训
北京 云计算原理与应用