docker pull命令解析

以docker pull ubuntu:14.04爲例docker

首先須要建立Docker Client,Docker Client的建立比較簡單,這裏暫時不說明。當用戶輸入docker pull Ubuntu:14.04後,進入解析工做,相關代碼以下:json

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)   
}

其中,cli.Cmd()函數的具體代碼以下:ubuntu

// 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...)
}

根據docker pull ubuntu:14.04命令,函數Cmd裏的形參args ...string所對應的實參爲pull ubuntu:14.04。若是實參的長度大於0,則繼續往下進行。cli調用getMethod處理args[0]即pull,獲得具體的處理方法method,getMethod的具體代碼以下:api

func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) {
    if len(name) == 0 {
        return nil, false
    }
    methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
    method := reflect.ValueOf(cli).MethodByName(methodName)
    if !method.IsValid() {
        return nil, false
    }
    return method.Interface().(func(...string) error), true
}

根據以上流程,getMethod返回的方法爲CmdPull,實參爲ubuntu:14.04.其中,CmdPull函數的具體代碼以下的所示:session

func (cli *DockerCli) CmdPull(args ...string) error {
    cmd := cli.Subcmd("pull", "NAME[:TAG]", "Pull an image or a repository from the registry")

經過cli的Subcmd方法,返回了一個Flagset類型的對象cmd,Subcmd的方法以下所示:app

func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet {函數

    flags := flag.NewFlagSet(name, flag.ContinueOnError)post

    flags.Usage = func() {this

        fmt.Fprintf(cli.err, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)url

        flags.PrintDefaults()

        os.Exit(2)

    }

}

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

爲cmd對象定義了一個類型爲string的flag,初始值爲空,目前這個flag參數基本已經棄用。

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

對args參數進行解析,此時args的實參爲ubuntu:14.04,解析過程當中,首先提取是否有符合tag這個flag參數。如有,則賦值給tag參數,其他的參數存入cmd.NArg();若沒有,則將全部的參數存入cmd.NArg()中。

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

判斷通過flag解析後的參數列表,若參數個數不爲1,則調用錯誤處理方法cmd.Usage()。ps,在docker的原先版本中是不支持同時下載多個鏡像的,docker1.10版本後支持了該功能。

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

建立一個map類型的變量v,該變量用來存放下拉鏡像時所需的URL參數;經過以上設置後,v的值爲{"fromImage":ubuntu, "tag":14.04]

remote, _ = parsers.ParseRepositoryTag(remote)
// Resolve the Repository name from fqn to hostname + name
hostname, _, err := registry.ResolveRepositoryName(remote)
if err != nil {
    return err
}
cli.LoadConfigFile()
// Resolve the Auth config relevant for this server
authConfig := cli.configFile.ResolveAuthConfig(hostname)

經過cli對象得到與Docker Server通訊所須要的配置信息。

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,函數最爲重要的部分是

cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{

    "X-Registry-Auth": registryAuthHeader,

})。

cli的stream函數代碼以下所示:

func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error {
    return cli.streamHelper(method, path, true, in, out, nil, headers)
}

cli的streamHelper函數代碼以下所示:

func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in io.Reader, stdout, stderr io.Writer, headers map[string][]string) error {
    if (method == "POST" || method == "PUT") && in == nil {
        in = bytes.NewReader([]byte{})
    }
    req, err := http.NewRequest(method, fmt.Sprintf("http://v%s%s", api.APIVERSION, path), in)
    if err != nil {
        return err
    }
    req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
    req.URL.Host = cli.addr
    req.URL.Scheme = cli.scheme
    if method == "POST" {
        req.Header.Set("Content-Type", "plain/text")
    }
    if headers != nil {
        for k, v := range headers {
            req.Header[k] = v
        }
    }
    resp, err := cli.HTTPClient().Do(req) 
    if err != nil { 
           if strings.Contains(err.Error(), "connection refused") { 
                  return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")  
           }
           return err
    }
    defer resp.Body.Close()
    if resp.StatusCode < 200 || resp.StatusCode >= 400 {
        body, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            return err
        }
        if len(body) == 0 {  
            return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode))
        }
        return fmt.Errorf("Error: %s", bytes.TrimSpace(body))
    }
    if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") {
        return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.terminalFd, cli.isTerminal)
    }
    ...
}

在stream函數裏構建的請求會發送到docker server,並路由至相應的處理方法。其中,路由規則以下所示:

"POST": {

    "/images/create":                postImagesCreate,

}

所以,docker client發送過來的請求會進一步的交給postImagesCreate函數處理,具體代碼以下所示:

