问题
使用go-containerregistry库 push image 时和 docker push image 时的layer hash不一致
### 本地docker push推送镜像 ###
# docker push 192.168.1.1:30500/alpine-java:1.0.0
The push refers to repository [192.168.1.1:30500/alpine-java]
372aafbe5d64: Preparing
5b7df235d876: Preparing
### 拉取go-containerregistry推送到registry的镜像 ###
# docker pull 192.168.1.1:30500/alpine-java:1.0.0
1.0.0: Pulling from alpine-java
4d23f0108a5c: Pull complete
20ec70f16685: Pull complete
Digest: sha256:ee64093d46b7c9f6e9676deca8b8e10ac935ab2970f1b25d24c06182872f265a
Status: Downloaded newer image for 192.168.1.1:30500/alpine-java:1.0.0
分析
-
两种方式push后,docker inspect 两个image后,发现内容是一致的,说明其实push的是同一个镜像
(左边是机器上docker inspect的结果,右边是本地docker inspect结果。rootfs:容器只读的文件系统) -
docker save -o两个image的tar包后,解压比对manifest.json文件

(左边是docker push manifest.json,右边是go-containerregistry push manifest.json) -
通过分析go-containerregistry源码发现,推送的image layer_id实际上是对layer.tar做了“再压缩”后计算的sha256 commit到manifest.json导致sha256发生变化。从而导致与docker push的layer sha256不一致。
(go-containerregistry代码计算的layer_id,与本文docker pull的layer_id一致)
在生成sha256同样的位置,对本地原文件计算sha256。可以得到与在终端使用sha256sum命令计算相同的结果。如下:
file, _ := os.Open("~/xxxxxxx/layer.tar")
hasher := sha256.New()
io.Copy(hasher, file) // 将文件copy到hasher中
// -> 5b7df235d876e8cd4a2a329ae786db3fb152eff939f88379c49bcaaabbaf
fmt.Printf("%x\n",hasher.Sum(nil))
//
分析原因:读取layer.tar文件时,gzip.ReadCloser()函数会在原先layer.tar的基础上做一次“再压缩”。压缩包内容与实际layer.tar不同,导致计算的sha256不同。
修改方案:将gzip.ReadCloser(u) -> u之后,获取到的layer_id与docker push的layer_id保持一致。
原理剖析
那么layer涉及到更深层次有什么原理吗?
-
先了解下docker pull的原理

-
主要需要了解两个配置文件:manifest(对应registry服务端的配置)和 image config(针对本地存储端的)。值得一提的是,这两个不同的配置文件中的layer_id其实是不同的。
简单举例,查看manifest.json关于layer的配置:
......,
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 2929796,
"digest": "sha256:4d23f0108a5cb4d445395bea6210f5c729774ff4d0a2cd5a720b03ed612b71ee"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 89480540,
"digest": "sha256:20ec70f166851563b685a57685d55a0db6b9ecd92bfd19f6824a91d04d8bf892"
}
]
再来看看image config里面关于layer存的是什么:
......,
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:5b7df235d876e8cd4a2a329ae786db3fb152eff939f88379c49bcaaabbafbd9c",
"sha256:372aafbe5d64e05dadee8a2e0ae22e4e7e963106208d58f3572c0724b6553c08"
]
},
......
- 为什么要拉取两个文件?
- 当我们去registry上拉layer的时候,拉的格式是根据请求中的media type决定的,因为layer存在本地的时候未压缩的,或者是已解压过的。
- 为了在网络上传输的更快,所有media type一般会指定压缩格式,如.tar。
- 结合docker pull可以发现。当docker发现本地不存在某个layer的时候,就会通过manifest.json里面的digest + mediaType(如上述配置中的application/vnd.docker.image.rootfs.diff.tar.gzip)去 docker registry 获取 image config 中的 diff_ids,再在本地找这些layer,还是拿不到就会去拿tar包解压缩。
- 每个 diffid 对应一个 layer tar 包的 sha256,可以利用sha256sum对layer.tar包进行分析

可以发现最终解析出来的值就是rootfs下的layer_id

参考链接:
https://zhuanlan.zhihu.com/p/95900321
https://github.com/google/go-containerregistry
本文探讨了使用go-containerregistry库push Docker镜像时与docker push产生的layer_id不一致的原因。分析发现,差异源于go-containerregistry在推送过程中对layer.tar进行了额外的压缩,导致计算的sha256值变化。通过调整代码,避免这一额外压缩,可以使layer_id与docker push保持一致。同时,文章还简要介绍了docker pull的原理,包括manifest和image config在其中的作用。
4898

被折叠的 条评论
为什么被折叠?



