Dockerized Spring Application with environment variables - java

I am currently trying to run a Java Spring application in a Docker container. This has worked without any problems so far. But now I want that in the application.properties there are no fixed values but only placeholders, which are passed as environment variables to the container. According to the Spring Docs this should be possible but when I try this I always get the error that no database connection can be established to the placeholder ${DB_CONFIG} (Caused by: java.lang.RuntimeException: Driver com.mysql.cj.jdbc.Driver claims to not accept jdbcUrl, ${DB_CONFIG}).
I already tried to pass the application.properties externally, instead of an env file pass the variables directly as Docker environment variable and instead of a custom variable (DB_CONFIG) for the JDBC url pass the Spring variable (SPRING_DATASOURCE_URL). Neither variant worked for me.
I hope I got my point across correctly and you guys can help me out.
For your information: I don't maintain the source code, I just get it from a CI/CD, copy the changed application.properties to its place and compile the project.
KR,
BlackRose01
docker-compose file:
version: '3'
services:
arrowhead-serviceregistry:
container_name: arrowhead-serviceregistry
image: 'openjdk:11-jre-slim-buster'
restart: always
env_file: '.env'
ports:
- 8443:8443/tcp
volumes:
- ./arrowhead-serviceregistry-4.3.0.jar:/service.jar
depends_on:
- mysql
command:
-java -noverify -XX:TieredStopAtLevel=1 -jar /service.jar
application.properties file:
############################################
### APPLICATION PARAMETERS ###
############################################
# Database connection (mandatory)
# Change the server timezone if neccessary
spring.datasource.url=${DB_CONFIG}
spring.datasource.username=${SERVICEREGISTRY_DB_USERNAME}
spring.datasource.password=${SERVICEREGISTRY_DB_PASSWORD}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
# use true only for debugging
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto=none
# Service Registry web-server parameters
server.address=0.0.0.0
server.port=${SERVICEREGISTRY_PORT}
domain.name=${SERVICEREGISTRY_ADDRESS}
domain.port=${SERVICEREGISTRY_PORT}
############################################
### CUSTOM PARAMETERS ###
############################################
# Name of the core system
core_system_name=SERVICE_REGISTRY
# Show all request/response in debug log
log_all_request_and_response=${SERVICEREGISTRY_LOG_REQUESTS}
# Service Registry has an optional feature to ping service providers in a fixed time interval,
# and remove service offerings where the service provider was not available
# use this feature (true/false)
ping_scheduled=${SERVICEREGISTRY_PING_ENABLE}
# how much time the Service Registry should wait for the ping response (in milliseconds)
ping_timeout=${SERVICEREGISTRY_PING_TIMEOUT}
# how frequently should the ping happen, in minutes
ping_interval=${SERVICEREGISTRY_PING_INTERVAL}
# Service Registry has an optional feature to automatically remove service offerings, where the endOfValidity
# timestamp field is in the past, meaning the offering expired
# use this feature (true/false)
ttl_scheduled=${SERVICEREGISTRY_TTL_ENABLE}
# how frequently the database should be checked for expired services, in minutes
ttl_interval=${SERVICEREGISTRY_TTL_INTERVAL}
# Interface names has to follow this format <PROTOCOL>-<SECURITY>-<FORMAT>, where security can be SECURE or INSECURE and protocol and format must be a sequence of letters, numbers and underscore.
# A regexp checker will verify that. If this setting is set to true then the PROTOCOL and FORMAT must come from a predefined set.
use_strict_service_intf_name_verifier=${SERVICEREGISTRY_STRICT_INTERFACE_NAMES}
############################################
### SECURE MODE ###
############################################
# configure secure mode
# Set this to false to disable https mode
server.ssl.enabled=${SSL_ENABLED}
server.ssl.key-store-type=${SERVICEREGISTRY_SSL_KEYSTORE_TYPE}
server.ssl.key-store=${SERVICEREGISTRY_SSL_KEYSTORE}
server.ssl.key-store-password=${SERVICEREGISTRY_SSL_KEYSTORE_PASSWORD}
server.ssl.key-alias=${SERVICEREGISTRY_SSL_KEY_ALIAS}
server.ssl.key-password=${SERVICEREGISTRY_SSL_KEY_PASSWORD}
server.ssl.client-auth=${SERVICEREGISTRY_SSL_CLIENT_AUTH}
server.ssl.trust-store-type=${SERVICEREGISTRY_SSL_TRUSTSTORE_TYPE}
server.ssl.trust-store=${SERVICEREGISTRY_SSL_TRUSTSTORE_PATH}
server.ssl.trust-store-password=${SERVICEREGISTRY_SSL_TRUSTSTORE_PASSWORD}
#If true, http client does not check whether the hostname is match one of the server's SAN in its certificate
#Just for testing, DO NOT USE this feature in production environment
disable.hostname.verifier=${SERVICEREGISTRY_DISABLE_HOSTNAME_VERIFIER}
Environment file:
# Basic settings
DB_CONFIG=jdbc:mysql://127.0.0.1:3306/arrowhead?serverTimezone=Europe/Berlin
SSL_ENABLED=false
# Service Registry settings
SERVICEREGISTRY_ADDRESS=127.0.0.1
SERVICEREGISTRY_PORT=8443
SERVICEREGISTRY_DB_USERNAME=myUser
SERVICEREGISTRY_DB_PASSWORD=xxx
SERVICEREGISTRY_LOG_REQUESTS=false
SERVICEREGISTRY_PING_ENABLE=false
SERVICEREGISTRY_PING_TIMEOUT=5000
SERVICEREGISTRY_PING_INTERVAL=60
SERVICEREGISTRY_TTL_ENABLE=false
SERVICEREGISTRY_TTL_INTERVAL=10
SERVICEREGISTRY_STRICT_INTERFACE_NAMES=false
SERVICEREGISTRY_DISABLE_HOSTNAME_VERIFIER=false
SERVICEREGISTRY_SSL_KEYSTORE_TYPE=PKCS12
SERVICEREGISTRY_SSL_KEYSTORE=classpath:certificates/service_registry.p12
SERVICEREGISTRY_SSL_KEYSTORE_PASSWORD=123456
SERVICEREGISTRY_SSL_KEY_ALIAS=service_registry
SERVICEREGISTRY_SSL_KEY_PASSWORD=123456
SERVICEREGISTRY_SSL_CLIENT_AUTH=need
SERVICEREGISTRY_SSL_TRUSTSTORE_TYPE=PKCS12
SERVICEREGISTRY_SSL_TRUSTSTORE_PATH=classpath:certificates/truststore.p12
SERVICEREGISTRY_SSL_TRUSTSTORE_PASSWORD=123456

