1. Overview

The Helm software is the package manager for Kubernetes. It simplifies the distribution of a Kubernetes application by packaging the necessary resources into a special package, known as the Helm chart. Then, the users of the Kubernetes application can install the application using the helm install command.

Typically, the application developers will make the Helm chart customizable by parameterizing certain values of the Kubernetes manifest in the chart. Then, the users can use the values file to customize the Helm chart.

In this tutorial, we’ll learn how to reference a Kubernetes secret resource in our Helm chart to allow users to specify their own Kubernetes secret during installation.

2. Kubernetes Secrets Primer

The Kubernetes secrets are Kubernetes resources that contain sensitive data, such as API keys, certificates, and passwords. A single Kubernetes secret is identified by its name and contains a list of key-value pairs. For example, we can define a Kubernetes secret resource manifest with a list of key-value pairs:

$ cat secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: my-secret
type: Opaque
data:
  # Base64 encoded data
  password: cGFzc3dvcmQ= # "password" base64 encoded
stringData:
  # Raw data (will be automatically base64 encoded by Kubernetes)
  api_key: myApiKey123

Importantly, the data field of the secret resource expects a base64 encoded value. To specify the secret value in the raw string, we’ll need to use the stringData field instead.

Then, we can inject the secret value into our pod’s environment variable using the valueFrom syntax to reference the secret resource:

$ cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-deployment
spec:
  replicas: 1
  selector:
    ...
    spec:
      containers:
      - name: my-container
        image: my-image:latest
        env:
        - name: USERNAME
          value: username
        - name: PASSWORD
          valueFrom:
            secretKeyRef:
              name: my-secret
              key: password

When the Kubernetes controller creates the Pod resource, it’ll look up the my-secret secret resource in the same namespace. Then, it reads the value of the password key in that secret and injects it into the Pod.

3. Referencing Kubernetes Secret in Helm Chart

When we’re packaging our Kubernetes application into a Helm chart, it’s a good idea to externalize any secret values that are highly environment-dependent.

Let’s say we want to package the deployment.yaml in the previous section into a Helm chart. We can hardcode the manifest to expect the PASSWORD environment variable to get the value from a secret resource name my-secret from the key password. But hardcoding the secret name and key in our template is inflexible because it severely restricts how the end users can pass in the secret value.

The better thing to do is to parameterize the secret name and the key we’ll get the value from:

$ cat templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-deployment
spec:
  replicas: 1
  selector:
    ...
    spec:
      containers:
      - name: my-container
        image: my-image:latest
        env:
        - name: USERNAME
          value: username
        - name: PASSWORD
          valueFrom:
            secretKeyRef:
              name: {{ .Values.password.secretName }}
              key: {{ .Values.password.secretKey }}

When the users install the Helm chart, they’re free to create the secret resource however they like. The contract only requires the users to let the Helm chart know the secret resource name and the key will get the secret value.

4. Example

To demonstrate the idea, we’ll walk through a simple example. Concretely, we’ll first develop a Helm chart that externalizes the secret reference. Then, we’ll install the Helm chart, passing in the secret reference using the values file.

4.1. Creating a Helm Chart

Let’s create a Helm chart that packages a Kubernetes application. Firstly, we’ll create a Chart.yaml file that defines our Helm chart:

$ cat > Chart.yaml <<EOF
apiVersion: v2
name: my-app
description: A simple Kubernetes application
version: 0.1.0
appVersion: "0.1.0"
EOF

Then, we’ll create a deployment.yaml file in the template directory at the same level as the Chart.yaml file:

$ cat > templates/deployments.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name }}-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: {{ .Chart.Name }}
  template:
    metadata:
      labels:
        app: {{ .Chart.Name }}
    spec:
      containers:
      - name: app-container
        image: ubuntu:latest
        command: ["bash", "-c", "while true; do echo $PASSWORD; sleep 3; done"]
        env:
        - name: PASSWORD
          valueFrom:
            secretKeyRef:
              name: {{ .Values.password.secretName }}
              key {{ .Values.password.secretKey }}

The deployment.yaml file above defines a pod resource with a single container. The container runs the ubuntu:latest image and defines an environment variable, PASSWORD, to be referenced from a secret resource. Notably, the secret resource to reference the value from is parameterized to the password.secretName and the key value will be inferred from the password.secretKey value

On start-up, the container will execute the command specified in the command field. Specifically, the container will print the value of the environment variable PASSWORD. Then, it sleeps for three seconds before repeating the loop.

4.2. Installing the Helm Chart

Before installing the Helm chart, we’ll need to create a Kubernetes secret resource in our cluster. Let’s create a secret resource and name it my-secret:

$ kubectl apply -f- <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: my-secret
type: Opaque
stringData:
  password: sensitive-password-123
EOF
secret/my-secret created

Then, we need to create a values.yaml file that will serve as the values file we use for configuring the Helm chart:

$ cat > values.yaml <<EOF
password:
  secretName: my-secret
  secretKey: password
EOF

Notably, the secretName corresponds to the name of the secret resource we’ve just created. Similarly, the secretKey corresponds to the key to the value we want to obtain from the secret resource.

Finally, we can install the Helm chart using the helm install command and passing the values.yaml file using the -f option flag:

$ helm install alpha . -f values.yaml
NAME: alpha
LAST DEPLOYED: Sat May 11 17:30:13 2024
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

4.3. Verifying Deployment

We can check that the pod is up and running using the kubectl get pods command to get a list of pods in our Kubernetes cluster:

$ kubectl get pods
NAME                                 READY   STATUS    RESTARTS   AGE
my-app-deployment-6f795c498f-jhxcf   1/1     Running   0          54s

From the output, we can see that our pod is up and running.

Let’s verify the standard output of the pod using the kubectl logs command:

$ kubectl logs my-app-deployment-6f795c498f-jhxcf
sensitive-password-123
sensitive-password-123
sensitive-password-123
...

As expected, the Kubernetes controller has successfully referenced the my-secret to inject the value for the environment variable PASSWORD.

5. Conclusion

In this tutorial, we’ve briefly looked at the Kubernetes secret resource as a store for sensitive data, such as passwords and API keys. Then, we learned that we can reference the Kubernetes secret in our Helm chart and even externalize the secret name through templating. Finally, we’ve walked through a demonstration that showcases the idea.