部署到 Docker 时外部化 Spring 启动属性

Externalising Spring Boot properties when deploying to Docker

在我的 Spring 引导应用程序中,我想将属性外部化到 Docker 容器中的 运行。首次部署时,当前位于 my-server/src/main/resources/application.yml 中的属性会按预期加载并由应用程序使用。一切正常。

但是,我的问题是我需要根据需要更新这些属性,因此我需要在 Docker 容器上访问一次 application.yml 文件。但此时,在 运行 执行 buildDocker 任务之前,它并未包含在 build/docker/ 目录中,因此在首次部署后不会被复制或访问。

所以,我尝试的是将 Yaml 文件复制到 docker/ 构建目录,将其复制到可访问目录 (/opt/meanwhileinhell/myapp/conf),然后使用 spring.config.location 属性 将配置位置传递到我的 Docker 文件中的 Jar:

ENTRYPOINT  ["java",\
...
"-jar", "/app.jar",\
"--spring.config.location=classpath:${configDirectory}"]

查看 Docker 容器上的命令 运行ning 我可以看到这是预期的:

/app.jar --spring.config.location=classpath:/opt/meanwhileinhell/myapp/conf]

但是,当我更新此文件中的 属性 并重新启动 Docker 容器时,它没有获取更改。文件权限为:

-rw-r--r-- 1 root root  618 Sep  5 13:59 application.yml

documentation 状态:

When custom config locations are configured, they are used in addition to the default locations. Custom locations are searched before the default locations.

我似乎无法弄清楚我做错了什么或误解了什么,但可能更重要的是,这是为此类 Docker 场景外部化配置的正确方法吗?

DOCKER 图像配置

如果您希望 the way Spring recommends 启动 Spring 启动驱动的 docker 容器,那么您会发现:

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

这意味着您的映像扩展了 openjdk,并且您的容器有自己的环境。如果你这样做,将你想要覆盖的内容声明为 environment properties 和 Spring Boot 将获取它们就足够了,因为 environment variables take precedence yml 文件。

环境变量也可以在您的 docker 命令中传递,以使用您想要的配置启动容器。如果您想为 JVM 内存设置一些限制,请参阅下面的 link。


DOCKER 组合样本

这里有一个示例,说明我如何使用 docker compose 启动一个简单的应用程序环境。如您所见,我在这里将 spring.datasource.url 属性 声明为环境变量,因此它会覆盖您在 application.yml 文件中的任何内容。

version: '2'
services:
    myapp:
        image: mycompany/myapp:1.0.0
        container_name: myapp
        depends_on:
        - mysql
        environment:
            - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/myapp?useUnicode=true&characterEncoding=utf8&useSSL=false
        ports:
            - 8080:8080

    mysql:
        image: mysql:5.7.19
        container_name: mysql
        volumes:
            - /home/docker/volumes/myapp/mysql/:/var/lib/mysql/
        environment:
            - MYSQL_USER=root
            - MYSQL_ALLOW_EMPTY_PASSWORD=yes
            - MYSQL_DATABASE=myapp
        command: mysqld --lower_case_table_names=1 --skip-ssl --character_set_server=utf8

另请参阅:

所以我设法让它工作了。而不是将类路径传递到我的 DockerFile:

中的目录
"--spring.config.location=classpath:${configDirectory}"]

我改为尝试传递文件的完整位置:

 "--spring.config.location=file:${configDirectory}/application.yml"]

这现在会在 Docker 容器重新启动时更新。

我个人会使用 Spring 云配置服务器,而不是尝试在所有地方设置属性文件。

tl;dr 它允许您在每个 environment/profile 级别的集中位置保存 git 中的属性(允许版本控制、分支等),然后由 REST 提供. Spring Boot 完全支持它;实际上,它只是在您的环境中结束的另一个 属性 来源。

https://spring.io/guides/gs/centralized-configuration/

您的方法绝对是一个可行的解决方案,但不推荐这样做,因为它会使您的映像无法在不同的生产和开发环境之间移植。容器应该是不可变的,所有环境配置都应该外部化。

对于spring引导,有一个非常强大的项目可以让您外部化配置。它被称为Spring Cloud Config。配置服务器允许您将特定于环境的配置保存在 git 存储库中,并将配置提供给需要它的应用程序。您基本上只需在 git 中保存相同的 application.yml 并将配置服务器指向存储库位置。

按照这种方法,您可以为不同的环境定义多个配置文件并保持 docker 容器不可变。

Xtreme Biker 的变体,这次是 部署 Spring 启动 war 到 docker 化 TomCat

我建议在您的应用中包含名义上的 application.yml,但使用 Docker 环境变量来覆盖任何需要特定环境变化的单独键。

我推荐这种方法(使用 Docker 环境变量)的原因是:

  • 您的docker图像可以使用完全与您可能用于本地开发
  • 相同的人工制品
  • 使用卷挂载很痛苦;您需要在您的 docker 主机上找到让他们居住的地方 — 这会将主机变成雪花
  • 使用 docker 秘密是痛苦的;图像或应用层需要更改为从文件系统中明确查找秘密

Spring Boot 的 Externalized Configuration docs 解释了两种通过命令行提供环境的方法:

  • UN*X 环境变量(即 SPRING_DATASOURCE_USERNAME=helloworld
  • Java 选项(即 -Dspring.datasource.username=helloworld

我更喜欢 Java 选项,因为它们表达了明确的意图:"this is intended for the following Java process, and only for that Java process".

最后:我将使用 TomCat 的 CATALINA_OPTS 作为传递那些 Java 选项的机制。来自 catalina.sh 的文档:

(Optional) Java runtime options used when the "start", "run" or "debug" command is executed. Include here and not in JAVA_OPTS all options, that should only be used by Tomcat itself, not by the stop process, the version command etc. Examples are heap size, GC logging, JMX ports etc.

因为 CATALINA_OPTS 比让您的 Docker 图像负责创建 setenv.sh 并将适当的 Docker env 声明传递给它更容易。


像这样构建你的 .war 工件:

./gradlew war

我们期望 .war 人工制品由 Gradle 输出到 build/libs/api-0.0.1-SNAPSHOT.war

使用这样的Docker文件:

FROM tomcat:8.5.16-jre8-alpine

EXPOSE 8080

COPY build/libs/api-0.0.1-SNAPSHOT.war /usr/local/tomcat/webapps/v1.war

CMD ["catalina.sh", "run"]

像这样构建您的 Docker 图像:

docker build . --tag=my-api

像这样将 CATALINA_OPTS 传递给您的容器:

docker run -it \
-p 8080:8080 \
-e CATALINA_OPTS="\
-Dspring.datasource.url='jdbc:mysql://mydatabase.whosebug.com:3306' \
-Dspring.datasource.username=myuser \
" \
my-api

docker-compose 变体如下所示:

version: '3.2'
services:
  web:
    image: my-api
    ports:
      - "8080:8080"
    environment:
      - >
        CATALINA_OPTS=
        -Dspring.datasource.url='jdbc:mysql://mydatabase.whosebug.com:3306'
        -Dspring.datasource.username=myuser

我个人会考虑两种选择:

  1. 每个配置使用一个环境变量

    app:
      image: my-app:latest
      ports:
        - "8080:8080"
      environment:
         SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/table
    
  2. 使用SPRING_APPLICATION_JSON

    app:
      image: my-app:latest
      ports:
        - "8080:8080"
      environment:
        SPRING_APPLICATION_JSON: '{
          "spring.datasource.url": "jdbc:mysql://db:3306/table",
        }'