While searching for a solution to my problem, I came across the Spring Cloud Config Server (also just a Spring application that is self-hostable). This provides an interface between the configuration file storage location and the Spring application. The application is set up so that instead of a static application.(properties|yml) file, a bootstrap.yml is stored with the URL to the config server. When the application starts up, it then connects to the Config Server and receives it via a REST interface. The configuration files can be stored in Git/a DB or as a file.
Spring Docs - Cloud Config Server
Spring - Instruction for Implementation
Baeldung - Tutorial
Baeldung - Tutorial Bootstrapping

Related

Use application.yml to configure logging of embedded Tomcat in Spring Boot

I have a number of questions here (and I'm new to Spring Boot).
Our project's existing codebase uses YAML but no .properties file that I can see anywhere. My reading of usual Spring Boot tutorial uses .properties file.
(1) Is it possible to use application.yml as replacement for application.properties?
(2) Where is the default directory/file where the Spring Boot's embedded Tomcat server dumps its logs?
I need to modify the configuration such that we have custom directory to dump embedded Tomcat server logs. According to here, it should be
server.tomcat.basedir=my-tomcat
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%t %a "%r" %s (%D ms)
That is how it's done if using application.properties way.
(3) Assuming YAML can replace the .properties file entirely, how can I do the above configuration in YAML? Do I need to edit something in the Java source files in order for the configuration in YAML to take effect (that is Tomcat logs going into specific directory that I want)?
Yes. Completely
Spring boot doesn't log to a file by default (as far I know).
Whether you use yaml or properties file, spring boot uses this configuration to bootstrap the application. The below from here
# LOGGING
logging.config= # Location of the logging configuration file. For instance `classpath:logback.xml` for Logback
logging.exception-conversion-word=%wEx # Conversion word used when logging exceptions.
logging.file= # Log file name. For instance `myapp.log`
logging.level.*= # Log levels severity mapping. For instance `logging.level.org.springframework=DEBUG`
logging.path= # Location of the log file. For instance `/var/log`
logging.pattern.console= # Appender pattern for output to the console. Only supported with the default logback setup.
logging.pattern.file= # Appender pattern for output to the file. Only supported with the default logback setup.
logging.pattern.level= # Appender pattern for log level (default %5p). Only supported with the default logback setup.
logging.register-shutdown-hook=false # Register a shutdown hook for the logging system when it is initialized.
server.tomcat.accept-count= # Maximum queue length for incoming connection requests when all possible request processing threads are in use.
server.tomcat.accesslog.buffered=true # Buffer output such that it is only flushed periodically.
server.tomcat.accesslog.directory=logs # Directory in which log files are created. Can be relative to the tomcat base dir or absolute.
server.tomcat.accesslog.enabled=false # Enable access log.
server.tomcat.accesslog.file-date-format=.yyyy-MM-dd # Date format to place in log file name.
server.tomcat.accesslog.pattern=common # Format pattern for access logs.
server.tomcat.accesslog.prefix=access_log # Log file name prefix.
server.tomcat.accesslog.rename-on-rotate=false # Defer inclusion of the date stamp in the file name until rotate time.
server.tomcat.accesslog.request-attributes-enabled=false # Set request attributes for IP address, Hostname, protocol and port used for the request.
server.tomcat.accesslog.rotate=true # Enable access log rotation.
server.tomcat.accesslog.suffix=.log # Log file name suffix.
server.tomcat.additional-tld-skip-patterns= # Comma-separated list of additional patterns that match jars to ignore for TLD scanning.
server.tomcat.background-processor-delay=30 # Delay in seconds between the invocation of backgroundProcess methods.
server.tomcat.basedir= # Tomcat base directory. If not specified a temporary directory will be used.
server.tomcat.internal-proxies=10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|\\
192\\.168\\.\\d{1,3}\\.\\d{1,3}|\\
169\\.254\\.\\d{1,3}\\.\\d{1,3}|\\
127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|\\
172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|\\
172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|\\
172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3} # regular expression matching trusted IP addresses.
server.tomcat.max-connections= # Maximum number of connections that the server will accept and process at any given time.
server.tomcat.max-http-post-size=0 # Maximum size in bytes of the HTTP post content.
server.tomcat.max-threads=0 # Maximum amount of worker threads.
server.tomcat.min-spare-threads=0 # Minimum amount of worker threads.
server.tomcat.port-header=X-Forwarded-Port # Name of the HTTP header used to override the original port value.
server.tomcat.protocol-header= # Header that holds the incoming protocol, usually named "X-Forwarded-Proto".
server.tomcat.protocol-header-https-value=https # Value of the protocol header that indicates that the incoming request uses SSL.
server.tomcat.redirect-context-root= # Whether requests to the context root should be redirected by appending a / to the path.
server.tomcat.remote-ip-header= # Name of the http header from which the remote ip is extracted. For instance `X-FORWARDED-FOR`
server.tomcat.uri-encoding=UTF-8 # Character encoding to use to decode the URI.
Yes. You can change these to YML. All you need is to replace properties file with yml file. You could even have both in your workspace (spring looks at both application.properties and application.yml)
You can manually do the conversion or even with plugins.
A simple line like
logging.level.netpl.com = DEBUG
changes to
logging:
level:
netpl.com: DEBUG

