image.png

引言

最近的工作中,我使用了很多种容器技术,其中最流行的技术当然是Docker。 除了允许你使用docker run命令轻松运行容器外,Docker还提供了一种构建容器镜像的方法以及生成的镜像的格式。 通过编写Dockerfile并执行docker build命令,可以轻松地创建在安装了Docker的任何位置(在一定约束条件下)运行的镜像。

比如,这是一个用于运行简单文件服务器的DockerFile。

FROM debian:jessie
RUN apt-get update
RUN apt-get install -y python
RUN mkdir -p /data
VOLUME ["/data"]
WORKDIR /data
EXPOSE 8000
CMD [ "python", "-m", "SimpleHTTPServer", "8000" ]

是一个非常简单的docker文件,但是如果你构建它并查看实际大小,你会发现它其实很大。

VIRTUAL SIZE
167.4 MB

为什么镜像看起来如此之大?

在上面的Dockerfile的第一行,注意到上面写的是FROM debian:jessie,这点很重要,因为这意味着我们将Docker镜像基于Docker Hub上的debian:jessie镜像。 之所以会如此之大,是因为该镜像包含了整个Debian 8.x安装,如果在这之后再安装gcc,g++或其他构建工具之类的东西,镜像将变得更大。

好的,让我们看一下如何为Redis创建Docker镜像,Dockerfile非常简单,下载redis源码进行编译安装,然后删除所有用于构建它的工具,因为不需要运行它们。

FROM debian:jessie
RUN apt-get update
RUN apt-get install -y gcc libc6-dev make
RUN curl -sSL "http://download.redis.io/releases/redis-3.0.5.tar.gz" -o
redis.tar.gz
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install
# 全部删除
RUN rm -f redis.tar.gz
RUN rm -f /usr/src/redis
RUN apt-get purge -y --auto-remove gcc libc6-dev make
...

Docker使用层layer创建镜像,Dockerfile中每一个命令都会创建一个新的层,每层都包含执行命令前后的状态之间镜像的文件系统更改。在上面的例子中即使我们在最后删除了构建工具,它们仍包含在Docker需要构建当前层的上一层中,因此,我们为缩小镜像所做的所有工作都没有任何效果!

怎样才能让镜像变小?

您可能已经猜到了正确的解决方案,缩小镜像的方法是在我们删除了构建工具和库之后仅创建一个镜像layer,可以通过在Dockerfile中的单个RUN命令中执行以上所有命令来做到这一点。

以下是Docker Hub上Redis Dockerfile(Docker BSD LICENSE)的摘录:

ENV REDIS_VERSION 3.0.5
ENV REDIS_DOWNLOAD_URL http://download.redis.io/releases/redis-3.0.5.tar.gz
ENV REDIS_DOWNLOAD_SHA1 ad3ee178c42bfcfd310c72bbddffbbe35db9b4a6
# for redis-sentinel see: http://redis.io/topics/sentinel
RUN buildDeps='gcc libc6-dev make' \
    && set -x \
    && apt-get update && apt-get install -y $buildDeps --no-install-recommends \
    && rm -rf /var/lib/apt/lists/* \
    && mkdir -p /usr/src/redis \
    && curl -sSL "$REDIS_DOWNLOAD_URL" -o redis.tar.gz \
    && echo "$REDIS_DOWNLOAD_SHA1 *redis.tar.gz" | sha1sum -c - \
    && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
    && rm redis.tar.gz \
    && make -C /usr/src/redis \
    && make -C /usr/src/redis install \
    && rm -r /usr/src/redis \
    && apt-get purge -y --auto-remove $buildDeps

可以看到它安装了所有构建依赖项,下载了Redis源代码,构建了Redis,然后删除了所有构建工具,并在一个RUN命令中全部清除了apt缓存,这确保了Docker仅在删除所有不必要的资源后才创建镜像层。

这不会解决包括整个操作系统的问题,但至少解决了镜像中包含额外的构建依赖项的问题。

依赖的地狱

Docker镜像通常从Debian或Ubuntu镜像继承,需要它们的一个原因是应用程序动态链接到许多库,而这些库是应用程序运行所必需的,造成这种情况的另一个原因是,所有正常的Linux工具都已安装,因此使用apt调试和安装新软件会更加容易。

在Google,数据中心中运行的所有应用程序都在容器内运行,由于需要将二进制文件复制到需要运行它们的计算机上,因此在容器镜像或archive文件中包含一堆东西是一种浪费,会增加额外的网络开销,为了使应用程序正常工作,它们都被构建为静态二进制文件。 我认为这是一种很棒的处理方式,因为它允许应用程序将所有必需的部分内置到镜像中并降低复杂性。 Go默认情况下静态编译应用程序的方式就是因为受到这种编译云原生应用程序的文化的影响。

在稍后有关该主题的文章中,我将对此进行更多介绍。


EngineerLeo
598 声望38 粉丝

专注于云原生、AI等相关技术