Spring Boot Docker入门模板与4个最佳实践

在本博客中,您将学习一些主要针对 Spring Boot 应用程序的 Docker 最佳实践。您将通过将这些实践应用到示例应用程序来学习这些实践。享受!

入门模板
将用作入门模板起点的 Dockerfile 如下:


FROM eclipse-temurin:17.0.5_8-jre-alpine@sha256:02c04793fa49ad5cd193c961403223755f9209a67894622e05438598b32f210e
WORKDIR /opt/app
RUN addgroup --system javauser && adduser -S -s /usr/sbin/nologin -G javauser javauser
ARG JAR_FILE
COPY target/${JAR_FILE} app.jar
RUN chown -R javauser:javauser .
USER javauser
ENTRYPOINT ["java", "-jar", "app.jar"]

这个 Dockerfile 执行以下操作:

  • FROM:以eclipse-temurin:17Java Docker镜像为基础镜像;
  • WORKDIR:设置/opt/app为工作目录;
  • RUN:创建系统组和系统用户;
  • ARG:提供一个参数JAR_FILE,这样就不必将 jar 文件名硬编码到 Dockerfile 中;
  • COPY:将jar文件复制到Docker镜像中;
  • RUN:将 的所有者更改WORKDIR为之前创建的系统用户;
  • USER:确保使用之前创建的系统用户;
  • ENTRYPOINT:启动 Spring Boot 应用程序。

在接下来的部分中,您将更改此 Dockerfile 以遵循最佳实践。每个段落生成的 Dockerfile 可在 git 存储库的Dockerfiles目录中找到。在每个段落的末尾,将在适用的情况下提及相应的最终 Dockerfile 的名称。

本博客中使用的代码可在GitHub上找到。

1、健康检查
将运行状况检查添加到 Dockerfile 中,以暴露容器的运行状况。根据此状态,可以重新启动容器。这可以通过HEALTHCHECK命令来完成。添加以下健康检查:

EALTHCHECK --interval=30s --timeout=3s --retries=1 CMD wget -qO- http://localhost:8080/actuator/health/ | grep UP || exit 1

  • interval间隔:每 30 秒执行一次愈合检查。对于生产应用,最好选择 5 分钟这样的间隔。为了进行一些测试,选择一个较小的值会比较容易。这样就不必每次都等待五分钟。
  • timeout超时:执行健康检查的三秒超时。
  • retries重试:表示在健康状态发生变化之前必须连续执行的检查次数。默认值为 3,这在生产中是个不错的数字。出于测试目的,可将其设置为一次,这意味着在一次不成功的检查后,健康状态将变为不健康。
  • command命令:Spring 激励器端点将用作健康检查。获取响应并将其导入 grep,以验证健康状态是否为 UP。建议不要为此目的使用 curl,因为并非每个镜像都有 curl。你需要在镜像中额外安装 curl,这会使镜像增大数 MB。


2、Docker Compose
Docker Compose 为您提供了用一条命令同时启动多个容器的机会。除此之外,它还能让你记录你的服务,即使你只有一个服务需要管理。Docker Compose 过去是与 Docker 分开安装的,但如今它已成为 Docker 本身的一部分。你需要编写一个包含该配置的 compose.yml 文件。让我们看看在健康检查中使用的两个容器是如何配置的。

services:
  dockerbestpractices:
    image: mydeveloperplanet/dockerbestpractices:0.0.1-SNAPSHOT
 
  autoheal:
    image: willfarrell/autoheal:1.2.0
    restart: always
    environment:
      AUTOHEAL_CONTAINER_LABEL: all
    volumes:
      - type: bind
        source: /var/run/docker.sock
        target: /var/run/docker.sock

配置了两个服务(读作:容器)。一个用于 dockerbestpractices 镜像,一个用于 autoheal 镜像。自动修复镜像会在重启后重新启动,它定义了一个环境变量,并挂载了一个卷。

在可以找到 compose.yml 文件的目录下执行以下命令:
$ docker compose up

在日志记录中,你会看到两个容器都已启动。打开另一个终端窗口,导航到可以找到 compose.yml 的目录。很多命令都可以与 Docker Compose 结合使用。

3、多阶段构建
有时,在 Docker 容器中构建应用程序会很方便。这样做的好处是,你不需要在系统中安装完整的开发环境,而且可以更方便地交换开发环境。不过,在容器内构建应用程序也有一个问题。尤其是当您想使用同一个容器运行应用程序时。源代码和完整的开发环境将出现在生产容器中,从安全角度来看,这不是一个好主意。

您可以编写单独的 Dockerfile 来规避这个问题:一个用于构建,一个用于运行应用程序。但这相当麻烦。解决办法是使用多阶段构建。