Spring boot Could not locate PropertySource: label not found

I am trying set Spring Cloud Config Server, but the service config server, it the running on port 8888 which is correct, and another service should run on port 18060, but for reason when I startup, it allocate port 8080 for me and the return a warning "Could not locate PropertySource: label not found", what should I do? Thank you !!!
Add #EnableConfigServer at configuration class level or main class of spring boot application and restart the service .it will work .
In my case it was because I had to set the default branch of my github URI (the branch of the repository set for spring.cloud.config.server.git.uri) in the config server by setting the following to the application.properties
spring.cloud.config.server.git.default-label=main
I ran into similar issue, when I run my server locally I see in the logs only on the stratup
o.s.c.c.c.ConfigServicePropertySourceLocator - Could not locate PropertySource: label not found
It turned out this is a problem with the health check for the config server, when health check is triggered to config server an empty environment is used so the application name is empty hence the spring cloud client will try to access {config-server}/application since application is the default application name, see issue for more info on this.
To solve the issue I just disabled the health check for config server
health.config.enabled=false
Try this one. I had the same issue and solved.
First enable debug mode in your app.(In your property file: logging.level.=DEBUG . This is only to make sure the issue is same as mine Or you may have any clue of where it can go wrong.)
Then start the application and see the logs. The log shows the configured server URI and what the URL to get all property resources.
Compare both URL s - the one in the log and your Configuration server URI.
Issue is that mistakenly the URL defined the property file can have empty space at the end. (This can happen when you copy past from somewhere ) Example:
the value for spring.cloud.config.uri=http:localhost:< port >< additional empty space >
If this is the case, the client's log shows localhost:/20%20%/ < app name > / < profile >
Simply remove the post empty space. Then it should work !
In your bootstrap.yml file
Add these lines
spring:
cloud:
config:
enabled: true
uri: http://localhost:8888 ##this is the link to the server config
label: main ##this is what you are missing (name of the git repo branch)
application:
name: currency-spring-cloud-config-client
profiles:
active: testing
server:
port: 8600
spring:
cloud:
config:
enabled: true
uri:
- http://localhost:9196 ### your port for cloud config server
label: main ### your git repo branch name
In my case my server was fetching cofnigs from github repository, and I forgot that I made it private :)

