← Articles

Flats, not houses: Namespaces

Published
Read
6 minutes
Part of
Kubernetes for the rest of us, ch. 03
Tags
Kubernetes · Namespaces · Ingress

Give development and production their own namespaces, with separate nginx instances and Ingress routes in one cluster.

Namespaces let development and production have their own copies of an application in the same cluster.

In chapter two, you gave nginx a front door. Add a development copy and naming things gets awkward. You could call everything nginx-development, nginx-production, and so on. Kubernetes gives you another place to put that distinction: the namespace.

Think of a cluster as a building with several flats. Each flat can have its own kitchen without the residents agreeing on a unique name for it. A namespace gives a group of Kubernetes resources that kind of address.

§ 01

Give each environment its own room

Development, staging, and production are common reasons to use namespaces. You can also organise them around applications or teams. Here, I’ll use development and production, with the same small nginx application in each.

A Pod named guide-nginx can exist in both. So can a Service and an Ingress with that name. Names must be unique for a resource type within its namespace; they do not need to be unique across the whole cluster.

Resources such as Pods and Services belong to namespaces, while Nodes belong to the cluster. Creating two namespaces does not create two sets of machines.

§ 02

You’ve been using default

Continue with the Docker Desktop kind cluster from the previous chapters, on Windows or macOS. Keep the nginx YAML files and the Traefik installation from chapter two. Stop any temporary port-forward with Ctrl+C.

terminal
kubectl config use-context docker-desktop
kubectl get namespaces
kubectl get pod,service,ingress -n default

You should find default and some Kubernetes system namespaces. Your existing guide-nginx resources and Traefik are in default.

That namespace let you get started without creating one yourself. Now the application gets two named environments. Traefik can stay where it is.

§ 03

Create development and production

Save this as namespaces.yaml:

yaml
apiVersion: v1
kind: Namespace
metadata:
  name: development
---
apiVersion: v1
kind: Namespace
metadata:
  name: production

The --- separates two objects in one YAML file.

terminal
kubectl apply -f namespaces.yaml
kubectl get namespaces

Both new namespaces should show Active.

Open nginx-pod.yaml and nginx-service.yaml from chapter two. Remove the namespace: default line from each, leaving the names, labels, and other fields unchanged. Their metadata should now look like this:

yaml
# nginx-pod.yaml
metadata:
  name: guide-nginx
  labels:
    app: guide-nginx
yaml
# nginx-service.yaml
metadata:
  name: guide-nginx

These are excerpts; keep the rest of each file. With no namespace written into them, you can choose the destination when applying them:

terminal
kubectl apply -n development -f nginx-pod.yaml -f nginx-service.yaml
kubectl apply -n production -f nginx-pod.yaml -f nginx-service.yaml
kubectl wait -n development --for=condition=Ready pod/guide-nginx --timeout=120s
kubectl wait -n production --for=condition=Ready pod/guide-nginx --timeout=120s

-n is short for --namespace. The same files now describe two copies of the application. This creates new resources; it does not move the originals out of default.

Inspect each environment:

terminal
kubectl get pod,service -n development
kubectl get pod,service -n production
kubectl get pods -A

Each namespace should contain a running guide-nginx Pod and its Service. -A lists Pods across all namespaces, with a NAMESPACE column to tell them apart.

If a Pod does not become ready, use kubectl describe pod guide-nginx -n development, substituting production when needed. Check the Events section for the cause.

§ 04

Give each flat an address

Each environment also gets its own Ingress. Save this as nginx-ingress-development.yaml:

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: guide-nginx
  namespace: development
spec:
  ingressClassName: traefik
  rules:
    - host: development.localhost
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: guide-nginx
                port:
                  number: 80

Make a copy named nginx-ingress-production.yaml and change these two values:

FieldDevelopment fileProduction file
metadata.namespacedevelopmentproduction
spec.rules[0].hostdevelopment.localhostproduction.localhost

Keep the rest of the copied file, including metadata.name.

terminal
kubectl apply -f nginx-ingress-development.yaml
kubectl apply -f nginx-ingress-production.yaml
kubectl get ingress -n development
kubectl get ingress -n production

Both Ingresses are named guide-nginx, with different hosts. The backend Service must be in the same namespace as its Ingress. The development rule therefore uses the development Service; production uses its own copy. Each Service selects matching Pods within that same namespace.

The existing Traefik controller watches all namespaces by default. The chapter-two configuration leaves that default in place, so you do not need another controller.

Open both addresses:

You should see the nginx welcome page at each. Names ending in .localhost are reserved for the local machine; they are useful here because both routes enter through the same Traefik installation on your laptop.

Namespaces distinguish the Kubernetes objects. The different hostnames tell Traefik which route to use. Giving both Ingresses the same hostname and path would not give the browser a way to choose an environment.

If an address does not work

Use plain http:// and the exact hostname from the rule. A Traefik 404 usually means no matching route; check kubectl describe ingress guide-nginx -n development. If nginx is unavailable, check its Pod and Service in that namespace.

If the browser cannot resolve a .localhost name, test routing without hostname resolution. On macOS:

terminal
curl --noproxy "*" -H "Host: development.localhost" http://127.0.0.1
curl --noproxy "*" -H "Host: production.localhost" http://127.0.0.1

On Windows, run the same commands with curl.exe in place of curl. Expect nginx’s welcome-page HTML. If these work, the Ingress routes work and the remaining problem is browser name resolution. If neither address connects, revisit the Traefik LoadBalancer and port-80 checks from chapter two.

§ 05

Take development down

With both pages working, remove only the development Pod:

terminal
kubectl delete pod guide-nginx -n development
kubectl get pods -n production

Refresh both pages, bypassing the browser cache. Development should fail while its only Pod is gone. Production should still serve nginx, and its Pod should remain running.

Recreate development using the shared Pod file:

terminal
kubectl apply -n development -f nginx-pod.yaml
kubectl wait -n development --for=condition=Ready pod/guide-nginx --timeout=120s

Refresh development again. Its Service and Ingress stayed in place, so the same address should work once nginx is ready.

I’m keeping -n explicit in these commands. It makes the destination visible before you press Enter, which is a useful habit when one of the names is production.

§ 06

Shared building, shared machinery

The two environments still use the same cluster and, here, the same Node. A cluster failure can affect both. Calling a namespace production does not make this laptop a production setup.

The locks on those flats need fitting separately. Namespaces give you a scope for access rules, but creating one alone does not restrict who can change its resources or block traffic from other namespaces. Permissions and network policies provide those controls. You can also choose separate clusters when environments need stronger separation.

§ 07

Leave the cluster ready

Once both new routes work again, remove the original nginx resources from default:

terminal
kubectl delete ingress guide-nginx -n default
kubectl delete service guide-nginx -n default
kubectl delete pod guide-nginx -n default

Keep Traefik running in default. Avoid deleting everything in that namespace: it contains the controller serving both new environments.

Keep namespaces.yaml, the shared Pod and Service files, and both new Ingress files. The old nginx-ingress.yaml is superseded and can be archived. The original http://localhost route is gone; use the two environment addresses from now on.

Keep both environments running for chapter four, which introduces scaling.