Kubernetes logo

Providing Variables to Kubernetes Applications

While we shouldn’t run naked Pods, we’ve already seen we can pass environment variables when creating a Pod:
kubectl run mydb --image=mysql --env="MYSQL_ROOT_PASSWORD=password"

When creating a Deployment, however, there’s no command line option to provide variables. We’ll need to create the Deployment first, then set the environment variables:

  • kubectl create deploy mydb --image=mysql
  • kubectl set env deploy mydb MYSQL_ROOT_PASSWORD=password

Obviously you could generate the Deployment YAML file first and add your variables to the YAML file before creating the Deployment.

[email protected]:~$ kubectl create deployment mydb --image=mariadb
deployment.apps/mydb created

[email protected]:~$ kubectl get pods
NAME                   READY   STATUS   RESTARTS   AGE
mydb-fb7ff4d78-kqbvj   0/1     Error    0          40s

[email protected]:~$ kubectl logs mydb-fb7ff4d78-kqbvj
2022-04-16 07:41:41+00:00 [Note] [Entrypoint]: Entrypoint script for MariaDB Server 1:10.7.3+maria~focal started.
2022-04-16 07:41:41+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
2022-04-16 07:41:41+00:00 [Note] [Entrypoint]: Entrypoint script for MariaDB Server 1:10.7.3+maria~focal started.
2022-04-16 07:41:41+00:00 [ERROR] [Entrypoint]: Database is uninitialized and password option is not specified
	You need to specify one of MARIADB_ROOT_PASSWORD, MARIADB_ALLOW_EMPTY_ROOT_PASSWORD and MARIADB_RANDOM_ROOT_PASSWORD

[email protected]:~$ kubectl set env deploy mydb MYSQL_ROOT_PASSWORD=password
deployment.apps/mydb env updated

[email protected]:~$ kubectl get pods
NAME                    READY   STATUS              RESTARTS      AGE
mydb-6df85bcdbb-thm2h   0/1     ContainerCreating   0             5s
mydb-fb7ff4d78-kqbvj    0/1     Error               3 (47s ago)   108s

[email protected]:~$ kubectl get pods
NAME                    READY   STATUS    RESTARTS   AGE
mydb-6df85bcdbb-thm2h   1/1     Running   0          13s

[email protected]:~$ kubectl get deploy mydb -o yaml > mydb.yml
...
[email protected]:~$ kubectl create deploy mynewdb --image=mariadb --dry-run=client -o yaml > mynewdb.yaml
[email protected]:~$ kubectl create -f mynewdb.yaml 
deployment.apps/mynewdb created

[email protected]:~$ kubectl set env deploy mynewdb MYSQL_ROOT_PASSWORD=password --dry-run=client -o yaml > mynewdb.yaml 
[email protected]:~$ grep -i password mynewdb.yaml 
        - name: MYSQL_ROOT_PASSWORD
          value: password

[email protected]:~$ kubectl describe deploy mynewdb | grep -i password
[email protected]:~$ kubectl apply -f mynewdb.yaml 
deployment.apps/mynewdb configured

[email protected]:~$ kubectl describe deploy mynewdb | grep -i password
      MYSQL_ROOT_PASSWORD:  password

ConfigMaps

Code should be static, which makes it portable so that it can be used in other environments. To achieve this we need to separate site-specific information, like environment variables, from the code. These should not be provided in the Deployment configuration.

ConfigMaps are the solution to this issue, we can define variables and have our Deployment point to the ConfigMap. ConfigMaps are created in a different way depending what it will be used for:

  • Variables
  • Configuration Files
  • Command line arguments

Providing Variables with ConfigMaps

We can create a ConfigMap for variables in two ways:

  • By passing a file that contains the variables in a key=value format:
    kubectl create cm mycm --from-env-file=myfile
  • By passing the variables directly:
    kubectl create cm mycm--from-literal=MYSQL_ROOT_PASSWORD=password

