閱讀553 返回首頁    go 阿裏雲 go 技術社區[雲棲]


《容器技術係列》一2.3 Docker命令執行

本節書摘來異步社區《容器技術係列》一書中的第2章 ,第2.3節,孫宏亮 著, 更多章節內容可以訪問雲棲社區“異步社區”公眾號查看。

2.3 Docker命令執行

main函數執行到這個階段,有以下內容需要為Docker命令的執行服務:創建完畢的Docker Client,docker命令中的請求參數(經flag解析後存放於flag.Arg())。也就是說,程序需要使用Docker Client來分析Docker命令中的請求參數,得出請求的類型,轉義為Docker Server可以識別的請求之後,最終發送給Docker Server。
Docker Client主要完成兩方麵的工作:解析請求命令,得出請求類型;執行具體類型的請求。本節將從這兩個方麵深入分析Docker Client。

2.3.1 Docker Client解析請求命令

Docker Client解析請求命令的工作,在Docker命令執行部分第一個完成。創建Docker Client之後,回到main函數中,繼續執行的源碼如下(位於./do```javascript
cker/docker/docker.go#L102-L110):
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)
}

學習以上源碼可以發現,正如之前所說,Docker Client首先解析存放於flag.Args()中的具體請求參數,執行的函數為cli對象的Cmd函數。Cmd函數的定義如下(位於./docker/api/client/cli.go#L51-L61):
```javascript
// 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函數執行具體的指令。在源碼實現中,首先判斷請求參數列表的長度是否大於0。若長度不大於0,則說明沒有請求信息,返回docker命令的Help信息;若長度大於1,則說明有請求信息,那麼Docker Client首先通過請求參數列表中的第一個元素args[0]來獲取具體的請求方法method。若上述method方法不存在,則返回docker命令的Help信息,若存在,調用具體的method方法,參數為args[1]及其之後所有的請求參數。
還是以一個具體的docker命令為例,docker --daemon=false --version=false pull Image_Name。通過以上Docker Client的分析,可以總結出以下執行流程。
1)解析flag參數之後,Docker將docker請求參數"pull"和"Image_Name"存放於flag.Args()。
2)創建好的Docker Client為cli,cli執行cli.Cmd(flag.Args()…)。
3)Cmd函數中,通過args[0]也就是"pull",執行cli.getMethod(args[0]),獲取method的名稱。
4)在getMothod方法中,Docker通過處理最終返回method值為"CmdPull"。
5)最終執行method(args[1:]…)也就是CmdPull(args[1:]…)。

2.3.2 Docker Client執行請求命令

上一小節通過一係列的命令解析,最終找到了具體的命令執行方法,本小節主要介紹Docker Client如何通過具體的執行方法,處理並發送請求。
不同的Docker盡管請求內容不同,但是請求執行流程大致相同,故本節依舊以一個例子來闡述其中的流程,例子為:docker pull Image_Name。該命令的作用為:DockerClient發起下載鏡像的請求,最終由Docker Server接收請求,由Docker Daemon完成鏡像的下載與存儲。
Docker Client在執行docker pull Image_Name請求命令時,執行CmdPull函數,傳入參數為args[1:]...,即Image_Name。源碼實現位於./docker/api/client/command.go#L1183-L1224。
下麵逐一分析CmdPull的源碼實現。

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

通過cli包中的Subcmd方法定義一個類型為Flagset的對象cmd。

tag := cmd.String([]string{"#t", "#-tag"}, "", "Download tagged image in a repository")

為cmd對象定義一個類型為String的flag,名為"#t"或"#-tag",初始值為空,目前這個flag參數基本已經被棄用。

if err := cmd.Parse(args); err != nil {
          return nil
}

將args參數進行第二次flag參數解析,解析過程中,先提取出是否有符合tag這個flag的參數。若有,將其賦值給tag參數,其餘的參數存入cmd.NArg();若沒有,則將所有的參數存入cmd.NArg()中。

if cmd.NArg() != 1 {
     cmd.Usage()
     return nil
}

判斷經過flag解析後的參數列表,若參數列表中參數的個數不為1,則說明需要下拉多個鏡像或者沒有指定下拉鏡像名稱,pull命令均不支持,則調用錯誤處理方法cmd.Usage(),並返回nil。

var (
     v      = url.Values{}
     remote = cmd.Arg(0)
)
v.Set("fromImage", remote)
if *tag == "" {
     v.Set("tag", *tag)
}

創建一個map類型的變量v,該變量用於存放下拉鏡像時所需的URL參數;隨後將參數列表的第一個值cmd.Arg(0)賦給remote變量,並將remote作為鍵將fromImage的值添加至v。

remote, _ = parsers.ParseRepositoryTag(remote)
// Resolve the Repository name from fqn to hostname + name
hostname, _, err := registry.ResolveRepositoryName(remote)
if err != nil {
     return err
}

通過remote變量首先得到鏡像的repository名稱,並賦給remote自身,隨後通過解析改變後的remote,得出鏡像所在的host地址,即Docker Registry的地址。若用戶沒有製定Docker Registry的地址,則Docker默認地址為Doc```javascript
ker Hub地址https://index.docker.io/v1/。
cli.LoadConfigFile()
// Resolve the Auth config relevant for this server
authConfig := cli.configFile.ResolveAuthConfig(hostname)

通過cli對象獲取與Docker Server通信所需要的認證配置信息。
```javascript
pull := func(authConfig registry.AuthConfig) error {
     buf, err := json.Marshal(authConfig)
     if err != nil {
          return err
     }
     registryAuthHeader := []string{
          base64.URLEncoding.EncodeToString(buf),
     }
        return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{
     "X-Registry-Auth": registryAuthHeader,
     })
}

定義一個名為pull的函數,傳入的參數類型為registry.AuthConfig,返回類型為error。函數執行塊中最主要的內容為:cli.stream(……)部分。該部分具體向Docker Server發送POST請求,請求的url為"/images/create?"+v.Encode(),請求的認證信息為:

map[string][]string{"X-Registry-Auth": registryAuthHeader,}。
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函數,這一步驟具體調用執行pull函數,實現實際意義上的下載請求發送。若返回成功則表明請求完成,程序直接退出,若返回錯誤,則做相應的錯誤處理。若返回錯誤為401,則表示用戶下載的鏡像必須用戶先登錄,隨即Docker Client轉至登錄環節,完成之後,繼續執行pull函數,若完成則最終返回。
以上便是pull請求的全部執行過程,其他請求的執行在流程上也是大同小異。總之,請求執行過程中,大多都是將命令行中關於請求的參數進行初步處理,並添加相應的輔助信息,最終通過指定的協議向Docker Server發送Docker Client和Docker Server約定好的API請求。

最後更新:2017-06-21 15:02:24

  上一篇:go  《容器技術係列》一2.4 總結
  下一篇:go  [幹貨]基礎機器學習算法