公众号关注 「奇妙的 Linux 世界」

设为「星标」,每天带你玩转 Linux !

e593698450461528e63dc53815d89f8c.png

1. 基础介绍

描述: 作为公司内部 PaaS toB 产品的打包发布人员,容器镜像对我们打工人而言就像是工地上的砖头 🧱,而我的一部分工作就是将这些砖头在各个仓库之间搬来搬去,最终将这些砖头打包放在产品的安装包中,形成一个完整的 PaaS 产品安装包。

  • Q: 在 PaaS (平台即服务)中的大家常说的 ToB 与 ToC 到底是什么?

ToC 面向普通用户服务, 主要是让用户体验感好,解决用户使用方面的问题记录,并返回给前后端开发。ToB 是面向企业用户服务, 产品可用、其中最关键是让Boss使用Happly!

  • Q: 假如有如下场景,我们从 dockerhub 公共仓库中下载一个 GB 以上的镜像,到本地的私有仓库中,我想通常你会这样做先 docker pull 到本地,然后使用 docker tag 更改为私有仓库地址加上镜像名称版本,最后再使用docker push 上传镜像到私有仓库中,以供其它内网机器拉取并使用。虽然该方法是可行,但是如果有多个大于 GB 以上的镜像需要上传到私有仓库,每次都要先解压 layer 到本地,然后再压缩 layer 上传到私有仓库中,你能想象此过程花费的时间有多久吗? 对于我们运维工程师来说时间就是金钱,所以需想尽一切方法来节约时间成本,那有没有一种办法可以直接将 registry 上的 blob 复制到另一个 registry,中间过程不涉及对镜像 layer 的解压缩,这岂不美哉。

解决方案当然是存在的,如果你不想使用docker进行images镜像拉取上传,我们完成可以使用skope工具来完全替代 docker-cli 来搬运镜像,skopeo是一个命令行实用程序,可对容器映像和映像存储库执行各种操作。

什么是 Skopeo ?

skopeo 使用 API V2 Registry,例如 Docker Registry、Atomic Registry、私有Registry、本地目录和本地 OCI 镜像目录。skopeo 不需要运行守护进程,它可以执行的操作包括:

  • 通过各种存储机制复制镜像,例如,可以在不需要特权的情况下将镜像从一个 Registry 复制到另一个 Registry

  • 检测远程镜像并查看其属性,包括其图层,无需将镜像拉到本地

  • 从镜像库中删除镜像

  • 当存储库需要时,skopeo 可以传递适当的凭据和证书进行身份验证

镜像存储特点

根据 Robin 大佬在 《镜像仓库中镜像存储的原理解析》文章里得出的结论:

  • 通过 Registry API 获得的两个镜像仓库中相同镜像的 manifest 信息完全相同。

  • 两个镜像仓库中相同镜像的 manifest 信息的存储路径和内容完全相同。

  • 两个镜像仓库中相同镜像的 blob 信息的存储路径和内容完全相同

项目信息

  • Github 官方地址: https://github.com/containers/skopeo

  • Gitee mirror: https://gitee.com/mirrors/skopeo

2. 安装编译

描述: Skopeo 官方安装&编译方式参考文档: https://github.com/containers/skopeo/blob/main/install.md

本节安装实践环境将在 Ubuntu 20.04 LTS 以及 docker 20.10.12 中进行实践源码编译以及 apt 仓库源下载安装实践。

2.1 源码编译(静态)

描述: 要构建 skopeo 二进制文件您至少需要 Go 1.12 版本以上, 其次构建 skopeo 有两种方法,即在容器中或者在本地环境中构建(安装环境较为复杂), 此处为了方便演示将采用容器方式进行编译构建。

# 1.拉取skopeo源码到本地
$ git clone --depth=1 https://github.com/containers/skopeo.git  # https://github.com/containers/skopeo.git
$ cd skopeo
$ sed -i 's#proxy.golang.org#https://goproxy.cn#g' skopeo/Makefile

# 2.下载镜像构建依赖
$ sudo apt-get install go-md2man  # 构建手册依赖于 go-md2man。
$ whereis go-md2man  # 获得本机中go-md2man路径。

# 3.构建静态二进制文件
$ BUILD_IMAGE="golang:latest"
$ docker run --name skopeo-build -v $PWD:/src -v /usr/bin/go-md2man:/go/bin/go-md2man -w /src -e CGO_ENABLED=0 -e GOPROXY=https://goproxy.cn,direct ${BUILD_IMAGE} \
sh -c 'make BUILDTAGS=containers_image_openpgp GO_DYN_FLAGS='
  # CGO_CFLAGS="" CGO_LDFLAGS="" GO111MODULE=on go build -mod=vendor  -ldflags '-X main.gitCommit=df4d82b960572c19e9333381a203c0ac475766d7 ' -gcflags "" -tags  "containers_image_openpgp" -o bin/skopeo ./cmd/skopeo

# 4.运行编译生成的skopeo可执行文件
$ cd ./bin # /opt/software/skopeo/bin
$ ./skopeo --help
  # Various operations with container images and container image registries
  # .......
  # Use "skopeo [command] --help" for more information about a command.

构建关键参数解析:

  • CGO_ENABLED=0 : 设置该环境变量, 禁用 CGO 会导致 Go 在可能的情况下更喜欢静态连接库,而不是动态链接到系统库 (解决可以在Ubuntu或者其它linux发行版中执行编译后二进制文件)。

  • GOPROXY=https://goproxy.cn,direct : Golong 依赖下载镜像站,加快go get依赖拉拉取。

  • BUILDTAGS=containers_image_openpgp : 设置该make参数消除了对libgpgme 及其配套库的依赖, Skopeo 的一些特性依赖于非 Go 库,例如 libgpgme 和 libdevmapper。

  • GO_DYN_FLAGS= : 清空该make参数 (否则会强制创建动态可执行文件)

2.2 分发包安装

描述: skopeo 可能已经打包在您的发行版中,此处以 ubuntu 20.04 为例进行安装。

