Introduction

Have you ever wondered about the ways in which you can manage environment variables when working with Kubernetes? If so, you have come to the right place!

This article will take you through how to set up and use environment variables in your Kubernetes cluster. I will first create a cluster with Civo, demonstrate how to set environment variables from ConfigMaps, share environment variables securely using Secrets, and finally show you how to use a tool called Doppler to securely store your environment variables.

There are many ways in which you can set up and use environment variables, this tutorial will go through some best practices.

Prerequisites

Creating a Cluster

In this section, we will be going through setting up a simple Kubernetes cluster with 2 nodes with Civo. We will use these clusters throughout this tutorial, that is why it is important you follow through.

We will need to get the API key in other to connect the CLI to our Civo account. You can get the API key by going to your Civo account dashboard and clicking on the Settings→Profile→Security then copy the API key you see there.

Attach your API Key to the Civo command-line client by running civo apikey save <name_your_API_key> <paste_your_API_key_here>. Now, to ensure that the CLI uses the API Key you just attached, run civo apikey current <name_your_API_key>.

We are now ready to create the cluster. Create the cluster with Civo by running the following command. You can check what each flag is doing by running civo kubernetes create --help and perhaps add/remove flag(s) if needed.

civo kubernetes create <name-your-cluster> --size "g4s.kube.small" --nodes 2 --wait --save --merge --region LON1

Note: <name-your-cluster> is env_tutorial in my case.

To switch the current context to the cluster we just created, run kubectl config use-context env_tutorial. You can now run kubectl get nodes to see all the nodes available in the cluster.

Setting Environment variables from ConfigMaps

ConfigMap is a Kubernetes object used for storing non-confidential environment variables in key-value pairs. When you store your environment variable in ConfigMap you will be able to access them in pods, command line queries and also volumes.

Before going into ConfigMaps I’d like to show you how you can state your environment variables directly in the deployment file and access it.

To begin, create a new file with name deployment.yaml and paste the following code. The code below is just a hello-world deployment that takes in some arbitrary environment variables.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hello-world
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      containers:
      - name: hello-world
        image: civo/docker-hello-world:latest
        env:
        - name: HOST_NAME
          value: "server1.com"
        - name: PORT_NUMBER
          value: "9001"
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: hello-world
spec:
  selector:
    app: hello-world
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
  type: ClusterIP

Now open your terminal and navigate to the directory where you created the file and run the following command to apply our deployment.

kubectl apply -f deployment.yaml

Next run kubectl get pods to display the pods you have available. Now, copy the name of one of the pods of the hello-world, then run kubectl exec -it <name-of-pod> -- /bin/sh and you will be taken inside the shell of the pod. You can run the following command to print the environment variables available.

printenv | sort

Now, you should see some environment variables, including the ones that were stated in the deployment file, namely HOST_NAME and PORT_NUMBER. Your list will look something like:

displaying the environment variables for the running pod

The disadvantage of doing this is older pods cannot access the environment variables in newer pods, so you may have to state one environment variable repeatedly. This is where ConfigMaps come in.

Using ConfigMaps

To set ConfigMaps, create a file with the name my-config.txt (the name is arbitrary) and paste the text below.

HOST_NAME="server2.com"
APP_PORT_NUMBER="3000"

You can now create the configmap by running the command below. You will add the file you want to generate the configmap from with the --from-file= flag.

kubectl create configmap random --from-file=my-config.txt

Once this has been created, you can see the details of your configmap by running kubectl get configmap random -o yaml.

Next, create a new deployment file with name configmap-deployment.yaml then paste the code below.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world-configmaps
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hello-world-configmaps
  template:
    metadata:
      labels:
        app: hello-world-configmaps
    spec:
      containers:
      - name: hello-world
        image: civo/docker-hello-world:latest
        envFrom:
          - configMapRef: #used to state the configmap you need.
              name: random
        ports:
        - containerPort: 8080

You can see that we specify that we are taking our environment variables from a configMap called random in the section that starts with envFrom:.

Apply the deployment with kubectl apply -f configmap-deployment.yaml.

Now, get the name of the pod you just created like you did previously, then run the following command to get the environment variables in the configmap directly from the pod.

kubectl exec -it <name-of-pod>   -- /bin/sh -c "printenv | sort"

This should output the environment variables straight onto your terminal.

Using ConfigMaps in Volumes

Create a new file with name configmaps-volumes.yaml and paste the code below. To access ConfigMap in volumes you have to name the volume, then have a .spec.volumes[].configMap.name field set to reference your ConfigMap object that you have created already (in our case it’s random). When you set the mountPath for the volumes, the environment variables will be pasted there in a file.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world-configmaps-volumes
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hello-world-configmaps-volumes
  template:
    metadata:
      labels:
        app: hello-world-configmaps-volumes
    spec:
      volumes:
        - name: configmap-volume
          configMap:
            name: random # name of configmap created earlier
      containers:
      - name: hello-world
        image: civo/docker-hello-world:latest
        ports:
        - containerPort: 8080
        volumeMounts:
          - name: configmap-volume
            mountPath: "/etc/configmap-volume" # destination inside the Pod where the volume is mounted. This is where you will find the environment variables

