ConfigMaps, Secrets, and Scaling

Externalizing configuration with ConfigMaps and Secrets, and autoscaling with the HPA.

Why not just bake configuration into the image?

Baking environment-specific configuration directly into a container image means building a separate image per environment (staging vs. production) just to change a database hostname or a feature flag — defeating the entire point of building one artifact and promoting it unchanged through environments. Kubernetes separates configuration from the image using two dedicated objects: ConfigMaps for non-sensitive configuration, and Secrets for sensitive values.

ConfigMaps

A ConfigMap stores non-sensitive configuration as key-value pairs, separately from your Pod's image:

YAML
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-app-config
data:
  LOG_LEVEL: "info"
  MAX_UPLOAD_SIZE_MB: "25"
  FEATURE_NEW_CHECKOUT: "true"
Bash
$ kubectl apply -f configmap.yaml
configmap/my-app-config created

Secrets

A Secret has the same shape as a ConfigMap but is intended for sensitive values — API keys, database passwords, tokens. Values are base64-encoded (not encrypted by default — treat that as an important caveat, not real security by itself; production clusters typically layer on encryption at rest and tighter RBAC access to Secrets specifically):

YAML
apiVersion: v1
kind: Secret
metadata:
  name: my-app-secret
type: Opaque
data:
  DATABASE_PASSWORD: czNjcjN0LXBhc3N3b3Jk
  API_KEY: YWJjZGVmMTIzNDU2Nzg5MA==

The data values are base64-encoded strings, not plaintext — you create them by encoding the real value first:

Bash
$ echo -n "s3cr3t-password" | base64
czNjcjN0LXBhc3N3b3Jk

# Or let kubectl handle the encoding for you from the command line
$ kubectl create secret generic my-app-secret \
    --from-literal=DATABASE_PASSWORD=s3cr3t-password \
    --from-literal=API_KEY=abcdef1234567890

Mounting both as environment variables

Both ConfigMaps and Secrets can be injected into a Pod as environment variables. Here's a Deployment's Pod spec consuming both:

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: app
          image: my-app:1.2
          envFrom:
            - configMapRef:
                name: my-app-config
            - secretRef:
                name: my-app-secret
          env:
            - name: DATABASE_URL
              value: "postgresql://appuser:$(DATABASE_PASSWORD)@db:5432/appdb"

envFrom with configMapRef/secretRef injects every key in that ConfigMap/Secret as an environment variable in one shot (LOG_LEVEL, MAX_UPLOAD_SIZE_MB, DATABASE_PASSWORD, API_KEY all become env vars automatically). The explicit env entry below it shows referencing an individual Secret key ($(DATABASE_PASSWORD)) to build up a composite value like a full connection string. Changing a ConfigMap or Secret's values doesn't automatically restart already-running Pods to pick them up — that typically requires a new rollout (e.g., kubectl rollout restart deployment/my-app).

Horizontal Pod Autoscaler (HPA)

A Horizontal Pod Autoscaler automatically adjusts a Deployment's replica count based on observed load (commonly CPU or memory usage, or custom metrics), scaling out under load and back in when it subsides — instead of a fixed replicas: 3 that never changes:

YAML
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-app
  minReplicas: 3
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

This HPA watches the my-app Deployment and keeps average CPU utilization across its Pods near 70% — scaling out (up to a ceiling of 10 replicas) when load pushes utilization higher, and scaling back in (down to a floor of 3) once it drops. This is exactly why the Deployment's resources.requests.cpu (covered in Pods, Deployments, and Services) matters: "70% utilization" is measured relative to that requested value, so an HPA can't make sensible scaling decisions without it set.

Bash
$ kubectl apply -f hpa.yaml
horizontalpodautoscaler.autoscaling/my-app-hpa created

$ kubectl get hpa
NAME         REFERENCE           TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
my-app-hpa   Deployment/my-app   45%/70%   3         10        3          5m

Common mistakes

  • Treating base64 encoding in a Secret as real encryption — it's trivially reversible and is only a data-format convention, not access control. Real security comes from RBAC restricting who/what can read Secrets, and (ideally) encryption at rest configured at the cluster level.
  • Committing a Secret's YAML (with real, sensitive values) into a public or shared repository — the base64 encoding gives a false sense of safety.
  • Setting an HPA's maxReplicas without confirming the cluster and any downstream dependencies (like a database's connection limit) can actually handle that many Pods at once.
  • Editing a ConfigMap or Secret and expecting running Pods to pick up the change immediately — they won't, until the Pods are recreated (e.g., via a rollout restart).

Interview questions

Q: What's the difference between a ConfigMap and a Secret? Structurally they're nearly identical — both store key-value configuration data that can be injected into Pods as environment variables or mounted files. The distinction is intent and handling: ConfigMaps are for non-sensitive configuration, while Secrets are meant for sensitive values and are base64-encoded (though not encrypted by default), with tighter default access controls and support for encryption at rest in a properly configured cluster.

Q: Is a Kubernetes Secret actually secure just because it's base64-encoded? No — base64 is a reversible encoding, not encryption, so anyone with read access to the Secret object can trivially recover the original value. Real protection comes from restricting who and what can read Secrets via RBAC, and from enabling encryption at rest at the cluster level; base64 alone should never be treated as a security boundary.

Q: What does a Horizontal Pod Autoscaler do, and what does it need to make good scaling decisions? It automatically increases or decreases a Deployment's replica count based on observed metrics (commonly CPU or memory utilization) against a target threshold, scaling out under load and back in once it subsides, within a configured min/max range. It relies on the Deployment's Pods having resources.requests set, since utilization percentages are calculated relative to what was requested — without that, the HPA has no baseline to measure against.