使用多阶段构建,可以将构建阶段与运行阶段分开。Dockerfile 文件如下:

FROM maven:3.8.6-eclipse-temurin-17-alpine@sha256:e88c1a981319789d0c00cd508af67a9c46524f177ecc66ca37c107d4c371d23b AS builder
WORKDIR /build
COPY . .
RUN mvn clean package -DskipTests
 
FROM eclipse-temurin:17.0.5_8-jre-alpine@sha256:02c04793fa49ad5cd193c961403223755f9209a67894622e05438598b32f210e
WORKDIR /opt/app
RUN addgroup --system javauser && adduser -S -s /usr/sbin/nologin -G javauser javauser
COPY --from=builder /build/target/mydockerbestpracticesplanet-0.0.1-SNAPSHOT.jar app.jar
RUN chown -R javauser:javauser .
USER javauser
HEALTHCHECK --interval=30s --timeout=3s --retries=1 CMD wget -qO- http://localhost:8080/actuator/health/ | grep UP || exit 1
ENTRYPOINT [
"java", "-jar", "app.jar"]


如您所见,该 Dockerfile 包含两条 FROM 语句。第一条用于构建应用程序:

  • FROM:包含 Maven 和 Java 17 的 Docker 镜像,这是构建应用程序所需的;
  • WORKDIR:设置工作目录;
  • COPY:将当前目录复制到容器中的工作目录;
  • RUN:构建 jar 文件的命令。

FROM 语句中还添加了其他内容。最后,添加 AS 生成器。这样,这个容器就有了标签,可以用来构建运行应用程序的镜像。第二部分与之前的 Dockerfile 完全相同,除了两行。

删除以下两行:
ARG JAR_FILE
COPY target/${JAR_FILE} app.jar

这几行确保将本地构建的 jar 文件复制到镜像中。这几行被替换为下面一行:
COPY --from=builder /build/target/mydockerbestpracticesplanet-0.0.1-SNAPSHOT.jar app.jar

通过这一行,您可以表明要将文件从生成容器复制到新镜像中。

当你构建这个 Dockerfile 时,你会发现构建容器会执行构建,最后创建了用于运行应用程序的镜像。在构建镜像的过程中,你还会注意到所有 Maven 依赖项都已下载。

生成的 Dockerfile 可在 git 仓库中找到,名称为 7-Dockerfile-multi-stage-build。

4、Spring Boot Docker 层

  • Docker 镜像由层组成。
  • Dockerfile 中的每一条命令都会产生一个新层。
  • 当你初始化 Docker 镜像时,所有层都会被检索和存储。
  • 如果你更新了 Docker 镜像,但只更改了 jar 文件,那么其他层就不会被重新检索。

这样,Docker 镜像的存储效率会更高。

但是,在使用 Spring Boot 时,会创建一个胖 jar。
也就是说,当你只修改了部分代码时,就会创建一个新的胖jar,其依赖关系保持不变。
因此,每次创建新的 Docker 镜像时,都会在新的层中添加几兆字节,而没有任何必要。

简而言之,Spring Boot 可以将胖 jar 分割成多个目录:

  • /dependencies
  • /spring-boot-loader
  • /snapshot-dependencies
  • /application

应用程序代码将存放在 application 目录中,而依赖项则存放在 dependencies 目录中。

为此,您将使用多阶段构建。

第一阶段将把 jar 文件复制到 JDK Docker 镜像中,然后提取胖 jar。

FROM eclipse-temurin:17.0.4.1_1-jre-alpine@sha256:e1506ba20f0cb2af6f23e24c7f8855b417f0b085708acd9b85344a884ba77767 AS builder
WORKDIR application
ARG JAR_FILE
COPY target/${JAR_FILE} app.jar
RUN java -Djarmode=layertools -jar app.jar extract

第二部分将把分割的目录复制到新的镜像中。COPY 命令会替换 jar 文件。

FROM eclipse-temurin:17.0.4.1_1-jre-alpine@sha256:e1506ba20f0cb2af6f23e24c7f8855b417f0b085708acd9b85344a884ba77767
WORKDIR /opt/app
RUN addgroup --system javauser && adduser -S -s /usr/sbin/nologin -G javauser javauser
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
RUN chown -R javauser:javauser .
USER javauser
HEALTHCHECK --interval=30s --timeout=3s --retries=1 CMD wget -qO- http://localhost:8080/actuator/health/ | grep UP || exit 1
ENTRYPOINT [
"java", "org.springframework.boot.loader.JarLauncher"]

构建并运行容器。在运行容器时,你不会注意到任何不同之处。主要优势在于 Docker 镜像的存储方式。

生成的 Dockerfile 位于 git 仓库,名称为 8-Dockerfile-spring-boot-docker-layers。