容器映像摘要简介


本文档介绍映像摘要,包括什么是映像摘要、如何查找映像摘要以及如何在 Kubernetes 集群中强制使用它们。本文档适用于构建和部署容器映像的开发者和运营商。

容器映像摘要是唯一且不可改变地标识容器映像。通过摘要部署映像时,您可以避免通过映像标记进行部署的缺点。

本文档中的命令假定您可以使用已安装的工具(例如 Google Cloud CLI、Docker、cURL、jqpack)访问 Linux 或 macOS shell 环境。或者,您可以使用预先安装了这些工具的 Cloud Shell

容器映像和映像标记

使用容器映像时,需要一种方法来引用所使用的映像。映像标签是引用映像不同版本的常用方法。一种常见的方法是在构建时用版本标识符标记映像。例如,v1.0.1 可以引用您称为 1.0.1 的版本。

标记使映像修订易于通过人类可读的字符串查找。但是,标记是可变引用,这意味着标签所引用的映像可以更改,如下图所示:

指向过时映像的映像标记。

如上图所示,如果您发布的映像所使用的标记与现有映像相同,则该标记会停止指向现有映像,并开始指向新映像。

映像标记的缺点

标记是可变的,因此当您使用它们部署映像时,它们具有以下缺点:

  • 在 Kubernetes 中,按标签部署可能会导致意外结果。例如,假设您有一个现有的 Deployment 资源,该资源通过标记 v1.0.1 引用了一个容器映像。要修复错误或进行一些小的更改,您的构建过程将创建一个具有相同标记 v1.0.1 的新映像。即使您不更改 Deployment 资源规范,通过 Deployment 资源创建的新 Pod 仍可以使用旧映像或新映像。此问题也适用于其他 Kubernetes 资源,例如StatefulSets、DaemonSets、ReplicaSets 和 Jobs。

  • 如果您使用工具扫描或分析映像,由这些工具生成的结果将仅对扫描的映像有效。为了确保您部署已经过扫描的映像,您无法依赖标记,因为该标记引用的映像可能已经更改。

  • 如果您将 Binary AuthorizationGoogle Kubernetes Engine (GKE) 搭配使用,则基于标记的部署会被禁止使用,因为无法确定创建 Pod 时使用的确切映像。

部署映像时,您可以使用映像摘要来避免使用标记的劣势。您仍然可以根据需要为映像添加标记,但并非必须这样做。

映像的结构

映像由以下组件组成:

这些组件如下图所示:

此映像组件显示了映像清单、配置对象、文件系统层和映像索引的详细信息。

上图显示了与映像组件有关的其他详细信息:

  • 映像清单是一个 JSON 文档,其中包含对配置对象、文件系统层和可选元数据的引用。
  • 映像清单使用 digest 特性引用配置对象和每个文件系统层。digest 特性的值是摘要所引用的内容的加密哈希值,通常使用 SHA-256 算法进行计算。
  • 摘要值用于构建对象的不可变地址。此过程称为内容可寻址的存储,这意味着您可以根据其摘要检索映像清单、映像索引、配置对象和层。
  • 映像摘要是映像索引或映像清单 JSON 文档的哈希值。
  • 配置对象是一个 JSON 文档,用于定义映像的特性,例如 CPU 架构、入口点、公开端口和环境变量。
  • 文件系统层数组定义了容器运行时堆叠层的顺序。这些层作为 tar 文件分发,通常使用 gzip 实用程序进行压缩。
  • 可选映像索引(有时称为清单列表)引用一个或多个映像清单。该参考是映像清单的摘要。在为不同的平台(例如 amd64arm64 架构)生成多个相关映像时,映像索引会非常有用。

如需了解详情,请参阅浏览映像清单、摘要和标记部分。

查找映像摘要

如需使用映像摘要进行部署,您必须先找到摘要。然后,您可以将摘要与部署命令一起使用,或将其包含在 Kubernetes 清单中。

您可以通过多种方式获取映像的摘要,具体取决于您当前的情况。以下部分包含不同产品和工具的示例。

在以下部分中,在 Cloud Shell 或已安装 gcloud CLI、Docker、cURL 和 jq 等工具的 shell 环境中运行命令。

Artifact Registry

Container Registry

  • 对于存储在 Container Registry 中的映像,您可以使用 gcloud container images describe 命令获取映像的摘要,方法是提供名称和标记。使用 --format 标志只显示摘要:

    gcloud container images describe \
        gcr.io/google-containers/pause-amd64:3.2 \
        --format 'value(image_summary.digest)'
    

    尽管您的摘要值可能有所不同,但输出类似于以下内容:

    sha256:4a1c4b21597c1b4415bdbecb28a3296c6b5e23ca4f9feeb599860a1dac6a0108
    