var (

    image = r.Form.Get("fromImage")

    repo  = r.Form.Get("repo")

    tag   = r.Form.Get("tag")

    job   *engine.Job

首先是解析請求參數,爲後續job的運行提供依據‘另外,Docker Server經過從HTTP Header中解析出authEncoded,還原出類型爲registry.AuthConfig的對象authConfig,源碼以下:

authEncoded := r.Header.Get("X-Registry-Auth")

authConfig := &registry.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 = &registry.AuthConfig{}

    }

}

當解析出的image參數不爲空的時候,則執行下述代碼:

job = eng.Job("pull", image, tag)

job.SetenvBool("parallel", version.GreaterThan("1.3"))

job.SetenvJson("metaHeaders", metaHeaders)

job.SetenvJson("authConfig", authConfig)

eng是docker中處理任務的基本單元job的載體,其中在docker daemon啓動的時候已經配置了「pull」所對應的處理方法,實際爲graph包中的CmdPull函數,具體代碼以下:

func (s *TagStore) Install(eng *engine.Engine) error {
    for name, handler := range map[string]engine.Handler{
        "image_set":      s.CmdSet,
        "image_tag":      s.CmdTag,
        "tag":            s.CmdTagLegacy, // FIXME merge with "image_tag"
        "image_get":      s.CmdGet,
        "image_inspect":  s.CmdLookup,
        "image_tarlayer": s.CmdTarLayer,
        "image_export":   s.CmdImageExport,
        "history":        s.CmdHistory,
        "images":         s.CmdImages,
        "viz":            s.CmdViz,
        "load":           s.CmdLoad,
        "import":         s.CmdImport,
        "pull":           s.CmdPull,
        "push":           s.CmdPush,
     } {
         if err := eng.Register(name, handler); err != nil {
             return fmt.Errorf("Could not register %q: %v", name, err)
         }
     }
     return nil
}

CmdPull的函數的執行分爲如下幾個步驟:

var (

    localName   = job.Args[0]

    sf          = utils.NewStreamFormatter(job.GetenvBool("json"))

    authConfig  = &registry.AuthConfig{}

    hostname, remoteName, err := registry.ResolveRepositoryName(localName)

    endpoint, err := registry.ExpandAndVerifyRegistryUrl(hostname)

localName表明鏡像的repository信息

tag表明鏡像的tag信息

authConfig表明用戶在指定的的Docker Registry上的認證信息

metaHeaders表明請求中的HTTP Headers信息

hostname表明Docker Registry信息

remoteName表明Docker鏡像的repository名稱信息

endpoint表明Docker Registry完整的URL

在TagStore類型中設計了pullingPool對象,用於保存正在下載的Docker鏡像,下載完畢以前禁止其餘docker client發起相同鏡像的下載請求,下載完畢以後pullingPool中的記錄被清楚。

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)

爲了下載docker鏡像,docker daemon採用了session機制從docker registry中下載鏡像,

r, err := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, true)

完成以上全部的配置以後,則進入真正的鏡像下載階段。

if err = s.pullRepository(r, job.Stdout, localName, remoteName, tag, sf, job.GetenvBool("parallel")); err != nil {

函數pullRepository的執行流程以下所示:

repoData, err := r.GetRepositoryData(remoteName)

函數GetRepositoryData的做用是得到鏡像名稱所在repository中全部image的ID信息。Docker Daemon經過RepositoryData和ImageData類型對象來存儲這個repository中的全部的image信息。

tagsList, err := r.GetRemoteTags(repoData.Endpoints, remoteName, repoData.Tokens)

函數GetRemoteTags的做用是獲取鏡像名稱所在repository中全部的tag信息。

if err := s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {

函數pullImage的做用是下載鏡像,具體流程以下:

history, err := r.GetRemoteHistory(imgID, endpoint, token)

函數GetRemoteHistory的做用是獲取指定image及其全部祖先image的id。

imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint, token)

函數GetRemoteImageJSON的做用是獲得表明image的json信息imgJSON。

img, err = image.NewImgJSON(imgJSON)

經過imgJSON對象建立一個image對象。

layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token, int64(imgSize))

函數GetRemoteImageLayer的做用是下載鏡像layer的內容:該image在parent image之上作的文件系統內容更新,包括文件的增、刪、改。

err = s.graph.Register(imgJSON,

    utils.ProgressReader(layer, imgSize, out, sf, false, utils.TruncateID(id), "Downloading"),

    img)

函數Register完成鏡像的存儲。

err := s.Set(localName, tag, id, true)

func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
        img, err := store.LookupImage(imageName)
        store.Lock()
        defer store.Unlock()
        if tag == "" {
            tag = DEFAULTTAG
        }
        if err := validateRepoName(repoName); err != nil {
            return err
        }
        if err := validateTagName(tag); err != nil {
        ...
        if err := store.reload(); err != nil
相關文章
相關標籤/搜索