目的:每次创建最小的工作docker镜像

当前

REPOSITORY          TAG       IMAGE ID            CREATED             SIZE
a-docker-image      latest    x                   42 minutes ago       1.92 GB


尝试

添加清理步骤在Dockerfile的末尾:

#clean
RUN apt-get purge -y wget
RUN rm -r a-build-dir
RUN apt-get purge -y a-package


减小了图像大小:

REPOSITORY          TAG       IMAGE ID            CREATED             SIZE
a-docker-image      latest    y                   2 minutes ago       1.86 GB


讨论

我构建了各种docker映像。每次尝试减小创建的图像的大小时,我总是觉得它太大。我正在寻找一个已经由github上的某个人创建的脚本,该脚本会从图像中删除所有多余的软件包,因此创建的图像的大小将尽可能小。

我一直说尝试减小图像的大小,但是我想应用一致的图像,以便从现在开始创建的每个图像都尽可能小。

问题

如何每次创建最小的工作docker映像?

#1 楼

涉及多种技术,没有一个解决方案。您可能需要执行以下几项操作:


首先,优化图像图层以供重用。稍后在Dockerfile中进行频繁更改的步骤,以增加从先前构建中缓存早期层的机会。在docker image ls中,重用的层将显示为更多的磁盘空间,但是,如果您检查基础文件系统,则每层只有一个副本存储在磁盘上。这意味着3张图像,每张2 GB,但是在构建的最后几层中只有50 MB的差异,尽管清单显示它使用的是6 GB,但清单仅显示2.1 GB的磁盘空间。重复计算每个重用的层。

层重用是为什么您会看到具有不经常变化的构建依赖关系的映像先将其安装在复制代码之前的原因。请参阅具有以下模式的任何python示例:

FROM python
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
# note how the code is copied only after the pip install
# since code changes but requirements.txt doesn't
COPY . .
CMD ["gunicorn", "app:app"]



选择最小的基本图像。这就是为什么您看到人们从ubuntudebian:slim(细长型变体较小,使用的工具较少)甚至是alpine的原因。这样可以减小起点的大小,如果您不断拉动基础图像的新版本,这将非常有帮助。但是,如果基本映像很少更改,则层重用将消除最小基本映像的大部分优点。仅对静态编译的二进制文件有用。否则,请选择一个包含所需工具的基本映像,而不使用不需要的大量工具。


接下来,任何更改或删除文件的步骤都应与创建该文件的先前步骤结合使用。否则,即使在更改文件许可权等操作时也使用写时复制的分层文件系统将在上一层具有原始文件,并且在删除文件时图像大小不会缩小。这就是为什么您的scratch命令对结果磁盘空间没有影响的原因。相反,您可以链接命令,例如:

RUN apt-get update \
 && apt-get install -y \
      a-package \
      wget \
 && ... \
 && apt-get purge -y wget \
 && rm -r a-build-dir \
 && apt-get purge -y a-package


请注意,过度使用命令链会减慢您的构建速度,因为您需要在先决条件下随时重新安装相同的工具集。更改(例如,使用wget提取的代码)。有关更好的选择,请参见下面的多阶段操作。


创建的任何文件都不需要在生成的图像中删除,该步骤必须在创建该文件的步骤中进行。这包括程序包缓存,日志,手册页等。要发现每一层中正在创建什么文件,可以使用wagoodman / dive之类的工具(我没有亲自对其进行过审查,因此请谨慎使用,因为它可以完全root用户访问在主机上),或者您可以在不修剪中间容器的情况下构建docker映像,然后使用以下命令查看差异:

# first create and leave containers from any RUN step using options on build
docker image build --rm=false --no-cache -t image_name . 
# review which layers use an unexpectedly large amount of space
docker image history image_name
# list all containers, particularly the exited ones from above
docker container ps -a 
# examine any of those containers
docker container diff ${container_id} 
# ... repeat the diff for other build steps
# then cleanup exited containers
docker container prune


对于每个中间容器,差异将显示在该步骤中添加,更改或删除的文件(在每个文件名之前用rmAC表示)。 diff显示的是容器特定的读/写文件系统,该文件系统是容器使用写时复制从映像状态更改的任何文件。


减小图像大小的最佳方法是从出厂的图像中消除任何不需要的组件,例如编译器。为此,多阶段构建使您可以在一个阶段进行编译,然后仅将生成的工件从构建阶段复制到运行时映像,该映像仅具有运行应用程序所需的最低要求。这样可以避免优化任何构建步骤的需要,因为它们不会随生成的图像一起提供。

FROM debian:9 as build
# still chain update with install to prevent stale cache issues
RUN apt-get update \
 && apt-get install -y \
      a-package \
      wget \
RUN ... # perform any download/compile steps

FROM debian:9-slim as release
COPY --from=build /usr/local/bin/app /usr/local/bin/app
CMD [ "/usr/local/bin/app" ]


