docker分析.docx

上传人:b****4 文档编号:11922771 上传时间:2023-04-16 格式:DOCX 页数:17 大小:41.28KB
下载 相关 举报
docker分析.docx_第1页
第1页 / 共17页
docker分析.docx_第2页
第2页 / 共17页
docker分析.docx_第3页
第3页 / 共17页
docker分析.docx_第4页
第4页 / 共17页
docker分析.docx_第5页
第5页 / 共17页
点击查看更多>>
下载资源
资源描述

docker分析.docx

《docker分析.docx》由会员分享,可在线阅读,更多相关《docker分析.docx(17页珍藏版)》请在冰豆网上搜索。

docker分析.docx

docker分析

该文为《Docker源码分析》系列第二篇,在Docker架构篇的基础上,继续从源码的角度出发,分析用户如何创建DockerClient,以及如何通过DockerClient发送用户具体请求。

可以说,发挥Docker最大魅力,从使用Docker做起,使用Docker,从精通DockerClient入手

1.前言

如今,Docker作为业界领先的轻量级虚拟化容器管理引擎,给全球开发者提供了一种新颖、便捷的软件集成测试与部署之道。

在团队开发软件时,Docker可以提供可复用的运行环境、灵活的资源配置、便捷的集成测试方法以及一键式的部署方式。

可以说,Docker的优势在简化持续集成、运维部署方面体现得淋漓尽致,它完全让开发者从前者中解放出来,把精力真正地倾注在开发上。

然而,把Docker的功能发挥到极致,并非一件易事。

在深刻理解Docker架构的情况下,熟练掌握DockerClient的使用也非常有必要。

前者可以参阅《Docker源码分析》系列之Docker架构篇,而本文主要针对后者,从源码的角度分析DockerClient,力求帮助开发者更深刻的理解DockerClient的具体实现,最终更好的掌握DockerClient的使用方法。

即本文为《Docker源码分析》系列的第二篇——DockerClient篇。

2.DockerClient源码分析章节安排

本文从源码的角度,主要分析DockerClient的两个方面:

创建与命令执行。

前四章安排如下:

第一章为前言,介绍Docker的作用以及研究DockerClient的必要性。

第二章介绍部分章节安排。

第三章从DockerClient的创建入手,进行源码分析,主要分为三小节。

在3.1节中,分析如何通过docker命令,解析出命令行flag参数,以及docker命令中的请求参数。

在3.2节中,分析如何处理具体的flag参数信息,并收集DockerClient所需的配置信息。

在3.3节中,分析如何创建一个DockerClient。

第四章在已有DockerClient的基础上,分析如何执行docker命令,分为两小节。

在4.1节中,分析如何解析docker命令中的请求参数,获取请求的类型。

在4.2节中,分析DockerClient如何将执行具体的请求命令,最终将请求发送至DockerServer。

3.DockerClient的创建

DockerClient的创建,实质上是Docker用户通过可执行文件docker,与DockerServer建立联系的客户端。

以下分三个小节分别阐述DockerClient的创建流程。

以下为整个docker源代码运行的流程图:

上图通过流程图的方式,使得读者更为清晰的了解DockerClient创建及执行请求的过程。

其中涉及了诸多源代码中的特有名词,在下文中会一一解释与分析。

3.1.Docker命令的flag参数解析

众所周知,在Docker的具体实现中,DockerServer与DockerClient均由可执行文件docker来完成创建并启动。

那么,了解docker可执行文件通过何种方式区分两者,就显得尤为重要。

对于两者,首先举例说明其中的区别。

DockerServer的启动,命令为docker-d或docker–daemon=true;而DockerClient的启动则体现为docker–daemon=falseps、dockerpullNAME等。

可以把以上Docker请求中的参数分为两类:

第一类为命令行参数,即docker程序运行时所需提供的参数,如:

-D、–daemon=true、–daemon=false等;

第二类为docker发送给DockerServer的实际请求参数,如:

ps、pullNAME等。

对于第一类,我们习惯将其称为flag参数,在go语言的标准库中,同时还提供了一个flag包,方便进行命令行参数的解析。

