优化Docker中的Spring Boot应用:双层镜像方法

优化Docker中的Spring Boot应用:单层镜像方法中,我们介绍了为Spring Boot应用构建Docker镜像的单层方法及其对CI/CD的影响。我提出了双层方法比单层方法有更大的好处,并且这些好处可以提高开发环境中的迭代效率。

在这里,我们介绍一种使用Open Liberty中的新工具springBootUtility,为现有Spring Boot应用创建双层Docker镜像。目前有多种方法可以为Spring Boot应用创建多层Docker镜像,但是本文方法的重点是从现有应用创建双层镜像,而不是更改maven或gradle构建步骤。

双层方法

在双层方法中,我们构建这样的Docker镜像:以使Spring Boot应用的依赖存在于应用代码下方的一层中。通过将不经常更改的依赖推入一个单独的层,并且仅将应用类保留在顶层,这样迭代重建和重新部署就会更快。

为了做到这一点,我们需要一种将Spring Boot应用拆分为这些独立组件的工具,例如:springBootUtility。

springBootUtility

springBootUtility是Open Liberty中的新工具,它将把Spring Boot应用分为两部分:依赖库(例如Spring Boot启动程序和其他第三方库)以及应用代码。依赖库放置在缓存中,应用代码用于构造一个精简后的应用程序。精简后的应用程序包含一个文件,该文件引用类路径上所需的库。然后可以将此精简应用程序部署到Open Liberty,它将从库缓存中生成完整的类路径。

Docker多阶段构建

用于构建双镜像层的Dockerfile,可以使用多阶段构建。多阶段构建允许单个Dockerfile创建多个镜像,其中一个镜像的内容可以复制到另一个镜像中,从而丢弃临时内容。这使你可以大幅度减小最终镜像的大小,而无需涉及多个Docker文件。我们使用此功能在Docker构建过程中拆分Spring Boot应用。

Docker镜像

Docker镜像可以使用带有Open J9Open LibertyOpen JDK。Open JDK为开源Java技术提供了坚实的基础。与 Open JDK附带的默认Java虚拟机相比, Open J9 带来了一些性能改进。Open Liberty是一个多程序模型运行时,支持Java EE,MicroProfile和Spring。这就方便开发团队可以使用具有运行时堆栈一致的各种编程模型。

Dockerfile示例

Dockerfile(我们将逐步介绍它的每一步操作)。

FROM adoptopenjdk/openjdk8-openj9 as staging

ARG JAR_FILE
ENV SPRING_BOOT_VERSION 2.0

