文档章节

docker pull命令解析

kellydestiny
 kellydestiny
发布于 2016/05/07 23:07
字数 1857
阅读 641
收藏 0
点赞 2
评论 0

以docker pull ubuntu:14.04为例

首先需要创建Docker Client,Docker Client的创建比较简单,这里暂时不说明。当用户输入docker pull Ubuntu:14.04后,进入解析工作,相关代码如下:

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()函数的具体代码如下:

// 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的具体代码如下:

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函数的具体代码如下的所示:

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的方法如下所示:

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

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

    flags.Usage = func() {

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

        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


© 著作权归作者所有

共有 人打赏支持
kellydestiny
粉丝 1
博文 25
码字总数 9730
作品 0
武汉
程序员
Docker理论与实践(二)

文章作者:Tyan 博客:noahsnail.com 1. Docker命令 1.1 docker run hello-world解析 这个命令总共有三部分: docker:告诉操作系统你使用的是docker程序 run:创建和运行docker容器的子命令...

Quincuntial
2016/09/22
0
0
k8s--DNS域名服务

在前面安装好的k8s集群环境下,继续增加DNS域名解析服务 Kubernetes提供的DNS由以下三个组件组成: 1. etcd:DNS存储 2. kube2sky:将kubernetes master中的service(服务)注册到etcd 3....

super李导
2017/12/25
0
0
docker入门与实践之【03-镜像】

获取镜像 1.1 获取镜像 docker获取镜像的命令是pull,命令格式为: 具体的选项可以通过 docker pull --help 命令看到,这里我们说一下镜像名称的格式。 Docker 镜像仓库地址:地址的格式一般...

Funcy1122
06/08
0
0
openshift/origin学习记录(8)——基于镜像安装多节点集群(Containerized Installer)

本节内容是Docker镜像以及Ansible实现多节点集群Containerized Installer。大体流程和基于RPM的安装过程类似。 本部分openshift集群的部署分为以下几个阶段: 主机准备。准备openshift集群需...

huqigang
2017/09/22
0
0
Docker私有仓库搭建

由于公有仓库有时连接会出现超时,下载速度慢等情况 故搭建私有仓库镜像 server端可以login官方的Doker Hub,可以pull,push和私有仓库 但client只能操作自己搭建的仓库 server 192.168.127.1...

JianYua
2017/10/11
0
0
docekr8使用Docker镜像的方法讲解

在之前的介绍中,我们知道镜像是 Docker 的三大组件之一。 Docker 运行容器前需要本地存在对应的镜像,如果本地不存在该镜像,Docker 会从镜像仓库下载该镜像。 本章将介绍更多关于镜像的内容...

王冠hurt的博客
2017/12/20
0
1
私有安全docker registry授权访问实验

关于私有安全docker registry的实验 实验环境 服务器端:使用registry v2.1启动容器。 客户端:安装了Docker的机器,准备pull/push操作 实验条件: registry端生成私钥以及证书: 1 openssl ...

山疯
2016/04/10
629
0
Docker image 存储路径解析

在生产环境中,经常遇到docker image 在资源池中的主机上存留的数据,由于随着业务系统的升级,旧的image 需要进行清理。这里梳理下,docker image的在linux 系统上的存储目录,以针对性的进...

wangxuwei
06/02
0
0
Docker 镜像及Docker仓库配置 [四]

Docker 镜像及Docker仓库配置 [四] Docker 镜像及Docker仓库配置 [四] 一、Docker 镜像介绍 Docker镜像构建分为两种,一种是,另一种是Dockerfile() Docker镜像手动构建案例: 我们基于镜像...

Abcdocker
06/26
0
0
Docker私库搭建和使用

上一篇中描述了一些Docker中常用的命令和操作,这篇我们来搭建一个自己的私库,用来存放和分发镜像,如果你对Docker基础命令不算熟悉,请参考《Docker常用命令和操作》 环境介绍: 操作系统:...

奋斗的寒霜
2017/05/11
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Java基础——异常

声明:本栏目所使用的素材都是凯哥学堂VIP学员所写,学员有权匿名,对文章有最终解释权;凯哥学堂旨在促进VIP学员互相学习的基础上公开笔记。 异常处理: 可以挖很多个陷阱,但是不要都是一样...

凯哥学堂
18分钟前
0
0
180723-Quick-Task 动态脚本支持框架之结构设计篇

文章链接:https://liuyueyi.github.io/hexblog/2018/07/23/180723-Quick-Task-动态脚本支持框架之结构设计篇/ Quick-Task 动态脚本支持框架之结构设计篇 相关博文: 180702-QuickTask动态脚本...

小灰灰Blog
21分钟前
0
0
SBT 常用开发技巧

SBT 一直以来都是 Scala 开发者不可言说的痛,最主要的原因就是官方文档维护质量较差,没有经过系统的、循序渐进式的整理,导致初学者入门门槛较高。虽然也有其它构建工具可以选择(例如 Mill...

joymufeng
26分钟前
0
0
HBase in Practice - 性能、监控及问题解决

李钰(社区ID:Yu Li),阿里巴巴计算平台事业部高级技术专家,HBase开源社区PMC&committer。开源技术爱好者,主要关注分布式系统设计、大数据基础平台建设等领域。连续4年基于HBase/HDFS设计和...

中国HBase技术社区
27分钟前
1
0
ES18-JAVA API 批量操作

1.批量查询 Multi Get API public static void multiGet() {// 批量查询MultiGetResponse response = getClient().prepareMultiGet().add("my_person", "my_index", "1")// 查......

贾峰uk
31分钟前
0
0
SpringBoot2.0使用health

1,引入actuator <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency> 2,application.properties ......

暗中观察
38分钟前
0
0
阿里巴巴Java开发规约

###编程规约 命名风格 【强制】代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束 【强制】代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。...

简心
43分钟前
0
0
如何用TypeScript来创建一个简单的Web应用

转载地址 如何用TypeScript来创建一个简单的Web应用 安装TypeScript 获取TypeScript工具的方式: 通过npm(Node.js包管理器) npm install -g typescript 构建你的第一个TypeScript文件 创建...

durban
47分钟前
0
0
分享好友,朋友圈自定义分享链接无效

这个问题是微信6.5.6版本以后,修改了分享规则:分享的连接必须在公众号后台设定的js安全域名内

LM_Mike
今天
0
0
2018年7月23日课程

一、LVS-DR介绍 director分配请求到不同的real server。real server 处理请求后直接回应给用户,这样director负载均衡器仅处理客户机与服务器的一半连接。负载均衡器仅处理一半的连接,避免了...

人在艹木中
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部