Once you have the ConfigMap, you can update your deployment so that it points to the ConfigMap: kubectl set env --from=configmap/mycm deploy/mydeployment

[email protected]:~$ cat dbvarsfile 
MYSQL_ROOT_PASSWORD=password
MYSQL_USER=joeri

[email protected]:~$ kubectl create cm mydbvars --from-env-file=dbvarsfile 
configmap/mydbvars created

[email protected]:~$ kubectl create deploy mydb --image=mariadb
deployment.apps/mydb created

[email protected]:~$ kubectl set env deploy mydb --from=configmap/mydbvars
deployment.apps/mydb env updated

[email protected]:~$ kubectl describe deploy mydb | grep MYSQL_
      MYSQL_ROOT_PASSWORD:  <set to the key 'MYSQL_ROOT_PASSWORD' of config map 'mydbvars'>  Optional: false
      MYSQL_USER:           <set to the key 'MYSQL_USER' of config map 'mydbvars'>           Optional: false

[email protected]:~$ kubectl get deploy mydb -o yaml > mydb.yaml
...

Providing Configuration Files with ConfigMaps

In addition to providing variables, we can provide configuration files to our application by making use of ConfigMaps:
kubectl create cm myconf --from-file=/my/file.conf

If a ConfigMap is created from a directory instead of a file, all files in that directory will be included in the ConfigMap. When using ConfigMap for configuration files the ConfigMap must be mounted in the application, it behaves similarly to a Volume.

From a high level, we need to:

  • Generate the base YAML code, then add the ConfigMap mount to it later
  • Define a Volume of the ConfigMap type in the application manifest
  • Mount this volume on a specific directory, the configuration file will appear inside that directory.

In the below example we’ll provide an index.html file to Nginx via a ConfigMap:

[email protected]:~$ echo "Hello World!" > index.html
[email protected]:~$ kubectl create cm myindex --from-file=index.html
configmap/myindex created

[email protected]:~$ kubectl describe cm myindex
Name:         myindex
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
index.html:
----
Hello World!


BinaryData
====

Events:  <none>

[email protected]:~$ kubectl create deploy myweb --image=nginx
deployment.apps/myweb created

We’ll edit the deployment and add volumes and volumeMounts to spec.template.spec:

[email protected]:~$ kubectl edit deploy myweb
...
    spec:
      volumes:
      - name: cmvol
        configMap:
          name: myindex
      containers:
        volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: cmvol
      - image: nginx
        imagePullPolicy: Always
        name: nginx
        resources: {}
...

Let’s verify our changes:

[email protected]:~$ kubectl describe deploy myweb
Pod Template:
  Labels:  app=myweb
  Containers:
   nginx:
    Image:        nginx
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:
      /usr/share/nginx/html from cmvol (rw)
  Volumes:
   cmvol:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      myindex
...

[email protected]:~$ kubectl get all --selector app=myweb
NAME                        READY   STATUS    RESTARTS   AGE
pod/myweb-ff8bf9988-287n2   1/1     Running   0          13m

NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/myweb   1/1     1            1           19m

NAME                              DESIRED   CURRENT   READY   AGE
replicaset.apps/myweb-8764bf4c8   0         0         0       19m
replicaset.apps/myweb-ff8bf9988   1         1         1       13m

[email protected]:~$ kubectl exec pod/myweb-ff8bf9988-287n2 -- cat /usr/share/nginx/html/index.html
Hello World!

Understanding Secrets

Secrets allow you to store sensitive data such as passwords, authentication tokens and SSH keys, outside of a Pod to reduce the risk of accidental expose. Some Secrets are automatically created by Kubernetes while others can be created by the user. System-created Secrets are important for Kubernetes resources to connect to other cluster resources.

Secrets are Base64 encoded and not encrypted.

