Go image registry


0. 前言

OpenShift image registry 概述 介绍了 OpenShift 平台上 registry 的基本结构。进一步地,本文将介绍在 Kubernetes 平台上,如何使用 Go 实现 image 的 push 操作。

1. 本地 CLI push image

在本地将 image tar 包(image 的静态文件形式) push 到 registry:

// 将 repo image ubuntu 存储到本地
[root@chunqiu tarball]# docker images | grep ubuntu
ubuntu                                                                         latest        ba6acccedd29   2 months ago    72.8MB
[root@chunqiu tarball]# docker save ubuntu -o ubuntu:latest.tar
[root@chunqiu tarball]# ls
ubuntu:latest.tar

// 删除仓库 image,重新上传本地 tar 到 repo
[root@chunqiu tarball]# docker rmi ubuntu
Untagged: ubuntu:latest
Untagged: ubuntu@sha256:626ffe58f6e7566e00254b638eb7e0f3b11d4da9675088f4781a50ae288f3322
Deleted: sha256:ba6acccedd2923aee4c2acc6a23780b14ed4b8a5fa4e14e252a23b846df9b6c1
Deleted: sha256:9f54eef412758095c8079ac465d494a2872e02e90bf1fb5f12a1641c0d1bb78b

[root@chunqiu tarball]# docker images | grep ubuntu

[root@chunqiu tarball]# docker load -i ubuntu\:latest.tar
9f54eef41275: Loading layer [==================================================>]  75.16MB/75.16MB
Loaded image: ubuntu:latest

[root@chunqiu tarball]# docker images | grep ubuntu
ubuntu                                                                         latest         ba6acccedd29   2 months ago    72.8MB

// 将 repo 的 image push 到 registry
// 提示 push 失败,是因为并未在 docker registry 认证
[root@chunqiu tarball]# docker push ubuntu:latest
The push refers to repository [docker.io/library/ubuntu]
9f54eef41275: Layer already exists
errors:
denied: requested access to the resource is denied
unauthorized: authentication required

注意:

  1. 不能直接将 tar 包 push 到 registry:

    [root@chunqiu tarball]# docker push ubuntu:latest.tar
    The push refers to repository [docker.io/library/ubuntu]
    An image does not exist locally with the tag: ubuntu
    
  2. 关于 registry,repo 的关系可看这段解释:

    Registries, Repositories, Images, and Tags
    
    There is a hierarchical system for storing images. The following terminology is used:
    
    Registry: A service responsible for hosting and distributing images. The default registry is the Docker Hub.
    
    Repository: A collection of related images (usually providing different versions of the same application or service).
    

2. Go push image

过了上节的流程,再看 Go 中是怎么 push image 到 repo 的。

2.1 tarball

首先用 tarball 将本地 tar 包转为 v1.Image,然后对 v1.Image 进行操作,这里举个例子,将本地 tar 包重新转换成不同 tag:

func main() {
        tag, err := name.NewTag("ubuntu")
        if err != nil {
            panic(err)
        }

        downloadPath := fmt.Sprintf("/home/hxia/vsWorkspace/studyGo/lib/tarball/ubuntu:latest.tar")
        img, err := tarball.ImageFromPath(downloadPath, &tag)
        if err != nil {
            panic(err)
        }

        // Write that tarball with a different tag.
        newTag, err := name.NewTag("ubuntu:newest")
        if err != nil {
            panic(err)
        }
        f, err := os.Create("chunqiu")
        if err != nil {
            panic(err)
        }
        defer f.Close()

        if err := tarball.Write(newTag, img, f); err != nil {
            panic(err)
        }
}

运行上述代码,发现本地路径生成名为 chunqiu 的 tar 包,将该包 load 到 repo 发现 tag 转换为 newest:

[root@chunqiu tarball]# go run main.go
[root@chunqiu tarball]# ls
chunqiu  go.mod  go.sum  main.go  ubuntu:latest.tar

[root@chunqiu tarball]# docker load -i chunqiu
9f54eef41275: Loading layer [==================================================>]   31.8MB/31.8MB
Loaded image: ubuntu:newest

[root@chunqiu tarball]# docker images | grep ubuntu
ubuntu                                                                         newest        ba6acccedd29   2 months ago    72.8MB

2.2 remote push image

将 image push 到远端 registry,需要用到 go-containerregistry ,它是对容器 registries 处理的 golang 包实现。

代码实现如下:

import (
    "github.com/google/go-containerregistry/pkg/name"
    "github.com/google/go-containerregistry/pkg/v1/remote"
    "github.com/google/go-containerregistry/pkg/v1/remote/transport"
)

ref, err := name.ParseReference(src, name.StrictValidation)
if err != nil {
    return err
}

options := make([]remote.Option, 0)
options = append(options, remote.WithAuthFromKeychain(authn.DefaultKeychain))

if auth != nil {
    options = append(options, remote.WithAuth(auth))
}

trs, err := transport.New(ref.Context().Registry, auth, &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, nil)
if err != nil {
    return err
}
options = append(options, remote.WithTransport(trs))

return remote.Write(ref, img, options...)

这里省去了部分代码实现,重点关注:

  1. name.ParseReference 对 src(registry+RepoTag) 进行解析,并且以格式 name.StrictValidation 方式解析;
  2. remote.WithAuthFromKeychain 定义 client 和远端 registry 交互的认证方式。其中,认证方式添加到 remote.Option 作为 remote.Write 的入参实现认证;
  3. remote.Write 需要搭配 transport 包处理 the authentication handshake and structured errors.

2.3 附录

应用层上调用各种框架实现功能是在学,在用。这不是核心,要抓核心,抓主要矛盾,核心在于内部实现,过程,原理以及模式。image 数据是怎么传到 remote 的,中间经历了什么,代码是怎么实现的,本地 client 和远端是怎么交互的,这是内核,是要深挖的东西。这里只讲用,后续会深挖这方面内容,敬请期待。