如何验证两张Docker图片的内容是否完全一样?

How to verify if the content of two Docker images is exactly the same?

如何判断两张Docker图像的文件系统结构完全相同,且无论文件时间戳如何,对应文件的内容都相同?

我尝试了图像 ID,但它们在从相同的 Docker 文件和干净的本地存储库构建时有所不同。我通过构建一个图像来完成此测试,清理本地存储库,然后触摸其中一个文件以更改其修改日期,然后构建第二个图像,并且它们的图像 ID 不匹配。我使用 Docker 17.06(我相信是最新版本)。

如果你想比较图像的内容,你可以使用 docker inspect <imageName> 命令,你可以查看 RootFS

部分

docker inspect redis

    "RootFS": {
        "Type": "layers",
        "Layers": [
            "sha256:eda7136a91b7b4ba57aee64509b42bda59e630afcb2b63482d1b3341bf6e2bbb",
            "sha256:c4c228cb4e20c84a0e268dda4ba36eea3c3b1e34c239126b6ee63de430720635",
            "sha256:e7ec07c2297f9507eeaccc02b0148dae0a3a473adec4ab8ec1cbaacde62928d9",
            "sha256:38e87cc81b6bed0c57f650d88ed8939aa71140b289a183ae158f1fa8e0de3ca8",
            "sha256:d0f537e75fa6bdad0df5f844c7854dc8f6631ff292eb53dc41e897bc453c3f11",
            "sha256:28caa9731d5da4265bad76fc67e6be12dfb2f5598c95a0c0d284a9a2443932bc"
        ]
    }

如果所有层都相同,则图像包含相同的内容

似乎没有执行此操作的标准方法。我能想到的最好方法是使用 Docker 多阶段构建功能。 例如,这里我比较的是 apline 和 debian 镜像。在你的情况下,将图像名称设置为你想要比较的名称

我基本上将每个图像中的所有文件复制到 git 存储库中,并在每次复制后提交。

FROM alpine as image1

FROM debian as image2

FROM ubuntu
RUN apt-get update && apt-get install -y git
RUN git config --global user.email "you@example.com" &&\
 git config --global user.name "Your Name"

RUN mkdir images
WORKDIR images
RUN git init

COPY --from=image1 / .
RUN git add . && git commit -m "image1"

COPY --from=image2 / .
RUN git add . && git commit -m "image2"

CMD tail > /dev/null

这将为您提供一个带有 git 存储库的图像,该存储库记录了两个图像之间的差异。

docker build -t compare .
docker run -it compare bash

现在,如果您执行 git log,您可以看到日志,并且可以使用 git diff <commit1> <commit2>

比较两个提交

注意:如果图像构建在第二次提交时失败,这意味着图像是相同的,因为如果没有要提交的更改,git 提交将失败。

经过一些研究,我想出了一个解决方案,根据我的测试,它既快速又干净。

整体解决方案是这样的:

  1. 通过 docker create ...
  2. 为您的图像创建一个容器
  3. 通过 docker export ...
  4. 将其整个文件系统导出到 tar 存档
  5. 将存档目录名称、符号链接名称、符号链接内容、文件名和文件内容通过管道传输到哈希函数(例如 MD5)
  6. 比较不同图像的哈希值以验证它们的内容是否相等

就是这样。

从技术上讲,这可以按如下方式完成:

1) 创建文件md5docker,并赋予其执行权限,如chmod +x md5docker:

#!/bin/sh
dir=$(dirname "[=10=]")
docker create  | { read cid; docker export $cid | $dir/tarcat | md5; docker rm $cid > /dev/null; }

2) 创建文件tarcat,并赋予其执行权限,如chmod +x tarcat:

#!/usr/bin/env python3
# coding=utf-8

if __name__ == '__main__':
    import sys
    import tarfile
    with tarfile.open(fileobj=sys.stdin.buffer, mode="r|*") as tar:
        for tarinfo in tar:
            if tarinfo.isfile():
                print(tarinfo.name, flush=True)
                with tar.extractfile(tarinfo) as file:
                    sys.stdout.buffer.write(file.read())
            elif tarinfo.isdir():
                print(tarinfo.name, flush=True)
            elif tarinfo.issym() or tarinfo.islnk():
                print(tarinfo.name, flush=True)
                print(tarinfo.linkname, flush=True)
            else:
                print("[0;31mIGNORING:[0m ", tarinfo.name, file=sys.stderr)

3) 现在调用 ./md5docker <image>,其中 <image> 是您的图像名称或 ID,以计算图像整个文件系统的 MD5 哈希值。

要验证两个图像是否具有相同的内容,只需检查它们的哈希是否与步骤 3 中计算的相同)。

请注意,此解决方案仅考虑内容目录结构、常规文件内容和符号链接(软链接和硬链接)。如果您需要更多,只需更改 tarcat 脚本,添加更多 elif 子句测试您希望包含的内容(请参阅 Python's tarfile,并查找与需要的内容)。

我在这个解决方案中看到的唯一限制是它对 Python 的依赖(我正在使用 Python3,但它应该很容易适应 Python2)。一个没有任何依赖的更好的解决方案,而且可能更快(嘿,这已经非常快了),是用一种支持静态链接的语言编写 tarcat 脚本,这样一个独立的可执行文件就足够了(即,一个不需要任何外部依赖项,但唯一的 OS)。我把它作为 C、Rust、OCaml 的未来练习,Haskell,你选择。

注意,如果 MD5 不适合您的需要,只需将第一个脚本中的 md5 替换为您的哈希实用程序。

希望这对阅读的人有所帮助。

令我惊讶的是 docker 并没有开箱即用。这是@mljrg 技术的变体:

#!/bin/sh

docker create  | {
  read cid
  docker export $cid | tar Oxv 2>&1 | shasum -a 256
  docker rm $cid > /dev/null
}

它更短,根本不需要 python 依赖项或第二个脚本,我确信它有缺点,但它似乎对我完成的少数测试有效。