Cloud Build

对于使用 Cloud Build 构建的映像,您可以结合使用 gcloud builds describe 命令--format 标志来获取映像摘要。无论您使用哪个注册表来发布映像,此方法都适用。

  • 对于已经完成的构建,请执行以下操作:

    1. 获取项目的构建列表:

      gcloud builds list
      

      记下 BUILD_ID

    2. 获取映像摘要:

      gcloud builds describe BUILD_ID \
          --format 'value(results.images[0].digest)'
      

      BUILD_ID 替换为 Cloud Build 分配给构建的唯一 ID。

  • 针对当前项目从 Cloud Build 获取最新构建的映像名称和摘要:

    gcloud builds describe \
        $(gcloud builds list --limit 1 --format 'value(id)') \
        --format 'value[separator="@"](results.images[0].name,results.images[0].digest)'
    
  • 如果您的构建生成了多张映像,请过滤输出并获取其中一个映像的摘要:

    gcloud builds describe BUILD_ID --format json \
        | jq -r '.results.images[] | select(.name=="YOUR_IMAGE_NAME") | .digest'
    

    YOUR_IMAGE_NAME 替换为 cloudbuild.yaml 文件中其中一个映像的名称。

  • 如果您使用 gcloud builds submit 命令向 Cloud Build 提交构建,则可以从环境变量的输出中捕获映像摘要:

    IMAGE_DIGEST=$(gcloud builds submit \
        --format 'value(results.images[0].digest)' | tail -n1)
    

Cloud Native Buildpacks

  • 如果您使用 Cloud Native Buildpack 和 Google Cloud 构建器来构建和发布映像,则可以将 --quiet 标志与 pack 命令结合使用来捕获映像名称和摘要。

    pack build --builder gcr.io/buildpacks/builder:v1 --publish --quiet \
        LOCATION-docker.pkg.dev/PROJECT_ID/REPOSITORY/IMAGE \
        > image-with-digest.txt
    

    请替换以下内容:

    • LOCATION:代码库的区域或多区域位置
    • PROJECT_ID:您的 Google Cloud 项目 ID
    • REPOSITORY:您的代码库名称
    • IMAGE:您的映像名称

    文件 image-with-digest.txt 包含映像名称和摘要。

    如果要为映像添加标记,请使用 --tag 标志。

Docker 客户端

  • docker 命令行客户端的 manifest 子命令可以从容器映像注册表中提取映像清单和清单列表。

    从映像 registry.k8s.io/pause:3.9 的清单列表获取摘要(针对 amd64 CPU 架构和 linux 操作系统):

    docker manifest inspect --verbose registry.k8s.io/pause:3.9 | \
        jq -r 'if type=="object"
            then .Descriptor.digest
            else .[] | select(.Descriptor.platform.architecture=="amd64" and
            .Descriptor.platform.os=="linux") | .Descriptor.digest
            end'
    

    输出类似于以下内容:

    sha256:8d4106c88ec0bd28001e34c975d65175d994072d65341f62a8ab0754b0fafe10
    
  • 对于存储在本地 Docker 守护程序中的映像,以及从映像注册表拉取或推送到映像注册表的映像,您可以使用 Docker 命令行工具来获取映像摘要:

    1. 将映像拉取到本地 Docker 守护程序

      docker pull docker.io/library/debian:bookworm
      
    2. 获取映像摘要:

      docker inspect docker.io/library/debian:bookworm \
          | jq -r '.[0].RepoDigests[0]' \
          | cut -d'@' -f2
      

      尽管您的摘要值可能有所不同,但输出类似于以下内容:

      sha256:3d868b5eb908155f3784317b3dda2941df87bbbbaa4608f84881de66d9bb297b
      
  • 列出本地 Docker 守护程序中的所有映像和摘要:

    docker images --digests
    

    输出会显示具有摘要值的映像的摘要。从映像注册表拉取映像或将映像推送到映像注册表时,映像只有摘要值。

cranegcrane