# 1.只支持 Ubuntu 20.10 and newer 发行版 
$ sudo apt-get -y update
$ sudo apt-get -y install skopeo

# 2.但 Kubic 项目为 Ubuntu 20.04 提供了软件包,我们可以通过如下方式在我们及其上进行安装。
$ . /etc/os-release
$ echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/ /" | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
$ curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_${VERSION_ID}/Release.key | sudo apt-key add -
$ sudo apt-get update
$ sudo apt-get -y upgrade
$ sudo apt-get -y install skopeo

2.3 容器安装运行

Skopeo 容器镜像可在 quay.io/skopeo/stable:latest 获得, 例如我们采用podman命令进行如下操作:

$ podman run docker://quay.io/skopeo/stable:latest copy --help

3. 快速上手

3.1 命令浅析

描述: skopen 是操作各种容器映像和容器映像仓库的工具,其使用方法及其可用命令如下:

$ ./skopeo --help    # 子命令可采用如下命令 skopeo [command] --help 命令
Usage:
  skopeo [flags]
  skopeo [command]
Available Commands: 
  copy          # 复制一个镜像从 A 到 B,这里的 A 和 B 可以为本地 docker 镜像或者 registry 上的镜像;
  delete        # 删除一个镜像 tag,可以是本地 docker 镜像或者 registry 上的镜像;
  help          # 帮助查看
  inspect       # 查看一个镜像的 manifest 或者 image config 详细信息;
  list-tags     # 列出存储库名称指定的镜像的tag
  login           # 登陆某个镜像仓库,类似于 docker login 命令
  logout          # 退出某个已认证的镜像仓库, 类似于 docker logout 命令
  manifest-digest # 计算文件的清单摘要是一个sha256sum 值
  standalone-sign   # 使用本地文件创建签名
  standalone-verify # 验证本地文件的签名
  sync              # 将一个或多个图像从一个位置同步到另一个位置 (该功能非常Nice)
Flags:
    --command-timeout duration   # 命令超时时间(单位秒)
    --debug                      # 启用debug模式
    --insecure-policy            # 在不进行任何策略检查的情况下运行该工具(如果没有配置 policy 的话需要加上该参数)
    --override-arch ARCH         # 处理镜像时覆盖客户端 CPU 体系架构,如在 amd64 的机器上用 skopeo 处理 arm64 的镜像
    --override-os OS             # 处理镜像时覆盖客户端 OS
    --override-variant VARIANT   # 处理镜像时使用VARIANT而不是运行架构变量
    --policy string              # 信任策略文件的路径 (为镜像配置安全策略情况下使用)
    --registries.d DIR           # 在目录中使用Registry配置文件(例如,用于容器签名存储)
    --tmpdir string              # 用于存储临时文件的目录
-h, --help                       help for skopeo 
-v, --version                    Version for Skopeo

3.2 Skopeo 初体验

描述: 在使用体验skopeo之前,我们需要了解一哈 Skopeo 可以在那些图像和存储库类型上执行镜像操作(官网文档走一波):

Repository typesDescribeExample
containers-storage:docker-reference适用于后端是 Podman, CRI-O, Buildah 的情况containers-storage:
dir:path适用于将manifest, layer tarballs 和 signatures 存储为单独文件的现有本地目录路径的情况dir:/tmp/alpine:latest
docker://docker-reference适用于Registry中实现"Docker Registry HTTP API V2"的镜像的情况docker://harbor.weiyigeek.top/myblog:v2.8
docker-archive:path[:docker-reference]适用于采用docker save命令导出镜像以tar格式存储的文件的情况docker-archive:alpine.tar
docker-daemon:docker-reference适用于存储在 docker 守护进程内部存储中的图像的情况docker-daemon:alpine:latest
oci:path:tag适用于符合"Open Container Image Layout Specification"的目录中的图像标记oci:alpine:latest

温馨提示: 同一个镜像存在的方式有可能不同,不同类型方式存储对镜像的 layer 处理的方式也不一样,。

测试环境说明

Docker 官方 hub 仓库 -> docker.io             # 官网地址: https://hub.docker.com/
私有 Harbor 仓库     -> harbor.weiyigeek.top
临时创建的本地仓库    -> 192.168.12.111:5000   # 一梭子解决: docker run -d -p 5000:5000 --name registry -v /opt/data/registry:/var/lib/registry registry:2

说明: 上述仓库都是在Registry中支持Docker Registry HTTP API V2版本的。

3.2.1 Skopeo login/loout - 远程仓库 Auth

描述: 在使用 skopeo 前如果 src 或 dest 镜像是在 registry 仓库中的并且配置了非 public 的镜像需要相应的 auth 认证, 此时我们可以使用 docker login 或者 skopeo login 的方式登录到 registry 仓库,然后默认会在~/.docker目录下生成 registry 登录配置文件 config.json ,该文件里保存了登录需要的验证信息,skopeo 拿到该验证信息才有权限往 registry push 镜像。

  • 登陆认证

# (1) skopeo login 登陆示例 (两种方式)
$ skopeo login -u WeiyiGeek -p testpassword harbor.weiyigeek.top
  # Login Succeeded!

# (2) docker login 登陆示例
$ docker login -u WeiyiGeek docker.io
$ docker login -u WeiyiGeek harbor.weiyigeek.top
$ docker login -u anonymous -p anonymous 192.168.12.111:5000  # 实际上临时仓库没有配置认证, 账号密码随意即可。
  # WARNING! Using --password via the CLI is insecure. Use --password-stdin.
  # WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
  # Configure a credential helper to remove this warning. See
  # https://docs.docker.com/engine/reference/commandline/login/#credentials-store
  # Login Succeeded

# (3) docker login 生成的 registry 登录配置文件(base64编码安全性不多说)
$ cat ~/.docker/config.json
{
  "auths": {
    "192.168.12.111:5000": {
        "auth": "YW5v*******Q=="
      },
    "harbor.weiyigeek.top": {
        "auth": "YWR*******LkA="
      },
    "https://index.docker.io/v1/": {
        "auth": "d2Vp**************kyZA=="
    }
  }
}
  • 注销认证

