Externalising Spring Boot properties when deploying to Docker

五迷三道 提交于 2019-11-28 21:21:34

DOCKER IMAGE CONFIGURATION

If you look to the way Spring recommends to launch a Spring Boot powered docker container, that's what you find:

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD target/gs-spring-boot-docker-0.1.0.jar app.jar
ENV JAVA_OPTS=""
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]

That means your image extends openjdk and your container has its own environment. If you're doing like that, it would be enough to declare what you want to override as environment properties and Spring Boot will fetch them, since environment variables take precedence over the yml files.

Environment variables can be passed in your docker command too, to launch the container with your desired configuration. If you want to set some limit for the JVM memory, see the link below.


DOCKER COMPOSE SAMPLE

Here you have an example of how I launch a simple app environment with docker compose. As you see, I declare the spring.datasource.url property here as an environment variable, so it overrides whatever you've got in your application.yml file.

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

See also:

Personally I'd use Spring Cloud Config Server instead of trying to set up properties files all over the place.

tl;dr it allows you to hold properties in git (which allows version control, branching etc) at a per environment/profile level in a centralised location, which are then served up by REST. Spring Boot has full support for it; in effect it's just another property source that ends up in your Environment.

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

A variation on Xtreme Biker's answer, this time for deployment of a Spring boot war into a dockerized TomCat

I recommend including a nominal application.yml in your app, but use Docker environment variables to override any individual keys which need environment-specific variation.

The reason I recommend this approach (using Docker environment variables) is:

  • your docker image can use exactly the same artefact as you might use for local development
  • using volume-mounts is painful; you need to find somewhere for them to live on your docker host — which turns that host into a snowflake
  • using docker secrets is painful; image or application layer need to be changed to explicitly lookup secrets from the filesystem

Spring Boot's Externalized Configuration docs explain two ways to supply environment via command-line:

  • UN*X env vars (i.e. SPRING_DATASOURCE_USERNAME=helloworld)
  • Java options (i.e. -Dspring.datasource.username=helloworld)

I prefer Java options, because they express an explicit intent: "this is intended for the following Java process, and only for that Java process".

Finally: I would use TomCat's CATALINA_OPTS as the mechanism for passing those Java options. Documentation from 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.

Because CATALINA_OPTS is an easier route than making your Docker image responsible for creating a setenv.sh and passing the appropriate Docker env declarations into it.


Build your .war artefact like so:

./gradlew war

We expect a .war artefact to be output by Gradle to build/libs/api-0.0.1-SNAPSHOT.war.

Use such a Dockerfile:

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"]

Build your Docker image like so:

docker build . --tag=my-api

Pass CATALINA_OPTS to your container like so:

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

And a docker-compose variant looks like this:

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

So I managed to get it working. Rather than passing the classpath to the directory in my DockerFile:

"--spring.config.location=classpath:${configDirectory}"]

I instead tried passing the full location of the file:

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

This now updates upon restart of the Docker container.

Your approach is definitely a viable solution, however it is not recommended, since it makes your image not portable between different production and dev environments. Containers should be immutable and all environment configuration should be externalized.

For spring boot, there is very powerful project that allows you to externalize configuration. Its called Spring Cloud Config. The config server allows you to save your environment specific configuration in a git repository and serve the configuration to applications that need it. You basically just save the same application.yml in git and point the config server to the repository location.

Following this approach you can define multiple configuration files for different environments and keep your docker container immutable.

I personally would consider two options:

  1. Using an environment variable

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

    app:
      image: my-app:latests
      ports:
        - "8080:8080"
      environment:
        SPRING_APPLICATION_JSON: '{
          "spring.datasource.url": "jdbc:mysql://db:3306/table",
        }'
    
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!