在优化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 J9和 Open Liberty的Open 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应用也可以获得类似的效率。这也是我们正在探索的另一个领域。
登录后评论
立即登录 注册