How to configure two instance mongodb use spring boot and spring data

The First instance is the embedded MongoDb, the second instance is the live MongoDb.
How do it configure use spring data and spring boot. How to switch easily these instances by properties file??
UPDATE
By default application should start build-in database and store data
into APPDIR/db directory
It should be possible to let application know that external database
will be used by configuring mongo.url property. In this case no need
to start internal database. Instead of that external connection
should be used
Paste some configuration, please.
UPDATE
I have:
<!--Embedded MongoDB-->
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>1.50.5</version>
</dependency>
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.uri=mongodb://localhost/test
spring.data.mongodb.database=test
# EMBEDDED MONGODB (EmbeddedMongoProperties)
#spring.mongodb.embedded.storage.databaseDir=c:/db
#spring.mongodb.embedded.version=3.2.2
if I'll specify external mongodb, then i want embedded mongodb shouldn't to startup.
java.io.IOException: Could not start process: <EOF>
at de.flapdoodle.embed.mongo.AbstractMongoProcess.onAfterProcessStart(AbstractMongoProcess.java:79) ~[de.flapdoodle.embed.mongo-1.50.5.jar!/:?]
at de.flapdoodle.embed.process.runtime.AbstractProcess.<init>(AbstractProcess.java:114) [de.flapdoodle.embed.process-1.50.2.jar!/:?]
at de.flapdoodle.embed.mongo.AbstractMongoProcess.<init>(AbstractMongoProcess.java:53) [de.flapdoodle.embed.mongo-1.50.5.jar!/:?]
at de.flapdoodle.embed.mongo.MongodProcess.<init>(MongodProcess.java:50) [de.flapdoodle.embed.mongo-1.50.5.jar!/:?]
at de.flapdoodle.embed.mongo.MongodExecutable.start(MongodExecutable.java:44) [de.flapdoodle.embed.mongo-1.50.5.jar!/:?]
at de.flapdoodle.embed.mongo.MongodExecutable.start(MongodExecutable.java:34) [de.flapdoodle.embed.mongo-1.50.5.jar!/:?]
at de.flapdoodle.embed.process.runtime.Executable.start(Executable.java:101) [de.flapdoodle.embed.process-1.50.2.jar!/:?]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_05]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_05]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_05]
at java.lang.reflect.Method.invoke(Method.java:483) ~[?:1.8.0_05]
I think you can use Spring profiles.
Here's the documentation.
Spring Profiles provide a way to segregate parts of your application
configuration and make it only available in certain environments.
UPDATE
Note : Everything that I will talk about below is indicated in the documentation I mentioned above... You should really take a look to this documentation. This documentation is great (no joke).
From Appendix A. Common application properties (Spring boot documentation)
Here's how to configuration remote MongoDB instance in application.properties :
# MONGODB (MongoProperties)
spring.data.mongodb.authentication-database= # Authentication database name.
spring.data.mongodb.database=test # Database name.
spring.data.mongodb.field-naming-strategy= # Fully qualified name of the FieldNamingStrategy to use.
spring.data.mongodb.grid-fs-database= # GridFS database name.
spring.data.mongodb.host=localhost # Mongo server host.
spring.data.mongodb.password= # Login password of the mongo server.
spring.data.mongodb.port=27017 # Mongo server port.
spring.data.mongodb.repositories.enabled=true # Enable Mongo repositories.
spring.data.mongodb.uri=mongodb://localhost/test # Mongo database URI. When set, host and port are ignored.
spring.data.mongodb.username= # Login user of the mongo server.
And here's how to configure embedded MongoDB instance in application.properties:
# EMBEDDED MONGODB (EmbeddedMongoProperties)
spring.mongodb.embedded.features=SYNC_DELAY # Comma-separated list of features to enable.
spring.mongodb.embedded.storage.databaseDir= # Directory used for data storage.
spring.mongodb.embedded.storage.oplogSize= # Maximum size of the oplog in megabytes.
spring.mongodb.embedded.storage.replSetName= # Name of the replica set.
spring.mongodb.embedded.version=2.6.10 # Version of Mongo to use.
From Change configuration depending on the environment (Spring boot documentation)
To do the same thing with properties files you can use
application-${profile}.properties to specify profile-specific values.
You can define the MongoDB embedded configuration into application-dev.properties and the MongoDB remote configuration into application-prod.properties
UPDATE II : The return
I'm assuming that you start your embedded MongoDB instance in a class like (from documentation) :
import de.flapdoodle.embed.mongo.config.ArtifactStoreBuilder;
...
MongodStarter starter = MongodStarter.getDefaultInstance();
String bindIp = "localhost";
int port = 12345;
IMongodConfig mongodConfig = new MongodConfigBuilder()
.version(Version.Main.PRODUCTION)
.net(new Net(bindIp, port, Network.localhostIsIPv6()))
.build();
MongodExecutable mongodExecutable = null;
You can assign a spring profile to this class like (from documentation) :
#Configuration
#Profile("dev")
public class ProductionConfiguration {
// ...
}
This way, your embedded MongoDB is started only when you choose dev profile.

