它始于一个咨询失误:政府组织A要求政府组织B开发一个Web应用程序。政府机构B将一些工作外包给某人。后来项目的托管和维护外包给了一家私人公司C,C公司发现之前外包的人(早就走了)构建了一个自定义的Docker镜像,并把它做成了系统构建依赖,但是那个人并没有提交原始Dockerfile。C公司根据合同有义务管理此Docker映像,但他们没有源代码。C公司偶尔会叫我来做各种工作,所以处理这个神秘的Docker镜像的一些事情就成了我的工作。幸运的是,Docker镜像的格式比您想象的要透明得多。虽然仍有一些侦探工作要做,但仅通过剖析图像文件就有很多发现。例如,这里有一个Prettier代码格式的镜像,可以快速查看。首先,让Docker守护进程拉取镜像,然后将镜像解压缩到一个文件中:dockerpulltmknom/prettier:2.0.5dockersavetmknom/prettier:2.0.5>prettier.tar是的,这个文件只是一个典型的tarball格式的归档文件:$tarxvfprettier.tar6c37da2ee7de579a0bf5495df32ba3e7807b0a42e2a02779206d165f55f1ba70/6c37da2ee7de579a0bf5495df32ba3e7807b0a42e2a02779206d165f55f1ba70/VERSION6c37da2ee7de579a0bf5495df32ba3e7807b0a42e2a02779206d165f55f1ba70/json6c37da2ee7de579a0bf5495df32ba3e7807b0a42e2a02779206d165f55f1ba70/layer.tar88f38be28f05f38dba94ce0c1328ebe2b963b65848ab96594f8172a9c3b0f25b.jsona9cc4ace48cd792ef888ade20810f82f6c24aaf2436f30337a2a712cd054dc97/a9cc4ace48cd792ef888ade20810f82f6c24aaf2436f30337a2a712cd054dc97/VERSIONa9cc4ace48cd792ef888ade20810f82f6c24aaf2436f30337a2a712cd054dc97/jsona9cc4ace48cd792ef888ade20810f82f6c24aaf2436f30337a2a712cd054dc97/layer.tard4f612de5397f1fc91272cfbad245b89eac8fa4ad9f0fc10a40ffbb54a356cb4/d4f612de5397f1fc91272cfbad245b89eac8fa4ad9f0fc10a40ffbb54a356cb4/VERSIOnd4f612de5397f1fc91272cfbad245b89eac8fa4ad9f0fc10a40ffbb54a356cb4/jsond4f612de5397f1fc91272cfbad245b89eac8fa4ad9f0fc10a40ffbb54a356cb4/layer.tarmanifest.jsonestrepositories可以看到,Docker在命名时经常使用hash。它是用难以阅读的压缩JSON编写的,但是JSON瑞士军刀jq可以很好地打印JSON:$jq。显现。:2.0.5"],"Layers":["a9cc4ace48cd792ef888ade20810f82f6c24aaf2436f30337a2a712cd054dc97/layer.tar","d4f612de5397f1fc91272cfbad245b89eac8fa4ad9f0fc10a40ffbb54a356cb4/layer.tar","6c37da2ee7de579a0bf5495df32ba3e7807b0a42e2a02779206d165f55f1ba70/layer.tar"]}]请注意,这三个层Layer对应三个以哈希命名的目录。我们稍后会看到。现在,让我们看一下Config键指向的JSON文件。它有点长,所以我只在这里转储第一部分:$jq。"","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":["--help"],"ArgsEscaped":true,"Image":"sha256:93e72874b338c1e0734025e1d8ebe259d4f16265dc2840f88c4c754e1c01ba0a",最重要的是历史列表,它列出了图像中的每一层。Docker镜像由这些层堆叠而成。几乎Dockerfile中的每个命令都会成为一个层,描述该命令对图像所做的更改。如果执行RUNscript.sh命令创建really_big_file,然后使用RUNrmreally_big_file命令删除文件,Docker镜像实际上生成了两层:一层包含realy_big_file,一层包含.wh.really_big_file条目删除它。整个图像文件的大小保持不变。这就是为什么您会经常看到像RUNscript.sh&&rmreally_big_file这样的Dockerfile命令链接在一起的原因-它保证所有更改都合并到一个层中。以下是此Docker映像中记录的所有层。请注意,大多数层不会更改文件系统映像,并且empty_layer被标记为true。下面只有三层是非空的,与我们之前描述的相匹配。$jq.history88f38be28f05f38dba94ce0c1328ebe2b963b65848ab96594f8172a9c3b0f25b.json[{"created":"2020-04-24T01:05:03.608058404Z","created_by":"/bin/sh-c#(nop)ADDfile:b91adb67b670d3a6ff9463e48b7def903ed516be66fc4282d22c53e41512be49in/"},{"created":"2020-04-24T01:05:03.92860976Z","created_by":"/bin/sh-c#(nop)CMD[\"/bin/sh\"]","empty_layer":true},{"created":"2020-04-29T06:34:06.617130538Z","created_by":"/bin/sh-c#(nop)ARGBUILD_DATE","empty_layer":true},{"created":"2020-04-29T06:34:07.020521808Z","created_by":"/bin/sh-c#(nop)ARGVCS_REF","empty_layer":true},{"created":"2020-04-29T06:34:07.36915054Z","created_by":"/bin/sh-c#(nop)ARGVERSION","empty_layer":true},{"created":"2020-04-29T06:34:07.708820086Z","created_by":"/bin/sh-c#(nop)ARGREPO_NAME","empty_layer":true},{"created":"2020-04-29T06:34:08.06429638Z","created_by":"/bin/sh-c#(nop)LABELorg.label-schema.vendor=tmknomorg.label-schema.name=tmknom/prettierorg.label-schema.description=Prettier是一个自以为是的代码格式化程序。org.label-schema.build-date=2020-04-29T06:34:01Zorg.label-schema.version=2.0.5org.label-schema.vcs-ref=35d2587org.label-schema.vcs-url=https://github.com/tmknom/prettierorg.label-schema.usage=https://github.com/tmknom/prettier/blob/master/README.md#usageorg.label-schema.docker.cmd=dockerrun--rm-v$PWD:/worktmknom/prettier--parser=markdown--write'**/*.md'org.label-schema.schema-version=1.0","empty_layer":true},{"created":"2020-04-29T06:34:08.511269907Z","created_by":"/bin/sh-c#(nop)ARGNODEJS_VERSION=12.15.0-r1","empty_layer":true},{"created":"2020-04-29T06:34:08.775876657Z","created_by":"/bin/sh-c#(nop)ARGPRETTIER_VERSION","empty_layer":true},{"created":"2020-04-29T06:34:26.399622951Z","created_by":"|6BUILD_DATE=2020-04-29T06:34:01ZNODEJS_VERSION=12.15.0-r1PRETTIER_VERSION=2.0.5REPO_NAME=tmknom/prettierVCS_REF=35d2587VERSION=2.0.5/bin/sh-cset-x&&apkadd--no-cachenodejs=${NODEJS_VERSION}nodejs-npm=${NODEJS_VERSION}&&npminstall-gprettier@${PRETTIER_VERSION}&&npm缓存清理--force&&apkdelnodejs-npm"},{"created":"2020-04-29T06:34:26.764034848Z","created_by":"/bin/sh-c#(nop)WORKDIR/work"},{"created":"2020-04-29T06:34:27.092671047Z","created_by":"/bin/sh-c#(nop)入口点[\"/usr/bin/prettier\"]","empty_layer":true},{"created":"2020-04-29T06:34:27.406606712Z","created_by":"/bin/sh-c#(nop)CMD[\"--help\"]","empty_layer":true}]太棒了!所有命令都在created_by字段中,我们几乎可以用这些命令重建Dockerfile,但不完全是。顶部的ADD命令实际上并没有给我们需要添加的文件。COPY命令也没有所有信息。我们还丢失了FROM语句,因为它们扩展到从基本Docker映像继承的所有层。我们可以通过查看时间戳按Dockerfile对层进行分组。大多数层的时间戳相差不到一分钟,代表每一层构建所花费的时间。但是前两层是2020-04-24,其余是2020-04-29。这是因为前两层来自基础Docker镜像。理想情况下,我们可以想出一个FROM命令来获取这个镜像,这样我们就有了一个可维护的Dockerfile。manifest.json显示第一个非空层是a9cc4ace48cd792ef888ade20810f82f6c24aaf2436f30337a2a712cd054dc97/layer.tar。让我们看看它:headbin/bin/archbin/ashbin/base64bin/bbconfigbin/busyboxbin/catbin/chgrpbin/chmodbin/chown看起来它可能是一个基础操作系统映像,这是您希望从典型的Dockerfile中看到的。Tarball中有488个条目,如果你四处浏览,你会发现一些有趣的条目:...dev/etc/etc/alpine-releaseetc/apk/etc/apk/archetc/apk/keys/etc/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pubetc/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pubetc/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pubetc/apk/protected_pa??ths.d/etc/apk/repositoriesetc/apk/worldetc/conf.d/...正如所料,这是一个Alpine镜像,如果你注意到其他层使用apk命令安装包,正如您可能已经猜到的那样。让我们解压tarball看看:$mkdir文件$cd文件$tarxf../layer.tar$lsbindevetchomelibmediamntoptprocrootrunsbinsrvsystmpusrvar$catetc/alpine-release3.11.6如果你拉取解压alpine:3.11.6,你会发现里面有一个非空层,layer.tar和Prettier镜像的baselayer中的layer.tar是一样的。出于兴趣,其他两个非空层是什么?第二层是包含Prettier安装包的主要层。它有528个条目,包含Prettier、一堆依赖项和证书更新:...usr/lib/libuv.so.1usr/lib/libuv.so.1.0.0usr/lib/node_modules/usr/lib/node_modules/漂亮/usr/lib/node_modules/漂亮/LICENSEusr/lib/node_modules/漂亮/README.mdusr/lib/node_modules/漂亮/bin-prettier.jsusr/lib/node_modules/漂亮/doc.jsusr/lib/node_modules/漂亮/index.jsusr/lib/node_modules/prettier/package.jsonusr/lib/node_modules/prettier/parser-angular.jsusr/lib/node_modules/prettier/parser-babel.jsusr/lib/node_modules/prettier/parser-flow.jsusr/lib/node_modules/prettier/parser-glimmer.jsusr/lib/node_modules/prettier/parser-graphql.jsusr/lib/node_modules/prettier/parser-html.jsusr/lib/node_modules/prettier/parser-markdown.jsusr/lib/node_modules/prettier/parser-postcss.jsusr/lib/node_modules/prettier/parser-typescript.jsusr/lib/node_modules/prettier/parser-yaml.jsusr/lib/node_modules/prettier/standalone.jsusr/lib/node_modules/prettier/third-party.jsusr/local/usr/local/share/usr/local/share/ca-certificates/usr/sbin/usr/sbin/update-ca-certificatesusr/share/usr/share/ca-certificates/usr/share/ca-certificates/mozilla/usr/share/ca-certificates/mozilla/ACCVRAIZ1.crtusr/share/ca-certificates/mozilla/AC_RAIZ_FNMT-RCM.crtusr/share/ca-certificates/mozilla/Actalis_Authentication_Root_CA.crt...第三层由WORKDIR/work命令创建,它只包含一个条目:$tartf6c37da2ee7de579a0bf5495df32ba3e7807b0a42e2a02779206d165f55f1ba70/layer.tarwork/rawDockerfileinPrettier'sgitrepository
