目录
一、前言
1、为什么容器化 Spring Boot 应用程序很重要?
2、一个简单的“Hello World”Spring Boot 应用程序
二、容器化 Spring Boot 代码的 9 个技巧
1、自动化所有手动步骤
2、使用特定的基础图像标签,而不是最新的
3、如果可能,使用 Eclipse Temurin 而不是 JDK
4、 使用多阶段构建
5、使用 .dockerignore
6、支持多架构 Docker 镜像
7、出于安全考虑,以非 root 用户身份运行
8、修复 Java 映像中的安全漏洞
9、使用 OpenTelemetry API 测量 Java 性能
结论
大量开发人员使用 Docker 容器来打包他们的Spring Boot应用程序。根据VMWare 的 2021 年春季状况报告,运行容器化 Spring 应用程序的组织数量飙升至 57%,而 2020 年为 44%。
是什么推动了这一显着增长?对减少 Web 应用程序的启动时间和优化资源使用的需求不断增长,这极大地提高了开发人员的工作效率。
在 Docker 容器中运行 Spring Boot 应用程序有很多好处。首先,Docker 友好的、基于 CLI 的工作流程允许开发人员为所有技能水平的其他开发人员构建、共享和运行容器化的 Spring 应用程序。其次,开发人员可以从一个包中安装他们的应用程序,并在几分钟内启动并运行。第三,Spring 开发者可以在本地编码和测试,同时保证开发和生产的一致性。
将 Spring Boot 应用程序容器化很容易。您可以通过将
.jar
or.war
文件直接复制到 JDK 基础映像中,然后将其打包为 Docker 映像来执行此操作。网上有很多文章可以帮助您有效地打包您的应用程序。然而,许多重要的问题,如 Docker 镜像漏洞、镜像膨胀、缺少镜像标签和糟糕的构建性能都没有得到解决。我们将解决这些常见问题,同时分享九个容器化 Spring Boot 代码的技巧。
为了更好地理解无人关注的问题,让我们构建一个示例“Hello World”应用程序。通过下载这个预先初始化的项目并生成一个 ZIP 文件来应用程序。然后解压缩并完成以下步骤以运行该应用程序。
在该目录下,您可以使用以下内容src/main/java/com/example/dockerapp/
修改您的文件:DockerappApplication.java
package com.example.dockerapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class DockerappApplication {
@RequestMapping("/")
public String home() {
return "Hello World!";
}
public static void main(String[] args) {
SpringApplication.run(DockerappApplication.class, args);
}
}
以下
命令获取您编译的代码并将其打包成可分发的格式,例如 JAR:
./mvnw package
java -jar target/*.jar
现在,您应该可以通过http://localhost:8080访问“Hello World” 。
为了 Dockerize 这个应用程序,你需要使用Dockerfile
. ADockerfile
是一个文本文档,其中包含用户可以在命令行上调用以组装 Docker 映像的每条指令。一个 Docker 镜像由一堆层组成,每个层代表Dockerfile
. 每个后续层都包含对其底层的更改。
通常,开发人员使用以下Dockerfile
模板来构建 Docker 映像。
FROM eclipse-temurin
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
第一行定义了大约 457 MB 的基本映像。ARG指令指定可用于COPY指令的 变量。COPY将JAR 文件从target/文件夹复制到 Docker 映像的根目录。EXPOSE指令通知 Docker 容器在运行时侦听指定的网络端口。最后,ENTRYPOINT允许您配置作为可执行文件运行的容器。它对应于您的命令。java -jar target/*.jar
您将使用docker build
命令构建图像,如下所示:
$ docker build -t spring-boot-docker .
Sending build context to Docker daemon 15.98MB
Step 1/5 : FROM eclipse-temurin
---a3562aa0b991
Step 2/5 : ARG JAR_FILE=target/*.jar
---Running in a8c13e294a66
Removing intermediate container a8c13e294a66
---aa039166d524
Step 3/5 : COPY ${JAR_FILE} app.jar
COPY failed: no source files were specified
上述示例的一个主要缺点是它没有完全容器化。./mvnw
您必须首先通过在主机系统上运行package命令来 创建 JAR 文件。这需要您手动安装 Java、设置 JAVA_HOME
环境变量并安装 Maven。简而言之,你的 JDK 必须驻留在你的 Docker 容器之外——这给你的构建环境增加了更多的复杂性。一定有更好的方法。
建议在您自己的构建过程中构建 JAR Dockerfile
。以下RUN
说明触发解决所有项目依赖项的目标,包括插件、报告及其依赖项:
FROM eclipse-temurin
WORKDIR /app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:go-offline
COPY src ./src
CMD ["./mvnw", "spring-boot:run"]
避免在编写 Dockerfile 时手动复制 JAR 文件
在构建 Docker 镜像时,始终建议指定有用的标签来编码版本信息、预期目标(例如产品或测试)、稳定性或其他有用的信息,以便在不同环境中部署应用程序。不要依赖自动创建的
latest
标签。使用latest
是不可预测的,可能会导致意外行为。每次拉取最新映像时,它都可能包含可能破坏您的应用程序的新版本或未经测试的版本。例如,使用
eclipse-temurin:latest
Docker 镜像作为基础镜像并不理想。相反,您应该使用特定的标签,例如eclipse-temurin:17-jdk-jammy
, eclipse-temurin:8u332-b09-jre-alpin etc.
避免FROM eclipse-temurin:latest
在你的Dockerfile
在 OpenJDK Docker Hub 页面上,您将找到推荐的 Docker 官方镜像列表,您应该在构建 Java 应用程序时使用这些镜像。上游的 OpenJDK 镜像不再提供 JRE,因此没有生成官方的 JRE 镜像。官方 OpenJDK 映像仅包含Oracle或相关项目负责人提供的 OpenJDK 的“香草”版本。
具有构建价值的 JDK 的最受欢迎的官方镜像之一是 Eclipse Temurin。Eclipse Temurin 项目提供了支持构建运行时二进制文件和相关技术的代码和流程。这些是高性能、企业级和跨平台的。
FROM eclipse-temurin:17-jdk-jammy
WORKDIR /app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:go-offline
COPY src ./src
CMD ["./mvnw", "spring-boot:run"]
通过多阶段构建,Docker 构建可以使用一个基础镜像进行编译、打包和单元测试。另一个图像保存应用程序的运行时。这使得最终图像更安全且尺寸更小(因为它不包含任何开发或调试工具)。多阶段Docker 构建是确保构建 100% 可重复且尽可能精简的好方法。您可以在 a 中创建多个阶段
Dockerfile
并控制构建该映像的方式。您可以使用多层方法将 Spring Boot 应用程序容器化。每一层都可能包含应用程序的不同部分,例如依赖项、源代码、资源,甚至快照依赖项。或者,您可以将任何应用程序构建为与包含可运行应用程序的最终映像不同的映像。为了更好地理解这一点,考虑以下内容
Dockerfile
:
FROM eclipse-temurin:17-jdk-jammy
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
Spring Boot 使用“胖 JAR”作为其默认打包格式。当检查胖 JAR 时,可以看到应用程序只占整个 JAR 的一小部分。这部分变化最频繁。其余部分包含 Spring Framework 依赖项。优化通常涉及将应用程序与 Spring Framework 依赖项隔离到一个单独的层中。您只需下载依赖层(构成胖 JAR 的主体)一次,并将其缓存在主机系统中。
以上
Dockerfile
假设胖 JAR 已经在命令行上构建。您也可以在 Docker 中使用多阶段构建并将结果从一个映像复制到另一个映像来执行此操作。除了使用 Maven 或 Gradle 插件,还可以使用Dockerfile
. 在使用 Docker 时,必须再执行两个步骤来提取图层并将其复制到最终图像中。
在第一阶段,将提取依赖项。在第二阶段,将提取的依赖项复制到最终图像中:
FROM eclipse-temurin:17-jdk-jammy as builder
WORKDIR /opt/app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:go-offline
COPY ./src ./src
RUN ./mvnw clean install
FROM eclipse-temurin:17-jre-jammy
WORKDIR /opt/app
EXPOSE 8080
COPY --from=builder /opt/app/target/*.jar /opt/app/*.jar
ENTRYPOINT ["java", "-jar", "/opt/app/*.jar" ]
第一个图像被标记为
builder
。使用它来运行eclipse-temurin:17-jdk-jammy
、构建胖 JAR 并解压缩它。请注意,这
Dockerfile
已分为两个阶段。后面的层包含构建配置和应用程序的源代码,前面的层包含完整的 Eclipse JDK 映像本身。这个小优化还使免于将target
目录复制到 Docker 映像——甚至是用于构建的临时映像。与第一阶段构建的 450MB 大小相比,最终图像只有 277 MB。
为了提高构建性能,建议.dockerignore
在您的Dockerfile
. 对于本教程,您的.dockerignore
文件应该只包含一行:
target
此行从 Docker 构建上下文中排除了包含来自 Maven 的输出的目标目录。仔细构建文件有很多充分的理由
.dockerignore
,但这个简单的文件现在已经足够了。现在解释它build context
以及为什么它是必要的。该命令从一个和一个“上下文”docker build
构建 Docker 映像。Dockerfile
此上下文是位于您指定的 PATH 或 URL 中的文件集。构建过程可以引用任何这些文件。同时,编译上下文是开发人员工作的地方。它可以是 Mac、Windows 或 Linux 目录上的文件夹。该目录包含所有必要的应用程序组件,如源代码、配置文件、库和插件。通过该
.dockerignore
文件,可以确定在构建新映像时要排除以下哪些元素,例如源代码、配置文件、库、插件等。
.dockerignore
如果您选择从构建中排除conf
、libraries
和,您的文件可能如下所示:plugins directory
您的 CPU 只能运行其本机架构的二进制文件。例如,为 x86 系统构建的 Docker 映像无法在基于 Arm 的系统上运行。随着 Apple 完全过渡到其定制的基于 Arm 的芯片,您的 x86(英特尔或 AMD)Docker 映像可能无法与 Apple 最近的 M 系列芯片一起使用。因此,始终建议构建多架构容器映像。下面是mplatform/mquery
Docker 镜像,可让您在任何公共注册表中查询任何公共镜像的多平台状态:
docker run --rm mplatform/mquery eclipse-temurin:17-jre-alpine
Image: eclipse-temurin:17-jre-alpine (digest: sha256:ac423a0315c490d3bc1444901d96eea7013e838bcf7cc09978cf84332d7afc76)
* Manifest List: Yes (Image type: application/vnd.docker.distribution.manifest.list.v2+json)
* Supported platforms:
- linux/amd64
这里介绍了docker buildx
帮助您构建多架构映像的命令。Buildx是一个 Docker 组件,它支持许多强大的构建功能和熟悉的 Docker 用户体验。通过 Buildx 执行的所有构建都通过Moby BuildKit构建器引擎运行。BuildKit 旨在擅长多平台构建,或者不仅仅是针对用户本地平台的构建。当您调用构建时,您可以设置--platform
标志来指定构建输出的目标平台,(如linux/amd64
、linux/arm64
或darwin/amd64
):
docker buildx build --platform linux/amd64, linux/arm64 -t spring-helloworld .
以用户权限运行应用程序更安全,因为它有助于降低风险。这同样适用于 Docker 容器。默认情况下,Docker 容器及其运行的应用程序具有 root 权限。因此,最好以非 root 用户身份运行 Docker 容器。您可以通过USER
在Dockerfile
. 该USER
指令在运行映像时设置首选用户名(或 UID)和可选的用户组(或 GID) - 以及任何后续RUN
、CMD
或ENTRYPOINT
指令:
FROM eclipse-temurin:17-jdk-alpine
RUN addgroup demogroup; adduser --ingroup demogroup --disabled-password demo
USER demo
WORKDIR /app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:go-offline
COPY src ./src
CMD ["./mvnw", "spring-boot:run"]
今天的开发人员在构建他们的服务时依赖第三方代码和应用程序。不小心使用外部软件,您的代码可能更容易受到攻击。利用受信任的图像并持续监控您的容器对于解决这个问题至关重要。每当您构建“Hello World”Docker 映像时,Docker Desktop 都会提示您运行映像的安全扫描以检测任何已知漏洞,例如 Log4Shell:
exporting to image 0.0s
== exporting layers 0.0s
== writing image sha256:cf6d952a1ece4eddcb80c8d29e0c5dd4d3531c1268291 0.0s
== naming to docker.io/library/spring-boot1 0.0s
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
使用Snyk Extension for Docker Desktop来检查Spring Boot 应用程序。首先,在您的Mac、Windows或Linux机器上安装Docker Desktop 4.8.0+并启用扩展市场。
Snyk 的扩展允许您快速扫描本地和远程 Docker 映像以检测漏洞。
安装Snyk
扩展并提供“Hello World”Docker 镜像。
Snyk 的工具发现了 70 个不同严重程度的漏洞。一旦你意识到这些,你就可以开始修复以激发你的形象。
为了执行漏洞检查,您可以直接对 : 使用以下命令Dockerfile
:docker scan -f Dockerfile spring-helloworld
Spring Boot 开发人员如何确保他们的应用程序更快、更高效?通常,开发人员依靠第三方可观察性工具来衡量其 Java 应用程序的性能。应用程序性能监控对于所有类型的 Java 应用程序都是必不可少的,开发人员必须创造一流的用户体验。
可观察性不仅限于应用程序性能。随着微服务架构的兴起,可观察性的三大支柱——指标、跟踪和日志——是前沿和中心。指标可帮助开发人员了解系统出了什么问题,而跟踪可帮助您发现问题所在。日志会告诉您错误的原因,让开发人员深入研究特定指标或跟踪以全面了解系统行为。
观察 Java 应用程序需要通过 JMX、底层主机指标和 Java 应用程序跟踪来监控您的 Java VM 指标。Java 开发人员应使用Java OpenTelemetry API监控、分析和诊断应用程序性能。OpenTelemetry 提供一组 API、库、代理和收集器服务,以从您的应用程序中捕获分布式跟踪和指标。
在这篇博文中,您看到了通过精心制作 Dockerfile 并使用 Snyk Docker Extension Marketplace 保护您的镜像来优化 Docker 镜像的许多方法。