SpringBoot与Docker集成

许多人正在使用容器包装其Spring Boot应用程序,而构建容器并不是一件容易的事。这是Spring Boot应用程序开发人员的指南,容器对于开发人员而言并非总是很好的抽象-它们会迫使您学习和思考非常低级的问题-但是有时您会被要求创建或使用容器,因此有必要了解这些基本要素。在这里,我们旨在向您展示一些您需要创建自己的容器时可以做出的选择。

我们将假定您知道如何创建和构建基本的Spring Boot应用程序。如果不这样做,请转到入门指南之一,例如有关构建REST服务的指南。从此处复制代码,并使用以下一些想法进行练习。

  在Docker上也有一个入门指南,这也是一个很好的起点,但是它没有涵盖我们在此所做的选择范围,也没有详细介绍。

基本的Dockerfile

Spring Boot应用程序很容易转换为可执行的JAR文件。所有入门指南都这样做,从Spring Initializr下载的每个应用程序都将具有一个创建可执行JAR的构建步骤。与Maven ./mvnw install和Gradle一起./gradlew build。然后,在项目的顶层,运行该JAR的基本Dockerfile如下所示:

Dockerfile

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

JAR_FILE可在作为的一部分被传递docker命令(这将是Maven和摇篮不同)。例如Maven:

