4

I want to use the direct translation from k8s secret-keys to SpringBoot properties. Therefore I have a helm chart (but similar with plain k8s):

apiVersion: v1
data:
  app.entry[0].name: {{.Values.firstEntry.name | b64enc }}
kind: Secret
metadata:
  name: my-secret
type: Opaque

With that my intention is that this behaves as if I'd set the spring property file:

app.entry[0].name: "someName"

But when I do this I get an error:

 Invalid value: "[app.entry[0].name]": a valid config key must consist of alphanumeric characters, '-', '_' or '.' (e.g. 'key.name',  or 'KEY_NAME',  or 'key-name', regex used for validation is '[-._a-zA-Z0-9]+'),

So, [0] seems not to be allowed as a key name for the secrets.

Any idea how I can inject an array entry into spring directly from a k8s secret name?

Shooting around wildly I tried these that all failed:

  • app.entry[0].name: ... -- k8s rejects '['
  • app.entry__0.name: ... -- k8s ok, but Spring does not recognize this as array (I think)
  • "app.entry[0].name": ... -- k8s rejects '['
  • 'app.entry[0].name': ... -- k8s rejects '['
mpromonet
  • 11,326
  • 43
  • 62
  • 91
towi
  • 21,587
  • 28
  • 106
  • 187
  • @xerx593 As far as I understand k8s secrets they are a key-value map. Thus yaml recursive dict's (like your deleted answer) will not work. I think. As you prpobably know the original format in spring is 'properties', ie basically plain key-value maps with a fancy key-semantic. That spring can read yaml files came later. So "appropriate" syntax would be the flattened key. Not sure, but thats how I understand it. – towi Oct 26 '22 at 10:09
  • Would something like this help? https://www.baeldung.com/spring-inject-arrays-lists. However, having flattened key make more sense, since its key-value concept. – Godwin Oct 26 '22 at 11:03
  • @Godwin Yeah... code-change. We are doing that now. But I was hoping not needing to change the client code. And a general solution for the future maybe. – towi Oct 27 '22 at 09:29

3 Answers3

4

You should be able to use environnment variables like described in sprint-boot-env.

app.entry[0].name property will be set using APP_ENTRY_0_NAME environment variable. This could be set in your deployment.

Using secret like:

apiVersion: v1
data:
  value: {{.Values.firstEntry.name | b64enc }}
kind: Secret
metadata:
  name: my-secret
type: Opaque

and then use it with

       env:
       - name: APP_ENTRY_0_NAME
         valueFrom:
           secretKeyRef:
             name: my-secret
             key: value
mpromonet
  • 11,326
  • 43
  • 62
  • 91
1

What you can do is passing the application.properties file specified within a k8s Secret to your Spring Boot application.

For instance, define your k8s Opaque Secret this way:

apiVersion: v1
kind: Secret
type: Opaque
metadata:
  name: my-secret
data:
  application.properties: "app.entry[0].name={{ .Values.firstEntry.name }}"

Of course you will have more properties that you want to set in your application.properties file, so just see this as an example with the type of entry that you need to specify, as stated in your question. I'm not a Spring Boot specialist, but an idea could be (if possible) to tell the Spring Boot application to look for more than a single application.properties file so that you would only need to pass some of the configuration parameters from the outside in instead of all of the parameters.

When using kubernetes secrets as files in pods, as specified within the official kubernetes documentation, each key in the secret data map becomes a filename under a volume mountpath (See point 4).

Hence, you can just mount the application.properties file defined within your k8s secret into your container in which your Spring Boot application is running. Assuming that you make use of a deployment template in your helm chart, here is a sample deployment.yaml template would do the job (please focus on the part where the volumes and volumeMount are specified):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "sample.fullname" . }}
  labels:
    {{- include "sample.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "sample.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      {{- with .Values.podAnnotations }}
      annotations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      labels:
        {{- include "sample.selectorLabels" . | nindent 8 }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "sample.serviceAccountName" . }}
      securityContext:
        {{- toYaml .Values.podSecurityContext | nindent 8 }}
      containers:
        - name: {{ .Chart.Name }}
          securityContext:
            {{- toYaml .Values.securityContext | nindent 12 }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          volumeMounts:
            - name: my-awesome-volume
              mountPath: /path/where/springboot/app/expects/application.properties
              subPath: application.properties
      volumes:
        - name: my-awesome-volume
          secret:
            secretName: my-secret
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}

As desired, this gives you a solution with no necessity of changing any of your application code. I hope that this gets you going in the intended way.

Martin H.
  • 538
  • 1
  • 7
  • 21
  • 1
    Worth exploring. Since the lib `fabric8` automatically adds the profile `kubernetes` I could use `application-kubernets.properties` insead of `application.properties`. So I have to try that out. – towi Nov 07 '22 at 14:49
  • [Also, here is some additional info](https://www.baeldung.com/spring-properties-file-outside-jar#load-config-commandline) on how to tell a spring boot app where to look for an external properties file. – Martin H. Nov 07 '22 at 15:32
0

You can do saving json file as a secret

Step 1: Create json file which needs to be stored as secret example : secret-data.json

{
  "entry": [
    {
      "name1": "data1",
      "key1": "dataX"
    },
    {
      "name2": "data2",
      "key2": "dataY"
    }
  ]
}

Step2 : Create a secret from a file

kubectl create secret generic data-1 --from-file=secret-data.json

Step 3: Attach secret to pod

env:
  - name: APP_DATA
    valueFrom:
      secretKeyRef:
        name: data-1
        key: secret-data.json

You can verify the same by exec into container and checking env

Nishan B
  • 627
  • 7
  • 11
  • But then I would have also "Step 4: Modify App-Code using `APP_DATA` instead of std spring mechanism", right? – towi Nov 03 '22 at 09:08
  • Yes you need to parse it that way – Nishan B Nov 03 '22 at 09:28
  • Hrm, got it. Src-Code modification required... not exactly what I need. Also, then there would even be a simpler way to do it: `stringData: application.yaml: | ...`. Then `...` can be _anything_, just like a normal yaml file (or properties). What I _need_ is a way to map k8s-secret-keys to spring-boot-properties. – towi Nov 04 '22 at 08:48