docker分析.docx
- 文档编号:11922771
- 上传时间:2023-04-16
- 格式:DOCX
- 页数:17
- 大小:41.28KB
docker分析.docx
《docker分析.docx》由会员分享,可在线阅读,更多相关《docker分析.docx(17页珍藏版)》请在冰豆网上搜索。
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
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- docker 分析