Three types of Secret types are offered:

  • docker-registry: Used for connecting to a Docker registry.
  • TLS: Used to store TLS key material.
  • generic: Creates a secret from a local file, directory or literal value

You need to specify the type when defining the Secret: kubectl create secret generic ...

How Kubernetes Uses Secrets

All Kubernetes resources need access to TLS keys in order to access the Kubernetes API. These keys are provided by Secrets and used through ServiceAccounts. By using the ServiceAccount, the application has access to its Secret.

Let’s inspect one of the secrets Kubernetes uses. As mentioned previously, Secrets are used through ServiceAccounts, so we need to find out the ServiceAccount first before we can inspect the details of the Secret:

[email protected]:~$ kubectl get pods -n kube-system
NAME                               READY   STATUS    RESTARTS        AGE
coredns-64897985d-lhqq6            1/1     Running   1 (6m49s ago)   25m
etcd-minikube                      1/1     Running   1 (6m49s ago)   25m
kube-apiserver-minikube            1/1     Running   1 (6m49s ago)   25m
kube-controller-manager-minikube   1/1     Running   1 (6m49s ago)   25m
kube-proxy-khgjl                   1/1     Running   1 (6m49s ago)   25m
kube-scheduler-minikube            1/1     Running   1 (6m49s ago)   25m
storage-provisioner                1/1     Running   2 (6m49s ago)   25m

[email protected]:~$ kubectl get pods -n kube-system coredns-64897985d-lhqq6 -o yaml | grep serviceAccount
  serviceAccount: coredns
  serviceAccountName: coredns
      - serviceAccountToken:

[email protected]:~$ kubectl get sa -n kube-system coredns -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: "2022-02-01T15:48:54Z"
  name: coredns
  namespace: kube-system
  resourceVersion: "299"
  uid: 519a806e-35c0-45be-a5a0-495d9f7c7586
secrets:
- name: coredns-token-j6qdj

