Most content in this document comes from "Learn Kubernetes in a Month of Lunches".
Environment variables are variables applied to your program/application dynamically during runtime, based on which the way your program/application acts is different. Examples of environment variables are a password of the database running for your app, paths for packages/libraries that your code depends on, and go build variables (GOARCH, GOOS) to create a binary file that conforms to your system.
ConfigMaps and Secrets are kubernetes resources to supply environment variables to the Pod your application is running on. They are just storage units intended for small amounts of data. Those storage units can be loaded into a Pod, becoming part of the container environment, so that application in the container can read the data.
In the below example, KIAMOL_CHAPTER
is specified at spec.template.spec.containers.env
of the Deployment yaml spec.
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
containers:
- name: sleep
image: kiamol/ch03-sleep
env:
- name: KIAMOL_CHAPTER
value: "04"
It is the most straight-forward way to supply environment variables to the Pod. A downside of it is that if you need to make configuration changes you need to perform an update with a replacement Pod, which will results in frequent Pod replacements.
Real application usually have more complex configuration requirements, which is when you use ConfigMaps. As mentioned above, ConfigMaps are just a storage unit that has data in the form of key-value pairs. To load a ConfigMap into a Pod, the ConfigMap needs to exist before you deploy the Pod. Below is the kubectl command to create a ConfigMap.
# create a ConfigMap with data from the command line:
❯ kubectl create configmap sleep-config-literal --from-literal=kiamol.section='4.1'
error: failed to create configmap: configmaps "sleep-config-literal" already exists
# check the ConfigMap details:
❯ kubectl get cm sleep-config-literal
NAME DATA AGE
sleep-config-literal 1 47h
Then deploy the updated app to use the ConfigMap.
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
containers:
- name: sleep
image: kiamol/ch03-sleep
env:
- name: KIAMOL_CHAPTER
value: "04"
- name: KIAMOL_SECTION # a new env var from ConfigMap
valueFrom:
configMapKeyRef:
name: sleep-config-literal # ConfigMap name
key: kiamol.section # the data item to load
After deploying the above yaml spec, check the KIAMOL_SECTION
environment variable.
# create a ConfigMap with data from the command line:
❯ kubectl exec deploy/sleep -- sh -c 'printenv | grep "^KIAMOL"'
KIAMOL_CHAPTER=04
To supply a lot of configuration data in one scoop, create an environment file that can be loaded to create a ConfigMap is a good option to consider. For example, save a file ch04.env
with the following content.
# Environment files use a new line for each variable.
KIAMOL_CHAPTER=ch04
KIAMOL_SECTION=ch04-4.1
KIAMOL_EXERCISE=try it now
Then create a ConfigMap with this env file.
# load an environment variable into a new ConfigMap:
❯ kubectl create configmap sleep-config-env-file --from-env-file=sleep/ch04.env
error: failed to create configmap: configmaps "sleep-config-env-file" already exists
# check the details of the ConfigMap:
❯ kubectl get cm sleep-config-env-file
NAME DATA AGE
sleep-config-env-file 3 47h
Next, update the Pod to use the new ConfigMap.
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
containers:
- name: sleep
image: kiamol/ch03-sleep
envFrom: # newly added block to reference a ConfigMap
- configMapRef: # newly added block to refernece a ConfigMap
name: sleep-config-env-file # newly added block to refernece a ConfigMap
env:
- name: KIAMOL_CHAPTER
value: "04"
- name: KIAMOL_SECTION
valueFrom:
configMapKeyRef:
name: sleep-config-literal
key: kiamol.section
After deploying a new Deployment yaml spec, print out the environment variables. You will meet the interesting facts.
# update the Pod to use the new ConfigMap:
❯ kubectl apply -f sleep/sleep-with-configMap-env-file.yaml
deployment.apps/sleep configured
# check the values in the container:
❯ kubectl exec deploy/sleep -- sh -c 'printenv | grep "^KIAMOL"'
error: unable to upgrade connection: container not found ("sleep")
snam@C02FL4Y9MD6T ch04 % kubectl exec deploy/sleep -- sh -c 'printenv | grep "^KIAMOL"'
KIAMOL_EXERCISE=try it now
KIAMOL_SECTION=4.1
KIAMOL_CHAPTER=04
When the same environment variable is supplied by multiple sources(one by ConfigMap defined under
env
and the other underenvFrom
in this example), the environment variables defined withenv
in the Pod spec override the values defined withenvFrom
.
This will be the most common way to use a ConfigMap. Key-value pairs for environment variables will be specified in the data
section inside of ConfigMap. Please refer to the below example.
apiVersion: v1
kind: ConfigMap
metadata:
name: todo-web-config-dev
data:
config.json: |-
{
"ConfigController": {
"Enabled" : true
}
}
The Pod bound with todo-web
Deployment(see the spec below) will load this ConfigMap and mount config.json
file onto the path /app/config
in the container. The ConfigMap is treated like a directory.
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-web
spec:
selector:
matchLabels:
app: todo-web
template:
metadata:
labels:
app: todo-web
spec:
containers:
- name: web
image: kiamol/ch04-todo-list
volumeMounts:
- name: config
mountPath: "/app/config" # ConfigMap mounted path
readOnly: true
volumes:
- name: config
configMap:
name: todo-web-config-dev # Names a ConfigMap to load
# now create a secret from a plain text literal:
❯ kubectl create secret generic sleep-secret-literal --from-literal=secret=shh...
deployment.apps/todo-web created
# show the friendly details of the Secret:
❯ kubectl describe secret sleep-secret-literal
Name: sleep-secret-literal
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
secret: 6 bytes
# retrieve the encoded Secret value:
❯ kubectl get secret sleep-secret-literal -o jsonpath='{.data.secret}'
c2hoLi4u%
# and decode the data:
❯ kubectl get secret sleep-secret-literal -o jsonpath='{.data.secret}' | base64 -d
shh...%
Creating Secrets from a literal value is equivalent to base64 encoding, which isn’t really a security feature but just does prevent accidental exposure of secrets to someone looking over your shoulder.
The way to load Secrets to the Pod is almost the same with ConfigMaps, as you can see below.
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
containers:
- name: sleep
image: kiamol/ch03-sleep
env: # Environment variables
- name: KIAMOL_SECRET # Variable name in the container
valueFrom: # loaded from an external source
secretKeyRef: # which is a Secret
name: sleep-secret-literal # Names the Secret
key: secret # Key of the Secret data item
Confirm that KIAMOL_SECRET
is supplied to the Pod.
# update the sleep Deployment:
❯ kubectl apply -f sleep/sleep-with-secret.yaml
deployment.apps/sleep configured
# check the environment variable in the Pod:
❯ kubectl exec deploy/sleep -- printenv KIAMOL_SECRET
shh...
As you can observe in this case, the sensitive data provided by a literal Secret is easily exposed. Providing a sensitive data in a Secret yaml spec like below has the same issue.
apiVersion: v1
kind: Secret # Secret is the resource type.
metadata:
name: todo-db-secret-test # Names the Secret
type: Opaque # Opaque secrets are for text data.
stringData: # stringData is for plain text.
POSTGRES_PASSWORD: "kiamol-2*2*" # The secret key and value.
apiVersion: v1
kind: Secret
metadata:
name: todo-db-secret-test
type: Opaque
stringData:
POSTGRES_PASSWORD: "kiamol-2*2*"
spec:
containers:
- name: db
image: postgres:11.6-alpine
env:
- name: POSTGRES_PASSWORD_FILE # Sets the path to the file
value: /secrets/postgres_password
volumeMounts: # Mounts a Secret volume
- name: secret # Names the volume
mountPath: "/secrets"
volumes:
- name: secret
secret: # Volume loaded from a Secret
secretName: todo-db-secret-test # Secret name
defaultMode: 0400 # Permissions to set for files
items: # Optionally names the data items
- key: POSTGRES_PASSWORD
path: postgres_password
Look at the above Pod spec. The value for POSTGRES_PASSWORD_FILE
is not a sensitive data itself but a path where the data is stored. When this Pod is deployed, Kubernetes loads the value of the Secret item into a file at the path /secrets/postgres_password
. That file will be set with 0400 permissions, which means it can be read by the container user but not by any other users, which effectively minimizes the exposure of the sensitive data.