EduFlow

Section 4 · Kubernetes Core

ConfigMaps, Secrets and Namespaces

20 min3 min readFill in the blanks

Lesson at a glance

Move notes-api’s settings into a ConfigMap and its credentials into a Secret, put everything in a notes namespace, and run a reviewable apply-and-diff workflow from the k8s folder.

  1. 1A namespace for notes-api
  2. 2ConfigMaps for settings
  3. 3Secrets for credentials
  4. 4Wiring them into notes-api
  5. 5The everyday workflow

No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.

Right now the notes-api Deployment has PORT written into it, the database Deployment has its password in plain text, and everything lives in the default namespace alongside whatever else people try in this cluster. That’s three ways to cause an incident: change a setting and you must edit the workload; read the repository and you have the password; run a careless kubectl delete in default and you hit someone else’s work.

A namespace for notes-api

A namespace is a scope for names. Two teams can both have a Deployment called api in different namespaces. Namespaces are also where you attach permissions (RBAC) and resource quotas, which makes them the natural unit for “one app” or “one team”.

k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: notes

Add namespace: notes to the metadata of every other object in k8s/, then make it your default for this context so you don’t type -n notes every time:

Terminal
kubectl apply -f k8s/namespace.yaml
kubectl config set-context --current --namespace=notes

What namespaces don’t do is isolate the network. A Pod in notes can connect to a Service in any other namespace unless a NetworkPolicy says otherwise, and your cluster’s network plugin enforces it. Don’t treat a namespace as a security wall on its own.

ConfigMaps for settings

A ConfigMap holds non-sensitive key-value configuration:

k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: notes-api-config
  namespace: notes
data:
  PORT: "3000"
  LOG_LEVEL: info

Values are always strings, which is why "3000" is quoted. Now the same notes-api image can run with info logging in production and debug in staging, by applying a different ConfigMap rather than building a different image.

Secrets for credentials

A Secret has the same shape as a ConfigMap but is meant for sensitive values. Create it from the command line (or from your secret manager) rather than from a committed file:

Terminal
kubectl create secret generic notes-db \
  --from-literal=POSTGRES_PASSWORD='dev-only-change-me' \
  --from-literal=DATABASE_URL='postgres://notes:dev-only-change-me@notes-db:5432/notes'

Here’s the uncomfortable part. Look at what’s stored:

Terminal
kubectl get secret notes-db -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d

The value comes straight back. Secret data is base64-encoded, not encrypted. What makes Secrets safer than ConfigMaps is everything around them: RBAC can grant read access to ConfigMaps but not Secrets, the cluster can encrypt Secrets at rest in etcd (managed providers usually do), and kubelets only receive Secrets for Pods scheduled on their node.

Wiring them into notes-api

Update the container in k8s/deployment.yaml:

k8s/deployment.yaml
      containers:
        - name: api
          image: ghcr.io/skylane/notes-api:1.4.0
          ports:
            - name: http
              containerPort: 3000
          envFrom:
            - configMapRef:
                name: notes-api-config
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: notes-db
                  key: DATABASE_URL

envFrom turns every key in the ConfigMap into an environment variable. secretKeyRef pulls one key from the Secret. In k8s/db.yaml, replace the literal POSTGRES_PASSWORD value with a secretKeyRef to the same Secret.

If a referenced key doesn’t exist, the container never starts. The Pod shows CreateContainerConfigError, and kubectl describe pod names the missing key.

Secrets and ConfigMaps can also be mounted as files through a volume. Mounted files are updated in running Pods after a short delay; environment variables are fixed at container start. After changing env-based config, roll the Pods:

Terminal
kubectl rollout restart deployment/notes-api

The everyday workflow

Your k8s/ folder now holds namespace.yaml, configmap.yaml, db.yaml, deployment.yaml and service.yaml. The loop for every change is the same:

Terminal
kubectl diff -f k8s/
kubectl apply -f k8s/
kubectl get all -l app.kubernetes.io/name=notes-api

diff is your review step: it shows what will change on the live cluster, field by field, before you change it. Make it a habit and you’ll catch the stray edit that would have reset someone’s emergency fix. When you need different values per environment, Kustomize (built into kubectl as kubectl apply -k) layers small overlays on top of these base files; Helm, in lesson 5.3, is the other common answer.

notes-api now runs on Kubernetes with proper networking and configuration. It’s not production-ready yet: Kubernetes has no idea whether it’s healthy, how much CPU it needs, or how to update it safely. Section 5 covers all three.

Key takeaways

  • ConfigMaps hold non-sensitive settings; Secrets hold credentials and are injected as environment variables or mounted files.
  • Secret values are only base64-encoded, not encrypted; protect them with RBAC, encryption at rest, and by keeping them out of Git.
  • Environment variables from a ConfigMap or Secret are read once at container start, so restart the Deployment after changing them.
  • Namespaces scope names, permissions and quotas for a team or app, but they don’t isolate network traffic on their own.
  • kubectl diff -f k8s/ before kubectl apply -f k8s/ shows exactly what will change, like a pull request for the cluster.

Further reading

Check your understanding

4 questions

Question 1 of 4

A teammate commits a Secret manifest whose data values are base64 strings, saying “it’s fine, they’re encoded”. What’s the problem?

Question 2 of 4

You change LOG_LEVEL in the notes-api-config ConfigMap and apply it. The Pods consume it through envFrom. What happens?

Question 3 of 4

A new Pod is stuck with status CreateContainerConfigError. What’s the most likely cause?

Question 4 of 4

notes-api and a payments service run in separate namespaces on the same cluster. Can a notes-api Pod open a connection to the payments database Service?

Practice

Fill in the blanks

Namespace, Secret, restart

Fill in the blanks to create the notes namespace and make it your default, store the database password in a Secret, decode it to prove it’s only base64, and restart notes-api so it picks up changed environment variables.

# Create the namespace and make it the default for this context kubectl create notes kubectl config set-context --current --=notes # Store the database password as a Secret kubectl create secret notes-db --from-literal=POSTGRES_PASSWORD=dev-only-change-me # Read it back (and see that it isn't encrypted) kubectl get secret notes-db -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 # Pick up changed environment variables in running Pods kubectl rollout deployment/notes-api