容器开发

概述

参考:

go-containerregistry 是一个用于控制容器镜像的 Go 库。这个库的整体设计理念是将容器镜像抽象为 3 个接口:

  • Image{} # 定义了与 OCI 标准的 Image 交互的接口
  • Layer{} # 定义了访问 OCI Image 标准的 Layer 的交互接口
  • ImageIndex{} # 定义了与 OCI Image 标准的 Index 交互的接口

这三个被抽象出来的接口可以通过多种 Medium(手段) 实现:

  • Registry(注册中心) # 控制各种容器镜像的 Registry 中的镜像。比如 docker.io、gcr.io 等等
  • Tarball(压缩文件) # 控制 docker save 之类命令生成的 tarball 的镜像。
  • Daemon(守护进程) # 控制 Docker 守护进程中的镜像。这个包还不够完善
  • …… 等等。随着库的扩展,可以实现三个接口的 Medium 将会越来越多

用人话说:

我们可以通过多个途径获取到容器镜像,比如容器镜像的注册中心,本地的压缩包(docker save 命令生成的文件等)、容器管理程序(Docker、Containerd 等)。这些可以获取到镜像的地方,称为 Medium(手段、介质)

go-containerregisty 中编写了很多处理镜像的逻辑(也就是函数),这些处理镜像的逻辑符合 OCI 标准,但是很多逻辑都需要将“镜像”作为参数传递以便处理它们,但是通过这些 Medium 获取到的镜像格式可能不太一样,那么就需要对这些“镜像”建模,以便进行统一管理,所以就将“镜像”抽象为 Image{}Layer{}ImageIndex{} 这三个接口。

此时,每个 Medium 就可以自己实现想要的镜像管理逻辑了,只要其定义的结构体可以实现上述三个接口,那么就可以通过 go-containerregistry 中的函数处理 OCI 标准的镜像了。

说白了,go-containerregsitry 本质就是处理容器镜像的通用逻辑,那些 Medium 相关的代码其实并不真正属于该库的一部分,而是对该库的一种调用。

简单示例

比如我们用 remote 这个 Medium 作为示例。

首先我们需要实例化一个 Image{}

img, _ := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))

想要实例化一个 Image{} 需要知道镜像的 tag、digest 中任意一个

ref, _ := name.ParseReference("nginx")

然后就可以通过实例化的 Image{} 处理镜像了,比如这里是获取镜像占用空间的大小

imageSize, _ := img.Size()

这几步合在一起

package main

import (
	"fmt"

	"github.com/google/go-containerregistry/pkg/authn"
	"github.com/google/go-containerregistry/pkg/name"
	"github.com/google/go-containerregistry/pkg/v1/remote"
)

func main() {
	// 通过镜像的 tag 或者 digest 实例化一个镜像的引用。其实就是告诉 Medium 需要操作的镜像
	// 这里之所以不在 remote.Image() 的第一个参数中只写填写镜像的 tag 或 digest,是为了可以在实例化 Image{} 之前对镜像名称进行一些操作,
	// 比如通过 Reference{},我们可以获取镜像的 注册中心、名称 等等信息,
	// 假如 ParseReference() 的参数是通过外部变量传递进来的,那么在实例化 Image{} 之前,我们可以先分析一下镜像的名称,对其进行过滤。
	ref, _ := name.ParseReference("nginx")

	// 通过镜像的引用实例化 Image{}
	img, _ := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))

	// 通过实例化的镜像控制镜像,这里是获取镜像所占容量的大小
	imageSize, _ := img.Size()
	fmt.Println(imageSize)
}

v1.Image

实现了该接口的结构体:

// Image  定义了与 OCI Image 交互的接口
type Image interface {
  // 返回了当前镜像的所有层级, 最老/最基础的层在数组的前面,最上面/最新的层在数组的后面
  Layers() ([]Layer, error)

  // 返回当前 image 的 MediaType
  MediaType() (types.MediaType, error)

  // 返回这个 Image manifest 的大小
  Size() (int64, error)

  // 返回这个镜像 ConfigFile 的hash值,也是这个镜像的 ImageID
  ConfigName() (Hash, error)

  // 返回这个镜像的 ConfigFile
  ConfigFile() (*ConfigFile, error)

  // 返回这个镜像的 ConfigFile 的字节数组
  RawConfigFile() ([]byte, error)

  // 返回这个Image Manifest 的sha256 值
  Digest() (Hash, error)

  // 返回这个Image Manifest
  Manifest() (*Manifest, error)

  // 返回 ImageManifest 的bytes数组
  RawManifest() ([]byte, error)

  // 返回这个镜像中的某一层layer, 根据 digest(压缩后的hash值) 来查找
  LayerByDigest(Hash) (Layer, error)

  // 返回这个镜像中的某一层layer, 根据 diffid (未压缩的hash值) 来查找
  LayerByDiffID(Hash) (Layer, error)
}

v1.ImageIndex

实现了该接口的结构体:

// ImageIndex 定义与 OCI ImageIndex 交互的接口
type ImageIndex interface {
  // 返回当前 imageIndex 的 MediaType
  MediaType() (types.MediaType, error)

  // 返回这个 ImageIndex manifest 的 sha256值。
  Digest() (Hash, error)

  // 返回这个 ImageIndex manifest 的大小
  Size() (int64, error)

  // 返回这个 ImageIndex 的 manifest 结构
  IndexManifest() (*IndexManifest, error)

  // 返回这个 ImageIndex 的 manifest 字节数组
  RawManifest() ([]byte, error)

  // 返回这个 ImageIndex 引用的 Image
  Image(Hash) (Image, error)

  // 返回这个 ImageIndex 引用的 ImageIndex
  ImageIndex(Hash) (ImageIndex, error)
}

v1.Layer

实现了该接口的结构体:

// Layer 定义了访问 OCI Image 特定 Layer 的接口
type Layer interface {
  // 返回了压缩后的layer的sha256 值
  Digest() (Hash, error)

  // 返回了 未压缩的layer 的sha256值.
  DiffID() (Hash, error)

  // 返回了压缩后的镜像层
  Compressed() (io.ReadCloser, error)

  // 返回了未压缩的镜像层
  Uncompressed() (io.ReadCloser, error)

  // 返回了压缩后镜像层的大小
  Size() (int64, error)

  // 返回当前 layer 的 MediaType
  MediaType() (types.MediaType, error)
}

最后修改 May 11, 2024: clearup (8e6d575b)