您可以使用开源 cranegcrane 命令行工具来获取映像摘要,而无需将映像拉取到本地 Docker 守护程序。

  1. cranegcrane 下载到当前目录:

    VERSION=$(curl -sL https://api.github.com/repos/google/go-containerregistry/releases/latest | jq -r .tag_name)
    curl -L "https://github.com/google/go-containerregistry/releases/download/${VERSION}/go-containerregistry_$(uname -s)_$(uname -m).tar.gz" | tar -zxf - crane gcrane
    
  2. 获取映像摘要:

    ./gcrane digest gcr.io/distroless/static-debian11:nonroot
    

    cranegcrane 具有本文档未介绍的其他功能。如需了解详情,请参阅 cranegcrane 的文档。

强制在 Kubernetes 部署中使用映像摘要

如果要对部署到 Kubernetes 集群的映像强制执行摘要,可以使用 Policy ControllerOpen Policy Agent (OPA) Gatekeeper。Policy Controller 是基于 OPA Gatekeeper 开源项目构建的。

Policy Controller 和 OPA Gatekeeper 都是在 OPA 政策引擎的基础上构建的。Policy Controller 和 OPA Gatekeeper 提供 Kubernetes 验证准许网络钩子强制执行政策,并且自定义资源定义 (CRD) 适用于限制条件模板限制条件

限制条件模板包含一种使用名为 Rego 的高级声明语言来表示的政策逻辑。以下限制条件模板可验证 Kubernetes 资源规范中的容器、init 容器临时容器是否使用含摘要的映像:

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8simagedigests
  annotations:
    metadata.gatekeeper.sh/title: "Image Digests"
    metadata.gatekeeper.sh/version: 1.0.0
    description: >-
      Requires container images to contain a digest.

      https://kubernetes.io/docs/concepts/containers/images/
spec:
  crd:
    spec:
      names:
        kind: K8sImageDigests
      validation:
        openAPIV3Schema:
          type: object
          description: >-
            Requires container images to contain a digest.

            https://kubernetes.io/docs/concepts/containers/images/
          properties:
            exemptImages:
              description: >-
                Any container that uses an image that matches an entry in this list will be excluded
                from enforcement. Prefix-matching can be signified with `*`. For example: `my-image-*`.

                It is recommended that users use the fully-qualified Docker image name (e.g. start with a domain name)
                in order to avoid unexpectedly exempting images from an untrusted repository.
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8simagedigests

        import data.lib.exempt_container.is_exempt

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not is_exempt(container)
          satisfied := [re_match("@[a-z0-9]+([+._-][a-z0-9]+)*:[a-zA-Z0-9=_-]+", container.image)]
          not all(satisfied)
          msg := sprintf("container <%v> uses an image without a digest <%v>", [container.name, container.image])
        }

        violation[{"msg": msg}] {
          container := input.review.object.spec.initContainers[_]
          not is_exempt(container)
          satisfied := [re_match("@[a-z0-9]+([+._-][a-z0-9]+)*:[a-zA-Z0-9=_-]+", container.image)]
          not all(satisfied)
          msg := sprintf("initContainer <%v> uses an image without a digest <%v>", [container.name, container.image])
        }

        violation[{"msg": msg}] {
          container := input.review.object.spec.ephemeralContainers[_]
          not is_exempt(container)
          satisfied := [re_match("@[a-z0-9]+([+._-][a-z0-9]+)*:[a-zA-Z0-9=_-]+", container.image)]
          not all(satisfied)
          msg := sprintf("ephemeralContainer <%v> uses an image without a digest <%v>", [container.name, container.image])
        }
      libs:
        - |
          package lib.exempt_container

          is_exempt(container) {
              exempt_images := object.get(object.get(input, "parameters", {}), "exemptImages", [])
              img := container.image
              exemption := exempt_images[_]
              _matches_exemption(img, exemption)
          }

          _matches_exemption(img, exemption) {
              not endswith(exemption, "*")
              exemption == img
          }

          _matches_exemption(img, exemption) {
              endswith(exemption, "*")
              prefix := trim_suffix(exemption, "*")
              startswith(img, prefix)
          }

上述政策包含一个正则表达式作为 re_match 函数的输入。此正则表达式与容器映像摘要一致,并且基于 Open Container Initiative 映像规范中的摘要格式

限制条件会通过与 kindnamespace 等特性进行匹配来将政策应用于 Kubernetes 资源。以下示例限制条件将限制条件模板中的政策应用于 default 命名空间中的所有 Pod 资源。

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sImageDigests
metadata:
  name: container-image-must-have-digest
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaces:
      - "default"

创建限制条件模板和限制条件后,default 命名空间中的任何新 pod 都必须使用映像摘要来引用容器映像。

如需查看完整示例,请参阅 Gatekeeper 政策库中的 imagedigests 政策

关于映像清单、摘要和标记