[email protected]:~$ kubectl get secret -n kube-system coredns-token-j6qdj -o yaml
apiVersion: v1
data:
  ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCakNDQWU2Z0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwdGFXNXAKYTNWaVpVTkJNQjRYRFRJeU1ETXdOekUxTlRrd05Wb1hEVE15TURNd05URTFOVGt3TlZvd0ZURVRNQkVHQTFVRQpBeE1LYldsdWFXdDFZbVZEUVRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBS0pDCnhPam5XYXRKSW9tdUcrSGtsM3J0aFhGV0NwUGhic3FXTkhsbGlqeTlWWVRvYlYwdHVrUW1sRkRvZGc2N1RULzQKWmlYNVFvUXdyV0NOSTYrYmtPMGpGMUhPRXJQNUF2S3ZJMEpabzliSTZzN1NPVmVsNHJsRGtRUGFScjBWajhrZwpGZTZNb2tUZGswQlBmQ1l5c2hhNmNBUGNaaHl1Wjl3clJRYi83dnZkS3BzZ2tLZ1ZOMmVEQnNqRzNGWFc1M2JvCkx6azJsT1NORHRxNndVSTdlZzIrNjR2UEQ5YkdWU09IU3JraVNMTVdtU3ZWL0d3SlV3dFd6YVhtZWhJZ1NLRVAKY3ZxMWtRN0dvUEVzTUF6TUtMb2F4bXdpZlUxQ0xISE93akhWTlZvVXcvVmNOQlZCOGlnRGd4cmJMSjg3bU9pOQpqbzJpck1BNTZqZExPVk1rUFlzQ0F3RUFBYU5oTUY4d0RnWURWUjBQQVFIL0JBUURBZ0trTUIwR0ExVWRKUVFXCk1CUUdDQ3NHQVFVRkJ3TUNCZ2dyQmdFRkJRY0RBVEFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQjBHQTFVZERnUVcKQkJTODl5UHdEYzJxZG13VGFlbWxZcndvclRqVTdqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFlOTdNNjV3WQpxUU5nR2NzT3A4Tm4rbzdGdXQ0cWMyWldjdll5bEZKUnFURjFIVjhwZDIzTFR0V3VoRkQraVk5SDJuLzFNdzdwCnVFcHdVUjAzVHpIUUVpL1JjTUJPV0JBakFGVzJHck5RelhVbzdyOE03a3FHdEN3MVd4WXduQVBhNGJ1SG41SWcKT0lhQTA4V25udW4rcFFRMW5WL25aU04yV2xwRzRrblhGcHAzcjhTQ21uVkd1L296VjV3bGZ3WU9Ea3prZExSMgp4bjA5SHhTWkJsclpDdFZqWUxDaVRYbkN3Q3pTVXZSNjhYWkNZVWRWTHF5ZzZyZXBYb0dsSkJzY0ZMZURtKzZrCnVZR1ZvY0Ezd0FpWktoazJPV2EzcGdnTFJod2xTdTRqaFZZNk82WFpvQXMzOHcvYzFTeWY1WDBqcFB1OVdPRW8KSDdsTEpCOTdhamhYdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
  namespace: a3ViZS1zeXN0ZW0=
  token: ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklsWlplWEJ4UkdoUVNUQnJaM1JTTjJGNVdUQTRlakJZUjBWS2VVaHdNRlJSYzFoQ1JFOXJPWGRGYmxFaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUpyZFdKbExYTjVjM1JsYlNJc0ltdDFZbVZ5Ym1WMFpYTXVhVzh2YzJWeWRtbGpaV0ZqWTI5MWJuUXZjMlZqY21WMExtNWhiV1VpT2lKamIzSmxaRzV6TFhSdmEyVnVMV28yY1dScUlpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVibUZ0WlNJNkltTnZjbVZrYm5NaUxDSnJkV0psY201bGRHVnpMbWx2TDNObGNuWnBZMlZoWTJOdmRXNTBMM05sY25acFkyVXRZV05qYjNWdWRDNTFhV1FpT2lJMU1UbGhPREEyWlMwek5XTXdMVFExWW1VdFlUVmhNQzAwT1RWa09XWTNZemMxT0RZaUxDSnpkV0lpT2lKemVYTjBaVzA2YzJWeWRtbGpaV0ZqWTI5MWJuUTZhM1ZpWlMxemVYTjBaVzA2WTI5eVpXUnVjeUo5LmN6cGp6SUM5NG9jSV81N21vZU5wZ2xXLWtVZnpHdUlUUktfa09qbkw3M0xuN1p2M2tLMWU2TjNqbUpPSW95d2RMcms5NWNwZC1pT1VjQWdpQVcxN3dJRUZ1THR4WnVkbmsyNnBwWU1sdDNLWHpBMkJycjdkYzZGM0xjdG9RNTdPMEY0MnEybXpJS0dnVDBVYkhmYTNwTjd4ZDY0Zk04RVFpZUc2bEZBSlNuYjlBTGVqSjd6X1JjeWdkLU1SOE9Qc2gtd05KMW1RSlVrUktzenVwTHdZcERKSXVCSGx6a093Rm04YXJ5ODZ3Y0pGdzNSbm5mcFo4ZTF0aWwtWUVSTmV3aDdMdzhvTGRrSzJNUnVVSnBKZmtGZ1kteWhWejdwa3MtNW53U05BUWVpTEk2RG9oUFBqd3BYa3hzWWVCbUhZVWo1b3JMNlZ5NG9Xb1ZZTnJIU0JvUQ==
kind: Secret
metadata:
  annotations:
    kubernetes.io/service-account.name: coredns
    kubernetes.io/service-account.uid: 519a806e-35c0-45be-a5a0-495d9f7c7586
  creationTimestamp: "2022-02-01T15:48:55Z"
  name: coredns-token-j6qdj
  namespace: kube-system
  resourceVersion: "294"
  uid: d5b84f10-e6fa-46d0-92ec-2b7895a799eb
