【问题标题】:How are Docker image names parsed?Docker 镜像名称是如何解析的?
【发布时间】:2016-10-18 02:59:02
【问题描述】:

在执行docker push 或拉取镜像时,Docker 如何确定镜像名称中是否存在注册表服务器,或者它是否是默认注册表(例如 Docker Hub)上的路径/用户名?

我从1.1 image specification 看到以下内容:

标签

标签用于将描述性的、用户给定的名称映射到任何单个图像 ID。标签值仅限于字符集 [a-zA-Z_0-9]。

存储库

在一个公共前缀下分组的标签集合(名称组件 前 :)。例如,在带有名称 my-app:3.1.4 标记的图像中, my-app 是名称的 Repository 组件。存储库名称是 由斜杠分隔的名称组件组成,可选前缀为 DNS 主机名。主机名必须遵循标准 DNS 规则, 但不能包含 _ 字符。如果存在主机名,它可能 可选地后跟一个格式为:8080 的端口号。姓名 组件可能包含小写字符、数字和分隔符。一种 分隔符定义为句点、一个或两个下划线或一个或 更多破折号。名称组件不能以分隔符开头或结尾。

对于 DNS 主机名,它是否需要完全限定为点,或者“my-local-server”是否是有效的注册表主机名?对于名称组件,我认为句点是有效的,这意味着“team.user/appserver”是一个有效的图像名称。如果注册服务器在端口 80 上运行,因此镜像名称中的主机名不需要端口号,则主机名和注册服务器上的路径之间似乎存在歧义。我很好奇 Docker 是如何解决这种歧义的。

