可用于生产环境的Spring Boot的Dockerfile配置


下面的文章是我的生产证明 Dockerfile,用于分层 Spring Boot 应用程序,具有自定义构建的 JRE,具体取决于使用 jlink 和 jdeps 所需的 Java 模块,解释了我添加或更改的内容。当然,我不保证 Dockerfile 完全符合你的需求,但它应该为你提供一个思路的框架,并作为一个成熟的起点为你服务。

Dockerfile配置需要满足这些质量条件:

  • 小图像尺寸
  • 官方支持和/或活跃的社区
  • 漏洞少,解决速度快
  • 轻松自动化、FE 健康检查、日志聚合
  • 开发人员方便调试,线程分析可能性
  • 解决安全问题/强化和应用最佳实践
  • 几乎没有停机时间,发布时间快,构建时间是次要的
  • 易于开发人员理解和扩展

用于 Spring Boot 应用程序的分层生产就绪 Dockerfile

# Making here use of a docker multi-stage build
# https://docs.docker.com/develop/develop-images/multistage-build/

# Build-time container
FROM eclipse-temurin:17.0.3_7-jdk-alpine as builder
ARG JAR_FILE
WORKDIR application
COPY $JAR_FILE application.jar
COPY build_application.sh ./
RUN sh build_application.sh

# Run-time container
FROM alpine:3.16.0

ARG APP_NAME=app
ARG USER=exie
ARG GROUP=party
ARG LOG_FOLDER=/srv/app/logs

ENV APP=$APP_NAME \
    JAVA_HOME=/opt/java \
    PATH=
"${JAVA_HOME}/bin:${PATH}"

## Adding programs for operation
RUN apk update && \
    apk --no-cache add dumb-init curl jq && \
    rm -rf /var/cache/apk
/*

WORKDIR /srv/$APP

COPY run_application.sh /etc

RUN addgroup -S $GROUP && \
    adduser -S -D -H $USER -G $GROUP && \
    mkdir -p $LOG_FOLDER && \
    chgrp $GROUP $LOG_FOLDER && \
    chmod g+rwx $LOG_FOLDER && \
    chmod +x /etc/run_application.sh

## Application-specif created JRE
COPY --from=builder /opt/java-runtime $JAVA_HOME

## Spring Boot Layers
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./

USER $USER
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/etc/run_application.sh"]


用于分层 Spring Boot 应用程序的生产就绪应用程序构建 Bash 脚本

  • build_application.sh:此文件的目的是完成应用程序构建所需的一切。自定义 JRE 是通过确定使用的 java 模块并提取 jar 作为使用 Spring Boot 层的最后一步来制作的。

#!/bin/sh -l

JAR_FILE=application.jar
if [ ! -f "$JAR_FILE" ]; then
    echo "build.error: application jar is missing!"
    exit 1
fi

jar xf application.jar
REQUIRED_JAVA_MODULES="$(jdeps \
                            --print-module-deps \
                            --ignore-missing-deps \
                            --recursive \
                            --multi-release 17 \
                            --class-path="./BOOT-INF/lib/*" \
                            --module-path="./BOOT-INF/lib/*" \
                            ./application.jar)"

if [ -z "$REQUIRED_JAVA_MODULES" ]; then
    echo "build.error: required java modules are not determined!"
    exit 1
fi

jlink \
  --no-header-files \
  --no-man-pages \
  --compress 2 \
  --add-modules "${REQUIRED_JAVA_MODULES}" \
  --output /opt/java-runtime

java -Djarmode=layertools -jar application.jar extract

exit 0


用于分层 Spring Boot 应用程序的生产就绪入口点 Bash 脚本

  • run_application.sh:此文件的目的是执行应用程序运行所需的一切。设置想要的环境变量并运行哑初始化和应用程序,

#!/bin/sh -l

JAVA_OPTS="-Dfile.encoding=UTF-8 -Duser.timezone=UTC"
PORT="8080"

cd /srv/"$APP" || exit
/opt/java/bin/java $JAVA_OPTS org.springframework.boot.loader.JarLauncher --bind 0.0.0.0:$PORT

用于测试的案例
https://github.com/botscripter/hello-world

说明
我选择Alpine Linux作为基础镜像,主要是因为它与debian-buster-slim镜像相比有较少的漏洞。其次,由于我试图使镜像尽可能的小,Alpine在他大约5MB的镜像大小上很方便。

Docker+Bash
我使用了Docker和Bash的组合来构建和运行我的spring boot应用程序。通过这样做,我相信我已经实现了拥有简单的技术,易于理解和扩展,并且在互联网上有许多例子和方法,仍然很强大。但主要是因为我们可以有一个docker层来执行。

jq, curl, dumb-init
我添加了这三个程序,以使生产运行的应用程序更容易操作、调试和安全。

  • dumb-init。省略init系统往往会导致对进程和信号的不正确处理,并可能导致无法优雅地停止的容器或应该被销毁的容器泄漏等问题。在大多数情况下,信号不会被充分处理,无主的僵尸进程也不会被正确收割。 dumb-init作为PID 1运行,像一个简单的初始系统。它启动一个单一的进程,然后将所有收到的信号代理给一个根植于该子进程的会话。
  • curl。Alpine Linux,我们用它作为我们的基本镜像,不包含curl,而是wget。当然,我们可以换成这个,但由于质量目标是开发人员的简单性和活跃的社区,我选择了curl,因为我在职业生涯中遇到了比wget调用更强大的curl。请访问以下网站进行更深入的讨论。wget与curl之间的区别是什么?
  • jq:需要有效地实现健康检查,因为我们经常服务于基于REST的Web服务,而spring boot的执行器也是用JSON响应的。jq是一个轻量级的、灵活的命令行JSON处理器。更多信息请参见jq-s官方网站:https://stedolan.github.io/jq/
  • 有了所有这些工具,我就可以运行我的应用程序了,由于我的spring boot应用程序启用了actuator,我可以运行以下命令来实现健康检查。

健康检查

curl --fail --silent localhost:8080/actuator/health | jq --exit-status -n 'inputs | if has(\"status\") then .status==\"UP\" else false end' > /dev/null || exit 1


非root镜像
不要以root身份运行你的应用程序。Docker默认以根用户的身份运行容器中的进程,这是一种不稳定的安全做法。相反,使用一个低权限的用户和适当的文件系统权限。这就是我的用户 "exie "诞生的原因。我确实喜欢这个名字