type: kubernetes.io/service-account-token

Notice how the values in the Secret Yaml output above are base64 encoded, e.g. for the namespace:

[email protected]:~$ echo a3ViZS1zeXN0ZW0= | base64 -d
kube-system

Configuring Applications to Use Secrets

There are different use cases for using Secrets in applications:

  • Providing TLS keys to the application:
    kubectl create secret tls my-tls-keys --cert=pathto/my.crt --key=pathto/my.key
  • Provide security to passwords:
    kubectl create generic my-secret-pw --from-literal=password=verysecret
  • Provide access to an SSH private key:
    kubectl create generic my-ssh-key --from-file=ssh-private-key=.ssh/id_rsa
  • Provide access to sensitive files which would be mounted in the application with root access only:
    kubectl create secret generic my-secret-file --from-file=/my/secretfile

Secrets are used in a similar way to using ConfigMaps in applications:

  • If your Secret contains variables (like a password), use kubectl set env.
  • If it contains files (like keys), mount the Secret. Consider using defaultMode: 0400 permissions when mounting the Secret in the Pod spec.

Mounted Secrets are automatically updated in the application when the Secret is updated.

Let’s demonstrate this:

[email protected]:~$ kubectl create secret generic dbpw --from-literal=ROOT_PASSWORD=password
secret/dbpw created

[email protected]:~$ kubectl describe secret dbpw
Name:         dbpw
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
ROOT_PASSWORD:  8 bytes

[email protected]:~$ kubectl get secret dbpw -o yaml
apiVersion: v1
data:
  ROOT_PASSWORD: cGFzc3dvcmQ=
kind: Secret
metadata:
  creationTimestamp: "2022-02-01T16:30:07Z"
  name: dbpw
  namespace: default
  resourceVersion: "1661"
  uid: 6aff6adf-73e1-4ffd-b99a-fd036a034c6b
type: Opaque

[email protected]:~$ echo cGFzc3dvcmQ= | base64 -d
password

Now, let’s deploy our Secret to an app:

[email protected]:~$ kubectl create deployment mynewdb --image=mariadb
deployment.apps/mynewdb created

Remember that mariadb is expecting at the very least a MYSQL_ROOT_PASSWORD environment variable. But since we created our Secret with ROOT_PASSWORD instead of MYSQL_ROOT_PASSWORD we would need to set a prefix when attaching the Secret to the application. This can come in handy in case I have other applications that could potentially use the same Secret.

[email protected]:~$ kubectl set env deployment mynewdb --from=secret/dbpw --prefix=MYSQL_
deployment.apps/mynewdb env updated

Now, while our password is base64 encoded, this isn’t the case inside the Pod where it’s in clear text:

[email protected]:~$ kubectl get pods
NAME                       READY   STATUS    RESTARTS   AGE
mynewdb-7cc5fb9c55-58wkz   1/1     Running   0          13m

[email protected]:~$ kubectl exec mynewdb-7cc5fb9c55-58wkz -- env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=mynewdb-7cc5fb9c55-58wkz
MYSQL_ROOT_PASSWORD=password

Configuring Docker Registry Access Secret

The docker-registry Secret type stores container registry (Docker Hub, Quay.io, self hosted, …) authentication credentials. While you don’t need to authenticate, it’s recommended to prevent pull rate errors in case you’re running a busy cluster.

There’s two ways to create the Secret: Either by directly passing the credentials, or by passing an existing Docker Config file which contains the credentials:

[email protected]:~$ kubectl create secret docker-registry -h
Examples:
  # If you don't already have a .dockercfg file, you can create a dockercfg secret directly by using:
  kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER
--docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL
  
  # Create a new secret named my-secret from ~/.docker/config.json
  kubectl create secret docker-registry my-secret --from-file=.dockerconfigjson=path/to/.docker/config.json