'Combining multiple k8s secrets into an env variable

My k8s namespace contains a Secret which is created at deployment time (by svcat), so the values are not known in advance.

apiVersion: v1
kind: Secret
type: Opaque
metadata:
  name: my-database-credentials
data:
  hostname: ...
  port: ...
  database: ...
  username: ...
  password: ...

A Deployment needs to inject these values in a slightly different format:

...

containers:
  env:
  - name: DATABASE_URL
    valueFrom:
      secretKeyRef:
        name: my-database-credentials
        key: jdbc:postgresql:<hostname>:<port>/<database> // ??

  - name: DATABASE_USERNAME
    valueFrom:
      secretKeyRef:
        name: my-database-credentials
        key: username

  - name: DATABASE_PASSWORD
    valueFrom:
      secretKeyRef:
        name: my-database-credentials
        key: password

The DATABASE_URL needs to be composed out of the hostname, port, 'database` from the previously defined secret.

Is there any way to do this composition?



Solution 1:[1]

Kubernetes allows you to use previously defined environment variables as part of subsequent environment variables elsewhere in the configuration. From the Kubernetes API reference docs:

Variable references $(VAR_NAME) are expanded using the previous defined environment variables in the container and any service environment variables.

This $(...) syntax defines interdependent environment variables for the container.

So, you can first extract the required secret values into environment variables, and then compose the DATABASE_URL with those variables.

...

containers:
  env:
  - name: DB_URL_HOSTNAME               // part 1
    valueFrom:
      secretKeyRef:
        name: my-database-credentials
        key: hostname

  - name: DB_URL_PORT                   // part 2
    valueFrom:
      secretKeyRef:
        name: my-database-credentials
        key: port

  - name: DB_URL_DBNAME                 // part 3
    valueFrom:
      secretKeyRef:
        name: my-database-credentials
        key: database

  - name: DATABASE_URL                  // combine
    value: jdbc:postgresql:$(DB_URL_HOSTNAME):$(DB_URL_PORT)/$(DB_URL_DBNAME)

...

Solution 2:[2]

If all the pre-variables are defined as env variables:

-  { name: DATABASE_URL, value: '{{ printf "jdbc:postgresql:$(DATABASE_HOST):$(DATABASE_PORT)/$(DB_URL_DBNAME)" }}'}

With this statement you may also bring in vlaues from the values.yaml file as well:

For Example:

If you may have defined DB_URL_DBNAME in the values file:

-  { name: DATABASE_URL, value: '{{ printf "jdbc:postgresql:$(DATABASE_HOST):$(DATABASE_PORT)/%s" .Values.database.DB_URL_DBNAME }}'}

Solution 3:[3]

You can do a couple of things I can think of:

  1. Use a secrets volume and make a startup script that reads the secrets from the volume and then starts your application with the DATABASE_URL environment variable.

    apiVersion: v1
    kind: Pod
    metadata:
      name: mypod
    spec:
      containers:
      - name: mypod
        image: your_db_container
        command: [ "yourscript.sh" ]
        volumeMounts:
        - name: mycreds
          mountPath: "/etc/credentials"
      volumes:
      - name: mycreds
        secret:
          secretName: my-database-credentials
          defaultMode: 256
    
  2. Pass the env variable in the command key of your container spec:

    apiVersion: v1
    kind: Pod
    metadata:
      name: mypod
    spec:
      containers:
      - name: mypod
        image: your_db_container
        command: [ "/bin/sh", "-c", "DATABASE_URL=jdbc:postgresql:<hostname>:<port>/<database>/$(DATABASE_USERNAME):$(DATABASE_PASSWORD) /start/yourdb" ]
    
        env:
        - name: DATABASE_USERNAME
          valueFrom:
            secretKeyRef:
              name: my-database-credentials
              key: username
        - name: DATABASE_PASSWORD
          valueFrom:
            secretKeyRef:
            name: my-database-credentials
            key: password
    

Solution 4:[4]

There are several ways to go (in increasing complexity order):

  1. Mangle the parameter before putting it into the Secret (extend whatever you use to insert the info there).

  2. Add a script into your Pod/Container to mangle the incoming parameters (environmental variables or command arguments) into what is needed. If you cannot or don't want to have your own container image, you can add your extra script as a Volume to the container, and set the Container's command field to override the container image start command.

  3. Add a facility to your Kubernetes to do an automatic mangling "behind the scenes": you can add a Dynamic Admission Controller to do your mangling, or you can create a Kubernetes Operator and add a Custom Resource Definition (the operator would be told by the CRD which Secrets to watch for changes, and the operator would read the values and generate whatever other entries you want).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 suresh Palemoni
Solution 3 Rico
Solution 4 Laszlo Valko