很长一段时间以来,我一直使用 Docker 和中间容器构建应用程序,例如,我会构建一个myapp:base
已复制所有核心文件的应用程序:
FROM ubuntu:17.10
ADD app /app
现在,主容器myapp:release
可能只需用这个就可以创建
FROM myapp:base
ADD app /app
目录中的少数文件可能会被修改app
,并且我最终会得到一个小的额外层。
不过最近,这个最终结果ADD
导致了一个更大的层,我怀疑它与 Docker 更新有关(我使用的是 Docker 版本 18.02.0-ce,构建 fc4de44)
Docker 判断差异的方式是否发生了变化?如何才能恢复到仅在某一层中包含少量更新文件的高效构建?
重现步骤:
# make a dummy dir with a 64kb file in it
mkdir -p files
truncate -s 64k files/64k.file
# base container has a copy of the files
cat << EOF > Dockerfile.base
FROM ubuntu:16.04
COPY files/ /root/
EOF
# derived container should just have any updates
cat << EOF > Dockerfile.derived
FROM q899941:base
COPY files/ /root/
EOF
# build them....
docker build --file Dockerfile.base -t q899941:base .
docker build --file Dockerfile.derived -t q899941:derived .
# now let's review the layers
docker history q899941:derived
我得到了这个结果
IMAGE CREATED CREATED BY SIZE
4e1eb5168d55 Less than a second ago /bin/sh -c #(nop) COPY dir:8e20ede288278c71e… 65.5kB
022626ac5cf0 Less than a second ago /bin/sh -c #(nop) COPY dir:8e20ede288278c71e… 65.5kB
0458a4468cbc 5 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 5 weeks ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
<missing> 5 weeks ago /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$… 2.76kB
<missing> 5 weeks ago /bin/sh -c rm -rf /var/lib/apt/lists/* 0B
<missing> 5 weeks ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 745B
<missing> 5 weeks ago /bin/sh -c #(nop) ADD file:a3344b835ea6fdc56… 112MB
即使没有文件改变,顶层也是 64kb。
答案1
这似乎是一个已知问题,但却没有足够多的人关心!
- 有没有办法将更改的文件作为新层添加到docker镜像中——而无需诉诸docker commit?是一个 StackOverflow 问题,涉及相同内容。
- 看来我预期的行为与覆盖存储驱动程序一起工作,但经过一些系统更新后,我开始使用 overlay2,它会将所有文件复制到一个层中,而不仅仅是更改的文件。
- 虽然一种解决方案是恢复为覆盖,但目前它似乎已被强烈弃用。此处详细介绍了构建“diff”层的替代方法
我最终做了一件在其中一个中提到的简单的事情评论在错误报告中:
#clean up any previous attempt...
docker rm -f uniquename 2> /dev/null
# now take your base container, mount the updated dir as /src
# then rsync from /src to the target dir - only updated files will
# actually be written, and we use --delete to ensure removed files are
# taken out...
docker run --name uniquename \
-v ~/repo/mycode:/src \
${REPO}/${IMAGE}:${BASE} \
rsync -ar --no-owner --no-group \
--exclude-from '/src/.dockerignore' --delete \
/src/ /app/
# we can commit that updated container with a tag
docker commit uniquename ${REPO}/${IMAGE}:${NEW_TAG}
还有一件事——这个解决方案似乎抹去了现有的CMD
提供容器默认启动命令的命令。因此,在添加这个 diff 层之后,我有另一个 Dockerfile 来从上面的“diff”构建中创建一个容器,并将其CMD
重新添加进去。