How do you properly set different Spring profiles in bootstrap file (for Spring Boot to target different Cloud Config Servers)?

We have different config servers per environment. Each spring boot application should target its corresponding config server. I have tried to achieve this by setting profiles in the bootstrap.properties file, e.g.:
spring.application.name=app-name
spring.cloud.config.uri=http://default-config-server.com
---
spring.profiles=dev
spring.cloud.config.uri=http://dev-config-server.com
---
spring.profiles=stage
spring.cloud.config.uri=http://stage-config-server.com
---
spring.profiles=prod
spring.cloud.config.uri=http://prod-config-server.com
And then I set the cla -Dspring.profiles.active=dev but the loaded config server is always the last one set in the file (i.e. prod config server would be loaded in the above settings, and then if prod is removed, stage would be loaded).
Is it possible to set bootstrap profiles for the cloud config server? I followed this example but can't seem to get it working. For what it's worth, these profiles work great to load the correct config (i.e. app-name-dev.properties will load if the dev profile is active), but aren't being pulled from the proper config server.
Specifying different profiles in a single file is only support for YAML files and doesn't apply to property files. For property files specify an environment specific bootstrap-[profile].properties to override properties from the default bootstrap.properties.
So in your case you would get 4 files bootstrap.properties, bootstrap-prod.properties, bootstrap-stage.properties and bootstrap-dev.properties.
However instead of that you could also only provide the default bootstrap.properties and when starting the application override the property by passing a -Dspring.cloud.config.uri=<desired-uri> to your application.
java -jar <your-app>.jar -Dspring.cloud.config.uri=<desired-url>
This will take precedence over the default configured values.
I solved a similar problem with an environment variable in Docker.
bootstrap.yml
spring:
application:
name: dummy_service
cloud:
config:
uri: ${CONFIG_SERVER_URL:http://localhost:8888/}
enabled: true
profiles:
active: ${SPR_PROFILE:dev}
Dockerfile
ENV CONFIG_SERVER_URL=""
ENV SPR_PROFILE=""
Docker-compose.yml
version: '3'
services:
dummy:
image: xxx/xxx:latest
restart: always
environment:
- SPR_PROFILE=docker
- CONFIG_SERVER_URL=http://configserver:8888/
ports:
- 8080:8080
depends_on:
- postgres
- configserver
- discovery
#LarryW (I cannot answer on the same comment):
I guess the advantage of explicitly adding the property is that it allows you to add a default value (in this case "dev") in case of not setting up the environment variable.

JMX doesn't work in Tomcat

I have the following configuration:
I deployed a sample SymmetricDS engine in Tomcat 8. It should have a JMX MBean I have to connect to. The configuration file symmetric-server.properties has the following values:
# Enable Java Management Extensions (JMX) web console.
#
jmx.http.enable=true
# Port number for Java Management Extensions (JMX) web console.
#
jmx.http.port=31417
# Enable Java Management Extensions (JMX) remote agent.
#
jmx.agent.enable=true
# Port number for the Java Management Extensions (JMX) remote agent.
#
jmx.agent.port=31418
And yet when I go to localhost:31417 I get 404 and when I launch the JConsole this application is nowhere to be found.
But when I start SymmetricDS with command bin\sym and it launches using the embedded jetty server, I can see the HTTP Adaptor on localhost:31417 and can connect via JConsole to the local application, yet I cannot connect remotely to localhost:31418:
I downloaded the sources of the SymmetricDS and in the file
symmetric-server\src\main\java\org\jumpmind\symmetric\SymmetricWebServer.java
there are only three configuguration taken from file symmetric-server.properties -- from default values it seems that they are jmx.http.port for HTTP Adaptor, https.port for HTTPS and http.port for SymmetricWebServer.
I also tried changing jmx.agent.enable to false and manually overriding java command line options in sym_service.conf by adding:
wrapper.java.additional.13=-Dcom.sun.management.jmxremote
wrapper.java.additional.14=-Dcom.sun.management.jmxremote.port=31417
wrapper.java.additional.15=-Dcom.sun.management.jmxremote.authenticate=false
wrapper.java.additional.16=-Dcom.sun.management.jmxremote.ssl=false
to no avail.
Could you please help me, what am I doing wrong?
Update
After greping sources I found SystemConstants.java, in which again there were ports for http, https and jmx.http, but none for remote agent

Categories