交待以上背景之后,随即进入实现DockerClient创建的源码,位于./docker/docker/docker.go,在该go文件中,包含了整个Docker的main函数,也就是整个Docker(不论DockerDaemon还是DockerClient)的运行入口。

部分main函数代码如下:

funcmain(){

ifreexec.Init(){

return

}

flag.Parse()

//FIXME:

validatedaemonflagshere

……

}```

在以上代码中,首先判断reexec.Init()方法的返回值,若为真,则直接退出运行,否则的话继续执行。

查看位于./docker/reexec/reexec.go中[**reexec.Init()**]的定义,可以发现由于在docker运行之前没有任何的Initializer注册,故该代码段执行的返回值为假。

紧接着,main函数通过调用flag.Parse()解析命令行中的flag参数。

查看源码可以发现Docker在**./docker/docker/flag.go**中定义了多个flag参数,并通过init函数进行初始化。

代码如下:

var( 

flVersion=flag.Bool([]string{"v","-version"},false,"Printversioninformationandquit")flDaemon=flag.Bool([]string{"d","-daemon"},false,"Enabledaemonmode")

flDebug=flag.Bool([]string{"D","-debug"},false,"Enabledebugmode")

flSocketGroup=flag.String([]string{"G","-group"},"docker","Grouptoassigntheunixsocketspecifiedby-Hwhenrunningindaemonmodeuse''(theemptystring)todisablesettingofagroup")

flEnableCors=flag.Bool([]string{"#api-enable-cors","-api-enable-cors"},false,"EnableCORSheadersintheremoteAPI")

flTls=flag.Bool([]string{"-tls"},false,"UseTLS;impliedbytls-verifyflags")

flTlsVerify=flag.Bool([]string{"-tlsverify"},false,"UseTLSandverifytheremote(daemon:

verifyclient,client:

verifydaemon)")

//theseareinitializedininit()belowsincetheirdefaultvaluesdependondockerCertPathwhichisn'tfullyinitializeduntilinit()runs

flCa*string

flCert*string

flKey*string

flHosts[]string

funcinit(){

flCa=flag.String([]string{"-tlscacert"},filepath.Join(dockerCertPath,defaultCaFile),"TrustonlyremotesprovidingacertificatesignedbytheCAgivenhere")

flCert=flag.String([]string{"-tlscert"},filepath.Join(dockerCertPath,defaultCertFile),"PathtoTLScertificatefile")

flKey=flag.String([]string{"-tlskey"},filepath.Join(dockerCertPath,defaultKeyFile),"PathtoTLSkeyfile")

opts.HostListVar(&flHosts,[]string{"H","-host"},"Thesocket(s)tobindtoindaemonmode\nspecifiedusingoneormoretcp:

//host:

port,unix:

///path/to/socket,fd:

//*orfd:

//socketfd.")

}```

这里涉及到了Golang的一个特性,即init函数的执行。

在Golang中init函数的特性如下:

init函数用于程序执行前包的初始化工作,比如初始化变量等; 

每个包可以有多个init函数;

包的每一个源文件也可以有多个init函数;

同一个包内的init函数的执行顺序没有明确的定义;

不同包的init函数按照包导入的依赖关系决定初始化的顺序;

init函数不能被调用,而是在main函数调用前自动被调用。

因此,在main函数执行之前,Docker已经定义了诸多flag参数,并对很多flag参数进行初始化。

定义的命令行flag参数有:

flVersion、flDaemon、flDebug、flSocketGroup、flEnableCors、flTls、flTlsVerify、flCa、flCert、flKey、flHosts等。

以下具体分析flDaemon:

定义:

flDaemon=flag.Bool([]string{“d”,“-daemon”},false,“Enabledaemonmode”) 

flDaemon的类型为Bool类型

flDaemon名称为”d”或者”-daemon”,该名称会出现在docker命令中

flDaemon的默认值为false

flDaemon的帮助信息为”Enabledaemonmode”

访问flDaemon的值时,使用指针*flDaemon解引用访问

