1乘盖、Docker Client 的創(chuàng)建
Docker Client 的創(chuàng)建焰檩,實(shí)質(zhì)上是 Docker 用戶通過可執(zhí)行文件 docker,與 Docker Server 建立聯(lián)系的客戶端订框。
docker 源代碼運(yùn)行的流程圖如下:
1.1 Docker 命令的 flag 參數(shù)解析
例1:
Docker Server 的啟動(dòng)命令為:docker -d 或 docker --daemon=true
Docker Client 的啟動(dòng)命令為:docker --daemon=false ps 析苫、docker pull NAME等
從上述例子中可以把請(qǐng)求中的參數(shù)分為兩類
1)命令行參數(shù):docker 程序運(yùn)行時(shí)所需要提供的參數(shù),如:-d穿扳、--daemon=true衩侥、--daemon=false等,我們通趁铮可以稱之為 flag 參數(shù)
2)請(qǐng)求參數(shù):docker 發(fā)送給Docker Server 的實(shí)際請(qǐng)求參數(shù)茫死,如:ps 、pull NAME等
例2:
docker 命令為例履羞,docker –daemon=false –version=false pull Name
根據(jù)流程圖可以分析流程為:
1)解析 flag 參數(shù)之后峦萎,將 docker 請(qǐng)求參數(shù)”pull”和“Name”存放于 flag.Args();
2)創(chuàng)建好的 Docker Client 為 cli,cli 執(zhí)行 cli.Cmd(flag.Args()…);
3)在 Cmd 函數(shù)中忆首,通過 args[0] 也就是”pull”, 執(zhí)行 cli.getMethod(args[0])爱榔,獲取 method 的名稱;
4)在 getMothod 方法中糙及,通過處理最終返回 method 的值為”CmdPull”;
5)最終執(zhí)行 method(args[1:]…) 也就是 CmdPull(args[1:]…)详幽。
2、Docker 命令執(zhí)行
main 函數(shù)執(zhí)行到目前為止丁鹉,有以下內(nèi)容需要為 Docker 命令的執(zhí)行服務(wù):創(chuàng)建完畢的 Docker Client妒潭,docker 命令中的請(qǐng)求參數(shù)(經(jīng) flag 解析后存放于 flag.Arg())。也就是說揣钦,需要使用 Docker Client 來分析 docker 命令中的請(qǐng)求參數(shù)雳灾,并最終發(fā)送相應(yīng)請(qǐng)求給 Docker Server。
4.1 Docker Client 解析請(qǐng)求命令
Docker Client 解析請(qǐng)求命令的工作冯凹,在 Docker 命令執(zhí)行部分第一個(gè)完成谎亩,直接進(jìn)入 main 函數(shù)之后的源碼部分
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)
}
查閱以上源碼炒嘲,可以發(fā)現(xiàn),正如之前所說匈庭,首先解析存放于 flag.Args() 中的具體請(qǐng)求參數(shù)夫凸,執(zhí)行的函數(shù)為 cli 對(duì)象的 Cmd 函數(shù)。進(jìn)入[./docker/api/client/cli.go 的 Cmd 函數(shù)]
// 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 函數(shù)執(zhí)行具體的指令夭拌。源碼實(shí)現(xiàn)中,首先判斷請(qǐng)求參數(shù)列表的長度是否大于 0衷咽,若不是的話鸽扁,說明沒有請(qǐng)求信息,返回 docker 命令的 Help 信息镶骗;若長度大于 0 的話桶现,說明有請(qǐng)求信息,則首先通過請(qǐng)求參數(shù)列表中的第一個(gè)元素 args[0] 來獲取具體的 method 的方法鼎姊。如果上述 method 方法不存在骡和,則返回 docker 命令的 Help 信息,若存在的話相寇,調(diào)用具體的 method 方法慰于,參數(shù)為 args[1] 及其之后所有的請(qǐng)求參數(shù)。
還是以一個(gè)具體的 docker 命令為例裆赵,docker –daemon=false –version=false pull Name东囚。通過以上的分析,可以總結(jié)出以下操作流程:
(1) 解析 flag 參數(shù)之后战授,將 docker 請(qǐng)求參數(shù)”pull”和“Name”存放于 flag.Args();
(2) 創(chuàng)建好的 Docker Client 為 cli页藻,cli 執(zhí)行 cli.Cmd(flag.Args()…);
在 Cmd 函數(shù)中,通過 args[0] 也就是”pull”, 執(zhí)行 cli.getMethod(args[0])植兰,獲取 method 的名稱份帐;
(3) 在 getMothod 方法中,通過處理最終返回 method 的值為”CmdPull”;
(4) 最終執(zhí)行 method(args[1:]…) 也就是 CmdPull(args[1:]…)楣导。
4.2 Docker Client 執(zhí)行請(qǐng)求命令
上一節(jié)通過一系列的命令解析废境,最終找到了具體的命令的執(zhí)行方法,本節(jié)內(nèi)容主要介紹 Docker Client 如何通過該執(zhí)行方法處理并發(fā)送請(qǐng)求筒繁。
由于不同的請(qǐng)求內(nèi)容不同噩凹,執(zhí)行流程大致相同,本節(jié)依舊以一個(gè)例子來闡述其中的流程毡咏,例子為:docker pull NAME驮宴。
Docker Client 在執(zhí)行以上請(qǐng)求命令的時(shí)候,會(huì)執(zhí)行 CmdPull 函數(shù)呕缭,傳入?yún)?shù)為 args[1:]…堵泽。源碼具體為./docker/api/client/command.go 中的CmdPull 函數(shù)修己。
以下逐一分析CmdPull 的源碼實(shí)現(xiàn)。
(1) 通過 cli 包中的 Subcmd 方法定義一個(gè)類型為 Flagset 的對(duì)象 cmd迎罗。
cmd := cli.Subcmd("pull", "NAME[:TAG]", "Pull an image or a repository from the registry")
(2) 給 cmd 對(duì)象定義一個(gè)類型為 String 的 flag睬愤,名為”#t”或”#-tag”,初始值為空纹安。
tag := cmd.String([]string{"#t", "#-tag"}, "", "Download tagged image in a repository")
(3) 將 args 參數(shù)進(jìn)行解析尤辱,解析過程中,先提取出是否有符合 tag 這個(gè) flag 的參數(shù)钻蔑,若有啥刻,將其給賦值給 tag 參數(shù)奸鸯,其余的參數(shù)存入 cmd.NArg(); 若無的話咪笑,所有的參數(shù)存入 cmd.NArg() 中。
if err := cmd.Parse(args); err != nil {
return nil }
(4) 判斷經(jīng)過 flag 解析后的參數(shù)列表娄涩,若參數(shù)列表中參數(shù)的個(gè)數(shù)不為 1窗怒,則說明需要 pull 多個(gè) image,pull 命令不支持蓄拣,則調(diào)用錯(cuò)誤處理方法 cmd.Usage()扬虚,并返回 nil。
if cmd.NArg() != 1 {
cmd.Usage()
return nil
}
(5) 創(chuàng)建一個(gè) map 類型的變量 v球恤,該變量用于存放 pull 鏡像時(shí)所需的 url 參數(shù)辜昵;隨后將參數(shù)列表的第一個(gè)值賦給 remote 變量,并將 remote 作為鍵為 fromImage 的值添加至 v咽斧;最后若有 tag 信息的話堪置,將 tag 信息作為鍵為”tag”的值添加至 v。
var (
v = url.Values{}
remote = cmd.Arg(0)
)
v.Set("fromImage", remote)
if *tag == "" {
v.Set("tag", *tag)
}
(6) 通過 remote 變量解析出鏡像所在的 host 地址张惹,以及鏡像的名稱舀锨。
remote, _ = parsers.ParseRepositoryTag(remote)
// Resolve the Repository name from fqn to hostname + name
hostname, _, err := registry.ResolveRepositoryName(remote)
if err != nil {
return err
}
(7) 通過 cli 對(duì)象獲取與 Docker Server 通信所需要的認(rèn)證配置信息。
cli.LoadConfigFile()
// Resolve the Auth config relevant for this server
authConfig := cli.configFile.ResolveAuthConfig(hostname)
(8) 定義一個(gè)名為 pull 的函數(shù)宛逗,傳入的參數(shù)類型為 registry.AuthConfig坎匿,返回類型為 error。函數(shù)執(zhí)行塊中最主要的內(nèi)容為:cli.stream(……) 部分雷激。該部分具體發(fā)起了一個(gè)給 Docker Server 的 POST 請(qǐng)求替蔬,請(qǐng)求的 url 為"/images/create?"+v.Encode(),請(qǐng)求的認(rèn)證信息為:map[string][]string{“X-Registry-Auth”: registryAuthHeader,}屎暇。
pull := func(authConfig registry.AuthConfig) error {
buf, err := json.Marshal(authConfig)
if err != nil {
return err
}
registryAuthHeader := []string{
ase64.URLEncoding.EncodeToString(buf),
}
return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{ " X-Registry-Auth": registryAuthHeader,
})
}
(9) 由于上一個(gè)步驟只是定義 pull 函數(shù)承桥,這一步驟具體調(diào)用執(zhí)行 pull 函數(shù),若成功則最終返回恭垦,若返回錯(cuò)誤快毛,則做相應(yīng)的錯(cuò)誤處理格嗅。若返回錯(cuò)誤為 401,則需要先登錄唠帝,轉(zhuǎn)至登錄環(huán)節(jié)屯掖,完成之后,繼續(xù)執(zhí)行 pull 函數(shù)襟衰,若完成則最終返回贴铜。
if err := pull(authConfig); err != nil {
if strings.Contains(err.Error(), "Status 401") {
fmt.Fprintln(cli.out, "\nPlease login prior to pull:")
if err := cli.CmdLogin(hostname); err != nil {
return err
}
authConfig := cli.configFile.ResolveAuthConfig(hostname) return pull(authConfig)
}
return err
}
以上便是 pull 請(qǐng)求的全部執(zhí)行過程,其他請(qǐng)求的執(zhí)行在流程上也是大同小異瀑晒∩馨樱總之,請(qǐng)求執(zhí)行過程中苔悦,大多都是將命令行中關(guān)于請(qǐng)求的參數(shù)進(jìn)行初步處理轩褐,并添加相應(yīng)的輔助信息,最終通過指定的協(xié)議給 Docker Server 發(fā)送 Docker Client 和 Docker Server 約定好的 API 請(qǐng)求玖详。