【问题讨论】:

    标签: docker docker-registry


    【解决方案1】:

    TL;DR:主机名必须包含. dns 分隔符、: 端口分隔符,或第一个 / 之前的值“localhost”。否则,代码假定您需要默认注册表 Docker Hub。


    在对代码进行了一些挖掘之后,我遇到了distribution/distribution/reference/reference.go,其中包含以下内容:

    // Grammar
    //
    //  reference                       := name [ ":" tag ] [ "@" digest ]
    //  name                            := [hostname '/'] component ['/' component]*
    //  hostname                        := hostcomponent ['.' hostcomponent]* [':' port-number]
    //  hostcomponent                   := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
    //  port-number                     := /[0-9]+/
    //  component                       := alpha-numeric [separator alpha-numeric]*
    //  alpha-numeric                   := /[a-z0-9]+/
    //  separator                       := /[_.]|__|[-]*/
    //
    //  tag                             := /[\w][\w.-]{0,127}/
    //
    //  digest                          := digest-algorithm ":" digest-hex
    //  digest-algorithm                := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]
    //  digest-algorithm-separator      := /[+.-_]/
    //  digest-algorithm-component      := /[A-Za-z][A-Za-z0-9]*/
    //  digest-hex                      := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
    

    它的实际实现是通过distribution/distribution/reference/regexp.go 中的正则表达式。

    但是通过一些挖掘和戳戳,我发现除了该正则表达式之外还有另一个检查(例如,如果您不包含 .:,您将收到大写主机名错误)。我在distribution/distribution/reference/normalize.go 中追踪了名称的实际拆分为以下内容:

    // splitDockerDomain splits a repository name to domain and remotename string.
    // If no valid domain is found, the default domain is used. Repository name
    // needs to be already validated before.
    func splitDockerDomain(name string) (domain, remainder string) {
        i := strings.IndexRune(name, '/')
        if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") {
            domain, remainder = defaultDomain, name
        } else {
            domain, remainder = name[:i], name[i+1:]
        }
        if domain == legacyDefaultDomain {
            domain = defaultDomain
        }
        if domain == defaultDomain && !strings.ContainsRune(remainder, '/') {
            remainder = officialRepoName + "/" + remainder
        }
        return
    }
    

    对我来说,重要的部分是在第一个 if 语句中的第一个 / 之前检查 .: 或主机名 localhost。有了它,主机名从第一个 / 之前分离出来,没有它,整个名称将传递给默认的注册表主机名。

    【讨论】:

    • 根据图像规范,标签限制为 127 个字符。所以我认为标签正则表达式应该是 /[\w][\w.-]{0,126}/
    • 正则表达式长度为 0 到 127 个字符,所以我认为这是正确的。如果不是,那将是一个 PR 来改变这个:github.com/docker/distribution/blob/master/reference/…
    • 我是这样想的......正则表达式开始 [/w][\w.-]{0,127} 并且没有 |在。。之间 [ ]。所以这意味着匹配一个 /w,然后匹配最多 127 个 \w 或点或连字符。在 Ruby 的 irb 中尝试 /^([\w][\w.-]{0,4})$/.match('ssss-') 确认消耗了 5 个字符...
    • 我的眼睛盯着第一个[\w],所以你是对的,正则表达式比规范长。如果您收到 PR 以使两者同步,请告诉我,我很乐意更新此答案。
    • 这很棒。简而言之,我会尝试这样记住它:[[host:port/]registry/]component[:tag][@digest]
    【解决方案2】:
    【解决方案3】:

    注意:许多 URL 解析库无法解析 docker 图像引用/标签,除非它们符合标准化的 URL 格式。

    Ansible Snippet 示例:

    - debug: #(FAILS)
        msg: "{{ 'docker.io/alpine' | urlsplit() }}"
    # ^-- This will fail, because the image reference isn't in standard URL format
    
    # If you can convert the docker image reference to standard URL format
    # Then most URL parsing libraries will work correctly
    
    - debug: #(WORKS)
        msg: "{{ ('https://' + 'docker.io/alpine') | urlsplit() }}"
    # ^-- Example: This becomes standard URL syntax, so it parses correctly
    
    - debug: #(FAILS)
        msg: "{{ ('http://' + 'busybox:1.34.1-glibc') | urlsplit('path') }}"
    # ^-- Unfortunately, this trick won't work to turn 100% of images into 
    #     Standard URL format for parsing. (This example fails as well)
    

    根据 BMitch 的回答,我意识到一个简单的 if 语句算法逻辑可用于将任意 docker 图像引用/标签转换为标准化的 URL 格式,从而允许大多数库解析它们。

    人类说话的算法:

    1. look for / in $TAG
    2. If / not found 
       Then return ("https://docker.io/" + $TAG)
    3. If / found, split $TAG into 2 parts by first /
       and test text left of /, to look for ".", ":", or "localhost"
    4. If (".", ":", or "localhost" found in text left of 1st /)
       Then return (https://" + $TAG)
    5. If (".", ":", or "localhost" not found in text left of 1st /)
       Then return (https://docker.io/ + $TAG)
    
    (This logic converts docker tags into standardized URL format 
    so they can be processed by URL parsing libraries.)
    

    Bash 中的算法:
    vi docker_tag_to_standardized_url_format.sh
    (复制粘贴以下)

    #!/bin/bash
    #This standardizes the naming of docker images
    #Basically busybox --------------------> https://docker.io/busybox
    #          myregistry.tld/myimage:tag -> https://myregistry.tld/myimage:tag
    STDIN=$(cat -)
    INPUT=$STDIN
    OUTPUT=""
    
    echo "$INPUT" | grep "/" > /dev/null
    if [ $? -eq 0 ]; then
      echo "$INPUT" | cut -d "/" -f1 | egrep "\.|:|localhost" > /dev/null
      #Note: grep considers . as wildcard, \ is escape character to treat \. as .
      if [ $? -eq 0 ]; then
        OUTPUT="https://$INPUT"
      else
        OUTPUT="https://docker.io/$INPUT"
      fi
    else
      OUTPUT="https://docker.io/$INPUT"
    fi
    
    echo $OUTPUT
    

    使其可执行:
    chmod +x ./docker_tag_to_standardized_url_format.sh

    用法示例:

    # Test data, to verify against edge cases
    A=docker.io/alpine
    B=docker.io/rancher/system-upgrade-controller:v0.8.0
    C=busybox:1.34.1-glibc
    D=busybox
    E=rancher/system-upgrade-controller:v0.8.0
    F=localhost:5000/helloworld:latest
    G=quay.io/go/go/gadget:arms
    ####################################
    echo $A | ./docker_tag_to_standardized_url_format.sh 
    echo $B | ./docker_tag_to_standardized_url_format.sh
    echo $C | ./docker_tag_to_standardized_url_format.sh
    echo $D | ./docker_tag_to_standardized_url_format.sh
    echo $E | ./docker_tag_to_standardized_url_format.sh
    echo $F | ./docker_tag_to_standardized_url_format.sh
    echo $G | ./docker_tag_to_standardized_url_format.sh
    

    【讨论】:

    • 虽然这很有趣,但问题是 docker 如何解析引用,而不是如何在 url 数据类型中保存引用。
    • 老实说,我的回答更针对这个问题stackoverflow.com/questions/42115777/…,但它已关闭并标记为与此问题重复,所以我在这里回答,因为我无法在关闭时发布我的答案问题。
    猜你喜欢
    • 2023-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-29
    • 2016-01-13
    • 2019-06-30
    • 2021-02-02
    • 1970-01-01
    相关资源
    最近更新 更多