多阶段是静态编译二进制文件的理想选择,您可以从头开始作为基本映像运行,或从JDK等编译环境过渡到JRE等运行时环境。这是在保持快速构建的同时显着减小图像大小的最简单方法。如果您具有更改或删除先前步骤中创建的文件的步骤,则仍可以在发布阶段执行步骤链接,但是在大多数情况下,来自另一个阶段的D可以将发布阶段与早期构建阶段经历的任何层膨胀隔离开。


请注意,我不建议挤压图像,因为这会缩小一个图像的大小,但以消除图层重用为代价。这意味着以后构建同一映像将需要更多磁盘和网络流量来发送更新。回到第一个示例,压扁可能会将您的映像从2 GB减少到1 GB,但是3张映像可能会占用3 GB而不是2.1 GB。

评论


我的情况是-出于安全原因,我正在中间层下载软件包。然后,将它们添加到最终图像。接下来使用RUN命令安装软件包并删除软件包。我的目标是在RUN命令安装后删除已下载的软件包。有什么方法可以a)将ADD和RUN(删除)合并到一层; b)在运行后删除ADD层?

–变量
20年6月3日,12:20



@variable是上述多阶段构建的方案。

–BMitch
20年6月3日,13:30

#2 楼

Dockerfile为文件中的每个命令创建一个新层。由于各层都很好,因此可以彼此叠加-您无法删除上一层添加的文件。这就是为什么在安装程序包,下载文件或在单独的命令中创建每个程序包的原因-即使在以后的层中将其删除,这些程序仍在映像中。

因此,只需更改此内容:

RUN apt-get update -y
RUN apt-get install -y wget a-package
# ...
RUN apt-get purge -y wget
RUN rm -r a-build-dir
RUN apt-get purge -y a-package


对此:

RUN apt-get update -y \
    && apt-get install -y wget a-package \
    && mkdir a-build-dir \
    && wget http://some-site/very-big-source-code.tar.gz \
    && tar xzvf very-big-source-code.tar.gz \
    && do-some-compilation \
    && apt-get purge -y wget \
    && cd .. \
    && rm -rf a-build-dir \
    && apt-get purge -y a-package


您将得到一个小得多的图像。


另一种选择是在构建图像后对其进行压缩。
问:新的docker --squash如何工作?


另一种选择是选择苗条的基本图像。例如,以Alpine Linux为基础而不是Debian的映像仅占用10-15mb而不是180-250mb。这是在添加您自己的应用程序和数据之前。 Docker Hub上的许多官方基础映像都具有高山版本。

评论


2.37和1.47 GB

– 030
17 Mar 7 '17 at 22:48

在所有apt-get安装命令中添加--no-install-recommends。

–吉里·克劳达(Jiri Klouda)
20 Sep 9 '20 at 7:59

#3 楼



考虑到这一点,已经创建了厨师的栖息地,创建了一个具有所有必要依赖关系的程序包,而没有多余的发行/基础映像加载。

通过一个简单的nodejs应用程序从此博客文章中提取重要的容器大小:


michael@ricardo-2:plans_pkg_part_2$ docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
mfdii/node-example   latest              36c6568c606b        40 minutes ago      655.9 MB
node                 latest              04c0ca2a8dad        16 hours ago        654.6 MB
mfdii/mytutorialapp  latest              534afd80d74d        2 minutes ago       182.1 MB



mdfii/node-example是来自经典dockerfile的docker映像,而mfdii/mytutorialapp是由栖息地生成的docker映像。这可能是您的解决方案。

#4 楼

还可以使用潜水

docker run --rm -it \
    -v /var/run/docker.sock:/var/run/docker.sock \
    wagoodman/dive:latest <dive arguments...>


获得有关可以从docker映像中删除哪些废物以减小尺寸的报告。

#5 楼

如果您想拥有可重用的开发层,但减少了磁盘的交付使用量,则可以生成一个合并的“交付层”,如下所示: (如果没有,则可以使用类似docker run IMAGE echo的命令(如果echo命令可用))
找到容器ID(也许使用docker container ls -l
docker export插入docker import以创建合并的图层(像docker export 20f192c6530a | docker import - project:merged一样)

这将保留您的开发层,但是会为您提供一个较小的合并图像,您可以交付。

#6 楼

多阶段构建。使用具有所有构建组件的映像来构建应用程序,并使用较轻的运行时映像。仅将构建工件复制到运行时映像。无需删除任何内容。

https://docs.docker.com/develop/develop-images/multistage-build/

#7 楼

使用docker ps检查当前正在运行的容器。例如:
FROM ubuntu16
    
MAINTAINER sreeni (email/domain)
    
RUN apt-get update
    
RUN apt-get install -y nginx
    
ENTRYPOINT [“/usr/sbin/nginx”,”-g”,”daemon off;”]
    
EXPOSE 80 (port)

使用以下docker命令运行容器:
docker run -d -p 80:80 --name web server ubuntu16

之后,检查localhost或ip地址:80(打开浏览器并检查)