$ docker build --build-arg JAR_FILE=target/*.jar -t myorg/myapp .

对于Gradle:

$ docker build --build-arg JAR_FILE=build/libs/*.jar -t myorg/myapp .

当然,一旦选择了构建系统,就不需要ARG-只需对jar位置进行硬编码即可。例如Maven:

Dockerfile

FROM openjdk:8-jdk-alpine
VOLUME /tmp
COPY target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

然后我们可以简单地用

$ docker build -t myorg/myapp .

并像这样运行它:

$ docker run -p 8080:8080 myorg/myapp.   ____          _            __ _ _/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/  ___)| |_)| | | | | || (_| |  ) ) ) )'  |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot ::        (v2.0.2.RELEASE)Nov 06, 2018 2:45:16 PM org.springframework.boot.StartupInfoLogger logStarting
INFO: Starting Application v0.1.0 on b8469cdc9b87 with PID 1 (/app.jar started by root in /)
Nov 06, 2018 2:45:16 PM org.springframework.boot.SpringApplication logStartupProfileInfo
...

如果您想在图像内部四处浏览,可以像这样打开其中的外壳(基本图像没有bash):

$ docker run -ti --entrypoint /bin/sh myorg/myapp
/ # ls
app.jar  dev      home     media    proc     run      srv      tmp      var
bin      etc      lib      mnt      root     sbin     sys      usr
/ #

  我们在示例中使用的高山基础容器没有bash,因此这是一个ash外壳。它具有某些功能,bash但不是全部。

如果您有一个正在运行的容器,并且想窥视它,请使用docker exec以下方法:

$ docker run --name myapp -ti --entrypoint /bin/sh myorg/myapp
$ docker exec -ti myapp /bin/sh
/ #

其中myapp--name传递给docker run命令。如果您不使用,--name则docker分配一个助记符名称,您可以从的输出中抓取该名称docker ps。您也可以使用容器的sha标识符代替名称,该名称也可以从中看到docker ps

入口

使用了Dockerfile 的exec形式,ENTRYPOINT因此没有包装Java进程的外壳。优点是java进程将响应KILL发送到容器的信号。实际上,这意味着,例如,如果您docker run在本地使用图像,则可以使用停止它CTRL-C。如果命令行有点长,您可以COPY在运行它之前将其提取到shell脚本中,然后提取到映像中。例:

Dockerfile

FROM openjdk:8-jdk-alpine
VOLUME /tmp
COPY run.sh .
COPY target/*.jar app.jar
ENTRYPOINT ["run.sh"]

请记住使用exec java …​启动Java进程(以便它可以处理KILL信号):

run.sh

#!/bin/sh
exec java -jar /app.jar

入口点另一个有趣的方面是您是否可以在运行时将环境变量注入到Java进程中。例如,假设您希望具有在运行时添加java命令lline选项的选项。您可以尝试执行以下操作:

Dockerfile

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","${JAVA_OPTS}","-jar","/app.jar"]

$ docker build -t myorg/myapp .
$ docker run -p 9000:9000 -e JAVA_OPTS=-Dserver.port=9000 myorg/myapp

这将失败,因为${}替换需要外壳程序;exec表单不使用外壳程序来启动进程,因此不会应用选项。您可以通过将入口点移动到脚本(如run.sh上面的示例),或在入口点中显式创建外壳来解决这个问题。例如:

Dockerfile

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"]

然后,您可以使用

$ docker run -p 8080:8080 -e "JAVA_OPTS=-Ddebug -Xmx128m" myorg/myapp
....   ____          _            __ _ _/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/  ___)| |_)| | | | | || (_| |  ) ) ) )'  |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot ::        (v2.2.0.RELEASE)
...
2019-10-29 09:12:12.169 DEBUG 1 --- [           main] ConditionEvaluationReportLoggingListener :============================
CONDITIONS EVALUATION REPORT
============================
...

(显示Spring Boot DEBUG生成的全部输出的一部分-Ddebug。)

ENTRYPOINT与上面的显式shell一起使用,意味着您可以将环境变量传递到java命令中,但是到目前为止,您还不能为Spring Boot应用程序提供命令行参数。此技巧无法在端口9000上运行应用程序:

$ docker run -p 9000:9000 myorg/myapp --server.port=9000.   ____          _            __ _ _/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/  ___)| |_)| | | | | || (_| |  ) ) ) )'  |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot ::        (v2.2.0.RELEASE)
...
2019-10-29 09:20:19.718  INFO 1 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080

它不起作用的原因是因为docker命令(该--server.port=9000部分)传递给了入口点(sh),而不是传递给了它启动的Java进程。要解决此问题,您需要将命令行从添加CMDENTRYPOINT

Dockerfile

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar ${0} ${@}"]

$ docker run -p 9000:9000 myorg/myapp --server.port=9000.   ____          _            __ _ _/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/  ___)| |_)| | | | | || (_| |  ) ) ) )'  |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot ::        (v2.2.0.RELEASE)
...
2019-10-29 09:30:19.751  INFO 1 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 9000

注意${0}“命令”(在此情况下为第一个程序参数)和${@}“命令参数”(其余程序参数)的用法。如果您使用脚本作为入口点,则不需要${0}/app/run.sh在上面的示例中)。例:

run.sh

#!/bin/sh
exec java ${JAVA_OPTS} -jar /app.jar ${@}

到目前为止,docker配置非常简单,并且生成的映像不是很有效。docker映像只有一个文件系统层,其中包含胖子罐,我们对应用程序代码进行的每一次更改都会更改该层,该层可能为10MB或更大(某些应用程序甚至为50MB)。我们可以通过将JAR分为多个层来改善这一点。

较小的图像

请注意,以上示例中的基本图像为openjdk:8-jdk-alpine。这些alpine图像比Dockerhub的标准openjdk库图像小。尚无Java 11的正式高山图像(AdoptOpenJDK已有一段时间,但不再出现在其Dockerhub页面上)。您还可以使用“ jre”标签而不是“ jdk”在基本映像中节省大约20MB。并非所有的应用程序都可以与JRE一起使用(而不是JDK),但是大多数的应用程序都可以,并且确实有些组织会强制执行每个应用程序必须遵循的规则,因为存在滥用某些JDK功能(例如编译)的风险。

可以使您缩小图像的另一个技巧是使用JLink,它与OpenJDK 11捆绑在一起。JLink允许您从完整JDK中的模块子集构建自定义JRE分发,因此您不需要JRE或JDK在基本图像中。原则上,这将使您的总映像大小小于使用openjdk官方docker映像。实际上,您还无法使用alpine带有JDK 11的基本映像,因此您对基本映像的选择将受到限制,并且可能会导致最终映像尺寸更大。另外,您自己的基本映像中的自定义JRE无法在其他应用程序之间共享,因为它们将需要不同的自定义。因此,对于所有应用程序来说,它们可能都有较小的映像,但是启动它们仍需要更长的时间,因为它们无法从缓存JRE层中受益。

最后一点突出了图像构建者的一个真正重要的关注点:目标不一定总是要构建尽可能小的图像。通常,较小的图像是一个好主意,因为它们只需要较少的时间就可以上传和下载,但是前提是它们中的所有层都没有被缓存。如今,图像注册表非常复杂,通过尝试巧妙地构建图像,您很容易失去这些功能的优势。如果使用公共基础层,则图像的总大小将变得无关紧要,并且随着注册管理机构和平台的发展,图像的总大小可能会变得更小。话虽如此,尝试并优化我们的应用程序映像中的各层仍然很重要且有用,但是目标始终应该是将变化最快的东西放在最高层中,

更好的Dockerfile

由于罐子本身的包装方式,Spring Boot胖子罐子自然具有“层”。如果我们先拆包,它将已经分为内部和外部依赖关系。为了在Docker构建中一步一步做到这一点,我们需要首先解压jar。例如(坚持使用Maven,但Gradle版本非常相似):

$ mkdir target/dependency
$ (cd target/dependency; jar -xf ../*.jar)
$ docker build -t myorg/myapp .

有了这个 Dockerfile

Dockerfile

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG DEPENDENCY=target/dependency
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

现在有3层,所有应用程序资源都位于后面的2层中。如果应用程序依存关系不变,那么第一层(from BOOT-INF/lib)将不会改变,因此构建会更快,并且只要基本层已经被缓存,容器在运行时的启动也会更快。

  我们使用了一个硬编码的主应用程序类hello.Application。对于您的应用程序,这可能会有所不同。如果需要,可以与另一个参数化ARG。您也可以将Spring Boot fat复制JarLauncher到映像中,然后使用它来运行应用程序-它可以工作,并且不需要指定主类,但是启动时会慢一些。

调整

如果您想尽快启动您的应用程序(大多数人都这样做),则可以考虑一些调整。这里有一些想法:

  • 使用spring-context-indexer(指向docs的链接)。对于小型应用程序而言,它不会增加太多,但对您有所帮助。

  • 如果负担不起,请不要使用执行器。

  • 使用Spring Boot 2.1和Spring 5.1。

  • 使用(命令行参数或系统属性等)修复Spring Boot配置文件的位置spring.config.location

  • 关闭JMX-您可能不需要在容器中使用- spring.jmx.enabled=false

  • 使用运行JVM -noverify。还请考虑-XX:TieredStopAtLevel=1(这将在以后降低JIT速度,但会节省启动时间)。

  • 使用Java 8的容器内存提示:-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap。对于Java 11,默认情况下是自动的。

您的应用程序在运行时可能不需要完整的CPU,但需要多个CPU才能尽快启动(至少2、4个更好)。如果您不介意启动速度较慢,则可以将CPU的速度降低到4以下。如果您被迫以少于4个CPU的速度启动,则可能会有所帮助,-Dspring.backgroundpreinitializer.ignore=true因为这会阻止Spring Boot创建可能不会创建的新线程能够使用(适用于Spring Boot 2.1.0及更高版本)。

多阶段构建

Dockerfile上述假设脂肪JAR已经建在命令行上。您也可以使用多阶段构建在Docker中执行此步骤,将结果从一个映像复制到另一个映像。使用Maven的示例:

Dockerfile

FROM openjdk:8-jdk-alpine as build
WORKDIR /workspace/appCOPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
COPY src srcRUN ./mvnw install -DskipTests
RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG DEPENDENCY=/workspace/app/target/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

第一个图像标记为“ build”,它用于运行Maven并构建胖罐,然后解压缩它。拆包也可以由Maven或Gradle完成(这是《入门指南》中采用的方法)-确实没有太大区别,只是必须编辑构建配置并添加插件。

请注意,源代码已分为4层。后面的层包含构建配置和应用程序的源代码,而前面的层包含构建系统本身(Maven包装器)。这是一个很小的优化,这也意味着我们不必将target目录复制到docker映像,即使是用于构建的临时目录也是如此。

源代码更改的每个构建都会很慢,因为必须在第一RUN部分中重新创建Maven缓存。但是您拥有一个完全独立的构建,只要拥有docker,任何人都可以运行它来使您的应用程序运行。在某些环境中,例如在需要与不懂Java的人共享代码的环境中,这很有用。

实验特征

Docker 18.06带有一些“实验性”功能,其中包括一种缓存构建依赖项的方法。要打开它们,您需要在守护程序(dockerd)中有一个标志,并且在运行客户端时还需要一个环境变量,然后可以在其中添加魔术第一行Dockerfile

Dockerfile

# syntax=docker/dockerfile:experimental

然后该RUN指令接受一个新标志--mount。这是一个完整的示例:

Dockerfile

# syntax=docker/dockerfile:experimental
FROM openjdk:8-jdk-alpine as build
WORKDIR /workspace/appCOPY mvnw .
COPY .mvn .mvn
COPY pom.xml .
COPY src srcRUN --mount=type=cache,target=/root/.m2 ./mvnw install -DskipTests
RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG DEPENDENCY=/workspace/app/target/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

然后运行它:

$ DOCKER_BUILDKIT=1 docker build -t myorg/myapp .
...=> /bin/sh -c ./mvnw install -DskipTests              5.7s=> exporting to image                                 0.0s=> => exporting layers                                0.0s=> => writing image sha256:3defa...=> => naming to docker.io/myorg/myapp

使用实验性功能,您可以在控制台上获得不同的输出,但是您可以看到,一旦缓存变热,Maven构建现在只需要几秒钟而不是几分钟。

Dockerfile配置的Gradle版本非常相似:

Dockerfile

# syntax=docker/dockerfile:experimental
FROM openjdk:8-jdk-alpine AS build
WORKDIR /workspace/appCOPY . /workspace/app
RUN --mount=type=cache,target=/root/.gradle ./gradlew clean build
RUN mkdir -p build/dependency && (cd build/dependency; jar -xf ../libs/*.jar)FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG DEPENDENCY=/workspace/app/build/dependency
COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

  这些功能处于试验阶段时,用于打开和关闭buildkit的选项取决于docker您所使用的版本。检查文档以了解您拥有的版本(上面的示例对于docker18.0.6 是正确的)。

安全方面

就像在传统的VM部署中一样,不应使用root权限运行进程。相反,映像应包含运行该应用程序的非root用户。

在中Dockerfile,可以通过添加另一层来添加一个(系统)用户和组,然后将其设置为当前用户(而不是默认用户root)来实现:

Dockerfile

FROM openjdk:8-jdk-alpineRUN addgroup -S demo && adduser -S demo -G demo
USER demo...

如果有人设法突破您的应用程序并在容器内运行系统命令,这将限制他们的功能(特权最小的原则)。

  一些其他Dockerfile命令仅以root用户身份运行,因此也许您必须将USER命令进一步向下移动(例如,如果您打算将更多软件包安装到仅以root用户身份使用的容器中)。
  不使用的其他方法Dockerfile可能更合适。例如,在稍后描述的buildpack方法中,大多数实现默认情况下将使用非root用户。

构建插件

如果您不想docker直接在构建中调用,那么Maven和Gradle就有很多丰富的插件可以为您工作。这里仅仅是少数。

Spotify Maven插件

在Spotify的Maven插件是一个受欢迎的选择。它要求应用程序开发人员编写a Dockerfile,然后docker为您运行,就像您在命令行上一样。docker image标签和其他内容有一些配置选项,但它使您的应用程序中的docker知识集中在Dockerfile,很多人都喜欢。

对于真正的基本用法,它无需额外配置即可直接使用:

$ mvn com.spotify:dockerfile-maven-plugin:build
...
[INFO] Building Docker context /home/dsyer/dev/demo/workspace/myapp
[INFO]
[INFO] Image will be built without a name
[INFO]
...
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.630 s
[INFO] Finished at: 2018-11-06T16:03:16+00:00
[INFO] Final Memory: 26M/595M
[INFO] ------------------------------------------------------------------------

这将构建一个匿名docker镜像。我们现在可以docker在命令行上用标记,或者使用Maven配置将其设置为repository。示例(不更改pom.xml):

$ mvn com.spotify:dockerfile-maven-plugin:build -Ddockerfile.repository=myorg/myapp

或在pom.xml

pom.xml

<build><plugins><plugin><groupId>com.spotify</groupId><artifactId>dockerfile-maven-plugin</artifactId><version>1.4.8</version><configuration><repository>myorg/${project.artifactId}</repository></configuration></plugin></plugins>
</build>

Palantir Gradle插件

该真知晶球摇篮插件工作有Dockerfile,它也能产生一个Dockerfile适合你,然后它运行docker,如果你是在命令行中运行它。

首先,您需要将插件导入您的build.gradle

build.gradle

buildscript {...dependencies {...classpath('gradle.plugin.com.palantir.gradle.docker:gradle-docker:0.13.0')}
}

然后最后应用插件并调用其任务:

build.gradle

apply plugin: 'com.palantir.docker'group = 'myorg'bootJar {baseName = 'myapp'version =  '0.1.0'
}task unpack(type: Copy) {dependsOn bootJarfrom(zipTree(tasks.bootJar.outputs.files.singleFile))into("build/dependency")
}
docker {name "${project.group}/${bootJar.baseName}"copySpec.from(tasks.unpack.outputs).into("dependency")buildArgs(['DEPENDENCY': "dependency"])
}

在此示例中,我们选择将Spring Boot胖子罐解压缩到build目录中的特定位置,该目录是docker构建的根目录。然后,上面的多层(不是多阶段)Dockerfile将起作用。

Jib Maven和Gradle插件

Google有一个名为Jib的开源工具,它相对较新,但出于多种原因却很有趣。可能最有趣的事情是您不需要docker来运行它-它使用与您获得的输出相同的标准输出来构建映像,docker builddocker除非您要求使用否则不使用-因此它可以在没有docker的环境中工作已安装(在构建服务器中并不罕见)。您也不需要Dockerfile(无论如何都会被忽略),也不需要任何东西pom.xml来获得在Maven中构建的映像(Gradle要求您至少在中安装插件build.gradle)。

Jib的另一个有趣的功能是,它对层有看法,并且以与Dockerfile上面创建的多层略有不同的方式优化了它们。就像在胖子罐中一样,Jib将本地应用程序资源与依赖项分离开来,但它走得更远,而且还将快照依赖项放入一个单独的层中,因为它们更容易发生变化。有一些配置选项可用于进一步自定义布局。

Maven的示例(不更改pom.xml):

$ mvn com.google.cloud.tools:jib-maven-plugin:build -Dimage=myorg/myapp

要运行上述命令,您将需要具有在myorg存储库前缀下推送到Dockerhub的权限。如果您docker在命令行上通过进行了身份验证,则可以在本地~/.docker配置中使用。您还可以在您的仓库中设置Maven“服务器”身份验证~/.m2/settings.xmlid存储库的身份很重要):

settings.xml

    <server><id>registry.hub.docker.com</id><username>myorg</username><password>...</password></server>

还有其他选项,例如,您可以docker使用dockerBuild目标而不是来针对docker守护程序在本地构建(例如在命令行上运行)build。还支持其他容器注册表,对于每个容器注册表,您将需要通过docker或Maven设置来设置本地身份验证。

一旦将gradle插件放入,它就具有类似的功能build.gradle,例如

build.gradle

plugins {...id 'com.google.cloud.tools.jib' version '1.8.0'
}

或使用入门指南中使用的较旧样式:

build.gradle

buildscript {repositories {maven {url "https://plugins.gradle.org/m2/"}mavenCentral()}dependencies {classpath('org.springframework.boot:spring-boot-gradle-plugin:2.2.1.RELEASE')classpath('com.google.cloud.tools.jib:com.google.cloud.tools.jib.gradle.plugin:1.8.0')}
}

然后您可以用

$ ./gradlew jib --image=myorg/myapp

与Maven构建一样,如果您docker在命令行上使用进行了身份验证,则映像推送将从您的本地~/.docker配置进行身份验证。

持续集成

如今(或应该如此),自动化已成为每个应用程序生命周期的一部分。人们用来实现自动化的工具往往非常擅长从源代码调用构建系统。因此,如果您得到一个docker映像,并且构建代理中的环境与开发人员自己的环境充分匹配,这可能就足够了。向Docker注册表进行身份验证可能是最大的挑战,但是所有自动化工具中都有一些功能可以帮助实现这一点。

但是,有时最好将容器创建完全留给自动化层,在这种情况下,可能不需要污染用户的代码。容器创建非常棘手,开发人员有时并不真正在意它。如果用户代码更简洁,则其他工具更有可能“做正确的事”,应用安全修复程序,优化缓存等。自动化有多种选择,并且这些天它们都将具有与容器相关的某些功能。我们只看几个。

Concourse

Concourse是基于管道的自动化平台,可用于CI和CD。它在Pivotal内部大量使用,该项目的主要作者在那里工作。除CLI外,Concourse中的所有内容都是无状态的,并且所有内容都在容器中运行。由于运行容器是自动化管道的主要业务顺序,因此很好地支持创建容器。该泊坞窗图像资源负责保持你构建的输出状态更新,如果它是一个容器图像。

这是一个示例管道,为上面的示例构建一个docker映像,假设它位于github上,myorg/myapp并且Dockerfile在根目录下有一个,并且在以下位置有一个构建任务声明src/main/ci/build.yml

resources:
- name: myapptype: gitsource:uri: https://github.com/myorg/myapp.git
- name: myapp-imagetype: docker-imagesource:email: {{docker-hub-email}}username: {{docker-hub-username}}password: {{docker-hub-password}}repository: myorg/myappjobs:
- name: mainplan:- task: buildfile: myapp/src/main/ci/build.yml- put: myapp-imageparams:build: myapp

管道的结构非常具有声明性:您可以定义“资源”(输入或输出或两者兼有)和“作业”(使用动作并将其应用于资源)。如果任何输入资源发生更改,则会触发新的构建。如果作业期间任何输出资源发生更改,则将对其进行更新。

可以在与应用程序源代码不同的位置定义管道。对于通用构建设置,任务声明也可以集中或外部化。如果这是滚动的方式,则可以将开发和自动化之间的关注点分离开。

Jenkins

Jenkins是另一种流行的自动化服务器。它具有广泛的功能,但在此处与其他自动化示例最接近的功能是管道功能。这是一个Jenkinsfile使用Maven构建Spring Boot项目,然后使用Dockerfile构建图像并将其推送到存储库的:

Jenkinsfile

node {checkout scmsh './mvnw -B -DskipTests clean package'docker.build("myorg/myapp").push()
}

对于需要在构建服务器中进行身份验证的(现实的)泊坞库,您可以使用将凭证添加到上述docker对象docker.withCredentials(…​)

构建包

Cloud Foundry多年来一直在内部使用容器,用于将用户代码转换为容器的技术的一部分是Build Packs,该思想最初是从Heroku借来的。当前的buildpacks(v2)生成通用二进制输出,该输出由平台组装到容器中。在新一代buildpacks的(v3)是Heroku与其他公司(包括Pivotal)之间的合作,它直接且显式地构建了容器映像。这对于开发人员和操作员而言非常有趣。开发人员不需要太在乎如何构建容器的细节,但是如果需要,他们可以轻松地创建一个容器。Buildpacks还具有许多用于缓存生成结果和依赖项的功能,因此,Buildpack的运行速度要比本地Docker构建快得多。操作员可以扫描容器以审核其内容,并对其进行转换以修补它们以进行安全更新。您可以在本地(例如,在开发人员机器上或在CI服务中)或在Cloud Foundry之类的平台上运行构建包。

buildpack生命周期的输出是一个容器映像,但是您不需要docker或a Dockerfile,因此它对CI和自动化友好。输出映像中的文件系统层由buildpack控制,通常,将进行许多优化而无需开发人员知道或关心它们。在较低层(如包含操作系统的基础映像)与较高层(如包含中间件和特定于语言的依赖关系)之间还有一个应用程序二进制接口。如果存在安全更新,则Cloud Foundry这样的平台可以修补较低的层,而不会影响应用程序的完整性和功能。

为了让您了解buildpack的功能,这里是一个从命令行使用Pack CLI的示例(它可以与我们在本指南中使用的示例应用程序一起使用,不需要Dockerfile任何特殊的构建配置):

$ pack build myorg/myapp --builder=cloudfoundry/cnb:bionic --path=.
2018/11/07 09:54:48 Pulling builder image 'cloudfoundry/cnb:bionic' (use --no-pull flag to skip this step)
2018/11/07 09:54:49 Selected run image 'packs/run' from stack 'io.buildpacks.stacks.bionic'
2018/11/07 09:54:49 Pulling run image 'packs/run' (use --no-pull flag to skip this step)
*** DETECTING:
2018/11/07 09:54:52 Group: Cloud Foundry OpenJDK Buildpack: pass | Cloud Foundry Build System Buildpack: pass | Cloud Foundry JVM Application Buildpack: pass
*** ANALYZING: Reading information from previous image for possible re-use
*** BUILDING:
-----> Cloud Foundry OpenJDK Buildpack 1.0.0-BUILD-SNAPSHOT
-----> OpenJDK JDK 1.8.192: Reusing cached dependency
-----> OpenJDK JRE 1.8.192: Reusing cached launch layer-----> Cloud Foundry Build System Buildpack 1.0.0-BUILD-SNAPSHOT
-----> Using Maven wrapperLinking Maven Cache to /home/pack/.m2
-----> Building applicationRunning /workspace/app/mvnw -Dmaven.test.skip=true package
...
---> Running in e6c4a94240c2
---> 4f3a96a4f38c
---> 4f3a96a4f38c
Successfully built 4f3a96a4f38c
Successfully tagged myorg/myapp:latest
$ docker run -p 8080:8080 myorg/myapp.   ____          _            __ _ _/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/  ___)| |_)| | | | | || (_| |  ) ) ) )'  |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot ::        (v2.0.5.RELEASE)2018-11-07 09:41:06.390  INFO 1 --- [main] hello.Application: Starting Application on 1989fb9a00a4 with PID 1 (/workspace/app/BOOT-INF/classes started by pack in /workspace/app)
...

--builder是一个运行buildpack生命周期的docker映像-通常,它将是所有开发人员或单个平台上所有开发人员的共享资源。您可以在命令行上设置默认构建器(在中创建文件~/.pack),然后在后续构建中忽略该标志。

  cloudfoundry/cnb:bionic构建器还知道如何从可执行的jar文件构建映像,因此您可以mvnw先使用构建,然后再将其指向--pathjar文件以获得相同的结果。

Knative

容器和平台领域的另一个新项目是Knative。Knative有很多东西,但是如果您不熟悉Knative,则可以将其视为构建无服务器平台的基础。它基于Kubernetes构建,因此最终它会使用容器映像,并将它们转换为平台上的应用程序或“服务”。但是,它的主要功能之一是能够使用源代码并为您构建容器,从而使其对开发人员和操作员更友好。基建是执行此操作的组件,它本身就是一个将用户代码转换为容器的灵活平台-您几乎可以按照自己喜欢的任何方式进行操作。一些模板提供了通用模式,例如Maven和Gradle构建,以及使用Kaniko的多阶段docker构建。还有一个使用Buildpacks的模板,这对我们来说很有趣,因为buildpacks一直对Spring Boot具有良好的支持。用户还可以使用Riff和Pivotal Function Service在Knative上进行构建包选择,以将用户功能转换为正在运行的无服务器应用程序。

结语

本指南提供了许多用于为Spring Boot应用程序构建容器映像的选项。所有这些都是完全有效的选择,现在由您决定需要哪一个。您的第一个问题应该是“我真的需要构建容器映像吗?” 如果答案是“是”,那么您的选择可能会受到效率和可缓存性以及关注点分离的影响。您是否希望使开发人员不必过多地了解如何创建容器映像?当需要修补操作系统和中间件漏洞时,是否要让开发人员负责更新映像?也许开发人员需要对整个过程进行完全控制,并且他们拥有所需的所有工具和知识。

SpringBoot与Docker集成相关推荐

  1. SpringBoot 2.x 集成 Redis

    SpringBoot 2.x 集成 Redis windows上搭建redis环境 添加依赖 此处redis客户端使用jedis. <!-- redis --> <dependenc ...

  2. springboot security 权限校验_十二、SpringBoot 优雅的集成Spring Security

    前言 至于什么是Spring security ,主要两个作用,用户认证和授权.即我们常说的,用户只有登录了才能进行其他操作,没有登录的话就重定向到登录界面.有的用户有权限执行某一操作,而有的用户不能 ...

  3. SpringBoot项目Docker化并上传DockerHub的使用过程

    . springboot项目 docker化 添加依赖 代码片段:<plugin><groupId>com.spotify</groupId><artifac ...

  4. SpringBoot与SpringCloud集成

    SpringBoot与SpringCloud集成 : 简介 Spring Cloud是一系列框架的有序集合.它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册 ...

  5. SpringBoot和Elasticsearch集成

    SpringBoot和Elasticsearch的集成: 步骤 依赖 在Maven的pom文件中 123456789 <!--SpringBoot默认使用SpringData ElasticSe ...

  6. springboot与docker整合

    一.springboot与docker整合 a.创建Dockerfile FROM java MAINTAINER "Wing"<1561815137@qq.com> ...

  7. SpringBoot使用JWT集成Ng-Alain之Token失效处理

    在 SpringBoot使用JWT集成Ng-Alain中,我们简单介绍了SpringBoot与Ng-Alain的集成,在这种前后端分离框架实践中,我们使用了JWT来作为交互的安全标识,考虑一个问题,从 ...

  8. 一个springboot 项目a集成另一个springboot 项目b

    一个springboot 项目a集成另一个springboot 项目b 并且可以运行访问b的controller层 操作1: 项目b打包依赖修改,把上面的springboot默认打包依赖注释,改为下面 ...

  9. springboot打包docker镜像部署

    springboot打包docker镜像部署 环境准备 机器 vultr一台,centos7 资源下载 jdk8 maven git yum install git docker yum instal ...

最新文章

  1. Hugo快速搭建Blog
  2. linux通配符和正则表达式的区别总结
  3. Git :LF will be replaced by CRLF in readme.txt的原因与解决方案
  4. Java基础day15
  5. 请举例说明如何在Spring 中注入一个Java 集合?
  6. SAP Spartacus 404 Not found页面的显示机制 - canActivateNotFoundPage
  7. jsp空白页面传html代码,echarts在HTML里测试一般,在jsp页面不显示,而且还把整个页面变成空白...
  8. 直播丨国产最强音:HTAP融合型分布式数据库EsgynDB架构详解
  9. 变了,iPhone 12变身iPhone 4模样;下一代只支持单种5G频段?
  10. win10 + python3.6.1 + tensorflow1.10 + cuda9.0 + cudnn7.2
  11. S5PV210体系结构与接口09:SD卡启动详解
  12. 蓝牙MESH学习笔记
  13. VMware 11.0 简体中文版|附永久密钥
  14. 码栈使用手册(二)---界面介绍
  15. 用LNMP+wordpress搭了一个网站
  16. 用pageOffice插件实现 word文档在线填充指定数据
  17. Viz-artist常用脚本操作
  18. 【houdini vop】Block
  19. 对学计算机学生礼仪,计算机系学生分会社团部主持人和礼仪队选拔大赛计划1.doc...
  20. 【认证】数字电视高清认证情况及关键技术指标

热门文章

  1. mysql —— 分表分区
  2. Docker logs 命令——查看docker容器日志
  3. 【shell资源限制】RLIMIT_MEMLOCK too small
  4. 【今日CV 计算机视觉论文速览 第124期】Tue, 4 Jun 2019
  5. 子查询 不同情况 mysql
  6. c#winform演练 ktv项目 通过下标选中歌曲并且列表高亮
  7. python 列表的增删改查操做1125 元组 的查操做
  8. 窗体间的跳转传值 1124
  9. 如何使用idea生成javaDoc文档
  10. python-字符串的切片操作