Kubernetes Secrets
How to Consume Environment Variable Secrets within a Pod
Prerequisites:
- Basic Kubernetes knowledge
- kubectl installed
- A running k3d cluster with at least 2 worker nodes
Objectives:
- Discuss Kubernetes Secrets
- Create literal secrets in source files
- Create a Secret object using the kubectl utility
- Observe how Kubernetes encodes Secrets
- Deploy a PersistentVolume and PersistentVolumeClaim to the cluster
- Create a mySQL Deployment to consume the Secrets
- Confirm Secrets were consumed by the Pod
Kubernetes Secrets
Kubernetes allows you to incorporate secrets like passwords, tokens, and keys into clusters via a Secret object. It’s important to note that Secrets are not encrypted by default but they are Base64 encoded. To make Secrets more secure, they should be encrypted before being applied to a Kubernetes cluster. There is much more to securing Secrets than what can be discussed here, but the purpose of this tutorial is to show how a Secret can by consumed by a Pod.
Secrets can be created and stored in a manifest separate from the Pods that use them. This allows you to keep your confidential data and your application code separate. Secret objects are declared and consumed similarly to ConfigMaps, but the latter should never be used for confidential data.
Pods can consume Secrets via the following methods:
- As a file or files within in a volume mounted to a container
- As environment variables within the container
- By the kubelet when it pulls images required for the Pod
In this tutorial, we will create a Secret using the kubectl utility, then consume that Secret by a Pod. Confirm you have a k3d Kubernetes cluster with at least 2 worker nodes running, then move on to the next section.
Store Secrets in Files
When using kubectl to manage your Secrets, you can either pass raw data into a command, or store the secrets in files, and pass those files in the command. Placing your Secrets in plain text files is not the most secure format. We will create our Secrets in source files, then pass those file paths in the kubectl command. The Secrets we create will consist of a sample username and password.
We will now create a username Secret and password Secret. The username we want to store is “admin” and the password is “1f2d1e2e67df”. Use the following commands to write the secrets into new txt files:
echo -n 'admin' > ./username.txt
echo -n '1f2d1e2e67df' > ./password.txtThe above commands take our “admin” username and “1f2d1e2e67df” password, and sends the encoded strings to new files within the current directory. You can view the new files with the ls command (disregard my other files).
If we look at the contents of those files, we will see this for the username.txt file:
And this for the password.txt file:
Create the Secrets with kubectl
We can now create our Secret objects using the following command:
kubectl create secret generic db-user \
--from-file=username=./username.txt \
--from-file=password=./password.txtThe above command uses kubectl create secret to create a generic Secret with the name “db-user”. It creates the Secret by reading the contents of the files “username.txt” and “password.txt” from the local directory and storing them as key-value pairs within the Secret. The key for the “username.txt” file is “username” and the key for the “password.txt” file is “password”. The value for each key is the contents of the files.
It looks like the Secret was created, but let’s verify that with this command:
kubectl get secretsPerfect, the Secret was in fact created. Note the “2” under the “Data” column. This 2 denotes that there are 2 secrets (a username and password). Typically when you create a Kubernetes object, you can get some information using the kubectl get all command. Secrets, however, are intentionally not included in this output. To view more details of the Secret, use the following command:
kubectl describe secret db-userWhen creating a Secret in Kubernetes, it gets Base64 encoded in the background. To confirm the Secrets have been Base64 encoded, use the following command:
kubectl get secret -o yamlYou can see the password value is MWYyZDFlMmU2N2RmCg==, which is 1f2d1e2e67df (our literal password) Base64 encoded. If you weren’t sure, you could paste the password into a web browser decoder like base64decode.org and click the “decode” button.
Likewise, YWRtaW4K is our literal username of admin Base64 encoded.
Deploy a PersistentVolume and PersistentVolumeClaim to the Cluster
MySQL databases typically use persistent volumes to store data, but that storage must be requested via a persistent volume claim. Persistent volumes and their claims must be created before a workload (in this case mySQL) attempts to consume it. Create a new file called storage.yaml and paste the following code:
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv-volume
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20GiApply the PersistentVolume and PersistentVolumeClaim to the cluster with the following command:
kubectl apply -f storage.yamlWe can check that these were deployed with kubectl get pv and kubectl get pvc . Note that the persistent volume has a status of “BOUND” meaning the PVC found a volume that met its requirements, and the PV and PVC are now bound. Now that the PV is bound, it is available for use.
Create a mySQL Deployment to Consume Secrets
We can now create a Pod on the cluster to consume the Secret. Create a new file called deployment.yaml and copy the following code into it:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:latest
env:
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: db-user
key: username
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: db-user
key: password
ports:
- containerPort: 3306
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-persistent-storage
emptyDir: {}Here’s an explanation of what this code is doing:
apiVersion: apps/v1specifies the API version for the Deployment.kind: Deploymentindicates a Deployment resource.metadatacontains information about the Deployment (ex. name).specdescribes the desired state of the Deployment.selectorspecifies the label selector used to identify the Pods managed by this Deployment. The Pods should have the labelapp: mysql.templatedefines the Pod template for creating new Pods.metadatawithintemplatespecifies metadata for the Pods.labelswithinmetadatasets the labels for the Pods. The labelapp: mysqlis used to match the selector defined in the Deployment.specwithintemplatedefines the specification for the Pods.containerslists the containers running in the Pod. In this case, there is one container namedmysqlwith themysql:latestimage.envspecifies the environment variables for the container.MYSQL_USERenvironment variable is set with the value from a Secret nameddb-userusingsecretKeyRef. The value comes from the keyusernamein the Secret.MYSQL_ROOT_PASSWORDenvironment variable is set with the value from the same Secret (db-user) usingsecretKeyRef. The value comes from the keypasswordin the Secret.portsdefines the container ports that should be exposed. In this case, port 3306 is exposed, which is the default for mySQL.volumeMountsspecifies the volumes to be mounted inside the container. In this case, a volume namedmysql-persistent-storageis mounted at the path/var/lib/mysql.volumesdefines the volumes available for the Pod. In this case, there is one volume namedmysql-persistent-storageof typeemptyDir, which is an empty volume that is tied to the Pod's lifetime.
Overall, this manifest deploys a MySQL database as a Deployment with a single container. The container has environment variables populated from a Secret, exposes port 3306, and mounts an emptyDir volume for persistent storage. Save the deployment.yaml file and apply it to the cluster using:
kubectl apply -f deployment.yamlGive the container a few minutes to create and then check the Pods:
Confirm Secrets Were Consumed by the Pod
Remember, this is a tutorial on Kubernetes Secrets and how they get consumed by a Pod. It is not a tutorial on how to use mySQL, because I simply have not used it much. Nonetheless, we can confirm that our username and password Secrets were in-fact consumed by the mySQL Pod. In our deployment.yaml file, we used our Secrets to define the MYSQL_USER and MYSQL_ROOT_PASSWORD environment variables.
To see if the secrets got passed to the Pod, we can simply look at the environment variables within the Pod. Find the Pod name with:
kubectl get podsNow let’s enter the container and look for its environment variables:
kubectl exec -it mysql-7b7989ffb-q84l7 -- envWe can see that the Secrets did in-fact get consumed by the Pod!
Conclusion and Clean Up
In this tutorial we discussed Kubernetes Secrets and how to create them from files. We referenced the Secrets in our deployment.yaml file, thereby allowing our mySQL Pod to consume them. We also reviewed steps on how to confirm that our Secrets were consumed.
To clean up what we just created, use the following command to delete the cluster and everything in it:
k3d cluster delete <cluster_name>Thank you for reading and keep watching for more DevOps tutorials!
