Skip to main content

Command Palette

Search for a command to run...

Kubernetes 101: Building Scalable Applications - ConfigMaps & Secrets

Published
8 min read

{{

}}

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.

student@minikube:~$ kubectl create deployment mydb --image=mariadb
deployment.apps/mydb created

student@minikube:~$ kubectl get pods
NAME                   READY   STATUS   RESTARTS   AGE
mydb-fb7ff4d78-kqbvj   0/1     Error    0          40s

student@minikube:~$ 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

student@minikube:~$ kubectl set env deploy mydb MYSQL_ROOT_PASSWORD=password
deployment.apps/mydb env updated

student@minikube:~$ 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

student@minikube:~$ kubectl get pods
NAME                    READY   STATUS    RESTARTS   AGE
mydb-6df85bcdbb-thm2h   1/1     Running   0          13s

student@minikube:~$ kubectl get deploy mydb -o yaml > mydb.yml
...
student@minikube:~$ kubectl create deploy mynewdb --image=mariadb --dry-run=client -o yaml > mynewdb.yaml
student@minikube:~$ kubectl create -f mynewdb.yaml 
deployment.apps/mynewdb created

student@minikube:~$ kubectl set env deploy mynewdb MYSQL_ROOT_PASSWORD=password --dry-run=client -o yaml > mynewdb.yaml 
student@minikube:~$ grep -i password mynewdb.yaml 
        - name: MYSQL_ROOT_PASSWORD
          value: password

student@minikube:~$ kubectl describe deploy mynewdb | grep -i password
student@minikube:~$ kubectl apply -f mynewdb.yaml 
deployment.apps/mynewdb configured

student@minikube:~$ 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

student@minikube:~$ cat dbvarsfile 
MYSQL_ROOT_PASSWORD=password
MYSQL_USER=joeri

student@minikube:~$ kubectl create cm mydbvars --from-env-file=dbvarsfile 
configmap/mydbvars created

student@minikube:~$ kubectl create deploy mydb --image=mariadb
deployment.apps/mydb created

student@minikube:~$ kubectl set env deploy mydb --from=configmap/mydbvars
deployment.apps/mydb env updated

student@minikube:~$ 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

student@minikube:~$ 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:

student@minikube:~$ echo "Hello World!" > index.html
student@minikube:~$ kubectl create cm myindex --from-file=index.html
configmap/myindex created

student@minikube:~$ kubectl describe cm myindex
Name:         myindex
Namespace:    default
Labels:       <none>
Annotations:  <none>

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


BinaryData
====

Events:  <none>

student@minikube:~$ kubectl create deploy myweb --image=nginx
deployment.apps/myweb created

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

student@minikube:~$ 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:

student@minikube:~$ 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
...

student@minikube:~$ 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

student@minikube:~$ 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:

student@minikube:~$ 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

student@minikube:~$ kubectl get pods -n kube-system coredns-64897985d-lhqq6 -o yaml | grep serviceAccount
  serviceAccount: coredns
  serviceAccountName: coredns
      - serviceAccountToken:

student@minikube:~$ 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

student@minikube:~$ 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:

student@minikube:~$ 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:

student@minikube:~$ kubectl create secret generic dbpw --from-literal=ROOT_PASSWORD=password
secret/dbpw created

student@minikube:~$ kubectl describe secret dbpw
Name:         dbpw
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
ROOT_PASSWORD:  8 bytes

student@minikube:~$ 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

student@minikube:~$ echo cGFzc3dvcmQ= | base64 -d
password

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

student@minikube:~$ 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.

student@minikube:~$ 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:

student@minikube:~$ kubectl get pods
NAME                       READY   STATUS    RESTARTS   AGE
mynewdb-7cc5fb9c55-58wkz   1/1     Running   0          13m

student@minikube:~$ 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:

student@minikube:~$ 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