20190219

此前,在早些时候,我发表过Distroless与多阶段构建,其中介绍了简单的多阶段构建方式。阅读本文,你将跟快的了解多阶段构建带来的便利以及使用方法。

本文中主要介绍多阶段构建的方式,这种方式本身就可以节省一部分空间,对于如何缩减镜像大小的几种方式类文章总结将会在此后进行编写发布,那将是后面会发生的事情。我们暂且来看多阶段构建带给我们的便利性

为什么镜像会变大?#

Docker就像一个版本控制系统。每次更改都会创建一个新图层。每次在Docker中运行新命令时,它都会创建一个新层。这就是为什么在Dockerfiles中你会看到链接的多个命令。下面的阶段代表一层:

RUN curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py \
         && python get-pip.py \
         && pip install awscli

因此,让我们假设您的容器需要您下载源文件,并构建容器(通常采用静态编译语言)。您必须从容器中删除源文件,否则源将成为镜像的一部分,从而增加了大小。并且,您无法做到,rm -rf $source_files_directory因为它只会创建一个新图层。

你怎么解决这个问题?我们将向您展示旧方式和更好的方式。

旧的方式:build#

如果使用最早的叠加累计的方式进行构建,如果是这样,事实上你使用的是两个docker镜像

您可能已经发现保持图层小的唯一方法是:

例如,以下是使用构建器模式的常见解决方案,用于调出Tomcat容器并在其上部署应用程序:

在甚至于,将常用软件打包在一个镜像中,这些软件包括:git ,jdk,mvn等。这样一来,镜像大小无限扩大,出现问题,排查也不是很方便,如下实例

FROM tomcat:9.0.16-jre8
MAINTAINER www.linuxea.com mark
WORKDIR /usr/src/project
RUN apt-get update \
    && apt-get install -y git openjdk-8-jdk \
    && git clone http://GITUSER:GITPASS@git.ds.com/mark/java.git \
    && wget https://downloads.gradle.org/distributions/gradle-4.8.1-bin.zip \
    && unzip gradle-4.8.1-bin.zip \
    && PATH=$PATH:$PWD/gradle-4.8.1/bin \
    && gradle prod \
    && mv /usr/src/project/build/ROOT.war /usr/local/tomcat/webapps/ROOT.war \
    && rm -rf /usr/src/project/java
COPY entrypoint.sh /
COPY tomcat_conf_prod/* /usr/local/tomcat/conf/
RUN chmod +x /entrypoint.sh

首先,我们进入/usr/src/project目录(或者COPY指令将我们当前的代码添加到容器中)。然后我们需要安装openjdkgrande,git克隆代码,编译它并构建ROOT.war。之后,我们将 ROOT.war文件移动到Tomcat目录中,然后我们进行一些最终配置,以使用entrypoint.sh脚本准备和启动容器。

但是这样的结果可能并不是我们想要的,容器本身看起来很不错,但是该镜像的大小(934MB)很大,尽管我们做了一些挽救措施,删除克隆的代码,甚至于可以删除git,但是最大的问题是jdk,因为这个镜像本身的大小是464M。

那么更好的办法,你可以使用alpine基础镜像包。我们继续往下看。

更妥善的方法:Docker Multi-Stage#

自Docker版本17。05(2017年10月)起可用的Docker Multi-Stage将通过删除不再需要的库,依赖项,包等来减少容器的最终大小。该过程包括:

我们可以将最后一个Dockerfile片段转换为下一个:

我们修改了方式,不在容器中克隆代码,而是复制进容器分段执行

FROM openjdk:8
MAINTAINER www.linuxea.com mark
COPY . /usr/src/project
WORKDIR /usr/src/project
RUN wget https://downloads.gradle.org/distributions/gradle-4.8.1-bin.zip \
    && unzip gradle-4.8.1-bin.zip \
    && PATH=$PATH:$PWD/gradle-4.8.1/bin \
    && gradle prod

FROM tomcat:9.0.16-jre8
MAINTAINER www.linuxea.com mark
COPY --from=0 /usr/src/project/build/ROOT.war /usr/local/tomcat/webapps/ROOT.war
COPY entrypoint.sh /
COPY tomcat_conf_prod/* /usr/local/tomcat/conf/
RUN chmod +x /entrypoint.sh

亦或者这样:

FROM maven:3-jdk-11
MAINTAINER www.linuxea.com mark
COPY linuxea /linuxea
RUN mvn -f /linuxea/pom.xml clean package

FROM marksugar/java:jdk1.8.0_131
MAINTAINER www.linuxea.com mark
COPY --from=build /linuxea /linuxea
EXPOSE 8086
CMD ["/linuxea/target/hello-world-0.0.6.jar"]

Docker Multi-Stage与使用Builder模式相比如何?#

主要区别在于,使用Docker Multi-Stage,我们在同一个Dockerfile中构建了两个不同的镜像。第一个是基于openjdk我们用它来编译我们的代码并生成ROOT.war文件。当我们声明第二个基于Tomcat的镜像时,就会发生改变,我们使用--from=0指令将ROOT.war从第一个镜像复制到第二个镜像。这样做,我们放弃了Gradle(构建工具)用于编译我们的应用程序的所有依赖项,并且只保留最重要的东西,我们的ROOT.war文件。

总结#

学习更多#

学习如何使用Docker CLI命令,Dockerfile命令,使用Bash命令可以帮助你更有效地使用Docker应用程序。查看Docker文档和我的其他帖子以了解更多信息。