热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

dockerpull命令实现与镜像存储(1)

我们在《docker命令解析》篇章我们了解了命令的解析过程,所以不再赘述。我们直接看执行命令任务的代码。定位到docker\cli\command\commands\

我们在《docker命令解析》篇章我们了解了命令的解析过程,所以不再赘述。我们直接看执行命令任务的代码。
定位到docker\cli\command\commands\commands.go的AddCommands函数,我们容易找到pull命令的实现函数 在hide(image.NewPullCommand(dockerCli))注册。我们进入该函数:

// NewPullCommand creates a new `docker pull` command
func NewPullCommand(dockerCli *command.DockerCli) *cobra.Command {var opts pullOptionscmd := &cobra.Command{Use: "pull [OPTIONS] NAME[:TAG|@DIGEST]",Short: "Pull an image or a repository from a registry",Args: cli.ExactArgs(1),RunE: func(cmd *cobra.Command, args []string) error { //镜像名字,如:docker pull ubuntu,则args[0]就是ubuntuopts.remote = args[0]return runPull(dockerCli, opts)},}flags := cmd.Flags()flags.BoolVarP(&opts.all, "all-tags", "a", false, "Download all tagged images in the repository")command.AddTrustedFlags(flags, true)return cmd
}

我们了解了命令的解析过程,容易知道将执行函数runPull,同时将拉取的镜像参数传入(镜像名,版本,是否所有tag等),我们看下函数runPull:

func runPull(dockerCli *command.DockerCli, opts pullOptions) error {//从参数中解析出带镜像仓库地址等信息的镜像引用,如果参数中没有仓库地址信息,则使用默认的docker.iodistributionRef, err := reference.ParseNamed(opts.remote)if err != nil {return err}// -a, --all-tags Download all tagged images in the repository//如果使用了all选项,但是又不是只有镜像名(包含tag),则报错处理if opts.all && !reference.IsNameOnly(distributionRef) {return errors.New("tag can't be used with --all-tags/-a")}//如果没有使用all选项,且只有镜像名,则添加一个默认的tag(latest)if !opts.all && reference.IsNameOnly(distributionRef) {distributionRef = reference.WithDefaultTag(distributionRef)fmt.Fprintf(dockerCli.Out(), "Using default tag: %s\n", reference.DefaultTag)}var tag stringswitch x := distributionRef.(type) {//标准的case reference.Canonical:tag = x.Digest().String()//name:tag形式case reference.NamedTagged:tag = x.Tag()}registryRef := registry.ParseReference(tag)// Resolve the Repository name from fqn to RepositoryInforepoInfo, err := registry.ParseRepositoryInfo(distributionRef)if err != nil {return err}ctx := context.Background()authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index)requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "pull")//如果没有添加disable-content-trust,而且没有附带数字摘要,则不对镜像进行校验if command.IsTrusted() && !registryRef.HasDigest() {// Check if tag is digesterr = trustedPull(ctx, dockerCli, repoInfo, registryRef, authConfig, requestPrivilege)} else {//向dockerd发送拉取镜像请求err = imagePullPrivileged(ctx, dockerCli, authConfig, distributionRef.String(), requestPrivilege, opts.all)}if err != nil {if strings.Contains(err.Error(), "target is a plugin") {return errors.New(err.Error() + " - Use `docker plugin install`")}return err}return nil
}

该函数做了两件事:解析输入参数填充Named结构对象,用字符串化的Named对象拉取镜像。在详细说明之前,我们有必要讲一下Named这个接口。

// Named is an object with a full name
type Named interface {// Name returns normalized repository name, like "ubuntu".Name() string// String returns full reference, like "ubuntu@sha256:abcdef..."String() string// FullName returns full repository name with hostname, like "docker.io/library/ubuntu"FullName() string// Hostname returns hostname for the reference, like "docker.io"Hostname() string// RemoteName returns the repository component of the full name, like "library/ubuntu"RemoteName() string
}

Named接口有两个子接口带数字摘要的Canonical和带tag的NamedTagged:

//带数字摘要的形式
// Canonical reference is an object with a fully unique
// name including a name with hostname and digest
type Canonical interface {NamedDigest() digest.Digest
}

//带tag的形式
// NamedTagged is an object including a name and tag.
type NamedTagged interface {NamedTag() string
}

我们知道拉取镜像的命令:docker pull NAME[:TAG|@DIGEST] ,TAG代表标签,DIGEST代表数字摘要,意思就是我们拉取镜像参数可以附带TAG或数字摘要,或者只带镜像名(系统会提供一个默认的标签latest)。如果我们提供的参数带TAG则使用NamedTagged描述 ,如果我们提供的参数带DIGEST则使用Canonical 描述。现在我们简单分析下这个解析过程,函数调用过程:
reference.ParseNamed(opts.remote)–>distreference.ParseNamed(s)–> Parse(s)
函数reference.ParseNamed(opts.remote)实现在docker\reference\reference.go

函数distreference.ParseNamed(s)和函数Parse(s)都是定义在文件docker\vendor\src\github.com\docker\distribution\reference\reference.go
可以发现文件名都为reference.go,感觉起来就有点蹊跷,事实上感觉是对的。我们看下两个文件的结构(上面是docker\reference\reference.go)

docker\reference\reference.go

docker\vendor\src\github.com\docker\distribution\reference\reference.go
两相对比,可以发现两个文件都定义了Named,Canonical,NamedTagged三个接口,而且接口间的关系也是一样的。实际上参数的正则匹配是在后者的Parse函数完成,一切做好之后,才在前者的reference.ParseNamed(opts.remote)函数中做一个转化(暂时还不了解为何要这样写代码),看reference.ParseNamed(opts.remote)函数:

// ParseNamed parses s and returns a syntactically valid reference implementing
// the Named interface. The reference must have a name, otherwise an error is
// returned.
// If an error was encountered it is returned, along with a nil Reference.
func ParseNamed(s string) (Named, error) {named, err := distreference.ParseNamed(s)if err != nil {return nil, fmt.Errorf("Error parsing reference: %q is not a valid repository/tag: %s", s, err)}// If no valid hostname is found, the default hostname is used./如果没有有效的主机名,则使用默认的主机名docker.io//将distreference.Namded转化为reference.Namedr, err := WithName(named.Name())if err != nil {return nil, err}if canonical, isCanonical := named.(distreference.Canonical); isCanonical {//将distreference.Canonical转化为reference.Canonicalreturn WithDigest(r, canonical.Digest())}if tagged, isTagged := named.(distreference.NamedTagged); isTagged {//将distreference.NamedTagged转化为reference.NamedTaggedreturn WithTag(r, tagged.Tag())}return r, nil
}

reference.ParseNamed(opts.remote)函数不过是个马甲,实际工作并不是自己做的,看下完成正则匹配的Parse函数:

// Parse parses s and returns a syntactically valid Reference.
// If an error was encountered it is returned, along with a nil Reference.
// NOTE: Parse will not handle short digests.
func Parse(s string) (Reference, error) {////matches := ReferenceRegexp.FindStringSubmatch(s)if matches == nil {if s == "" {return nil, ErrNameEmpty}// TODO(dmcgowan): Provide more specific and helpful errorreturn nil, ErrReferenceInvalidFormat}if len(matches[1]) > NameTotalLengthMax {return nil, ErrNameTooLong}ref := reference{name: matches[1],tag: matches[2],}//带数字摘要,有SHA256, SHA384, SHA512,一般为SHA256if matches[3] != "" {var err error//主要是校验ref.digest, err = digest.ParseDigest(matches[3])if err != nil {return nil, err}}//这里根据解析参数是否包含镜像名,是否包含标签,以及是否包含数字摘要来决定返回引用的类型r := getBestReferenceType(ref)if r == nil {return nil, ErrNameEmpty}return r, nil
}

ReferenceRegexp匹配规则定义docker\vendor\src\github.com\docker\distribution\reference\regexp.go

ReferenceRegexp = anchored(capture(NameRegexp),optional(literal(":"), capture(TagRegexp)),optional(literal("@"), capture(DigestRegexp)))

可以看到跟我们的命令的形式是对应的,如果带镜像名,则matches[1]不为空,如果带tag,则matches[2]不为空,如果带数字摘要,则matches[3]不为空。getBestReferenceType根据各个matchs是否为空,返回对应的引用Reference。我们接着分析下函数getBestReferenceType:

func getBestReferenceType(ref reference) Reference {//只带数字摘要if ref.name == "" {// Allow digest only referencesif ref.digest != "" {return digestReference(ref.digest)}return nil}//带数字摘要和镜像名if ref.tag == "" {if ref.digest != "" {return canonicalReference{name: ref.name,digest: ref.digest,}}return repository(ref.name)}//带标签和镜像名if ref.digest == "" {return taggedReference{name: ref.name,tag: ref.tag,}}return ref
}

函数逻辑很简单,就是根据是否带相应的部分返回不同类型的Reference 。
好了,我们把上面的过程梳理下:
第一,我们传入拉取镜像的参数,如我们执行docker pull ubuntu:latest,则“ubuntu:latest”将被Parse解析为三个部分matches[1]=ubuntu,matches[2]=latest,matches[3]=”“,并返回NamedTagged类型的Reference对象(distreference.Named为Reference的子接口,也即是返回distreference.Named对象)

第二,reference.ParseNamed(opts.remote)将Reference(distreference.Named)对象转化为reference.Named对象
第三,reference.ParseNamed(opts.remote)返回reference.Named给pull函数使用
转了那么多圈,也就干了这么点事情。


推荐阅读
  • Spring Boot 实战(一):基础的CRUD操作详解
    在《Spring Boot 实战(一)》中,详细介绍了基础的CRUD操作,涵盖创建、读取、更新和删除等核心功能,适合初学者快速掌握Spring Boot框架的应用开发技巧。 ... [详细]
  • 如何使用 net.sf.extjwnl.data.Word 类及其代码示例详解 ... [详细]
  • 如何在Spark数据排序过程中有效避免内存溢出(OOM)问题
    本文深入探讨了在使用Spark进行数据排序时如何有效预防内存溢出(OOM)问题。通过具体的代码示例,详细阐述了优化策略和技术手段,为读者在实际工作中遇到类似问题提供了宝贵的参考和指导。 ... [详细]
  • 本文深入解析了 Apache 配置文件 `httpd.conf` 和 `.htaccess` 的优化方法,探讨了如何通过合理配置提升服务器性能和安全性。文章详细介绍了这两个文件的关键参数及其作用,并提供了实际应用中的最佳实践,帮助读者更好地理解和运用 Apache 配置。 ... [详细]
  • 开发心得:深入探讨Servlet、Dubbo与MyBatis中的责任链模式应用
    开发心得:深入探讨Servlet、Dubbo与MyBatis中的责任链模式应用 ... [详细]
  • 深入解析Gradle中的Project核心组件
    在Gradle构建系统中,`Project` 是一个核心组件,扮演着至关重要的角色。通过使用 `./gradlew projects` 命令,可以清晰地列出当前项目结构中包含的所有子项目,这有助于开发者更好地理解和管理复杂的多模块项目。此外,`Project` 对象还提供了丰富的配置选项和生命周期管理功能,使得构建过程更加灵活高效。 ... [详细]
  • 如何在Java中高效构建WebService
    本文介绍了如何利用XFire框架在Java中高效构建WebService。XFire是一个轻量级、高性能的Java SOAP框架,能够简化WebService的开发流程。通过结合MyEclipse集成开发环境,开发者可以更便捷地进行项目配置和代码编写,从而提高开发效率。此外,文章还详细探讨了XFire的关键特性和最佳实践,为读者提供了实用的参考。 ... [详细]
  • Go语言实现Redis客户端与服务器的交互机制深入解析
    在前文对Godis v1.0版本的基础功能进行了详细介绍后,本文将重点探讨如何实现客户端与服务器之间的交互机制。通过具体代码实现,使客户端与服务器能够顺利通信,赋予项目实际运行的能力。本文将详细解析Go语言在实现这一过程中的关键技术和实现细节,帮助读者深入了解Redis客户端与服务器的交互原理。 ... [详细]
  • POJ 1696: 空间蚂蚁算法优化与分析
    针对 POJ 1696 的空间蚂蚁算法进行了深入的优化与分析。本研究通过改进算法的时间复杂度和空间复杂度,显著提升了算法的效率。实验结果表明,优化后的算法在处理大规模数据时表现优异,能够有效减少计算时间和内存消耗。此外,我们还对算法的收敛性和稳定性进行了详细探讨,为实际应用提供了可靠的理论支持。 ... [详细]
  • Go语言中Goroutine与通道机制及其异常处理深入解析
    在Go语言中,Goroutine可视为一种轻量级的并发执行单元,其资源消耗远低于传统线程,初始栈大小仅为2KB,而普通线程则通常需要几MB。此外,Goroutine的调度由Go运行时自动管理,能够高效地支持成千上万个并发任务。本文深入探讨了Goroutine的工作原理及其与通道(channel)的配合使用,特别是在异常处理方面的最佳实践,为开发者提供了一套完整的解决方案,以确保程序的稳定性和可靠性。 ... [详细]
  • 本文详细解析了AbstractFormController的工作流程及其应用。首先,当表单提交时,控制器会获取命令对象。若非会话表单,则通过`formBackingObject`方法创建新的命令对象。进一步地,文章探讨了表单验证、数据绑定及视图渲染等关键步骤,并结合实际案例分析了其在Web应用中的具体应用。 ... [详细]
  • 如何使用 org.geomajas.configuration.FontStyleInfo.getColor() 方法及其代码示例详解 ... [详细]
  • 深入解析十大经典排序算法:动画演示、原理分析与代码实现
    本文深入探讨了十种经典的排序算法,不仅通过动画直观展示了每种算法的运行过程,还详细解析了其背后的原理与机制,并提供了相应的代码实现,帮助读者全面理解和掌握这些算法的核心要点。 ... [详细]
  • 本文探讨了协同过滤算法在推荐系统中的应用,重点介绍了基于用户和基于物品的两种协同过滤方法。通过引入相似度评估技术和交替最小二乘优化技术,显著提升了推荐系统的准确性和鲁棒性。实验结果表明,该方法在处理大规模数据集时表现出色,能够有效提高用户满意度和系统性能。 ... [详细]
  • 第五章详细探讨了 Red Hat Enterprise Linux 6 中的 Ext3 文件系统。5.1 节介绍了如何创建 Ext3 文件系统,包括必要的命令和步骤,以及在实际操作中可能遇到的问题和解决方案。此外,还涵盖了 Ext3 文件系统的性能优化和维护技巧,为用户提供全面的操作指南。 ... [详细]
author-avatar
关于孔益旺
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有