# Install unzip; needed to unzip Open Liberty
RUN apt-get update \
    && apt-get install -y --no-install-recommends unzip \
    && rm -rf /var/lib/apt/lists/*

# Install Open Liberty
ENV LIBERTY_SHA 4170e609e1e4189e75a57bcc0e65a972e9c9ef6e
ENV LIBERTY_URL https://public.dhe.ibm.com/ibmdl/export/pub/software/openliberty/runtime/release/2018-06-19_0502/openliberty-18.0.0.2.zip

RUN curl -sL "$LIBERTY_URL" -o /tmp/wlp.zip \
   && echo "$LIBERTY_SHA  /tmp/wlp.zip" > /tmp/wlp.zip.sha1 \
   && sha1sum -c /tmp/wlp.zip.sha1 \
   && mkdir /opt/ol \
   && unzip -q /tmp/wlp.zip -d /opt/ol \
   && rm /tmp/wlp.zip \
   && rm /tmp/wlp.zip.sha1 \
   && mkdir -p /opt/ol/wlp/usr/servers/springServer/ \
   && echo spring.boot.version="$SPRING_BOOT_VERSION" > /opt/ol/wlp/usr/servers/springServer/bootstrap.properties \
   && echo \
'<?xml version="1.0" encoding="UTF-8"?> \
<server description="Spring Boot Server"> \
  <featureManager> \
    <feature>jsp-2.3</feature> \
    <feature>transportSecurity-1.0</feature> \
    <feature>websocket-1.1</feature> \
    <feature>springBoot-${spring.boot.version}</feature> \
  </featureManager> \
  <httpEndpoint id="defaultHttpEndpoint" host="*" httpPort="9080" httpsPort="9443" /> \
  <include location="appconfig.xml"/> \
</server>' > /opt/ol/wlp/usr/servers/springServer/server.xml \
   && /opt/ol/wlp/bin/server start springServer \
   && /opt/ol/wlp/bin/server stop springServer \
   && echo \
'<?xml version="1.0" encoding="UTF-8"?> \
<server description="Spring Boot application config"> \
  <springBootApplication location="app" name="Spring Boot application" /> \
</server>' > /opt/ol/wlp/usr/servers/springServer/appconfig.xml

# Stage the fat JAR
COPY ${JAR_FILE} /staging/myFatApp.jar

# Thin the fat application; stage the thin app output and the library cache
RUN /opt/ol/wlp/bin/springBootUtility thin \
 --sourceAppPath=/staging/myFatApp.jar \
 --targetThinAppPath=/staging/myThinApp.jar \
 --targetLibCachePath=/staging/lib.index.cache

# unzip thin app to avoid cache changes for new JAR
RUN mkdir /staging/myThinApp \
   && unzip -q /staging/myThinApp.jar -d /staging/myThinApp

# Final stage, only copying the liberty installation (includes primed caches)
# and the lib.index.cache and thin application
FROM adoptopenjdk/openjdk8-openj9

VOLUME /tmp

# Create the individual layers
COPY --from=staging /opt/ol/wlp /opt/ol/wlp
COPY --from=staging /staging/lib.index.cache /opt/ol/wlp/usr/shared/resources/lib.index.cache
COPY --from=staging /staging/myThinApp /opt/ol/wlp/usr/servers/springServer/apps/app

# Start the app on port 9080
EXPOSE 9080
CMD ["/opt/ol/wlp/bin/server", "run", "springServer"]

Dockerfile细节解读

使用Docker的多阶段构建和Open Liberty中的springBootUtility,Dockerfile就可以把Spring Boot应用拆分。

我们把基础镜像openjdk8-openj9命名为stagging镜像。

首先,我们安装unzip。

接下来,我们下载Open Liberty并进行一些配置。所有这些准备工作都需要使用Open Liberty工具。虽然它现在很简陋,但是我们将在Docker镜像18.0.0.2发布 时改进完善。

镜像具有所需的所有工具后,将程序JAR文件复制到stagging镜像中并进行拆分。

在/staging/myFatApp.jar基础上我们会创建一个精简后的应用,将其解压后可以进行进一步的优化。解压使应用程序直接从类文件中托管。如果类文件没有更改,后续重建可以重新使用应用层。

现在准备工作已经完成,然后我们就可以使用COPY命令,拷贝整个Liberty安装,依赖库和精简后的应用。在Dockerfile中,单独的COPY命令会生成单独的层。较大的库依赖层(34.2MB)和较小的应用层(1.01MB)将会是“双重层”的含义。

$ docker history openlibertyio/spring-petclinic
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
883ee6374f66        7 minutes ago       /bin/sh -c #(nop)  CMD ["/opt/ol/wlp/bin/ser…   0B
e3ba1351fc05        7 minutes ago       /bin/sh -c #(nop)  EXPOSE 9080                  0B
86c646de6626        7 minutes ago       /bin/sh -c #(nop) COPY dir:589967d5ae0ade9a5…   1.01MB
8f98ce0a6c10        7 minutes ago       /bin/sh -c #(nop) COPY dir:d764c6a82219ed564…   34.2MB
240306c081cd        7 minutes ago       /bin/sh -c #(nop) COPY dir:0b45938a62d056d88…   200MB
161006b94f8e        22 minutes ago      /bin/sh -c #(nop)  VOLUME [/tmp]                0B
f50ba84462ab        3 weeks ago         /bin/sh -c #(nop)  ENV PATH=/opt/java/openjd…   0B
<missing>           3 weeks ago         /bin/sh -c set -eux;     ARCH="$(dpkg --prin…   193MB
<missing>           3 weeks ago         /bin/sh -c #(nop)  ENV JAVA_VERSION=jdk8u162…   0B
<missing>           3 weeks ago         /bin/sh -c rm -rf /var/lib/apt/lists/* && ap…   16MB
<missing>           3 weeks ago         /bin/sh -c #(nop)  MAINTAINER Dinakar Gunigu…   0B
<missing>           2 months ago        /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>           2 months ago        /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B
<missing>           2 months ago        /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$…   2.76kB
<missing>           2 months ago        /bin/sh -c rm -rf /var/lib/apt/lists/*          0B
<missing>           2 months ago        /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   745B
<missing>           2 months ago        /bin/sh -c #(nop) ADD file:592c2540de1c70763…   113MB

现在,当进行应用更改时,仅需要更改应用层。

Dockerfile运行效果

你可以复制此Dockerfile,并运行它。

$ docker build --build-arg JAR_FILE=target/spring-petclinic-2.0.0.BUILD-SNAPSHOT.jar -t openlibertyio/spring-petclinic .

生成的Docker镜像如下所示:

注意:整个Docker镜像并不像单层方法那么小。因为基本镜像不是基于Alpine Linux的,Liberty的安装也不是最简化。我们也正在努力改善这一点。

未来的方向

到目前为止,我们对所构建的内容感到满意,但是,构建这些镜像的用户体验不是很好。我们将在接下来的几个月中继续努力。还将发布包含预配置的Open Liberty实例的Docker镜像。这将大大降低Dockerfile的复杂性。

我们还认识到,将这些双层构建集成到持续交付中,还有改进的空间。我们也有兴趣去解决在Docker中的Spring Boot应用体验。

最后,这种从应用中分离静态依赖的方法并不是Spring Boot应用独有的!使用Java EE或MicroProfile应用也可以获得类似的效率。这也是我们正在探索的另一个领域。

参考文献

[1] https://github.com/gclayburg/dockerPreparePlugin

K8S中文社区微信公众号

评论 抢沙发

登录后评论

立即登录