在本部分中,您将了解如何使用 curldocker 等命令行工具探索注册表中的现有映像。在 Cloud Shell 或已安装 gcloud CLI、Docker、cURL 和 jq 等工具的 shell 环境中运行命令。以下命令使用 Container Registry 中的公共映像。

  • 使用 cURL 和清单网址获取映像 gcr.io/google-containers/pause-amd64:3.2 的清单:

    curl -s https://gcr.io/v2/google-containers/pause-amd64/manifests/3.2
    

    输出内容类似如下:

    {
       "schemaVersion": 2,
       "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
       "config": {
          "mediaType": "application/vnd.docker.container.image.v1+json",
          "size": 759,
          "digest": "sha256:80d28bedfe5dec59da9ebf8e6260224ac9008ab5c11dbbe16ee3ba3e4439ac2c"
       },
       "layers": [
          {
             "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
             "size": 296534,
             "digest": "sha256:c74f8866df097496217c9f15efe8f8d3db05d19d678a02d01cc7eaed520bb136"
          }
       ]
    }
    

    config 部分有一个摘要特性,您可以使用此值来检索配置对象。同样,每个层都有一个 digest 特性,您可以使用该特性检索该层的 tar 文件。

  • 如果映像包含可选的映像索引,则使用标记发送到清单网址的 HTTP GET 请求会返回映像索引,而不是映像清单。

    获取映像 gcr.io/google-containers/pause:3.2 的映像索引:

    curl -s https://gcr.io/v2/google-containers/pause/manifests/3.2
    

    输出类似于以下内容:

    {
       "schemaVersion": 2,
       "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
       "manifests": [
          {
             "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
             "size": 526,
             "digest": "sha256:4a1c4b21597c1b4415bdbecb28a3296c6b5e23ca4f9feeb599860a1dac6a0108",
             "platform": {
                "architecture": "amd64",
                "os": "linux"
             }
          },
          {
             "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
             "size": 526,
             "digest": "sha256:bbb7780ca6592cfc98e601f2a5e94bbf748a232f9116518643905aa30fc01642",
             "platform": {
                "architecture": "arm",
                "os": "linux",
                "variant": "v7"
             }
          },
          {
             "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
             "size": 526,
             "digest": "sha256:31d3efd12022ffeffb3146bc10ae8beb890c80ed2f07363515580add7ed47636",
             "platform": {
                "architecture": "arm64",
                "os": "linux"
             }
          },
          {
             "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
             "size": 526,
             "digest": "sha256:7f82fecd72730a6aeb70713476fb6f7545ed1bbf32cadd7414a77d25e235aaca",
             "platform": {
                "architecture": "ppc64le",
                "os": "linux"
             }
          },
          {
             "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
             "size": 526,
             "digest": "sha256:1175fd4d728641115e2802be80abab108b8d9306442ce35425a4e8707ca60521",
             "platform": {
                "architecture": "s390x",
                "os": "linux"
             }
          }
       ]
    }
    
  • 对结果进行过滤,以提取所需平台的映像摘要。获取 amd64 CPU 架构和 linux 操作系统的映像清单的摘要:

    curl -s https://gcr.io/v2/google-containers/pause/manifests/3.2 | \
        jq -r '.manifests[] | select(.platform.architecture=="amd64" and .platform.os=="linux") | .digest'
    

    此命令中的过滤操作会模拟容器运行时(例如 containerd)如何从映像索引中选择与目标平台匹配的映像。

    输出类似于以下内容:

    sha256:4a1c4b21597c1b4415bdbecb28a3296c6b5e23ca4f9feeb599860a1dac6a0108
    

    映像摘要是将防冲突哈希应用于映像索引或映像清单的结果,通常是 SHA-256 算法。

  • 获取映像 gcr.io/google-containers/pause-amd64:3.2 的摘要:

    curl -s https://gcr.io/v2/google-containers/pause-amd64/manifests/3.2 \
        | shasum -a 256 \
        | cut -d' ' -f1
    

    输出内容类似如下:

    4a1c4b21597c1b4415bdbecb28a3296c6b5e23ca4f9feeb599860a1dac6a0108
    

    您可以使用映像摘要值来引用此映像,如下所示:

    gcr.io/google-containers/pause-amd64@sha256:4a1c4b21597c1b4415bdbecb28a3296c6b5e23ca4f9feeb599860a1dac6a0108
    
  • 使用内容可寻址的存储概念,将摘要用作参考,以获取映像清单:

    curl -s https://gcr.io/v2/google-containers/pause-amd64/manifests/sha256:4a1c4b21597c1b4415bdbecb28a3296c6b5e23ca4f9feeb599860a1dac6a0108
    
  • 许多容器映像注册表会在 Docker-Content-Digest 标头中返回清单摘要、映像索引、配置对象和文件系统层,以响应 HTTP HEAD 请求。获取映像 gcr.io/google-containers/pause-amd64:3.2 的映像索引的摘要:

    curl -s --head https://gcr.io/v2/google-containers/pause/manifests/3.2 \
        | grep -i Docker-Content-Digest \
        | cut -d' ' -f2
    

    输出类似于以下内容:

    sha256:927d98197ec1141a368550822d18fa1c60bdae27b78b0c004f705f548c07814f
    

    Open Container Initiative Distribution 规范并未规定 Docker-Content-Digest 标头,因此此方法可能并不适用于所有容器映像注册表。您可以将其与 Artifact RegistryContainer Registry 搭配使用。

  • 如需使用映像清单中的摘要值检索映像配置对象,请执行以下操作:

    1. 获取配置摘要:

      CONFIG_DIGEST=$(curl -s https://gcr.io/v2/google-containers/pause-amd64/manifests/3.2 \
          | jq -r '.config.digest')
      
    2. 使用配置摘要来检索配置对象,并使用 jq 设置输出格式,使其更易于阅读:

      curl -sL https://gcr.io/v2/google-containers/pause-amd64/blobs/$CONFIG_DIGEST \
          | jq
      

      输出内容类似如下:

      {
        "architecture": "amd64",
        "config": {
          "Env": [
            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
          ],
          "Entrypoint": [
            "/pause"
          ],
          "WorkingDir": "/",
          "OnBuild": null
        },
        "created": "2020-02-14T10:51:50.60182885-08:00",
        "history": [
          {
            "created": "2020-02-14T10:51:50.60182885-08:00",
            "created_by": "ARG ARCH",
            "comment": "buildkit.dockerfile.v0",
            "empty_layer": true
          },
          {
            "created": "2020-02-14T10:51:50.60182885-08:00",
            "created_by": "ADD bin/pause-amd64 /pause # buildkit",
            "comment": "buildkit.dockerfile.v0"
          },
          {
            "created": "2020-02-14T10:51:50.60182885-08:00",
            "created_by": "ENTRYPOINT [\"/pause\"]",
            "comment": "buildkit.dockerfile.v0",
            "empty_layer": true
          }
        ],
        "os": "linux",
        "rootfs": {
          "type": "layers",
          "diff_ids": [
            "sha256:ba0dae6243cc9fa2890df40a625721fdbea5c94ca6da897acdd814d710149770"
          ]
        }
      }
      
  • 如需使用映像清单中的摘要值检索文件系统层,请执行以下操作:

    1. 获取要检索的层的摘要:

      LAYER_DIGEST=$(curl -s https://gcr.io/v2/google-containers/pause-amd64/manifests/3.2 \
          | jq -r '.layers[0].digest')
      
    2. 使用层摘要来检索层 tar 文件,并列出内容:

      curl -sL https://gcr.io/v2/google-containers/pause-amd64/blobs/$LAYER_DIGEST \
          | tar --list
      

      此层只有一个文件,名为 pause

  • 如需查找与映像摘要关联的标记,请执行以下操作:

    1. 定义您想查找的摘要:

      IMAGE_DIGEST=$(curl -s https://gcr.io/v2/google-containers/pause-amd64/manifests/3.2 \
          | shasum -a 256 \
          | cut -d' ' -f1)
      

      IMAGE_DIGEST 环境变量包含标记 3.2 所引用映像的摘要。

    2. 使用映像标记列表端点 /tags/list 列出标记信息,并提取摘要值对应的标记:

      curl -s "https://gcr.io/v2/google-containers/pause-amd64/tags/list?n=1" \
          | jq ".manifest.\"sha256:$IMAGE_DIGEST\".tag"
      

      输出类似于以下内容:

      [
        "3.2"
      ]
      
  • 如需使用 cURL 从 Artifact Registry 容器映像代码库获取映像的清单,请在 Authorization 请求标头中添加访问令牌

    curl -s -H "Authorization: Bearer $(gcloud auth print-access-token)" \
        https://LOCATION-docker.pkg.dev/v2/PROJECT_ID/REPOSITORY/IMAGE/manifests/DIGEST
    

    请替换以下内容:

    • LOCATION:代码库的区域或多区域位置
    • PROJECT_ID:您的 Google Cloud 项目 ID
    • REPOSITORY:您的代码库名称
    • IMAGE:您的映像名称
    • DIGEST:映像摘要,格式为 sha256:DIGEST_VALUE

后续步骤