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 信息的存储路径和内容完全相同
项目信息
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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # 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 2 3 4 5 6 7 8 9 10 11 # 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命令进行如下操作:
1 $ podman run docker://quay.io/skopeo/stable:latest copy --help
3. 快速上手
3.1 命令浅析
描述: skopen 是操作各种容器映像和容器映像仓库的工具,其使用方法及其可用命令如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 $ ./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 types
Describe
Example
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 处理的方式也不一样,。
测试环境说明
1 2 3 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # (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==" } } }
1 $ skopeo logout myregistrydomain.com:5000
温馨提示: 如果企业自建harbor仓库(一般都会设置自签证书)或者其它私有仓库配置证书,为了防止出错建议进行以下操作(正式环境请根据需要进行配置)。
1 2 3 4 5 6 7 # (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 策略和证书校验参数如下:
1 2 3 --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 镜像的属性相关信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 $ 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 镜像的容器配置相关信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 $ 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(摘要)
1 $ 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 镜像。
1 2 3 4 5 6 $ 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 目录中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 $ 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 格式保存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ 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 功能)
1 2 3 4 5 6 7 8 9 10 11 # 在 /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
3.2.4 Skopeo sync - 镜像同步命令
描述: Skopeo sync可以在容器仓库和本地目录之间同步镜像,其功能类似于阿里云的 image-syncer (https://github.com/AliyunContainerService/image-syncer ) 工具, 实际上其比 image-syncer 更强大、灵活性更强一些,废话不多说实践为王。
skopeo sync 镜像同步文件示例:
步骤 01. 将仓库中所有 busybox 镜像版本同步到本地目录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $ 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
1 2 3 4 5 6 7 8 $ 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
步骤 03. 从 hub 容器仓库中同步 alpine-jenkins-jnlp:v2.285 镜像到本地临时容器仓库中。
1 2 3 4 5 6 $ 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. 以配置文件方式进行同步, 首先我们需要准备一个需要同步的资源清单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 # 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 文件中镜像匹配复制镜像说明:
3.2.5 Skopeo list-tags - 仓库中镜像 Tag 查看
描述: 利用该命令我们可以列出 registry 上的某个镜像的所有 tag ,它是使用标准的 registry API 来获取镜像 tag。
简单示例:
1 $ 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # 方式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 进行执行。
1 2 3 4 5 6 7 8 # 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #!/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
1 2 3 4 5 6 $ 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,减少镜像的总大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 #!/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 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #!/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 中。
1 $ bash skopeo-copy.sh docker.io localhost:5000
步骤 02. 使用该脚本将镜像从 registry 存储中捞出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #!/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 。