在解析命令行flag参数时,以下的语言为合法的:

-d,–daemon 

-d=true,–daemon=true

-d=”true”,–daemon=”true”

-d=’true’,–daemon=’true’

当解析到第一个非定义的flag参数时,命令行flag参数解析工作结束。

举例说明,当执行docker命令docker–daemon=false–version=falseps时,flag参数解析主要完成两个工作:

完成命令行flag参数的解析,名为-daemon和-version的flag参数flDaemon和flVersion分别获得相应的值,均为false; 

遇到第一个非flag参数的参数ps时,将ps及其之后所有的参数存入flag.Args(),以便之后执行DockerClient具体的请求时使用。

如需深入学习flag的解析,可以参见源码命令行参数flag的解析。

3.2.处理flag信息并收集DockerClient的配置信息

有了以上flag参数解析的相关知识,分析Docker的main函数就变得简单易懂很多。

通过总结,首先列出源代码中处理的flag信息以及收集DockerClient的配置信息,然后再一一对此分析:

处理的flag参数有:

flVersion、flDebug、flDaemon、flTlsVerify以及flTls; 

为DockerClient收集的配置信息有:

protoAddrParts(通过flHosts参数获得,作用为提供DockerClient与Server的通信协议以及通信地址)、tlsConfig(通过一系列flag参数获得,如flTls、flTlsVerify,作用为提供安全传输层协议的保障)。

随即分析处理这些flag参数信息,以及配置信息。

在flag.Parse()之后的代码如下:

if*flVersion{

showVersion()

return

}```

不难理解的是,当经过解析flag参数后,若flVersion参数为真时,调用showVersion()显示版本信息,并从main函数退出;否则的话,继续往下执行。

if*flDebug{

os.Setenv("DEBUG","1")

}```

若flDebug参数为真的话,通过os包的中Setenv函数创建一个名为DEBUG的系统环境变量,并将其值设为”1”。

继续往下执行。

