Skip to main content
mastering ckad certified kubernetes application developer

ConfigMap and Secret Solutions

6 min read Chapter 56 of 87
Summary

Step-by-step solutions for Exercises 1 and 2: creating...

Step-by-step solutions for Exercises 1 and 2: creating a ConfigMap from a file with dual consumption via volume mount and valueFrom environment variable, and creating a TLS Secret mounted into an nginx container with read-only verification.

ConfigMap and Secret Solutions

Solution: Exercise 1 — ConfigMap as Environment Variables and Volume Mount

This exercise requires creating a ConfigMap from a file, consuming it as both a volume mount and a specific environment variable, and demonstrating update propagation behavior.

Step 1: Create the Configuration File

cat <<EOF > app.properties
db.host=postgres.prod.svc
db.port=5432
cache.ttl=300
EOF

Step 2: Create the ConfigMap

kubectl create configmap app-settings --from-file=app.properties

Verify:

kubectl get configmap app-settings -o yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-settings
data:
  app.properties: |
    db.host=postgres.prod.svc
    db.port=5432
    cache.ttl=300

The entire file is stored as a single key app.properties. The value is the file content.

Note on the valueFrom requirement: The exercise asks for db.host to be injected as an environment variable via valueFrom. Since the ConfigMap was created with --from-file, the key is app.properties (the whole file), not db.host. To use valueFrom with specific keys, we need a second ConfigMap created from literals, or we create the ConfigMap with --from-env-file. The best approach: create an additional ConfigMap from literals for the env var, or use --from-env-file.

Let’s use --from-env-file for the environment variable and --from-file for the volume mount. We can create one ConfigMap that serves both purposes by creating it from both sources, or create two ConfigMaps. The cleanest approach is a single ConfigMap with individual keys plus the file:

# Delete the previous ConfigMap
kubectl delete configmap app-settings

# Create with both individual keys and the file
kubectl create configmap app-settings \
  --from-literal=db.host=postgres.prod.svc \
  --from-literal=db.port=5432 \
  --from-literal=cache.ttl=300 \
  --from-file=app.properties

This ConfigMap contains four keys: db.host, db.port, cache.ttl (individual keys for env vars) and app.properties (the entire file, for volume mount).

Step 3: Create the Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: config-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: config-app
  template:
    metadata:
      labels:
        app: config-app
    spec:
      volumes:
        - name: config-volume
          configMap:
            name: app-settings
            items:
              - key: app.properties
                path: app.properties
      containers:
        - name: app
          image: busybox
          command: ["sh", "-c", "sleep 3600"]
          env:
            - name: DATABASE_HOST
              valueFrom:
                configMapKeyRef:
                  name: app-settings
                  key: db.host
          volumeMounts:
            - name: config-volume
              mountPath: /etc/config
              readOnly: true

Key decisions in this manifest:

  • Volume mount: Uses items to mount only the app.properties key, projecting it as the file /etc/config/app.properties.
  • Environment variable: Uses valueFrom with configMapKeyRef to inject the db.host key as DATABASE_HOST.
  • readOnly: Prevents accidental writes to the ConfigMap mount.

Apply:

kubectl apply -f config-app.yaml

Step 4: Verify

Wait for all replicas to be ready:

kubectl rollout status deployment/config-app

Verify the volume mount:

POD=$(kubectl get pods -l app=config-app -o jsonpath='{.items[0].metadata.name}')

kubectl exec $POD -- cat /etc/config/app.properties

Expected output:

db.host=postgres.prod.svc
db.port=5432
cache.ttl=300

Verify the environment variable:

kubectl exec $POD -- env | grep DATABASE_HOST

Expected output:

DATABASE_HOST=postgres.prod.svc

Step 5: Demonstrate Update Propagation

Update the ConfigMap:

kubectl edit configmap app-settings
# Change db.host to postgres.staging.svc in both the literal key and the app.properties content

Or use a patch:

kubectl create configmap app-settings \
  --from-literal=db.host=postgres.staging.svc \
  --from-literal=db.port=5432 \
  --from-literal=cache.ttl=300 \
  --from-file=app.properties \
  --dry-run=client -o yaml | kubectl apply -f -

Wait 60–90 seconds, then check:

# Volume mount — should show updated value
kubectl exec $POD -- cat /etc/config/app.properties

# Environment variable — should still show old value
kubectl exec $POD -- env | grep DATABASE_HOST

The volume-mounted file reflects the updated ConfigMap. The environment variable retains the original value (set at container startup). This confirms the behavior described in Chapter 16: volume mounts are dynamic, environment variables are static.

Troubleshooting

Volume mount shows stale data after 2+ minutes: Verify the ConfigMap was actually updated with kubectl get configmap app-settings -o yaml. If using subPath, note that subPath mounts never auto-update.

Pod fails to start with CreateContainerConfigError: Check that the ConfigMap exists in the same namespace as the Pod. A typo in configMapRef or configMapKeyRef causes this error.

Environment variable is empty: Verify the key name matches exactly. db.host has a dot — make sure the ConfigMap key matches the configMapKeyRef.key field.


Solution: Exercise 2 — TLS Secret with Nginx

This exercise requires creating a TLS Secret from certificate/key files and mounting it into an nginx container.

Step 1: Generate the Certificate

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout tls.key -out tls.crt \
  -subj "/CN=myapp.example.com"

This generates two files:

  • tls.crt — the self-signed certificate
  • tls.key — the private key

Step 2: Create the TLS Secret

kubectl create secret tls nginx-tls --cert=tls.crt --key=tls.key

Verify:

kubectl get secret nginx-tls -o yaml
apiVersion: v1
kind: Secret
metadata:
  name: nginx-tls
type: kubernetes.io/tls
data:
  tls.crt: LS0tLS1CRUdJTi...
  tls.key: LS0tLS1CRUdJTi...

The Secret type is kubernetes.io/tls and contains exactly two keys: tls.crt and tls.key.

Step 3: Create the Pod

apiVersion: v1
kind: Pod
metadata:
  name: secure-nginx
spec:
  volumes:
    - name: tls-volume
      secret:
        secretName: nginx-tls
  containers:
    - name: nginx
      image: nginx:1.25
      ports:
        - containerPort: 443
      volumeMounts:
        - name: tls-volume
          mountPath: /etc/nginx/ssl
          readOnly: true

Apply:

kubectl apply -f secure-nginx.yaml

Step 4: Verify

Wait for the Pod to be ready:

kubectl wait --for=condition=ready pod/secure-nginx --timeout=30s

Verify the TLS files are mounted:

kubectl exec secure-nginx -- ls -la /etc/nginx/ssl/

Expected output:

total 0
drwxrwxrwt 3 root root 120 ... .
drwxr-xr-x 3 root root  28 ... ..
lrwxrwxrwx 1 root root  14 ... tls.crt -> ..data/tls.crt
lrwxrwxrwx 1 root root  14 ... tls.key -> ..data/tls.key

Both tls.crt and tls.key are present as symbolic links (this is how Kubernetes manages volume-mounted Secrets for atomic updates).

Verify the certificate content:

kubectl exec secure-nginx -- cat /etc/nginx/ssl/tls.crt | openssl x509 -noout -subject

Expected output:

subject=CN = myapp.example.com

Verify the mount is read-only:

kubectl exec secure-nginx -- touch /etc/nginx/ssl/test

Expected output:

touch: /etc/nginx/ssl/test: Read-only file system

Troubleshooting

error: failed to read certificate: The certificate or key file is malformed. Regenerate using the openssl command above. Ensure you are not accidentally base64-encoding the files before passing them to kubectl create secret tls.

Pod stuck in ContainerCreating: Run kubectl describe pod secure-nginx and check the Events section. A common issue is a missing Secret (typo in secretName).

Files not visible in /etc/nginx/ssl/: Verify the mountPath and volumeMounts.name match the volume name in spec.volumes. A name mismatch silently skips the mount.