Kubernetes basics

Configuration and state management

ConfigMaps, Secrets, and persistent storage (PV, PVC, StorageClass) and connecting them to pods.

5 min read

Configuration and state management

In this section you will learn to separate an application’s configuration and secrets from the code, and to understand how Kubernetes handles persistent storage. You will go through ConfigMaps, Secrets, and the storage concepts PV, PVC, and StorageClass. At the end you will connect environment variables and secrets to a running pod.


1. Why separate configuration from the application?

The same container image is meant to be run in different environments (development, test, production) without recompiling. That is why environment-specific configuration is kept separate from the code:

  • ConfigMap – non-secret configuration (settings, addresses, flags).
  • Secret – secret data (passwords, API keys, certificates).

This way the same application gets different settings in different environments simply by swapping the ConfigMap or Secret.


2. ConfigMaps

A ConfigMap stores non-secret configuration as key–value pairs. You can create it with a command or from a manifest:

bash
# Yksittäisistä arvoista
kubectl create configmap app-config \
  --from-literal=APP_GREETING="Hei ConfigMapista" \
  --from-literal=APP_TIER=frontend

# Tiedostosta
kubectl create configmap app-config --from-file=./asetukset.properties

As a manifest:

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  APP_GREETING: "Hei ConfigMapista"
  APP_TIER: frontend

A ConfigMap’s values can be connected to a pod in three ways:

  • as a single environment variable (configMapKeyRef),
  • all keys at once as environment variables (envFrom),
  • as files on disk (volume mount).

3. Secrets

A Secret is like a ConfigMap but intended for secret data. The values are stored base64-encoded (note: encoding ≠ encryption).

bash
kubectl create secret generic app-secret \
  --from-literal=DB_PASSWORD='s3cret-pw'

Common Secret types:

  • Opaque – free-form key–value (default),
  • kubernetes.io/dockerconfigjson – container registry credentials,
  • kubernetes.io/tls – TLS certificate and key.

⚠️ Security: base64 is not encryption — anyone can decode it. Protect Secrets with RBAC, enable etcd encryption at rest (RKE2 supports this), and do not store secrets in version control in plain text. Use external secret management when needed (e.g. Vault, Sealed Secrets).

Connecting a Secret to a pod works the same way as a ConfigMap: as an environment variable (secretKeyRef / envFrom) or as a volume.


4. Persistent storage: PV, PVC, and StorageClass

Pods are short-lived: when a Pod is removed, its containers’ file system is gone. Persistent storage decouples the data from the Pod’s lifecycle.

  • PersistentVolume (PV) – the actual storage resource in the cluster (a disk, NFS, a cloud volume). A cluster-level object.
  • PersistentVolumeClaim (PVC) – a Pod’s request for storage of a certain size and type. A Pod references a PVC, not a PV directly.
  • StorageClass – describes how storage is created automatically (dynamic provisioning). When a PVC references a StorageClass, a suitable PV is created automatically.
flowchart LR
    POD[Pod] --> PVC[PersistentVolumeClaim]
    PVC -->|binds to| PV[PersistentVolume]
    SC[StorageClass] -->|provisions| PV

Key concepts:

  • accessModes – ReadWriteOnce (one node), ReadOnlyMany, ReadWriteMany.
  • reclaimPolicy – what happens to the PV when the PVC is deleted (Delete or Retain).

💡 In an RKE2/Rancher environment, dynamic provisioning is handled by some StorageClass, e.g. a cloud disk service or local-path. Check the available classes with the command kubectl get storageclass.


5. Hands-on: connecting environment variables and secrets to a pod

You will create a ConfigMap and a Secret and connect them to a running pod as environment variables and files. First make sure you have the cluster’s kubeconfig in use (see the exercise in the architecture section).

1. Create and switch to your own namespace:

bash
# Korvaa <oma-nimi> omalla nimelläsi
kubectl create namespace <oma-nimi> --dry-run=client -o yaml | kubectl apply -f -
kubectl config set-context --current --namespace=<oma-nimi>

2. Create a ConfigMap and a Secret:

bash
kubectl create configmap app-config \
  --from-literal=APP_GREETING="Hei ConfigMapista" \
  --from-literal=APP_TIER=frontend

kubectl create secret generic app-secret \
  --from-literal=DB_PASSWORD='s3cret-pw'

3. Deploy a Deployment that consumes both:

bash
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: config-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: config-demo
  template:
    metadata:
      labels:
        app: config-demo
    spec:
      containers:
        - name: app
          image: nginx
          env:
            - name: APP_GREETING
              valueFrom:
                configMapKeyRef:
                  name: app-config
                  key: APP_GREETING
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: app-secret
                  key: DB_PASSWORD
          volumeMounts:
            - name: config-vol
              mountPath: /etc/app-config
              readOnly: true
      volumes:
        - name: config-vol
          configMap:
            name: app-config
EOF

4. Verify that the values are visible in the running pod:

bash
# Ympäristömuuttujat (ConfigMapista ja Secretistä)
kubectl exec deploy/config-demo -- printenv APP_GREETING DB_PASSWORD

# ConfigMap tiedostoina volumessa
kubectl exec deploy/config-demo -- ls /etc/app-config
kubectl exec deploy/config-demo -- cat /etc/app-config/APP_GREETING

🔎 Observation: When you update a ConfigMap or Secret, the environment variables do not update in the running pod automatically (they are read at startup). Values mounted as a volume, on the other hand, update after a delay. Changing environment variables requires restarting the pod (kubectl rollout restart deploy/config-demo).

5. (Optional) Request persistent storage with a PVC:

bash
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 1Gi
EOF

kubectl get pvc data

If the cluster has a default StorageClass, the PVC reaches the Bound state. Otherwise it stays Pending until a suitable StorageClass or PV is available.

6. Clean up the exercise by deleting your namespace:

bash
kubectl delete namespace <oma-nimi>

Summary

  • A ConfigMap stores non-secret and a Secret stores secret configuration; both are connected to a pod as environment variables or as a volume.
  • A Secret is only base64-encoded — protect it with RBAC and etcd encryption.
  • Environment variables are read at pod startup; a change requires a restart.
  • Persistent storage is built from three parts: PV (the resource), PVC (the request), and StorageClass (dynamic provisioning).
  • A Pod always references a PVC, never a PV directly.