<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [TOC=3] ## 1.前言 說Docker Image是Docker體系的價值所在,沒有絲毫得夸大其詞。Docker Image作為容器運行環境的基石,徹底解放了Docker容器創建的生命力,也激發了用戶對于容器運用的無限想象力。 玩轉Docker,必然離不開Docker Image的支持。然而“萬物皆有源”,Docker Image來自何方,Docker Image又是通過何種途徑傳輸到用戶機器,以致用戶可以通過Docker Image創建容器?回憶初次接觸Docker的場景,大家肯定對兩條命令不陌生:docker pull和docker run。這兩條命令中,正是前者實現了Docker Image的下載。Docker Daemon在執行這條命令時,會將Docker Image從Docker Registry下載至本地,并保存在本地Docker Daemon管理的graph中。 談及Docker Registry,Docker愛好者首先聯想到的自然是[Docker Hub](https://hub.docker.com/)。Docker Hub作為Docker官方支持的Docker Registry,擁有全球成千上萬的Docker Image。全球的Docker愛好者除了可以下載Docker Hub開放的鏡像資源之外,還可以向Docker Hub貢獻鏡像資源。在Docker Hub上,用戶不僅可以享受公有鏡像帶來的便利,而且可以創建私有鏡像庫。Docker Hub是全國最大的Public Registry,另外Docker還支持用戶自定義創建Private Registry。Private Registry主要的功能是為私有網絡提供Docker鏡像的專屬服務,一般而言,鏡像種類適應用戶需求,私密性較高,且不會占用公有網絡帶寬。 ## 2.本文分析內容安排 本文作為《Docker源碼分析》系列的第十篇——Docker鏡像下載篇,主要從源碼的角度分析Docker下載Docker Image的過程。分析流程中,docker的版本均為1.2.0。 分析內容的安排如以下4部分: (1) 概述Docker鏡像下載的流程,涉及Docker Client、Docker Server與Docker Daemon; (2) Docker Client處理并發送docker pull請求; (3) Docker Server接收docker pull請求,并創建鏡像下載任務并觸發執行; (4) Docker Daemon執行鏡像下載任務,并存儲鏡像至graph。 ## 3.Docker鏡像下載流程 Docker Image作為Docker生態中的精髓,下載過程中需要Docker架構中多個組件的協作。Docker鏡像的下載流程如圖3.1: ![](https://box.kancloud.cn/2015-11-12_56443b0bb7560.jpg) 圖3.1 Docker鏡像下載流程圖 如上圖,下載流程,可以歸納為以上3個步驟: (1) 用戶通過Docker Client發送pull請求,作用為:讓Docker Daemon下載指定名稱的鏡像; (2) Docker Daemon中負責Docker API請求的Docker Server,接收Docker鏡像的pull請求,創建下載鏡像任務并觸發執行; (3) Docker Daemon執行鏡像下載任務,從Docker Registry中下載指定鏡像,并將其存儲與本地的graph中。 下文即從三個方面分析docker pull請求執行的流程。 ## 4.Docker Client Docker架構中,Docker用戶的角色絕大多數由Docker Client來扮演。因此,用戶對Docker的管理請求全部由Docker Client來發送,Docker鏡像下載請求自然也不例外。 為了更清晰的描述Docker鏡像下載,本文結合具體的命令進行分析,如下: ~~~ docker pull ubuntu:14.04 ~~~ 以上的命令代表:用戶通過docker二進制可執行文件,執行pull命令,鏡像參數為ubuntu:14.04,鏡像名稱為ubuntu,鏡像標簽為14.04。此命令一經觸發,第一個接受并處理的Docker組件為Docker Client,執行內容包括以下三個步驟: (1) 解析命令中與Docker鏡像相關的參數; (2) 配置Docker下載鏡像時所需的認證信息; (3) 發送RESTful請求至Docker Daemon。 ### 4.1 解析鏡像參數 通過docker二進制文件執行docker pull ubuntu:14.04 時,Docker Client首先會被創建,隨后通過參數處理分析出請求類型pull,最終執行pull請求相應的處理函數。關于Docker Client的創建與命令執行可以參見[《Docker源碼分析》系列第二篇——Docker Client篇](http://www.infoq.com/cn/articles/docker-source-code-analysis-part2)。 Docker Client執行pull請求相應的處理函數,源碼位于[./docker/api/client/command.go#L1183-L1244](https://github.com/docker/docker/blob/v1.2.0/api/client/commands.go#L1183-L1244),有關提取鏡像參數的源碼如下: ~~~ func (cli *DockerCli) CmdPull(args ...string) error { cmd := cli.Subcmd("pull", "NAME[:TAG]", "Pull an image or a repository from the registry") tag := cmd.String([]string{"#t", "#-tag"}, "", "Download tagged image in a repository") if err := cmd.Parse(args); err != nil { return nil } if cmd.NArg() != 1 { cmd.Usage() return nil } var ( v = url.Values{} remote = cmd.Arg(0) ) v.Set("fromImage", remote) if *tag == "" { v.Set("tag", *tag) } remote, _ = parsers.ParseRepositoryTag(remote) // Resolve the Repository name from fqn to hostname + name hostname, _, err := registry.ResolveRepositoryName(remote) if err != nil { return err } …… } ~~~ 結合命令docker pull ubuntu:14.04,來分析CmdPull函數的定義,可以發現,該函數傳入的形參為args,實參只有一個字符串ubuntu:14.04。另外,縱觀以上源碼,可以發現Docker Client解析的鏡像參數無外乎4個:tag、remote、v和hostname,四者各自的作用如下: * tag:帶有Docker鏡像的標簽; * remote:帶有Docker鏡像的名稱與標簽; * v:類型為url.Values,實質是一個map類型,用于配置請求中URL的查詢參數; * hostname:Docker Registry的地址,代表用戶希望從指定的Docker Registry下載Docker鏡像。 #### 4.1.1 解析tag參數 Docker鏡像的tag參數,是第一個被Docker Client解析的鏡像參數,代表用戶所需下載Docker鏡像的標簽信息,如:docker pull ubuntu:14.04請求中鏡像的tag信息為14.04,若用戶使用docker pull ubuntu請求下載鏡像,沒有顯性指定tag信息時,Docker Client會默認該鏡像的tag信息為latest。 Docker 1.2.0版本除了以上的tag信息傳入方式,依舊保留著代表鏡像標簽的flag參數tag,而這個flag參數在1.2.0版本的使用過程中已經被遺棄,并會在之后新版本的Docker中被移除,因此在使用docker 1.2.0版本下載Docker鏡像時,不建議使用flag參數tag。傳入tag信息的方式,建議使用docker pull NAME[:TAG]的形式。 Docker 1.2.0版本依舊保留的flag參數tag,其定義與解析的源碼位于:[./docker/api/client/commands.go#1185-L1188](https://github.com/docker/docker/blob/v1.2.0/api/client/commands.go#L1185-L1188),如下: ~~~ tag := cmd.String([]string{"#t", "#-tag"}, "", "Download tagged image in a repository") if err := cmd.Parse(args); err != nil { return nil } ~~~ 以上的源碼說明:CmdPull函數解析tag參數時,Docker Client首先定義一個flag參數,flag參數的名稱為”#t”或者 “#-tag”,用途為:指定Docker鏡像的tag參數,默認值為空字符串;隨后通過cmd.Parse(args)的執行,解析args中的tag參數。 #### 4.1.2 解析remote參數 Docker Client解析完tag參數之后,同樣需要解析出Docker鏡像所屬的repository,如請求docker pull ubuntu:14.04中,Docker鏡像為ubuntu:14.04,鏡像的repository信息為ubuntu,鏡像的tag信息為14.04。 Docker Client通過解析remote參數,使得remote參數攜帶repository信息和tag信息。Docker Client解析remote參數的第一個步驟,源碼如下: ~~~ remote = cmd.Arg(0) ~~~ 其中,cmd的第一個參數賦值給remote,以docker pull ubuntu:14.04為例,cmd.Arg(0)為ubuntu:14.04,則賦值后remote值為ubuntu:14.04。此時remote參數即包含Docker鏡像的repository信息也包含tag信息。若用戶請求中帶有Docker Registry的信息,如docker pull localhost.localdomain:5000/docker/ubuntu:14.04,cmd.Arg(0)為localhost.localdomain:5000/docker/ubuntu:14.04,則賦值后remote值為localhost.localdomain:5000/docker/ubuntu:14.04,此時remote參數同時包含repository信息、tag信息以及Docker Registry信息。 隨后,在解析remote參數的第二個步驟中,Docker Client通過解析賦值完畢的remote參數,從中解析中repository信息,并再次覆寫remote參數的值,源碼如下: ~~~ remote, _ = parsers.ParseRepositoryTag(remote) ~~~ ParseRepositoryTag的作用是:解析出remote參數的repository信息和tag信息,該函數的實現位于[./docker/pkg/parsers/parsers.go#L72-L81](https://github.com/docker/docker/blob/v1.2.0/pkg/parsers/parsers.go#L72-L81),源碼如下: ~~~ func ParseRepositoryTag(repos string) (string, string) { n := strings.LastIndex(repos, ":") if n < 0 { return repos, "" } if tag := repos[n+1:]; !strings.Contains(tag, "/") { return repos[:n], tag } return repos, "" } ~~~ 以上函數的實現過程,充分考慮了多種不同Docker Registry的情況,如:請求docker pull ubuntu:14.04中remote參數為ubuntu:14.04,而請求docker pull localhost.localdomain:5000/docker/ubuntu:14.04中用戶指定了Docker Registry的地址localhost.localdomain:5000/docker,故remote參數還攜帶了Docker Registry信息。 ParseRepositoryTag函數首先從repos參數的尾部往前尋找”:”,若不存在,則說明用戶沒有顯性指定Docker鏡像的tag,返回整個repos作為Docker鏡像的repository;若”:”存在,則說明用戶顯性指定了Docker鏡像的tag,”:”前的內容作為repository信息,”:”后的內容作為tag信息,并返回兩者。 ParseRepositoryTag函數執行完,回到CmdPull函數,返回內容的repository信息將覆寫remote參數。對于請求docker pull localhost.localdomain:5000/docker/ubuntu:14.04,remote參數被覆寫后,值為localhost.localdomain:5000/docker/ubuntu,攜帶Docker Registry信息以及repository信息。 #### 4.1.3 配置url.Values Docker Client發送請求給Docker Server時,需要為請求配置URL的查詢參數。CmdPull函數的執行過程中創建url.Value并配置的源碼實現位于[./docker/api/client/commands.go#L1194-L1203](https://github.com/docker/docker/blob/v1.2.0/api/client/commands.go#L1194-L1203),如下: ~~~ var ( v = url.Values{} remote = cmd.Arg(0) ) v.Set("fromImage", remote) if *tag == "" { v.Set("tag", *tag) } ~~~ 其中,變量v的類型url.Values,配置的URL查詢參數有兩個,分別為”fromImage”與”tag”,”fromImage”的值是remote參數沒有被覆寫時值,”tag”的值一般為空,原因是一般不使用flag參數tag。 #### 4.1.4 解析hostname參數 Docker Client解析鏡像參數時,還有一個重要的環節,那就是解析Docker Registry的地址信息。這意味著用戶希望從指定的Docker Registry中下載Docker鏡像。 解析Docker Registry地址的代碼實現位于[./docker/api/client/commands.go#L1207](https://github.com/docker/docker/blob/v1.2.0/api/client/commands.go#L1207),如下: ~~~ hostname, _, err := registry.ResolveRepositoryName(remote) ~~~ Docker Client通過包registry中的函數ResolveRepositoryName來解析hostname參數,傳入的實參為remote,即去tag化的remote參數。ResolveRepositoryName函數的實現位于[./docker/registry/registry.go#L237-L259](https://github.com/docker/docker/blob/v1.2.0/registry/registry.go),如下: ~~~ func ResolveRepositoryName(reposName string) (string, string, error) { if strings.Contains(reposName, "://") { // It cannot contain a scheme! return "", "", ErrInvalidRepositoryName } nameParts := strings.SplitN(reposName, "/", 2) if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && !strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") { // This is a Docker Index repos (ex: samalba/hipache or ubuntu) err := validateRepositoryName(reposName) return IndexServerAddress(), reposName, err } hostname := nameParts[0] reposName = nameParts[1] if strings.Contains(hostname, "index.docker.io") { return "", "", fmt.Errorf("Invalid repository name, try \"%s\" instead", reposName) } if err := validateRepositoryName(reposName); err != nil { return "", "", err } return hostname, reposName, nil } ~~~ ResolveRepositoryName函數首先通過”/”分割字符串reposName,如下: ~~~ nameParts := strings.SplitN(reposName, "/", 2) ~~~ 如果nameParts的長度為1,則說明reposName中不含有字符”/”,意味著用戶沒有指定Docker Registry。另外,形如”samalba/hipache”的reposName同樣說明用戶并沒有指定Docker Registry。當用戶沒有指定Docker Registry時,Docker Client默認返回IndexServerAddress(),該函數返回常量INDEXSERVER,值為”https://index.docker.io/v1”。也就是說,當用戶下載Docker鏡像時,若不指定Docker Registry,默認情況下,Docker Client通知Docker Daemon去Docker Hub上下載鏡像。例如:請求docker pull ubuntu:14.04,由于沒有指定Docker Registry,Docker Client默認使用全球最大的Docker Registry——Docker Hub。 當不滿足返回默認Docker Registry時,Docker Client通過解析reposNames,得出用戶指定的Docker Registry地址。例如:請求docker pull localhost.localdomain:5000/docker/ubuntu:14.04中,解析出的Docker Registry地址為localhost.localdomain:5000。 至此,與Docker鏡像相關的參數已經全部解析完畢,Docker Client將攜帶這部分重要信息,以及用戶的認證信息,構建RESTful請求,發送給Docker Server。 ### 4.2 配置認證信息 用戶下載Docker鏡像時,Docker同樣支持用戶信息的認證。用戶認證信息由Docker Client配置;Docker Client發送請求至Docker Server時,用戶認證信息也被一并發送;隨后,Docker Daemon處理下載Docker鏡像請求時,用戶認證信息在Docker Registry被驗證。 Docker Client配置用戶認證信息包含兩個步驟,實現源碼如下: ~~~ cli.LoadConfigFile() // Resolve the Auth config relevant for this server authConfig := cli.configFile.ResolveAuthConfig(hostname) ~~~ 可見,第一個步驟是使cli(Docker Client)加載ConfigFile,ConfigFile是Docker Client用來存放有關Docker Registry的用戶認證信息的對象。DockerCli、ConfigFile以及AuthConfig三種數據結構之間的關系如圖4.1: ![](https://box.kancloud.cn/2015-11-12_56443b0c02f23.jpg) 圖4.1 DockerCli、ConfigFile以及AuthConfig關系圖 DockerCli結構體的屬性configFile為一個指向registry.ConfigFile的指針,而ConfigFile結構體的屬性Configs屬于map類型,其中key為string,代表Docker Registry的地址,value的類型為AuthConfig。AuthConfig類型具體含義為用戶在某個Docker Registry上的認證信息,包含用戶名,密碼,認證信息,郵箱地址等。 加載完用戶所有的認證信息之后,Docker Client第二個步驟是:通過用戶指定的Docker Registry,即之前解析出的hostname參數,從用戶所有的認證信息中找出與指定hostname相匹配的認證信息。新創建的authConfig,類型即為AuthConfig,將會作為用戶在指定Docker Registry上的認證信息,發送至Docker Server。 ### 4.3 發送API請求 Docker Client解析完所有的Docker鏡像參數,并且配置完畢用戶的認證信息之后,Docker Client需要使用這些信息正式發送鏡像下載的請求至Docker Server。 Docker Client定義了pull函數,來實現發送鏡像下載請求至Docker Server,源碼位于[./docker/api/client/commands.go#L1217-L1229](https://github.com/docker/docker/blob/v1.2.0/api/client/commands.go#L1217-L1229),如下: ~~~ 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函數的實現較為簡單,首先通過authConfig對象,創建registryAuthHeader,最后發送POST請求,請求的URL為"/images/create?"+v.Encode(),在URL中傳入查詢參數包括”fromImage”與”tag”,另外在請求的HTTP Header中添加認證信息registryAuthHeader,。 執行以上的pull函數時,Docker鏡像下載請求被發送,隨后Docker Client等待Docker Server的接收、處理與響應。 ## 5.Docker Server Docker Server作為Docker Daemon的入口,所有Docker Client發送請求都由Docker Server接收。Docker Server通過解析請求的URL與請求方法,最終路由分發至相應的handler來處理。Docker Server的創建與請求處理,可以參看[《Docker源碼分析》系列之Docker Server篇](http://www.infoq.com/cn/articles/docker-source-code-analysis-part5)。 Docker Server接收到鏡像下載請求之后,通過路由分發最終由具體的handler——postImagesCreate來處理。postImagesCreate的實現位于[./docker/api/server/server.go#L466-L524](https://github.com/docker/docker/blob/v1.2.0/api/server/server.go#L466-L524),的、其執行流程主要分為3個部分: (1) 解析HTTP請求中包含的請求參數,包括URL中的查詢參數、HTTP header中的認證信息等; (2) 創建鏡像下載job,并為該job配置環境變量; (3) 觸發執行鏡像下載job。 ### 5.1 解析請求參數 Docker Server接收到Docker Client發送的鏡像下載請求之后,首先解析請求參數,并未后續job的創建與運行提供參數依據。Docker Server解析的請求參數,主要有:HTTP請求URL中的查詢參數”fromImage”、”repo”以及”tag”,以及有HTTP請求的header中的”X-Registry-Auth”。 請求參數解析的源碼如下: ~~~ var ( image = r.Form.Get("fromImage") repo = r.Form.Get("repo") tag = r.Form.Get("tag") job *engine.Job ) authEncoded := r.Header.Get("X-Registry-Auth") ~~~ 需要特別說明的是:通過”fromImage”解析出的image變量包含鏡像repository名稱與鏡像tag信息。例如用戶請求為docker pull ubuntu:14.04,那么通過”fromImage”解析出的image變量值為ubuntu:14.04,并非只有Docker鏡像的名稱。 另外,Docker Server通過HTTP header中解析出authEncoded,還原出類型為registry.AuthConfig的對象authConfig,源碼實現如下: ~~~ authConfig := ?istry.AuthConfig{} if authEncoded != "" { authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // for a pull it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty authConfig = ?istry.AuthConfig{} } } ~~~ 解析出HTTP請求中的參數之后,Docker Server對于image參數,再次進行解析,從中解析出屬于repository與tag信息,其中repository有可能暫時包含Docker Registry信息,源碼實現如下: ~~~ if tag == "" { image, tag = parsers.ParseRepositoryTag(image) } ~~~ Docker Server的參數解析工作至此全部完成,在這之后Docker Server將創建鏡像下載任務并開始執行。 ### 5.2 創建并配置job Docker Server只負責接收Docker Client發送的請求,并將其路由分發至相應的handler來處理,最終的請求執行還是需要Docker Daemon來協作完成。Docker Server在handler中,通過創建job并觸發job執行的形式,把控制權交于Docker Daemon。 Docker Server創建鏡像下載job并配置環境變量的源碼實現如下: ~~~ job = eng.Job("pull", image, tag) job.SetenvBool("parallel", version.GreaterThan("1.3")) job.SetenvJson("metaHeaders", metaHeaders) job.SetenvJson("authConfig", authConfig) ~~~ 其中,創建的job名為pull,含義是下載Docker鏡像,傳入參數為image與tag,配置的環境變量有parallel、metaHeaders與authConfig。 ### 5.3 觸發執行job Docker Server創建完Docker鏡像下載job之后,需要觸發執行該job,實現將控制權交于Docker Daemon。 Docker Server觸發執行job的源碼如下: ~~~ if err := job.Run(); err != nil { if !job.Stdout.Used() { return err } sf := utils.NewStreamFormatter(version.GreaterThan("1.0")) w.Write(sf.FormatError(err)) } ~~~ 由于Docker Daemon在啟動時,已經配置了名為”pull”的job所對應的handler,實際為graph包中的CmdPull函數,故一旦該job被觸發執行,控制權將直接交于Docker Daemon的CmdPull函數。Docker Daemon啟動時Engine的handler注冊,可以參見[《Docker源碼分析》系列的第三篇——Docker Daemon啟動篇](http://www.infoq.com/cn/articles/docker-source-code-analysis-part3)。 ## 6.Docker Daemon Docker Daemon是完成job執行的主要載體。Docker Server為鏡像下載job準備好所有的參數配置之后,只等Docker Daemon來完成執行,并返回相應的信息,Docker Server再將響應信息返回至Docker Client。Docker Daemon對于鏡像下載job的執行,涉及的內容較多:首先解析job參數,獲取Docker鏡像的repository、tag、Docker Registry信息等;隨后與Docker Registry建立session;然后通過session下載Docker鏡像;接著將Docker鏡像下載至本地并存儲于graph;最后在TagStore標記該鏡像。 Docker Daemon對于鏡像下載job的執行主要依靠CmdPull函數。這個CmdPull函數與Docker Client的CmdPull函數完全不同,前者是為了代替用戶發送鏡像下載的請求至Docker Daemon,而Docker Daemon的CmdPull函數則是實現代替用戶真正完全鏡像下載的任務。調用CmdPull函數的對象類型為TagStore,其源碼實現位于[./docker/graph/pull.go](https://github.com/docker/docker/blob/v1.2.0/graph/pull.go)。 ### 6.1 解析job參數 正如Docker Client與Docker Server,Docker Daemon執行鏡像下載job時的第一個步驟也是解析參數。解析工作一方面確保傳入參數無誤,另一方面按需為job提供參數依據。表6.1羅列Docker Daemon解析的job參數,如下: 表6.1 Docker Daemon解析job參數列表 | 參數名稱 | 參數描述 | |---|---| | localName | 代表鏡像的repository信息,有可能攜帶Docker Registry信息 | | tag | 代表鏡像的標簽信息,默認為latest | | authConfig | 代表用戶在指定Docker Registry上的認證信息 | | metaHeaders | 代表請求中的header信息 | | hostname | 代表Docker Registry信息,從localName解析獲得,默認為Docker Hub地址 | | remoteName | 代表Docker鏡像的repository名稱信息,不攜帶Docker Registry信息 | | endpoint | 代表Docker Registry完整的URL,從hostname擴展獲得 | 參數解析過程中,Docker Daemon還添加了一些精妙的設計。如:在TagStore類型中設計了pullingPool對象,用于保存正在被下載的Docker鏡像,下載完畢之前禁止其他Docker Client發起相同鏡像的下載請求,下載完畢之后pullingPool中的該記錄被清除。Docker Daemon一旦解析出localName與tag兩個參數信息,則立即檢測pullingPool,實現源碼位于[./docker/graph/pull.go#L36-L46](https://github.com/docker/docker/blob/v1.2.0/graph/pull.go#L36-L46),如下: ~~~ c, err := s.poolAdd("pull", localName+":"+tag) if err != nil { if c != nil { // Another pull of the same repository is already taking place; just wait for it to finish job.Stdout.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", localName)) <-c return engine.StatusOK } return job.Error(err) } defer s.poolRemove("pull", localName+":"+tag) ~~~ ### 6.2 創建session對象 下載Docker鏡像,Docker Daemon與Docker Registry需要建立通信。為了保障兩者通信的可靠性,Docker Daemon采用了session機制。Docker Daemon每收到一個Docker Client的鏡像下載請求,都會創建一個與相應Docker Registry的session,之后所有的網絡數據傳輸都在該session上完成。包registry定義了session,位于[./docker/registry/registry.go](https://github.com/docker/docker/blob/v1.2.0/registry/registry.go),如下: ~~~ type Session struct { authConfig *AuthConfig reqFactory *utils.HTTPRequestFactory indexEndpoint string jar *cookiejar.Jar timeout TimeoutType } ~~~ CmdPull函數中創建session的源碼實現如下: ~~~ r, err := registry.NewSession(authConfig, registry.HTTPRequestFactory (metaHeaders), endpoint, true) ~~~ 創建的session對象為r,在下一階段的鏡像下載過程中,多數與鏡像相關的數據傳輸均在r這個seesion的基礎上完成。 ### 6.3 執行鏡像下載 Docker Daemon之前所有的操作,都屬于配置階段,從解析job參數,到建立session對象,而并未與Docker Registry建立實際的連接,并且也還未真正傳輸過有關Docker鏡像的內容。 完成所有的配置之后,Docker Daemon進入Docker鏡像下載環節,實現Docker鏡像下載的源碼位于[./docker/graph/pull.go#L69-L71](https://github.com/docker/docker/blob/v1.2.0/graph/pull.go#L69-L71),如下: ~~~ if err = s.pullRepository(r, job.Stdout, localName, remoteName, tag, sf, job.GetenvBool("parallel")); err != nil { return job.Error(err) } ~~~ 以上代碼中pullRepository函數包含了鏡像下載整個流程的林林總總,該流程可以參見圖6.1: ![](https://box.kancloud.cn/2015-11-12_56443b0c19349.jpg) 圖6.1 pullRepository流程圖 關于上圖的各個環節,下表給出簡要的功能介紹: 表6.2 pullRepository各環節功能介紹表 | 函數名稱 | 功能介紹 | |---|---| | r.GetRepositoryData() | 獲取指定repository中所有image的id信息 | | r.GetRemoteTags() | 獲取指定repository中所有的tag信息 | | r.pullImage() | 從Docker Registry下載Docker鏡像 | | r.GetRemoteHistory() | 獲取指定image所有祖先image id信息 | | r.GetRemoteImageJSON() | 獲取指定image的json信息 | | r.GetRemoteImageLayer() | 獲取指定image的layer信息 | | s.graph.Register() | 將下載的鏡像在TagStore的graph中注冊 | | s.Set() | 在TagStore中添加新下載的鏡像信息 | 分析pullRepository的整個流程之前,很有必要了解下pullRepository函數調用者的類型TagStore。TagStore是Docker鏡像方面涵蓋內容最多的數據結構:一方面TagStore管理Docker的Graph,另一方面TagStore還管理Docker的repository記錄。除此之外,TagStore還管理著上文提到的對象pullingPool以及pushingPool,保證Docker Daemon在同一時刻,只為一個Docker Client執行同一鏡像的下載或上傳。TagStore結構體的定義位于[./docker/graph/tags.go#L20-L29](https://github.com/docker/docker/blob/v1.2.0/graph/tags.go#L20-L29),如下: ~~~ type TagStore struct { path string graph *Graph Repositories map[string]Repository sync.Mutex // FIXME: move push/pull-related fields // to a helper type pullingPool map[string]chan struct{} pushingPool map[string]chan struct{} } ~~~ 以下將重點分析pullRepository的整個流程。 #### 6.3.1 GetRepositoryData 使用Docker下載鏡像時,用戶往往指定的是Docker鏡像的名稱,如:請求docker pull ubuntu:14.04中鏡像名稱為ubuntu。GetRepositoryData的作用則是獲取鏡像名稱所在repository中所有image的 id信息。 GetRepositoryData的源碼實現位于[./docker/registry/session.go#L255-L324](https://github.com/docker/docker/blob/v1.2.0/registry/session.go#L255-L324)。獲取repository中image的ID信息的目標URL地址如以下源碼: ~~~ repositoryTarget := fmt.Sprintf("%srepositories/%s/images", indexEp, remote) ~~~ 因此,docker pull ubuntu:14.04請求被執行時,repository的目標URL地址為[https://index.docker.io/v1/repositories/ubuntu/images](https://index.docker.io/v1/repositories/ubuntu/images),訪問該URL可以獲得有關ubuntu這個repository中所有image的 id信息,部分image的id信息如下: ~~~ [{"checksum": "", "id": " 2427658c75a1e3d0af0e7272317a8abfaee4c15729b6840e3c2fca342fe47bf1"}, {"checksum": "", "id": "81fbd8fa918a14f4ebad9728df6785c537218279081c7a120d72399d3a5c94a5" }, {"checksum": "", "id": "ec69e8fd6b0236b67227869b6d6d119f033221dd0f01e0f569518edabef3b72c" }, {"checksum": "", "id": "9e8dc15b6d327eaac00e37de743865f45bee3e0ae763791a34b61e206dd5222e" }, {"checksum": "", "id": "78949b1e1cfdcd5db413c300023b178fc4b59c0e417221c0eb2ffbbd1a4725cc" },……] ~~~ 獲取以上信息之后,Docker Daemon通過RepositoryData和ImgData類型對象來存儲ubuntu這個repository中所有image的信息,RepositoryData和ImgData的數據結構關系如圖6.2: ![](https://box.kancloud.cn/2015-11-12_56443b0c32e68.jpg) 圖6.2 RepositoryData和ImgData的數據結構關系圖 GetRepositoryData執行過程中,會為指定repository中的每一個image創建一個ImgData對象,并最終將所有ImgData存放在RepositoryData的ImgList屬性中,ImgList的類型為map,key為image的ID,value指向ImgData對象。此時ImgData對象中只有屬性ID與Checksum有內容。 #### 6.3.2 GetRemoteTags 使用Docker下載鏡像時,用戶除了指定Docker鏡像的名稱之外,一般還需要指定Docker鏡像的tag,如:請求docker pull ubuntu:14.04中鏡像名稱為ubuntu,鏡像tag為14.04,假設用戶不顯性指定tag,則默認tag為latest。GetRemoteTags的作用則是獲取鏡像名稱所在repository中所有tag的信息。 GetRemoteTags的源碼實現位于[./docker/registry/session.go#L195-234](https://github.com/docker/docker/blob/v1.2.0/registry/session.go#L255-L324)。獲取repository中所有tag信息的目標URL地址如以下源碼: ~~~ endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository) ~~~ 獲取指定repository中所有tag信息之后,Docker Daemon根據tag對應layer的ID,找到ImgData,并對填充ImgData中的Tag屬性。此時,RepositoryData的ImgList屬性中,有的ImgData對象有Tag內容,有的ImgData對象中沒有Tag內容。這也和實際情況相符,如下載一個ubuntu:14.04鏡像,該鏡像的rootfs中只有最上層的layer才有tag信息,這一層layer的parent Image并不一定存在tag信息。 #### 6.3.3 pullImage Docker Daemon下載Docker鏡像時是通過image id來完成。GetRepositoryData和GetRemoteTags則成功完成了用戶傳入的repository和tag信息與image id的轉換。如請求docker pull ubuntu:14.04中,repository為ubuntu,tag為14.04,則對應的image id為2d24f826。 Docker Daemon獲得下載鏡像的image id之后,首先查驗pullingPool,判斷是否有其他Docker Client同樣發起了該鏡像的下載請求,如果沒有的話Docker Daemon才繼續下載任務。 執行pullImage函數的源碼實現位于[./docker/graph/pull.go#L159](https://github.com/docker/docker/blob/v1.2.0/graph/pull.go#L159),如下: ~~~ s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf) ~~~ 而pullImage函數的定義位于[./docker/graph/pull.go#L214-L301](https://github.com/docker/docker/blob/v1.2.0/graph/pull.go#L214-L301)。圖6.1中,可以看到pullImage函數的執行可以分為4個步驟:GetRemoteHistory、GetRemoteImageJson、GetRemoteImageLayer與s.graph.Register()。 GetRemoteHistory的作用很好理解,既然Docker Daemon已經通過GetRepositoryData和GetRemoteTags找出了指定tag的image id,那么Docker Daemon所需完成的工作為下載該image 及其所有的祖先image。GetRemoteHistory正是用于獲取指定image及其所有祖先iamge的id。 GetRemoteHistory的源碼實現位于[./docker/registry/session.go#L72-L101](https://github.com/docker/docker/blob/v1.2.0/registry/session.go#L72-L101)。 獲取所有的image id之后,對于每一個image id,Docker Daemon都開始下載該image的全部內容。Docker Image的全部內容包括兩個方面:image json信息以及image layer信息。Docker所有image的json信息都由函數GetRemoteImageJSON來完成。分析GetRemoteImageJSON之前,有必要闡述清楚什么是Docker Image的json信息。 Docker Image的json信息是一個非常重要的概念。這部分json唯一的標志了一個image,不僅標志了image的id,同時也標志了image所在layer對應的config配置信息。理解以上內容,可以舉一個例子:docker build。命令docker build用以通過指定的Dockerfile來創建一個Docker鏡像;對于Dockerfile中所有的命令,Docker Daemon都會為其創建一個新的image,如:RUN apt-get update, ENV path=/bin, WORKDIR /home等。對于命令RUN apt-get update,Docker Daemon需要執行apt-get update操作,對應的rootfs上必定會有內容更新,導致新建的image所代表的layer中有新添加的內容。而如ENV path=/bin, WORKDIR /home這樣的命令,僅僅是配置了一些容器運行的參數,并沒有鏡像內容的更新,對于這種情況,Docker Daemon同樣創建一層新的layer,并且這層新的layer中內容為空,而命令內容會在這層image的json信息中做更新。總結而言,可以認為Docker的image包含兩部分內容:image的json信息、layer內容。當layer內容為空時,image的json信息被更新。 清楚了Docker image的json信息之后,理解GetRemoteImageJSON函數的作用就變得十分容易。GetRemoteImageJSON的執行代碼位于[./docker/graph/pull.go#L243](https://github.com/docker/docker/blob/v1.2.0/graph/pull.go#L243),如下: ~~~ imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint, token) ~~~ GetRemoteImageJSON返回的兩個對象imgJSON代表image的json信息,imgSize代表鏡像的大小。通過imgJSON對象,Docker Daemon立即創建一個image對象,創建image對象的源碼實現位于[./docker/graph/pull.go#L251](https://github.com/docker/docker/blob/v1.2.0/graph/pull.go#L251),如下: ~~~ img, err = image.NewImgJSON(imgJSON) ~~~ 而NewImgJSON函數位于包image中,函數返回類型為一個Image對象,而Image類型的定義而下: ~~~ type Image struct { ID string `json:"id"` Parent string `json:"parent,omitempty"` Comment string `json:"comment,omitempty"` Created time.Time `json:"created"` Container string `json:"container,omitempty"` ContainerConfig runconfig.Config `json:"container_config,omitempty"` DockerVersion string `json:"docker_version,omitempty"` Author string `json:"author,omitempty"` Config *runconfig.Config `json:"config,omitempty"` Architecture string `json:"architecture,omitempty"` OS string `json:"os,omitempty"` Size int64 graph Graph } ~~~ 返回img對象,則說明關于該image的所有元數據已經保存完畢,由于還缺少image的layer中包含的內容,因此下一個步驟即為下載鏡像layer的內容,調用函數為GetRemoteImageLayer,函數執行位于[./docker/graph/pull.go#L270](https://github.com/docker/docker/blob/v1.2.0/graph/pull.go#L270),如下: ~~~ layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token, int64(imgSize)) ~~~ GetRemoteImageLayer函數返回當前image的layer內容。Image的layer內容指的是:該image在parent image之上做的文件系統內容更新,包括文件的增添、刪除、修改等。至此,image的json信息以及layer內容均被Docker Daemon獲取,意味著一個完整的image已經下載完畢。下載image完畢之后,并不意味著Docker Daemon關于Docker鏡像下載的job就此結束,Docker Daemon仍然需要對下載的image進行存儲管理,以便Docker Daemon在執行其他如創建容器等job時,能夠方便使用這些image。 Docker Daemon在graph中注冊image的源碼實現位于[./docker/graph/pull.go#L283-L285](https://github.com/docker/docker/blob/v1.2.0/graph/pull.go#L283-L285),如下: ~~~ err = s.graph.Register(imgJSON,utils.ProgressReader(layer, imgSize, out, sf, false, utils.TruncateID(id), "Downloading"),img) ~~~ Docker Daemon通過graph存儲image是一個很重要的環節。Docker在1.2.0版本中可以通過AUFS、DevMapper以及BTRFS來進行image的存儲。在Linux 3.18-rc2版本中,OverlayFS已經被內核合并,故從1.4.0版本開始,Docker 的image支持OverlayFS的存儲方式。 Docker鏡像的存儲在Docker中是較為獨立且重要的內容,故將在《Docker源碼分析》系列的第十一篇專文分析。 #### 6.3.4 配置TagStore Docker鏡像下載完畢之后,Docker Daemon需要在TagStore中指定的repository中添加相應的tag。每當用戶查看本地鏡像時,都可以從TagStore的repository中查看所有含有tag信息的image。 Docker Daemon配置TagStore的源碼實現位于[./docker/graph/pull.go#L206](https://github.com/docker/docker/blob/v1.2.0/graph/pull.go#L206),如下: ~~~ if err := s.Set(localName, tag, id, true); err != nil { return err } ~~~ TagStore類型的Set函數定義位于[./docker/graph/tags.go#L174-L205](https://github.com/docker/docker/blob/v1.2.0/graph/tags.go#L174-L205)。Set函數的指定流程與簡要介紹如圖6.3: ![](https://box.kancloud.cn/2015-11-12_56443b0c4a715.jpg) 圖6.3 TagStore中Set函數執行流程圖 當Docker Daemon將已下載的Docker鏡像信息同步到repository之后,Docker下載鏡像的job就全部完成,Docker Daemon返回響應至Docker Server,Docker Server返回相應至Docker Client。注:本地的repository文件位于Docker的根目錄,根目錄一般為/var/lib/docker,如果使用aufs的graphdriver,則repository文件名為repositories-aufs。 ## 7.總結 Docker鏡像給Docker容器的運行帶來了無限的可能性,諸如Docker Hub之類的Docker Registry又使得Docker鏡像在全球的開發者之間共享。Docker鏡像的下載,作為使用Docker的第一個步驟,Docker愛好者若能熟練掌握其中的原理,必定能對Docker的很多概念有更為清晰的認識,對Docker容器的運行、管理等均是有百利而無一害。 Docker鏡像的下載需要Docker Client、Docker Server、Docker Daemon以及Docker Registry四者協同合作完成。本文從源碼的角度分析了四者各自的扮演的角色,分析過程中還涉及多種Docker概念,如repository、tag、TagStore、session、image、layer、image json、graph等。 ## 8.作者介紹 **孫宏亮**,[DaoCloud](http://www.daocloud.io/)初創團隊成員,軟件工程師,浙江大學VLIS實驗室應屆研究生。讀研期間活躍在PaaS和Docker開源社區,對Cloud Foundry有深入研究和豐富實踐,擅長底層平臺代碼分析,對分布式平臺的架構有一定經驗,撰寫了大量有深度的技術博客。2014年末以合伙人身份加入DaoCloud團隊,致力于傳播以Docker為主的容器的技術,推動互聯網應用的容器化步伐。郵箱:[allen.sun@daocloud.io](mailto:allen.sun@daocloud.io) ## 9.參考文獻 [https://docs.docker.com/terms/image/](https://docs.docker.com/terms/image/) [https://docs.docker.com/terms/layer](https://docs.docker.com/terms/layer) [http://docs.studygolang.com/pkg/](http://docs.studygolang.com/pkg/)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看