$ skopeo logout myregistrydomain.com:5000

温馨提示: 如果企业自建harbor仓库(一般都会设置自签证书)或者其它私有仓库配置证书,为了防止出错建议进行以下操作(正式环境请根据需要进行配置)。

# (1) 在 /etc/docker/daemon.json 中配置 insecure-registries 字段,表示允许不安全的仓库。
"insecure-registries": ["harbor.weiyigeek.top","192.168.12.111:5000"]
 
# (2) 从官方文档可知客户端要使用tls与Harbor通信使用的还是`自签证书`,那么必须建立一个目录:`/etc/docker/certs.d`
# 如果配置可能会出现 x509: certificate signed by unknown authority 错误提示。
$ mkdir -vp /etc/docker/certs.d/harbor.weiyigeek.top
$ cp -a /deployapp/harbor/harbor.pem  /etc/docker/certs.d/harbor.weiyigeek.top/harbor.crt

温馨提示: 为了防止后续执行 skopeo 命令操作镜像时出错, 建议忽略 policy 策略和证书校验参数如下:

--insecure-policy \
--src-tls-verify=false \ 
--dest-tls-verify=false \
3.2.2 Skopeo inspect - 检查存储库中的镜像

描述: skopeo 能够检查容器 Registry 上的存储库并获取图像层。检查命令获取存储库的清单,它能够向您显示有关整个存储库或标签的类似 docker inspect 的 json 输出。与 docker inspect 相比,此工具可帮助您在拉取存储库或标签之前收集有用的信息(使用磁盘空间), 检查命令可以向您显示给定存储库可用的标签、映像具有的标签、映像的创建日期和操作系统等。

支持传输的类型 : containers-storage, dir, docker, docker-archive, docker-daemon, oci, oci-archive, ostree, tarball

  • 步骤 01. 显示 busybox:latest 镜像的属性相关信息。

$ skopeo inspect docker://docker.io/busybox:latest
{
  "Name": "docker.io/library/busybox",
  "Digest": "sha256:5acba83a746c7608ed544dc1533b87c737a0b0fb730301639a0179f9344b1678",
  "RepoTags": [
      "1",
      "1-glibc",
      "1-musl",
      "1-ubuntu",
      "1-uclibc",
      "1.21-ubuntu",
      "1.21.0-ubuntu",
        .......          # 镜像历史tags
      "unstable-uclibc"
  ],
  "Created": "2021-12-30T19:19:41.006954958Z",
  "DockerVersion": "20.10.7",
  "Labels": null,
  "Architecture": "amd64",
  "Os": "linux",
  "Layers": [
      "sha256:5cc84ad355aaa64f46ea9c7bbcc319a9d808ab15088a27209c9e70ef86e5a2aa"
  ],
  "Env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  ]
}
  • 步骤 02. 显示 busybox:latest 镜像的容器配置相关信息。

$ skopeo inspect --config docker://docker.io/busybox:latest  | jq
{
  "created": "2021-12-30T19:19:41.006954958Z",
  "architecture": "amd64",
  "os": "linux",
  "config": {
    "Env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
    ],
    "Cmd": [
      "sh"
    ]
  },
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:01fd6df81c8ec7dd24bbbd72342671f41813f992999a3471b9d9cbc44ad88374"
    ]
  },
  "history": [
    {
      "created": "2021-12-30T19:19:40.833034683Z",
      "created_by": "/bin/sh -c #(nop) ADD file:6db446a57cbd2b7f4cfde1f280177b458390ed5a6d1b54c6169522bc2c4d838e in / "
    },
    {
      "created": "2021-12-30T19:19:41.006954958Z",
      "created_by": "/bin/sh -c #(nop)  CMD [\"sh\"]",
      "empty_layer": true
    }
  ]
}
  • 步骤 03. 显示未经验证的图像 Digest(摘要)

$ skopeo inspect --format "Name: {{.Name}} Digest: {{.Digest}}" docker://docker.io/busybox:latest
3.2.3 Skopeo copy - 仓库镜像拷贝

描述: skopeo 可以在各种存储机制之间复制容器镜像,支持包括容器仓库(The Quay, Docker Hub, OpenShift, GCR, ,Artifactory ...)以及容器存储后端 (Podman, CRI-O, Docker) 等、本地目录、本地 OCI-layout 目录。

例如,此处我从 hub 仓库复制 busybox:latest 镜像到私有 harbor 仓库中,在从私有 harbor 仓库中拷贝到本地指定目录中。

  • 步骤 01. 从 regsitry A 到 registry B 复制 busybox:latest 镜像。

$ skopeo copy --insecure-policy --src-tls-verify=false --dest-tls-verify=false --dest-authfile /root/.docker/config.json docker://docker.io/busybox:latest docker://harbor.weiyigeek.top/devops/busybox:latest
  # Getting image source signatures
  # Copying blob 5cc84ad355aa done
  # Copying config beae173cca done
  # Writing manifest to image destination
  # Storing signatures

Tips: 由上述日志可以看到 skopeo 是直接从 registry 中 copy 镜像 layer 的 blob 文件,传输是镜像在 registry 中存储的原始格式。

  • 步骤 02. 从 registry B 复制 busybox:latest 镜像到本地 busybox:latest 目录中。

$ skopeo copy --insecure-policy --src-tls-verify=false docker://harbor.weiyigeek.top/devops/busybox:latest dir:busybox:latest
# Getting image source signatures
# Copying blob 5cc84ad355aa done
# Copying config beae173cca done
# Writing manifest to image destination
# Storing signatures