Now apply this deployment (kubectl apply -f configmaps-volumes.yaml) and like we did in the first example, execute a shell on the pod that was just created using kubectl exec -it <name-of-pod> -- /bin/sh. Now, run ls /etc/configmap-volume and you will see your environment variables in a file.

Display configmaps

Using Secrets to share Environment Variables Securely

When working directly with pods, you may need to use sensitive environment variables like API keys and passwords. These need to be stored securely. This is where Secrets come in. A secret is a Kubernetes object used to store sensitive data like API keys, tokens and passwords.

The most direct way you can create a secret is by running a command like the one below, which takes input you give and creates the secret based on it.

kubectl create secret generic <name-of-secret> --from-literal=USERID=admin --from-literal=PASSWORD='pssWd@!70T5n%'

Now you can run the command below to confirm that the secret has been created. As you can see the details of the secrets are concealed from view.

kubectl describe secret <name-of-secret>

Displaying secrets output, showing opaque display of values

The code sample below shows how you can use the environment variables from secrets.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world-secrets
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hello-world-secrets
  template:
    metadata:
      labels:
        app: hello-world-secrets
    spec:      
      containers:
      - name: hello-world
        image: civo/docker-hello-world:latest
        envFrom:
        - secretRef:
            name: login-detail #name of secret
        ports:
        - containerPort: 8080

The environment variable is encoded in base64 and can be decoded by running the command below.

echo $(kubectl get secret <name-of-secret> --template={{.data.USERID}} | base64 --decode )

How to use Doppler to Store Environment Variables for Kubernetes

There are external tools that help manage and synchronise environment variables across applications. Doppler is an example of such a tool that is used to keep sensitive environment variables and app configurations. Here I will go into what Doppler is and the benefits of using it to secure your environment variables.

Also, with Doppler, you have a log of changes in environment variables in case a change is made that is breaks the software you can always revert to older values.

To get started, install the Operator using Helm or kubectl.

You will need a service token to get your secrets from Doppler. Access the service token by creating an account and a project on the Doppler site, then select a config from your dashboard.

Doppler configuration button on the dashboard

Next, click on the ACCESS tab then click on the Generate button and give it a name. You can click on the SECRETS tab to add your environment variables. Copy the service token you get from this page.

Next, generate your Doppler token secret. This is a Kubernetes secret containing the Service Token for the config to synchronise in your Kubernetes application. Generate your Doppler token secret by running the following command:

kubectl create secret generic doppler-token-secret --namespace doppler-operator-system   --from-literal=serviceToken=<paste-service-token>

Now you need to create a custom resource definition (DopplerSecret) to continuously synchronize secret updates in Doppler to its managed Kubernetes secrets. You can do this by creating a new .yaml file (secrets_dopplersecrets.yaml) and pasting the code below. Then apply the file to your cluster like you did in the previous sections with kubectl apply -f secrets_dopplersecrets.yaml.

apiVersion: secrets.doppler.com/v1alpha1
kind: DopplerSecret
metadata:
  name: dopplersecret-test 
  namespace: doppler-operator-system # Doppler Service token namespace
spec:
  tokenSecret: 
    name: doppler-token-secret # Kubernetes service token secret
  managedSecret: # Kubernetes managed secret (will be created if does not exist)
    name: doppler-test-secret
    namespace: default # Should match the namespace of deployments that will use the secret

Now create a new .yaml file for deployment (doppler_deployment.yaml), paste the code below and then apply this to your cluster as above.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: doppler-test-deployment-envfrom
  annotations:
    secrets.doppler.com/reload: 'true'
spec:
  replicas: 2
  selector:
    matchLabels:
      app: doppler-test
  template:
    metadata:
      labels:
        app: doppler-test
    spec:
      containers:
        - name: doppler-test
          image: alpine
          command:
            - /bin/sh
            - -c
            # Print all non-Kubernetes environment variables
            - apk add --no-cache tini > /dev/null 2>&1 &&
              echo "### This is a simple deployment running with this env:" &&
              printenv | grep -v KUBERNETES_ &&
              tini -s tail -f /dev/null # Pretend to be a long-running process
          imagePullPolicy: Always
          envFrom:
            - secretRef:
                name: doppler-test-secret # Kubernetes secret name

After you apply this deployment, you can access the environment variables you save on Doppler by running kubectl logs <name-of-pod>. You can test them changing by updating their values on the Doppler site and monitoring the logs in the container pod.

Conclusion

In this article, we have be able to go through how to set up and use environment variables in your Kubernetes cluster. We went from creating a cluster with Civo, demonstrated how to set environment variables from ConfigMaps, store sensitive environment variables securely, and finally show you how to use Doppler as an example of an external tool to securely store and manage your environment variables.

Hopefully with this information, you will be able to confidently start working with environment variables in your Kubernetes application.