In the previous chapter, you gave Pods persistent storage — the external lockers of our Kubernetes city. But applications also need configuration: database hostnames, feature flags, log levels. Some of it is sensitive — passwords, API tokens, TLS keys. This chapter covers ConfigMaps for non-sensitive data and Secrets for sensitive.
5.2.1 Configuration Separation Principle
The Twelve-Factor App methodology states: store config in environment, not in code. Embed a password into an image and it is permanently etched into every layer, discoverable by anyone who pulls.
Externalizing configuration means the same image runs everywhere. Kubernetes provides ConfigMaps for non-sensitive data and Secrets for sensitive data.
5.2.2 ConfigMaps: The Public Bulletin Board
A ConfigMap stores non-sensitive data as key-value pairs or files. It has two fields: data (UTF-8 text) and binaryData (base64-encoded binary). Hard limit: 1MB (etcd constraint). Don't stuff large files in; use an init container to download at runtime.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DATABASE_HOST: "mysql.default.svc.cluster.local"
LOG_LEVEL: "INFO"
app.properties: |
max.connections=100
timeout.seconds=30
Consumption Pattern 1: Environment Variables
spec:
containers:
- name: app
image: myapp:1.0
envFrom:
- configMapRef:
name: app-config
Each key becomes an environment variable. For selective injection, use valueFrom.
Consumption Pattern 2: Volume Mounts
spec:
containers:
- name: app
image: myapp:1.0
volumeMounts:
- name: config-vol
mountPath: /etc/config
volumes:
- name: config-vol
configMap:
name: app-config
Each key becomes a file; each value becomes the file contents. This is the only pattern that supports hot-reload — files refresh automatically when the ConfigMap changes, without restarting the Pod. Environment variables are set at startup and never change.
Consumption Pattern 3: Command-Line Arguments
spec:
containers:
- name: app
image: myapp:1.0
command: ["/bin/myapp"]
args: ["--log-level=$(LOG_LEVEL)"]
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
Visual Description: ConfigMap Consumption Patterns
🛑 PAUSE & RECALL — 2 minutes
Without looking back:
- What is the size limit for a ConfigMap, and why does it exist?
- Which consumption pattern supports hot-reloading without a Pod restart?
- What is the difference between the
dataandbinaryDatafields?Rate your confidence (0–4).
5.2.3 Secrets: The Filing Cabinet
A Secret stores sensitive data — passwords, tokens, keys — using the same structure as a ConfigMap. Kubernetes Secrets use base64 encoding, not encryption. Base64 is transport encoding, not security. Anyone with read access can decode instantly: echo 'cGFzc3dvcmQxMjM=' | base64 -d. No key, no decryption.
⚠️ Common Misconception: "I put my password in a Secret, so it's secure." No. The Secret type offers RBAC scoping, but base64 alone provides zero confidentiality.
# Create a Secret
kubectl create secret generic db-password \
--from-literal=password=Sup3rS3cret!
# View encoded, then decode — anyone can do this
kubectl get secret db-password -o jsonpath='{.data.password}'
kubectl get secret db-password -o jsonpath='{.data.password}' | base64 -d
Built-in types: Opaque (default), kubernetes.io/tls, kubernetes.io/dockerconfigjson, kubernetes.io/service-account-token.
Secrets support the same three consumption patterns. Prefer volume mounts over env vars — env vars appear in process listings and crash dumps; mounted files stay out of the process environment.
Visual Description: Encoding vs. Encryption
5.2.4 Secret Security: Beyond Base64
To actually secure Secrets, use three layers:
Encryption at rest. Configure the API server with EncryptionConfiguration (AES-GCM or KMS). GKE enables this by default with Google-managed keys; upgrade to CMEK for more control.
RBAC restriction. Never grant blanket Secret read access. Use namespace-scoped Roles. Audit with kubectl auth can-i --list.
External secret management. Use External Secrets Operator to sync from Google Secret Manager or Vault — like moving documents from a filing cabinet to a bank safety deposit box with rotation and audit logging.
Avoiding exposure. Never print Secrets in logs. Prefer volume mounts. Ensure logging libraries redact known Secret patterns.
🛑 PAUSE & RECALL — 3 minutes
Without looking back:
- If base64 is not encryption, what does it actually do?
- Name two reasons volume mounts are safer than env vars for Secrets.
- What is the purpose of an external secret manager in a Kubernetes workflow?
Rate your confidence (0–4).
5.2.5 GKE Integration
GKE Note: GKE provides powerful integrations for config and secret management.
Workload Identity is the GKE best practice for Pod-to-GCP authentication without storing service account keys. Bind a Kubernetes ServiceAccount to a Google IAM ServiceAccount. GKE's metadata server exchanges the Kubernetes identity for a short-lived OAuth2 token at runtime. No keys stored, no rotation needed.
Google Secret Manager CSI Driver mounts secrets directly from Secret Manager into Pods as volumes. Secrets never touch etcd — they flow from vault to filesystem, with automatic rotation.
Config Connector manages GCP resources as Kubernetes objects, keeping infrastructure and app config in the same Git workflow.
5.2.6 Analogy: Community Bulletin Boards and Sealed Envelopes
Analogy: Community Bulletin Boards and Sealed Envelopes
Imagine the Kubernetes cluster as a residential community. In the lobby, a community bulletin board posts notices anyone can read: trash schedules, pool hours, Wi-Fi names. This is your ConfigMap — public, non-sensitive, meant for broad consumption. Any resident (Pod) walks up and reads it.
Behind the reception desk, a locked filing cabinet holds sealed envelopes: SSNs, bank details, spare keys. This is your Secret — sensitive, access-controlled. Only the building manager with the right keycard (RBAC-authorized ServiceAccount) can open it.
For the most sensitive items, the community uses an off-site bank vault with armed guards and MFA — your external secret manager with rotation, audit logs, and disaster recovery.
Analogy Check: Where does this stretch? Bulletin boards have limited space — that 1MB limit. A base64-encoded Secret is more like an envelope with a transparent window; anyone can read it without real encryption.
5.2.7 Visual Description: Office Filing System
Visual Description: Office Filing System Diagram
This shows the spectrum: ConfigMaps on the open board, Secrets in the locked cabinet, external managers in the bank vault.
🤔 TRY BEFORE YOU SEE
You're designing a three-tier app on GKE: web frontend, API backend, MySQL database. Classify each item — ConfigMap, Kubernetes Secret, or Google Secret Manager? Write before checking.
- MySQL root password
- API log level (DEBUG/INFO/WARN)
- GCP service account JSON key for Cloud Storage
- Database connection hostname
- TLS certificate and private key
- Feature flags for beta UI
Reveal:
- Google Secret Manager — highest sensitivity; rotates frequently
- ConfigMap — non-sensitive operational tuning
- Workload Identity — never store a JSON key! Use WI instead
- ConfigMap — non-sensitive infrastructure address
- Secret (type: tls) — built-in type fits; rotate regularly
- ConfigMap — non-sensitive behavior flags
Chose Secret for item 3? Workload Identity eliminates stored GCP keys entirely.
Lab: LAB-5.2 — Configuration and Secrets (45 min)
Step 1: Create and Consume a ConfigMap (10 min)
kubectl create configmap app-config \
--from-literal=LOG_LEVEL=DEBUG \
--from-literal=MAX_CONNECTIONS=100
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: config-demo
spec:
containers:
- name: demo
image: busybox:1.36
command: ["sh", "-c", "env && ls /etc/config && sleep 3600"]
envFrom:
- configMapRef:
name: app-config
volumeMounts:
- name: cfg-vol
mountPath: /etc/config
volumes:
- name: cfg-vol
configMap:
name: app-config
EOF
kubectl wait --for=condition=Ready pod/config-demo
kubectl logs config-demo
Expected: LOG_LEVEL=DEBUG and MAX_CONNECTIONS=100 in env vars, plus files under /etc/config.
Step 2: Verify ConfigMap Hot-Reload (5 min)
kubectl patch configmap app-config --type merge -p '{"data":{"LOG_LEVEL":"INFO"}}'
sleep 5
kubectl exec config-demo -- cat /etc/config/LOG_LEVEL
Expected: INFO — file updated without restart. Env var still shows DEBUG (set at startup, immutable).
Step 3: Create and Inspect a Secret (10 min)
kubectl create secret generic db-creds --from-literal=password=SuperSecret123!
# Decode — NOT decryption
kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d
# Mount as volume
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: secret-demo
spec:
containers:
- name: demo
image: busybox:1.36
command: ["sh", "-c", "cat /etc/secrets/password && sleep 3600"]
volumeMounts:
- name: sec-vol
mountPath: /etc/secrets
readOnly: true
volumes:
- name: sec-vol
secret:
secretName: db-creds
EOF
kubectl wait --for=condition=Ready pod/secret-demo
kubectl exec secret-demo -- cat /etc/secrets/password
Expected: SuperSecret123! — base64 is trivially reversible.
Step 4: Configure Workload Identity (20 min)
Requires GKE with Workload Identity enabled.
export GSA_NAME=app-workload-sa
export PROJECT_ID=$(gcloud config get-value project)
# Create Google IAM SA
gcloud iam service-accounts create $GSA_NAME \
--display-name="Workload Identity SA"
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:$GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/storage.objectViewer"
# Create K8s SA and annotate for Workload Identity
kubectl create serviceaccount app-ksa
kubectl annotate serviceaccount app-ksa \
iam.gke.io/gcp-service-account="$GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com"
# Bind IAM SA to K8s SA
gcloud iam service-accounts add-iam-policy-binding \
"$GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/iam.workloadIdentityUser" \
--member="serviceAccount:$PROJECT_ID.svc.id.goog[default/app-ksa]"
# Test — zero stored keys
kubectl run wi-test --rm -it --serviceaccount=app-ksa \
--image=google/cloud-sdk:slim -- bash -c "gcloud auth list && gsutil ls"
Expected: gcloud auth list shows the IAM SA email. gsutil ls works with zero keys.
Step 5: Cleanup
kubectl delete pod/config-demo pod/secret-demo
kubectl delete configmap app-config
kubectl delete secret db-creds
kubectl delete serviceaccount app-ksa
gcloud iam service-accounts delete "$GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com" --quiet
Chapter Summary
ConfigMaps and Secrets externalize configuration from container images. ConfigMaps store non-sensitive data on the public bulletin board and support hot-reload via volume mounts. Secrets store sensitive data in the locked filing cabinet, but base64 is encoding, not encryption. Real security requires encryption at rest, RBAC, and external secret managers. On GKE, Workload Identity eliminates stored GCP service account keys.
📇 KEY CONCEPT CARDS
- Q: What is the fundamental difference between a ConfigMap and a Secret?
A: ConfigMaps store non-sensitive configuration in plain text. Secrets store sensitive data base64-encoded (not encrypted by default). Both support the same three consumption patterns: environment variables, volume mounts, and command-line arguments.
- Q: Why are mounted ConfigMap volumes preferred over env vars for configuration that might change?
A: Volume-mounted ConfigMaps update automatically in running Pods when modified (hot-reload). Environment variables are set at container startup and never change.
- Q: What is the critical difference between base64 encoding and encryption?
A: Base64 encoding is a reversible transformation with no key — anyone can decode it instantly. Encryption requires a key and is hard to reverse without it. Kubernetes Secrets use base64 by default; encryption at rest must be explicitly configured.
- Q: How does GKE Workload Identity allow Pods to access GCP APIs without storing service account keys?
A: Workload Identity binds a Kubernetes ServiceAccount to a Google IAM ServiceAccount. The GKE metadata server exchanges the K8s identity for a short-lived OAuth2 token at runtime. No keys are stored or exposed.