$ ls && tree busybox\:latest/
busybox:latest/
├── 5cc84ad355aaa64f46ea9c7bbcc319a9d808ab15088a27209c9e70ef86e5a2aa   # blob 块文件 -> vnd.docker.image.rootfs.diff.tar.gzip
├── beae173ccac6ad749f76713cf4440fe3d21d1043fe616dfbe30775815d1d0f6a   # 镜像配置信息文件 -> vnd.docker.container.image.v1+json 
├── manifest.json
└── version
0 directories, 4 files

# 查看镜像的 manifest 文件
$ cat busybox\:latest/manifest.json
{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
   "config": {
      "mediaType": "application/vnd.docker.container.image.v1+json",
      "size": 1456,
      "digest": "sha256:beae173ccac6ad749f76713cf4440fe3d21d1043fe616dfbe30775815d1d0f6a"
   },
   "layers": [
      {
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": 772788,
         "digest": "sha256:5cc84ad355aaa64f46ea9c7bbcc319a9d808ab15088a27209c9e70ef86e5a2aa"
      }
   ]
}

# 根据 manifest 文件查看镜像的 image config 文件 (存放镜像Build指令与镜像相关配置信息)
$ jq '.' busybox\:latest/beae173ccac6ad749f76713cf4440fe3d21d1043fe616dfbe30775815d1d0f6a
{
  "architecture": "amd64",
  "config": {
    "Hostname": "",
    "Domainname": "",
    "User": "",
    "AttachStdin": false,
    "AttachStdout": false,
    "AttachStderr": false,
    "Tty": false,
    "OpenStdin": false,
    "StdinOnce": false,
    "Env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
    ],
    "Cmd": [
      "sh"
    ],
    "Image": "sha256:da658412c37aa24e561eb7e16c61bc82a9711340d8fb5cf1a8f39d8e96d7f723",
    "Volumes": null,
    "WorkingDir": "",
    "Entrypoint": null,
    "OnBuild": null,
    "Labels": null
  },
........
  "history": [
    {
      "created": "2021-12-30T19:19:40.833034683Z",
      "created_by": "/bin/sh -c #(nop) ADD file:6db446a57cbd2b7f4cfde1f280177b458390ed5a6d1b54c6169522bc2c4d838e in / "
    },
    {
      "created": "2021-12-30T19:19:41.006954958Z",
      "created_by": "/bin/sh -c #(nop)  CMD [\"sh\"]",
      "empty_layer": true
    }
  ],
  "os": "linux",
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:01fd6df81c8ec7dd24bbbd72342671f41813f992999a3471b9d9cbc44ad88374"
    ]
  }

}
  • 步骤 03. 将 busybox:latest 镜像从 registry B 复制到本地目录,并以 OCI 格式保存

$ skopeo copy --insecure-policy --src-tls-verify=false docker://harbor.weiyigeek.top/devops/busybox:latest oci:busybox-latest
  # Getting image source signatures
  # Copying blob 5cc84ad355aa done
  # Copying config 48edd9298a done
  # Writing manifest to image destination
  # Storing signatures

$ tree -h busybox-latest/
  # busybox-latest/
  # ├── [4.0K]  blobs
  # │   └── [4.0K]  sha256
  # │       ├── [ 347]  1612e16ff3f6b0d09eefdc4e9d5c5c0624f63032743e016585b095b958778016
  # │       ├── [ 575]  48edd9298a25de2c97cd574a5523026f87576c6b7202330a2b60ce7d304ec307
  # │       └── [755K]  5cc84ad355aaa64f46ea9c7bbcc319a9d808ab15088a27209c9e70ef86e5a2aa  # Blob 块 -
  # ├── [ 186]  index.json
  # └── [  31]  oci-layout
  # 2 directories, 5 files
  • 步骤 04. 将 alpine:3.13.1 镜像从 docker 本地存储( /var/lib/docker/image) push 到 registry B中(实际上替代 docker push 功能)

# 在  /var/lib/docker/ 目录中此处主要关心 image (主要存放镜像中layer层的元数据) 和 overlay2 (各层的具体信息)
$ docker images alpine:3.13.1
  # REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
  # alpine       3.13.1    e50c909a8df2   11 months ago   5.61MB

$ skopeo copy --insecure-policy --dest-tls-verify=false --dest-authfile /root/.docker/config.json docker-daemon:alpine:3.13.1 docker://harbor.weiyigeek.top/devops/alpine:3.13.1
  # Getting image source signatures
  # Copying blob 1119ff37d4a9 done
  # Copying config e50c909a8d done
  # Writing manifest to image destination
  # Storing signatures
9c8054582f4bb2be9e76f079060d57d0.png
3.2.4 Skopeo sync - 镜像同步命令