iflen(flHosts)==0{

defaultHost:

=os.Getenv("DOCKER_HOST")

ifdefaultHost==""||*flDaemon{

//Ifwedonothaveahost,defaulttounixsocket

defaultHost=fmt.Sprintf("unix:

//%s",api.DEFAULTUNIXSOCKET)

}

if_,err:

=api.ValidateHost(defaultHost);err!

=nil{

log.Fatal(err)

}

flHosts=append(flHosts,defaultHost)

}```

以上的源码主要分析内部变量flHosts。

flHosts的作用是为DockerClient提供所要连接的host对象,也为DockerServer提供所要监听的对象。

分析过程中,首先判断flHosts变量是否长度为0,若是的话,通过os包获取名为DOCKER_HOST环境变量的值,将其赋值于defaultHost。

若defaultHost为空或者flDaemon为真的话,说明目前还没有一个定义的host对象,则将其默认设置为unixsocket,值为api.DEFAULTUNIXSOCKET,该常量位于[**./docker/api/common.go**],值为”/var/run/docker.sock”,故defaultHost为”unix:

///var/run/docker.sock”。

验证该defaultHost的合法性之后,将defaultHost的值追加至flHost的末尾。

继续往下执行。

if*flDaemon{

mainDaemon()

return

}```

若flDaemon参数为真的话,则执行mainDaemon函数,实现DockerDaemon的启动,若mainDaemon函数执行完毕,则退出main函数,一般mainDaemon函数不会主动终结。

由于本章节介绍DockerClient的启动,故假设flDaemon参数为假,不执行以上代码块。

继续往下执行。

iflen(flHosts)>1{

log.Fatal("Pleasespecifyonlyone-H")

protoAddrParts:

=strings.SplitN(flHosts[0],":

//",2)```

以上,若flHosts的长度大于1的话,则抛出错误日志。

接着将flHosts这个string数组中的第一个元素,进行分割,通过”:

//”来分割,分割出的两个部分放入变量protoAddrParts数组中。

protoAddrParts的作用为解析出与DockerServer建立通信的协议与地址,为DockerClient创建过程中不可或缺的配置信息之一。

var(

cli*client.DockerCli

tlsConfigtls.Config

tlsConfig.InsecureSkipVerify=true```

由于之前已经假设过flDaemon为假,则可以认定main函数的运行是为了DockerClient的创建与执行。

在这里创建两个变量:

一个为类型是client.DockerCli指针的对象cli,另一个为类型是tls.Config的对象tlsConfig。

并将tlsConfig的InsecureSkipVerify属性设置为真。

TlsConfig对象的创建是为了保障cli在传输数据的时候,遵循安全传输层协议(TLS)。

安全传输层协议(TLS)用于两个通信应用程序之间保密性与数据完整性,该协议有两层组成:

TLS记录协议和TLS握手协议。

tlsConfig是DockerClient创建过程中可选的配置信息。

//Ifweshouldverifytheserver,weneedtoloadatrustedca

if*flTlsVerify{

*flTls=true

certPool:

=x509.NewCertPool()

file,err:

=ioutil.ReadFile(*flCa)

iferr!

=nil{

log.Fatalf("Couldn'treadcacert%s:

%s",*flCa,err)

}

certPool.AppendCertsFromPEM(file)

tlsConfig.RootCAs=certPool

tlsConfig.InsecureSkipVerify=false

}``` 

若flTlsVerify这个flag参数为真的话,则说明需要验证server端的安全性,tlsConfig对象需要加载一个受信的ca文件。

该ca文件的路径为*flCA参数的值,最终完成tlsConfig对象中RootCAs属性的赋值,并将InsecureSkipVerify属性置为假。

//Iftlsisenabled,trytoloadandsendclientcertificatesif flTls||*flTlsVerify{_,errCert:

=os.Stat(flCert)_,errKey:

=os.Stat(flKey)iferrCert nil&&errKey nil{*flTls=truecert,err:

=tls.LoadX509KeyPair(flCert,*flKey)iferr!

=nil{log.Fatalf("Couldn'tloadX509keypair:

%s.Keyencrypted?

",err)}tlsConfig.Certificates=[]tls.Certificate{cert}}}```

如果flTls和flTlsVerify两个flag参数中有一个为真,则说明需要加载以及发送client端的证书。

最终将证书内容交给tlsConfig的Certificates属性。

至此,flag参数已经全部处理,并已经收集完毕DockerClient所需的配置信息。

之后的内容为DockerClient如何实现创建并执行。

3.3.DockerClient的启动

DockerClient的创建其实就是在已有配置参数信息的情况,通过Client包中的NewDockerCli方法创建一个实例cli,源码实现如下:

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)

}```

 

如果flag参数flTls为真或者flTlsVerify为真的话,则说明需要使用TLS协议来保障传输的安全性,故创建DockerClient的时候,将TlsConfig参数传入;否则的话,同样创建DockerClient,只不过TlsConfig为nil。

关于Client包中的NewDockerCli函数的实现,可以具体参见[**./docker/api/client/cli.go**](

funcNewDockerCli(inio.ReadCloser,out,errio.Writer,proto,addrstring,tlsConfig*tls.Config)*DockerCli{

var(

isTerminal=false

terminalFduintptr

scheme="http"

iftlsConfig!

=nil{

scheme="https"

}

ifin!

=nil{

iffile,ok:

=out.(*os.File);ok{

terminalFd=file.Fd()

isTerminal=term.IsTerminal(terminalFd)

}

}

iferr==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”,另外还有关于输入,输出以及错误显示的配置,最终返回该对象。

通过调用NewDockerCli函数,程序最终完成了创建DockerClient,并返回main函数继续执行。

4.Docker命令执行

main函数执行到目前为止,有以下内容需要为Docker命令的执行服务:

创建完毕的DockerClient,docker命令中的请求参数(经flag解析后存放于flag.Arg())。

也就是说,需要使用DockerClient来分析docker命令中的请求参数,并最终发送相应请求给DockerServer。

4.1.DockerClient解析请求命令

Dock

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 经管营销 > 经济市场

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1