Spring Boot: override application.yml properties from Kubernetes ConfigMap - spring-boot

I need to oveeride some of properties defined in a application.yml of Spring Boot application running in k8s. How can I do this? The only way I found is to mount whole application.yml but I only need to override one property.

Could be doable in another way, that's why i'm answering again.
Do it in an easy way.
Create your application.yml in a configmap and mount it as a sub directory called config into the same directory where the spring boot jar ins located.
The documentation of spring boot (external application properties) says:
Spring Boot will automatically find and load application.properties and application.yaml files from the following locations when your application starts:
The classpath root
The classpath /config package
The current directory
The /config subdirectory in the current directory
Immediate child directories of the /config subdirectory
Which means we don't have to take care of setting anything. It should find the config inside the subdirectory config.
apiVersion: v1
kind: ConfigMap
metadata:
name: spring-application-config
data:
application.yml: |
spring:
application:
name: This is just an example, add as many values as you want.
pod.yaml:
...
volumeMounts:
- name: spring-application-config
mountPath: /app/config
- name: spring-application-config
configMap:
name: spring-application-config
...
Assuming that your spring boot jar file is located in the path /app

Spring Boot Apps should override properties in the configuration files (aka, in their application.yml most of the times) with environment variables.
Assuming the App is deployed on Kubernetes with a Pod (but it doesn't matter, Deployments, StatefulSets, Jobs are all the same regarding environment) you can inject environment variables inside the container by either directly assigning them to the Deployment itself, or with a ConfigMap (or a Secret, should the property be more secure and masked)
apiVersion: v1
kind: Pod
metadata:
name: example
spec:
containers:
- name: example-container
image: example-image:latest
env:
- name: THIS_IS_AN_ENV_VARIABLE
value: "Hello from the environment"
- name: spring.persistence.url
value: "The persistence url, for example"
Even better, you can inject all the content of a ConfigMap as environment variables:
apiVersion: v1
kind: ConfigMap
metadata:
name: example-config
data:
spring.persistence.url: "your persistence url"
spring.mail.user: "your mail user"
And then your Pod:
apiVersion: v1
kind: Pod
metadata:
name: example
spec:
containers:
- name: example-container
image: example-image:latest
envFrom:
- configMapRef:
name: example-config
Inside the container, the variables will be in the environment.. and Spring should use them to override variables with same name which are defined (or maybe not even defined) in your application.yml
For more info:
https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/
https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/
https://docs.spring.io/spring-boot/docs/1.3.3.RELEASE/reference/html/boot-features-external-config.html

We had to do something similar. And i explain you how we did. Possibly this helps.
If you can modify your Dockerfile then create an entrypoint.sh script.
It could contain the following:
entrypoint.sh
#!/bin/bash
set -e
# Source custom scripts, if any
if [ -d /etc/spring.d ]; then
for f in /etc/spring.d/*; do
if [ -x "$f" ]; then
echo "Running $f ..." >&2
"$f"
else
echo "Could not run $f, because it's missing execute permission (+x)." >&2
fi
done
unset f
fi
exec "$#"
The entrypoint.sh is executing custom scripts inside the /etc/spring.d directory on startup. You can put any exectuble script inside, whatever you want.
Inside the /etc/spring.d you can create a copy script which copies an application.yml into same directory where the spring boot jar file is located. Example follows.
Your Dockerfile could look like
FROM adoptopenjdk:15-jre
RUN mkdir /app
COPY application.jar /app/
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["java", "-jar", "/app/application.jar"]
If this is prepared, you can use a configMap to define the copy script and mount it to the directory /etc/spring.d
apiVersion: v1
kind: ConfigMap
metadata:
name: spring-d
data:
copy-yaml.sh: |
#!/bin/bash
cp /config/application.yml /app/application.yml
The copy script will take care, that you application.yaml (which will be mounted as a configMap as well) will be copied to the right place.
And a further configMap for your application.yml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-yaml
data:
application.yml: |
spring:
application:
name: This is just an example, add as many values as you want.
Inside your pod yaml you could do something like this:
...
volumeMounts:
- name: spring-d
mountPath: /etc/spring.d
- name: app-yaml
mountPath: /config
- name: spring-d
configMap:
name: spring-d
defaultMode: 0777
- name: app-yaml
configMap:
name: app-yaml
...
THIS CODE IS CURRENTLY UNTESTED, IT'S JUST SHOWING YOU AN EXAMPLE HOW YOU COULD SOLVE YOUR PROBLEM IN A VERY FLEXIBLE WAY.
I used snippets from my scripts and copied it here together, so maybe there could be small mistakes inside. Please let me know.

Related

How to read spring boot configuration file in Kubernetes deployment

Im new in Kubernetes and having a hard time making to read application.properties in the deployment. I have attached our ConfigMap as a mounted volume under the /config path.
This is my deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: 34343434.dkr.ecr.asia-2.amazonaws.com/myapp:latest
ports:
- containerPort: 80
volumeMounts:
- name: application-properties
mountPath: /config
volumes:
- name: application-properties
configMap:
name: application-properties
I have created configmap using kubectl command from a file that is located in my local computer.
kubectl create configmap application-properties -–from-file=/users/me/application.properties
Now the issue is the application.property file which i am setting it using the kubectl configmap is not getting picked up. Can you help me on this?
Based on the discussion, the issue was the configmap, instead of the property file, it was rendered as a string in the configmap.
kubectl get configmap application-properties -o yaml
>shows the contents but with all in oneline format. separated by \n
Converting it to YAML application.yml did the trick.

How to populate application.properties file value from kubernetes Secrets mounted as file

I am working on Springboot and Kubernetes and I have really simple application that connects to Postgres database. I want to get the value of datasource from configmap and password from secrets as mount file.
Configmap file :
apiVersion: v1
kind: ConfigMap
metadata:
name: customer-config
data:
application.properties: |
server.forward-headers-strategy=framework
spring.datasource.url=jdbc:postgresql://test/customer
spring.datasource.username=postgres
Secrets File :
apiVersion: v1
kind: Secret
metadata:
name: secret-demo
data:
spring.datasource.password: cG9zdGdyZXM=
deployment file :
spec:
containers:
- name: customerc
image: localhost:8080/customer
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8282
volumeMounts:
- mountPath: /workspace/config/default
name: config-volume
- mountPath: /workspace/secret/default
name: secret-volume
volumes:
- name: config-volume
configMap:
name: customer-config
- name: secret-volume
secret:
secretName: secret-demo
items:
- key: spring.datasource.password
path: password
If I move spring.datasource.password prop from secret to configmap then it works fine or If I populate its value as env variable then also work fine.
But as we know both are not secure way to do so, can someone tell me what's wrong with file mounting for secrets.
Spring Boot 2.4 added support for importing a config tree. This support can be used to consume configuration from a volume mounted by Kubernetes.
As an example, let’s imagine that Kubernetes has mounted the following volume:
etc/
config/
myapp/
username
password
The contents of the username file would be a config value, and the contents of password would be a secret.
To import these properties, you can add the following to your application.properties file:
spring.config.import=optional:configtree:/etc/config/
This will result in the properties myapp.username and myapp.password being set . Their values will be the contents of /etc/config/myapp/username and /etc/config/myapp/password respectively.
By default, consuming secrets through the API is not enabled for security reasons.Spring Cloud Kubernetes requires access to Kubernetes API in order to be able to retrieve a list of addresses of pods running for a single service. The simplest way to do that when using Minikube is to create default ClusterRoleBinding with cluster-admin privilege.
Example on how to create one :-
$ kubectl create clusterrolebinding admin --clusterrole=cluster-admin --serviceaccount=default:default
You need to give secret type in manifest file. Hope it will work.
apiVersion: v1
kind: Secret
metadata:
name: secret-demo
type: Opaque
data:
spring.datasource.password: cG9zdGdyZXM

How to properly configure the environment in kubernetes cluster?

I have a spring boot application with two profiles, dev and prod, my docker file is:
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG DEPENDENCY=target/dependency
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-Dspring.profiles.active=dev","-cp","app:app/lib/*","com.my.Application"]
please not that, when building the image, I specify the entrypoint as command line argument.
This is the containers section of my kubernetes deployment where I use this image:
containers:
- name: myapp
image: myregistry.azurecr.io/myapp:0.1.7
imagePullPolicy: "Always"
ports:
- containerPort: 8080
name: myapp
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
timeoutSeconds: 3
periodSeconds: 20
failureThreshold: 3
It works but has a major flaw: how can I now switch to the production environment without rebuilding the image?
The best would be to remove that ENTRYPOINT in my docker file and give this configuration in my kubernetes yml so that I could always use the same image...is this possible?
edit: I saw that there is a lifecycle istruction but note that I have a readiness probe based on the spring boot's actuator. It would always fail if I used this construct.
You can override an image's ENTRYPOINT by using the command property of a Kubernetes Pod spec. Likewise, you could override CMD by using the args property (also see the documentation):
containers:
- name: myapp
image: myregistry.azurecr.io/myapp:0.1.7
imagePullPolicy: "Always"
command: ["java","-Dspring.profiles.active=prod","-cp","app:app/lib/*","com.my.Application"]
ports:
- containerPort: 8080
name: myapp
Alternatively, to provide a higher level of abstraction, you might write your own entrypoint script that reads the application profile from an environment variable:
#!/bin/sh
PROFILE="${APPLICATION_CONTEXT:-dev}"
exec java "-Dspring.profiles.active=$PROFILE" -cp app:app/lib/* com.my.Application
Then, you could simply pass that environment variable into your pod:
containers:
- name: myapp
image: myregistry.azurecr.io/myapp:0.1.7
imagePullPolicy: "Always"
env:
- name: APPLICATION_CONTEXT
value: prod
ports:
- containerPort: 8080
name: myapp
Rather than putting spring.profiles.active in dockerfile in the entrypoint.
Make use of configmaps and application.properties.
Your ENTRYPOINT in dockerfile should look like:
ENTRYPOINT ["java","-cp","app:app/lib/*","com.my.Application","--spring.config.additional-location=/config/application-dev.properties"]
Create a configmap that acts as application.properties for your springboot application
---
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
namespace: flow
data:
application-dev.properties: |
spring.application.name=myapp
server.port=8080
spring.profiles.active=dev
NOTE: Here we have specified spring.profiles.active.
In containers section of my kubernetes deployment mount the configmap inside container that will act as application.properties.
containers:
- name: myapp
image: myregistry.azurecr.io/myapp:0.1.7
imagePullPolicy: "Always"
command: ["java","-cp","app:app/lib/*","com.my.Application","--spring.config.additional-location=/config/application-dev.properties"]
ports:
- containerPort: 8080
name: myapp
volumeMounts:
- name: myapp-application-config
mountPath: "/config"
readOnly: true
volumes:
- name: myapp-application-config
configMap:
name: myapp-config
items:
- key: application-dev.properties
path: application-dev.properties
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
timeoutSeconds: 3
periodSeconds: 20
failureThreshold: 3
NOTE: --spring.config.additional-location points to location of application.properties that we created in configmaps.
So making use of configmaps and application.properties one can override any configuration of your application wihtout rebuilding the image.
If you want to add a new config or update value of existing config, just make appropriate changes in configmap and kubectl apply it. Then scale down and scale up your application pod, to bring the new config in action.
Hope this helps.
There are many many ways to set Spring configuration values. With some rules, you can use ordinary environment variables to specify individual property values. You might see if you can use this instead of having a separate Spring profile control.
Using environment variables has two advantages here: it means you (or your DevOps team) can change deploy-time settings without recompiling the application; and if you're using a deployment manager like Helm where some details like hostnames are intrinsically unpredictable, this lets you specify values that can't be known until deploy time.
For example, let's say you have a Redis dependency:
cache:
redis:
url: redis://localhost:6379/0
You could override this at deploy time by setting
containers:
- name: myapp
env:
- name: CACHE_REDIS_URL
value: "redis://myapp-redis.default.svc.cluster.local:6379/0"
One way to do this is using spring cloud Kubernetes as described here
https://docs.spring.io/spring-cloud-kubernetes/docs/current/reference/html/index.html#configmap-propertysource
You can define your profiles in a configmap like below
kind: ConfigMap
apiVersion: v1
metadata:
name: demo
data:
application.yml: |-
greeting:
message: Say Hello to the World
farewell:
message: Say Goodbye
---
spring:
profiles: development
greeting:
message: Say Hello to the Developers
farewell:
message: Say Goodbye to the Developers
---
spring:
profiles: production
greeting:
message: Say Hello to the Ops
And can then select the desired profile by passing an environment variable in your Kubernetes deployment manifest
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-name
labels:
app: deployment-name
spec:
replicas: 1
selector:
matchLabels:
app: deployment-name
template:
metadata:
labels:
app: deployment-name
spec:
containers:
- name: container-name
image: your-image
env:
- name: SPRING_PROFILES_ACTIVE
value: "development"

ConfigMap data (yml format) - Kubernetes

I have an application.yml (Spring) file, which has almost 70 fields, want to move those fields to ConfigMap.
In the process of setup ConfigMap, have realized all the 70 fields has be flatened example : webservice.endpoint.transferfund
It's gonna be a painful task to convert all the 70 fields as flat, is there any alternative.
Please suggest.
Below Config is working:
apiVersion: v1
kind: ConfigMap
metadata:
name: configmapname
namespace: default
data:
webservice.endpoint.transferfund: http://www.customer-service.app/api/tf
webservice.endpoint.getbalance: http://www.customer-service.app/api/balance
webservice.endpoint.customerinfo: http://www.customer-service.app/api/customerinfo
Below config is not working, tried it as yml format.
apiVersion: v1
kind: ConfigMap
metadata:
name: configmapname
namespace: default
data:
application.yaml: |-
webservice:
endpoint:
transferfund: http://www.customer-service.app/api/tf
getbalance: http://www.customer-service.app/api/balance
customerinfo: http://www.customer-service.app/api/customerinfo
in src/main/resources/application.yml have below fields to access ConfigMap keys:
webservice:
endpoint:
transferfund: ${webservice.endpoint.transferfund}
getbalance: ${webservice.endpoint.getbalance}
customerinfo: ${webservice.endpoint.customerinfo}
Updated:
ConfigMap Description:
C:\Users\deskktop>kubectl describe configmap configmapname
Name: configmapname
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
application.yaml:
----
webservice:
endpoint:
transferfund: http://www.customer-service.app/api/tf
getbalance: http://www.customer-service.app/api/balance
customerinfo: http://www.customer-service.app/api/customerinfo
Events: <none>
Deployment script: (configMapRef name provided as configmap name as shown above)
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: configmap-sample
spec:
replicas: 1
strategy:
type: RollingUpdate
template:
metadata:
labels:
app: configmap-sample
spec:
containers:
- name: configmap-sample
image: <<image>>
ports:
- name: http-api
containerPort: 9000
envFrom:
- configMapRef:
name: configmapname
resources:
limits:
memory: 1Gi
requests:
memory: 768Mi
env:
- name: JVM_OPTS
value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -Xms768M"
A ConfigMap is a dictionary of configuration settings. It consists of key-value pairs of strings. Kubernetes then adds those values to your containers.
In your case you have to make them flat, because Kubernetes will not understand them.
You can read in the documentation about Creating ConfigMap that:
kubectl create configmap <map-name> <data-source>
where is the name you want to assign to the ConfigMap and is the directory, file, or literal value to draw the data from.
The data source corresponds to a key-value pair in the ConfigMap, where
key = the file name or the key you provided on the command line, and
value = the file contents or the literal value you provided on the command line.
You can use kubectl describe or kubectl get to retrieve information about a ConfigMap.
EDIT
You could create a ConfigMap from a file with defined key.
Define the key to use when creating a ConfigMap from a file
Syntax might look like this:
kubectl create configmap my_configmap --from-file=<my-key-name>=<path-to-file>
And the ConfigMap migh look like the following:
apiVersion: v1
kind: ConfigMap
metadata:
creationTimestamp: 2019-07-03T18:54:22Z
name: my_configmap
namespace: default
resourceVersion: "530"
selfLink: /api/v1/namespaces/default/configmaps/my_configmap
uid: 05f8da22-d671-11e5-8cd0-68f728db1985
data:
<my-key-name>: |
key=value
key=value
key=value
key=value
Also I was able to find Create Kubernetes ConfigMaps from configuration files.
Functionality
The projector can:
Take raw files and stuff them into a ConfigMap
Glob files in your config repo, and stuff ALL of them in your configmap
Extract fields from your structured data (yaml/json)
Create new structured outputs from a subset of a yaml/json source by pulling out some fields and dropping others
Translate back and forth between JSON and YAML (convert a YAML source to a JSON output, etc)
Support for extracting complex fields like objects+arrays from sources, and not just scalars!
You need to mount the ConfigMap as Volume. Otherwise the content would live in environment variables. The example i post here is from https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#add-configmap-data-to-a-volume
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
- name: test-container
image: k8s.gcr.io/busybox
command: [ "/bin/sh", "-c", "ls /etc/config/" ]
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
# Provide the name of the ConfigMap containing the files you want
# to add to the container
name: special-config
restartPolicy: Never
You mentioned, you're using the application.yaml in context of a Spring project. So if you don't care whether you use .yaml or .property configuration-files, you can just use property-files because configMap generation supports them. It works with the --from-env-file flag:
kubectl create configmap configmapname --from-env-file application.properties
So in your deployment-file you can directly access the keys:
...
env:
- KEYNAME
valueFrom:
configMapKeyRef:
name: configmapname
key: KeyInPropertiesFile

can't populate volume using configMap

I am trying run a spring boot application whose application.properties file will be used as a kubernetes configMap.
While deploying the app, I am adding a volume for this config file. But somehow, it is not creating properties file at mount path.
configMap name : integration-properties
Data :
application.properties:
http.stub.api.host=localhost
http.stub.api.port=8080
http.stub.api.path=stub-api
Deployment.yaml file :
volumeMounts:
- name: config-volume
mountPath: /opt/build/
volumes:
- name: config-volume
configMap:
name: integration-properties
items:
- key: application.properties
path: application.properties
When I run this application, it says, "/opt/build/application.properties" does not exists.
Please let me know for any further configuration required, if any and steps to do them.
So define configMap as:
data:
application.properties: |
http.stub.api.host=localhost
http.stub.api.port=8080
http.stub.api.path=stub-api
or you can also create directly from the application.properties file
kubectl create configmap integration-properties --from-file=application.properties=<path to file>
Your deployment.yaml file looks good to me there isn't any problem.

Resources