描述: Skopeo sync可以在容器仓库和本地目录之间同步镜像,其功能类似于阿里云的 image-syncer (https://github.com/AliyunContainerService/image-syncer) 工具, 实际上其比 image-syncer 更强大、灵活性更强一些,废话不多说实践为王。

skopeo sync 镜像同步文件示例:

  • 步骤 01. 将仓库中所有 busybox 镜像版本同步到本地目录。

$ skopeo sync --insecure-policy --src-tls-verify=false --src docker --dest dir harbor.weiyigeek.top/devops/busybox /tmp
  # INFO[0000] Tag presence check                            imagename=harbor.weiyigeek.top/devops/busybox tagged=false
  # INFO[0000] Getting tags                                  image=harbor.weiyigeek.top/devops/busybox
  # INFO[0000] Copying image ref 1/1                         from="docker://harbor.weiyigeek.top/devops/busybox:latest" to="dir:/tmp/busybox:latest"
  # Getting image source signatures
  # Copying blob 5cc84ad355aa done
  # Copying config beae173cca done
  # Writing manifest to image destination
  # Storing signatures
  # INFO[0000] Synced 1 images from 1 sources

$ tree -h /tmp/busybox:latest
/tmp/busybox:latest
├── [755K]  5cc84ad355aaa64f46ea9c7bbcc319a9d808ab15088a27209c9e70ef86e5a2aa
├── [1.4K]  beae173ccac6ad749f76713cf4440fe3d21d1043fe616dfbe30775815d1d0f6a
├── [ 527]  manifest.json
└── [  33]  version
0 directories, 4 files
  • 步骤 02. 从本地目录 /tmp/ 同步到 docker 的 hub 容器仓库中,此外我们可以通过浏览器看到 weiyigeek 用户下的 busybox 镜像 (https://hub.docker.com/u/weiyigeek)。

$ skopeo sync --insecure-policy --dest-tls-verify=false --src dir --dest docker /tmp weiyigeek
  # INFO[0000] Copying image ref 1/1                         from="dir:/tmp/busybox:latest" to="docker://weiyigeek/busybox:latest"
  # Getting image source signatures
  # Copying blob 5cc84ad355aa skipped: already exists
  # Copying config beae173cca done
  # Writing manifest to image destination
  # Storing signatures
  # INFO[0021] Synced 1 images from 1 sources
159983751698beb051f3cceedaa4c97d.png
  • 步骤 03. 从 hub 容器仓库中同步 alpine-jenkins-jnlp:v2.285 镜像到本地临时容器仓库中。

$ skopeo sync --insecure-policy --src-tls-verify=false --dest-tls-verify=false --src docker --dest docker weiyigeek/alpine-jenkins-jnlp:v2.285 192.168.12.111:5000/
  # INFO[0000] Tag presence check imagename="weiyigeek/alpine-jenkins-jnlp:v2.285" tagged=true
  # INFO[0000] Copying image ref 1/1 from="docker://weiyigeek/alpine-jenkins-jnlp:v2.285" to="docker://192.168.12.111:5000/alpine-jenkins-jnlp:v2.285"
  # Getting image source signatures
  # Copying blob 68517a8c32d3 [======>-------------------------------] 45.0MiB / 255.7MiB
  # Copying blob 4c0d98bf9879 done
  • 步骤 04. 以配置文件方式进行同步, 首先我们需要准备一个需要同步的资源清单。

# YAML 文件内容(用于 **--src yaml** 的源)
$ cat <<'EOF' > skopeo-sync.yml
registry.example.com:
  images:
    busybox: []
    redis:
      - "1.0"
      - "2.0"
      - "sha256:111111"
  images-by-tag-regex:
      nginx: ^1\.13\.[12]-alpine-perl$
  credentials:
      username: john
      password: this is a secret
  tls-verify: true
  cert-dir: /home/john/certs
quay.io:
  tls-verify: false
  images:
    coreos/etcd:
      - latest
EOF

# 以yaml文件方式进行同步镜像到 my-registry.local.lan/repo/ 仓库中
$ skopeo sync --src yaml --dest docker skopeo-sync.yml my-registry.local.lan/repo/

skopeo-sync.yml 文件中镜像匹配复制镜像说明:

  • registry.example.com/busybox : 所有版本的镜像.

  • registry.example.com/redis : 标记为“1.0”和“2.0”的图像以及带有摘要的图像"sha256:0000000000000000000000000000000011111111111111111111111111111111".

  • registry.example.com/nginx : 图片标记为“1.13.1-alpine-perl”和“1.13.2-alpine-perl”.

  • quay.io/coreos/etcd : 拉取最新版本的镜像。

3.2.5 Skopeo list-tags - 仓库中镜像 Tag 查看

描述: 利用该命令我们可以列出 registry 上的某个镜像的所有 tag ,它是使用标准的 registry API 来获取镜像 tag。

简单示例:

$ skopeo list-tags docker://harbor.weiyigeek.top/devops/busybox:latest
3.2.6 Skopeo delete - 删除仓库中镜像 Tag

描述: 使用该命令我们可以删除镜像 Tag,注意此处仅仅只是通过 registry API 来删除镜像的 tag(即删除了 tag 对 manifests 文件的引用)并非真正将镜像删除掉,如果想要删除镜像的 layer 还是需要通过 registry GC 的方式。

# 方式1. 利用 skopeo delete
$ skopeo delete docker://harbor.weiyigeek.top/devops/busybox:latest --debug
  # DEBU[0000] Loading registries configuration "/etc/containers/registries.conf"
  # DEBU[0000] Found credentials for harbor.weiyigeek.top in credential helper containers-auth.json in file /home/weiyigeek/.docker/config.json
  # DEBU[0000] Using registries.d directory /etc/containers/registries.d for sigstore configuration
  # DEBU[0000]  No signature storage configuration found for harbor.weiyigeek.top/devops/busybox:latest, using built-in default file:///home/weiyigeek/.local/share/containers/sigstore
  # DEBU[0000] Looking for TLS certificates and private keys in /etc/docker/certs.d/harbor.weiyigeek.top
  # DEBU[0000]  crt: /etc/docker/certs.d/harbor.weiyigeek.top/harbor.crt
  # DEBU[0000] GET https://harbor.weiyigeek.top/v2/
  # DEBU[0000] Ping https://harbor.weiyigeek.top/v2/ status 401
  # DEBU[0000] GET https://harbor.weiyigeek.top/service/token?account=WeiyiGeek&scope=repository%3Adevops%2Fbusybox%3A%2A&service=harbor-registry
  # DEBU[0000] GET https://harbor.weiyigeek.top/v2/devops/busybox/manifests/latest
  # DEBU[0000] DELETE https://harbor.weiyigeek.top/v2/devops/busybox/manifests/sha256:62ffc2ed7554e4c6d360bce40bbcf196573dd27c4ce080641a2c59867e732dee
  # DEBU[0000] Deleting /home/weiyigeek/.local/share/containers/sigstore/devops/busybox@sha256=62ffc2ed7554e4c6d360bce40bbcf196573dd27c4ce080641a2c59867e732dee/signature-1

# 方式2.利用 curl 命令进行 registery 进行删除 Tag。
$ curl --header "Accept: application/vnd.docker.distribution.manifest.v2+json" -I -X GET http://192.168.12.111:5000/v2/busybox/manifests/latest
  # HTTP/1.1 200 OK
  # Content-Length: 527
  # Content-Type: application/vnd.docker.distribution.manifest.v2+json
  # Docker-Content-Digest: sha256:62ffc2ed7554e4c6d360bce40bbcf196573dd27c4ce080641a2c59867e732dee
  # Docker-Distribution-Api-Version: registry/2.0
  # Etag: "sha256:62ffc2ed7554e4c6d360bce40bbcf196573dd27c4ce080641a2c59867e732dee"
  # X-Content-Type-Options: nosniff
  # Date: Thu, 20 Jan 2022 13:18:28 GMT

# 一把梭织搞定
$ Docker-Content-Digest=$(curl -s --header "Accept: application/vnd.docker.distribution.manifest.v2+json" -I -X GET http://192.168.12.111:5000/v2/busybox/manifests/latest | grep "Docker-Content-Digest" | cut -d ' ' -f 2)
$ curl -I -X DELETE http://192.168.12.111:5000/v2/busybox/manifests/${Docker-Content-Digest}

4. 镜像同步最佳实践

本节,主要参考我前同事木子.其博客地址(https://blog.k8s.li)。

4.1 指定文本中镜像同步

假如,给你一个镜像列表 images-list.txt, 其格式如下, 我们可以直接采用 shell 脚本调用 skopeo 进行执行。

# images-list.txt
cat <<'EOF' > images-list.txt
kubesphere/kube-apiserver:v1.20.6
kubesphere/kube-scheduler:v1.20.6
kubesphere/kube-proxy:v1.20.6
kubesphere/kube-controller-manager:v1.20.6
kubesphere/kube-apiserver:v1.19.8
EOF
  • 同步的 shell 脚本 skopeo-copy.sh

#!/bin/bash
GREEN_COL="\\033[32;1m"
RED_COL="\\033[1;31m"
NORMAL_COL="\\033[0;39m"

SOURCE_REGISTRY=$1
TARGET_REGISTRY=$2

# shell 变量赋值,当没有从命令行中传递值给SOURCE_REGISTRY和TARGET_REGISTRY变量时,便采用下述值进行覆盖。
: ${IMAGES_LIST_FILE:="images-list.txt"}
: ${TARGET_REGISTRY:="hub.k8s.li"}
: ${SOURCE_REGISTRY:="docker.io"}

BLOBS_PATH="docker/registry/v2/blobs/sha256"
REPO_PATH="docker/registry/v2/repositories"

set -eo pipefail

CURRENT_NUM=0
ALL_IMAGES="$(sed -n '/#/d;s/:/:/p' ${IMAGES_LIST_FILE} | sort -u)"
TOTAL_NUMS=$(echo "${ALL_IMAGES}" | wc -l)

# shopeo 拷贝函数,注意其传递的参数,此处值得学习记录。
skopeo_copy() {
 if skopeo copy --insecure-policy --src-tls-verify=false --dest-tls-verify=false \
 --override-arch amd64 --override-os linux -q docker://$1 docker://$2; then
  echo -e "$GREEN_COL Progress: ${CURRENT_NUM}/${TOTAL_NUMS} sync $1 to $2 successful $NORMAL_COL"
 else
  echo -e "$RED_COL Progress: ${CURRENT_NUM}/${TOTAL_NUMS} sync $1 to $2 failed $NORMAL_COL"
  exit 2
 fi
}

# 调用拷贝函数并记录当前执行序号。
for image in ${ALL_IMAGES}; do
 let CURRENT_NUM=${CURRENT_NUM}+1
 skopeo_copy ${SOURCE_REGISTRY}/${image} ${TARGET_REGISTRY}/${image}
done
  • 执行命令和结果:

$ bash sync.sh docker.io localhost:5000
Progress: 1/143 sync docker.io/alpine:3.14 to localhost:5000/alpine:3.14 successful
Progress: 2/143 sync docker.io/busybox:1.31.1 to localhost:5000/busybox:1.31.1 successful
....
Progress: 142/143 sync docker.io/weaveworks/scope:1.13.0 to localhost:5000/weaveworks/scope:1.13.0 successful
Progress: 143/143 sync docker.io/wordpress:4.8-apache to localhost:5000/wordpress:4.8-apache successful

4.2 使用 registry 存储特性同步

描述: 将镜像从 registry 中同步到本地目录,使用 registry 存储的特性,将本地目录中的镜像转换成 registry 存储的格式, 这样的好处就是可以去除一些 skopeo dir 中重复的 layers,减少镜像的总大小。

  • convert-images.sh

#!/bin/bash
set -eo pipefail

GREEN_COL="\\033[32;1m"
RED_COL="\\033[1;31m"
NORMAL_COL="\\033[0;39m"

# 命令行参数
SOURCE_REGISTRY=$1
TARGET_REGISTRY=$2
IMAGES_DIR=$2

: ${IMAGES_DIR:="images"}
: ${IMAGES_LIST_FILE:="images-list.txt"}
: ${SOURCE_REGISTRY:="docker.io"}
: ${TARGET_REGISTRY:="hub.k8s.li"}

# hub.k8s.li 仓库服务器中的目录
BLOBS_PATH="docker/registry/v2/blobs/sha256"
REPO_PATH="docker/registry/v2/repositories"

# 记录当前数和总镜像数
CURRENT_NUM=0
ALL_IMAGES="$(sed -n '/#/d;s/:/:/p' ${IMAGES_LIST_FILE} | sort -u)"
TOTAL_NUMS=$(echo "${ALL_IMAGES}" | wc -l)

# 从远程仓库同步指定镜像到本地目录中。
skopeo_sync() {
 if skopeo sync --insecure-policy --src-tls-verify=false --dest-tls-verify=false \
 --override-arch amd64 --override-os linux --src docker --dest dir $1 $2 > /dev/null; then
  echo -e "$GREEN_COL Progress: ${CURRENT_NUM}/${TOTAL_NUMS} sync $1 to $2 successful $NORMAL_COL"
 else
  echo -e "$RED_COL Progress: ${CURRENT_NUM}/${TOTAL_NUMS} sync $1 to $2 failed $NORMAL_COL"
  exit 2
 fi
}

convert_images() {
 rm -rf ${IMAGES_DIR}; mkdir -p ${IMAGES_DIR}
 for image in ${ALL_IMAGES}; do
  let CURRENT_NUM=${CURRENT_NUM}+1
  
  # 取 images-list.txt 文本中的每一行,并分隔存储。
  image_name=${image%%:*}
  image_tag=${image##*:}
  image_repo=${image%%/*}

  # 函数调用 从仓库同步镜像到本地images目录
  skopeo_sync ${SOURCE_REGISTRY}/${image} ${IMAGES_DIR}/${image_repo}

  # 在本地images目录中,取得get image manifest sha256sum 信息
  manifest="${IMAGES_DIR}/${image}/manifest.json"
  manifest_sha256=$(sha256sum ${manifest} | awk '{print $1}')      # 62ffc2ed7554e4c6d360bce40bbcf196573dd27c4ce080641a2c59867e732dee
  mkdir -p ${BLOBS_PATH}/${manifest_sha256:0:2}/${manifest_sha256} # docker/registry/v2/blobs/sha256/62/62ffc2ed7554e4c6d360bce40bbcf196573dd27c4ce080641a2c59867e732dee
  ln -f ${manifest} ${BLOBS_PATH}/${manifest_sha256:0:2}/${manifest_sha256}/data  #  该 data 文件实际上是镜像的 manifest.json 文件。

  # make image repositories dir
  mkdir -p ${REPO_PATH}/${image_name}/{_uploads,_layers,_manifests}
  mkdir -p ${REPO_PATH}/${image_name}/_manifests/revisions/sha256/${manifest_sha256}
  mkdir -p ${REPO_PATH}/${image_name}/_manifests/tags/${image_tag}/{current,index/sha256}
  mkdir -p ${REPO_PATH}/${image_name}/_manifests/tags/${image_tag}/index/sha256/${manifest_sha256}

  # create image tag manifest link file
  echo -n "sha256:${manifest_sha256}" > ${REPO_PATH}/${image_name}/_manifests/revisions/sha256/${manifest_sha256}/link  # sha256:62ffc2ed7554e4c6d360bce40bbcf196573dd27c4ce080641a2c59867e732deer
  echo -n "sha256:${manifest_sha256}" > ${REPO_PATH}/${image_name}/_manifests/tags/${image_tag}/current/link
  echo -n "sha256:${manifest_sha256}" > ${REPO_PATH}/${image_name}/_manifests/tags/${image_tag}/index/sha256/${manifest_sha256}/link

  # link image layers file to registry blobs dir
  for layer in $(sed '/v1Compatibility/d' ${manifest} | grep -Eo "\b[a-f0-9]{64}\b"); do  # 匹配 manifest.json 中"digest"两个不带sha256的值
    mkdir -p ${BLOBS_PATH}/${layer:0:2}/${layer}                 # 5cc84ad355aaa64f46ea9c7bbcc319a9d808ab15088a27209c9e70ef86e5a2aa 、 beae173ccac6ad749f76713cf4440fe3d21d1043fe616dfbe30775815d1d0f6a
    mkdir -p ${REPO_PATH}/${image_name}/_layers/sha256/${layer}  # 5cc84ad355aaa64f46ea9c7bbcc319a9d808ab15088a27209c9e70ef86e5a2aa 、 beae173ccac6ad749f76713cf4440fe3d21d1043fe616dfbe30775815d1d0f6a
    echo -n "sha256:${layer}" > ${REPO_PATH}/${image_name}/_layers/sha256/${layer}/link  # sha256:5cc84ad355aaa64f46ea9c7bbcc319a9d808ab15088a27209c9e70ef86e5a2aa
    ln -f ${IMAGES_DIR}/${image}/${layer} ${BLOBS_PATH}/${layer:0:2}/${layer}/data     # 复制images目录中 "application/vnd.docker.container.image.v1+json" 容器配置 config 与 多个 "application/vnd.docker.image.rootfs.diff.tar.gzip" layer 
  done
 done
}

convert_images
  • install.sh : 使用这个脚本将 registry 存储中的镜像转换成 skopeo dir 的方式,然后再将镜像同步到 registry 中。

#!/bin/bash
REGISTRY_DOMAIN="harbor.k8s.li"
REGISTRY_PATH="/var/lib/registry"

# 切换到 registry 存储主目录下
cd ${REGISTRY_PATH}
gen_skopeo_dir() {
   # 定义 registry 存储的 blob 目录 和 repositories 目录,方便后面使用
    BLOB_DIR="docker/registry/v2/blobs/sha256"
    REPO_DIR="docker/registry/v2/repositories"
    # 定义生成 skopeo 目录
    SKOPEO_DIR="docker/skopeo"
    # 通过 find 出 current 文件夹可以得到所有带 tag 的镜像,因为一个 tag 对应一个 current 目录
    for image in $(find ${REPO_DIR} -type d -name "current"); do
        # 根据镜像的 tag 提取镜像的名字
        name=$(echo ${image} | awk -F '/' '{print $5"/"$6":"$9}')
        link=$(cat ${image}/link | sed 's/sha256://')
        mfs="${BLOB_DIR}/${link:0:2}/${link}/data"
        # 创建镜像的硬链接需要的目录
        mkdir -p "${SKOPEO_DIR}/${name}"
        # 硬链接镜像的 manifests 文件到目录的 manifest 文件
        ln ${mfs} ${SKOPEO_DIR}/${name}/manifest.json
        # 使用正则匹配出所有的 sha256 值,然后排序去重
        layers=$(grep -Eo "\b[a-f0-9]{64}\b" ${mfs} | sort -n | uniq)
        for layer in ${layers}; do
          # 硬链接 registry 存储目录里的镜像 layer 和 images config 到镜像的 dir 目录
            ln ${BLOB_DIR}/${layer:0:2}/${layer}/data ${SKOPEO_DIR}/${name}/${layer}
        done
    done
}
sync_image() {
    # 使用 skopeo sync 将 dir 格式的镜像同步到 harbor
    for project in $(ls ${SKOPEO_DIR}); do
        skopeo sync --insecure-policy --src-tls-verify=false --dest-tls-verify=false \
        --src dir --dest docker ${SKOPEO_DIR}/${project} ${REGISTRY_DOMAIN}/${project}
    done
}
gen_skopeo_dir

温馨提示: 此种方式是有些复杂对于大镜像的复制是推荐的, 而对于一些小镜像且显得多余。

4.3 从 registry 存储中 select 出镜像进行同步

描述: 先将镜像同步到一个 registry 中,再将镜像从 registry 存储中捞出来,该 registry 可以当作一个镜像存储的池子,我们使用 Linux 中硬链接的特性将镜像"复制"一份出来,然后再打一个 tar 包, 这样做的好处就是每次打包镜像的时候都能复用历史的镜像数据,而且性能极快。

  • 步骤 01. 先将镜像同步到一个固定的 registry 中。

$ bash skopeo-copy.sh docker.io localhost:5000
  • 步骤 02. 使用该脚本将镜像从 registry 存储中捞出来

#!/bin/bash
set -eo pipefail
# 命令行变量
IMAGES_LIST="$1"
REGISTRY_PATH="$2"
OUTPUT_DIR="$3"

# Registry 仓库数据目录
BLOB_DIR="docker/registry/v2/blobs/sha256"
REPO_DIR="docker/registry/v2/repositories"

# 判断输出目录是否存在如不存在则移除。
if [ -d ${OUTPUT_DIR} ];then
  rm -rf ${OUTPUT_DIR};
fi
mkdir -p ${OUTPUT_DIR}

for image in $(find ${IMAGES_LIST} -type f -name "*.list" | xargs grep -Ev '^#|^/' | grep ':'); do
  # 镜像名称和Tag
  image_name=${image%%:*}
  image_tag=${image##*:}

  # link 路径获取
  tag_link=${REGISTRY_PATH}/${REPO_DIR}/${image_name}/_manifests/tags/${image_tag}/current/link
  manifest_sha256=$(sed 's/sha256://' ${tag_link})
  manifest=${REGISTRY_PATH}/${BLOB_DIR}/${manifest_sha256:0:2}/${manifest_sha256}/data
  mkdir -p ${OUTPUT_DIR}/${BLOB_DIR}/${manifest_sha256:0:2}/${manifest_sha256}

  # 强制硬链接到指定目录
  ln -f ${manifest} ${OUTPUT_DIR}/${BLOB_DIR}/${manifest_sha256:0:2}/${manifest_sha256}/data

  # make image repositories dir
  mkdir -p ${OUTPUT_DIR}/${REPO_DIR}/${image_name}/{_uploads,_layers,_manifests}
  mkdir -p ${OUTPUT_DIR}/${REPO_DIR}/${image_name}/_manifests/revisions/sha256/${manifest_sha256}
  mkdir -p ${OUTPUT_DIR}/${REPO_DIR}/${image_name}/_manifests/tags/${image_tag}/{current,index/sha256}
  mkdir -p ${OUTPUT_DIR}/${REPO_DIR}/${image_name}/_manifests/tags/${image_tag}/index/sha256/${manifest_sha256}

  # create image tag manifest link file  
  echo -n "sha256:${manifest_sha256}" > ${OUTPUT_DIR}/${REPO_DIR}/${image_name}/_manifests/tags/${image_tag}/current/link
  echo -n "sha256:${manifest_sha256}" > ${OUTPUT_DIR}/${REPO_DIR}/${image_name}/_manifests/revisions/sha256/${manifest_sha256}/link
  echo -n "sha256:${manifest_sha256}" > ${OUTPUT_DIR}/${REPO_DIR}/${image_name}/_manifests/tags/${image_tag}/index/sha256/${manifest_sha256}/link

  # 强制创建 /docker/registry/v2/blobs/ 各 layer data 文件到指定目录之中
  for layer in $(sed '/v1Compatibility/d' ${manifest} | grep -Eo '\b[a-f0-9]{64}\b' | sort -u); do
      mkdir -p ${OUTPUT_DIR}/${BLOB_DIR}/${layer:0:2}/${layer}
      mkdir -p ${OUTPUT_DIR}/${REPO_DIR}/${image_name}/_layers/sha256/${layer}
      ln -f ${BLOB_DIR}/${layer:0:2}/${layer}/data ${OUTPUT_DIR}/${BLOB_DIR}/${layer:0:2}/${layer}/data
      echo -n "sha256:${layer}" > ${OUTPUT_DIR}/${REPO_DIR}/${image_name}/_layers/sha256/${layer}/link
  done
done

至此完毕!

本文转载自:「 WeiyiGeek 」,原文:https://url.hi-linux.com/xCmXo/ ,版权归原作者所有。欢迎投稿,投稿邮箱: editor@hi-linux.com。

f1cc668ad21589a9dc525a7a72030e3a.gif

最近,我们建立了一个技术交流微信群。目前群里已加入了不少行业内的大神,有兴趣的同学可以加入和我们一起交流技术,在 「奇妙的 Linux 世界」 公众号直接回复 「加群」 邀请你入群。

cd51c774505bd7c9de40a794494729ff.png

你可能还喜欢

点击下方图片即可阅读

6b509ec36338b12e414497ea92e48539.png

轻量级 Kubernetes 集群发行版 K3s 完全进阶指南

75cf2b5c1261bfd78a44f370d55e09d4.png

点击上方图片,『美团|饿了么』大额外卖红包天天免费领

a7b922f3c6465e005a86cc9a0d3347d9.png

更多有趣的互联网新鲜事,关注「奇妙的互联网」视频号全了解!

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