二、Docker Client 創(chuàng)建與命令執(zhí)行(摘自《Docker源碼分析》)

1、Docker Client 的創(chuàng)建

Docker Client 的創(chuàng)建,實(shí)質(zhì)上是 Docker 用戶通過可執(zhí)行文件 docker,與 Docker Server 建立聯(lián)系的客戶端。

docker 源代碼運(yùn)行的流程圖如下:

docker 源代碼運(yùn)行的流程圖.jpg
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等

從上述例子中可以把請求中的參數(shù)分為兩類

1)命令行參數(shù):docker 程序運(yùn)行時(shí)所需要提供的參數(shù),如:-d、--daemon=true、--daemon=false等,我們通??梢苑Q之為 flag 參數(shù)
2)請求參數(shù):docker 發(fā)送給Docker Server 的實(shí)際請求參數(shù),如:ps 、pull NAME等

例2:

docker 命令為例,docker –daemon=false –version=false pull Name

根據(jù)流程圖可以分析流程為:
1)解析 flag 參數(shù)之后,將 docker 請求參數(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 命令中的請求參數(shù)(經(jīng) flag 解析后存放于 flag.Arg())。也就是說,需要使用 Docker Client 來分析 docker 命令中的請求參數(shù),并最終發(fā)送相應(yīng)請求給 Docker Server。

4.1 Docker Client 解析請求命令

Docker Client 解析請求命令的工作,在 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() 中的具體請求參數(shù),執(zhí)行的函數(shù)為 cli 對象的 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)中,首先判斷請求參數(shù)列表的長度是否大于 0,若不是的話,說明沒有請求信息,返回 docker 命令的 Help 信息;若長度大于 0 的話,說明有請求信息,則首先通過請求參數(shù)列表中的第一個(gè)元素 args[0] 來獲取具體的 method 的方法。如果上述 method 方法不存在,則返回 docker 命令的 Help 信息,若存在的話,調(diào)用具體的 method 方法,參數(shù)為 args[1] 及其之后所有的請求參數(shù)。

還是以一個(gè)具體的 docker 命令為例,docker –daemon=false –version=false pull Name。通過以上的分析,可以總結(jié)出以下操作流程:

(1) 解析 flag 參數(shù)之后,將 docker 請求參數(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í)行請求命令

上一節(jié)通過一系列的命令解析,最終找到了具體的命令的執(zhí)行方法,本節(jié)內(nèi)容主要介紹 Docker Client 如何通過該執(zhí)行方法處理并發(fā)送請求。

由于不同的請求內(nèi)容不同,執(zhí)行流程大致相同,本節(jié)依舊以一個(gè)例子來闡述其中的流程,例子為:docker pull NAME。

Docker Client 在執(zhí)行以上請求命令的時(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 的對象 cmd。

 cmd := cli.Subcmd("pull", "NAME[:TAG]", "Pull an image or a repository from the registry")

(2) 給 cmd 對象定義一個(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 對象獲取與 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 請求,請求的 url 為"/images/create?"+v.Encode(),請求的認(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 請求的全部執(zhí)行過程,其他請求的執(zhí)行在流程上也是大同小異??傊?,請求執(zhí)行過程中,大多都是將命令行中關(guān)于請求的參數(shù)進(jìn)行初步處理,并添加相應(yīng)的輔助信息,最終通過指定的協(xié)議給 Docker Server 發(fā)送 Docker Client 和 Docker Server 約定好的 API 請求。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容