Definitions don't stick; running examples do. Each object below is introduced with the instance living in the QuickBites cluster — real names, real output, exactly what you'd meet on the job.
A pod is one or more containers that share a network identity (one IP) and lifecycle. You almost never create pods by hand — controllers (Deployments, StatefulSets, DaemonSets) create them for you.
$ kubectl get pods -n orders-prod -o wide NAME READY STATUS RESTARTS AGE IP NODE mongodb-0 1/1 Running 1 (6h ago) 46d 10.244.194.68 node-1 orders-backend-84d8667cdc-hwtgx 1/1 Running 1 (6h ago) 46d 10.244.194.70 node-1 orders-backend-84d8667cdc-zm2xs 1/1 Running 1 (6h ago) 46d 10.244.194.72 node-1 orders-frontend-79d7f88fcc-4ldl4 1/1 Running 7 (6h ago) 46d 10.244.194.126 node-1 orders-frontend-79d7f88fcc-9hz57 1/1 Running 7 (6h ago) 46d 10.244.194.125 node-1
Notice the two name shapes: mongodb-0 (a StatefulSet pod — stable, numbered) versus
orders-backend-84d8667cdc-hwtgx (a Deployment pod —
deployment-name + ReplicaSet hash + random suffix, disposable by design).
The 10.244.x.x addresses are pod IPs handed out by Calico.
A Deployment says "keep N copies of this pod template running, and when the template changes, roll to the new version gradually." It does this through a middleman, the ReplicaSet:
kubectl set image actually triggers.
See it live — both apps still keep their old -dev ReplicaSets at desired 0:
$ kubectl get rs -n orders-prod NAME DESIRED CURRENT READY AGE orders-backend-75fc56966 0 0 0 56d ← old version, parked orders-backend-84d8667cdc 2 2 2 55d ← live orders-frontend-79d7f88fcc 2 2 2 55d orders-frontend-967c94788 0 0 0 56d
Pods die and get new IPs. A Service gives a fixed virtual IP + DNS name and load-balances to whatever pods currently match its selector. This cluster shows all three types you need to know:
$ kubectl get svc -n orders-prod NAME TYPE CLUSTER-IP PORT(S) SELECTOR mongodb ClusterIP None 27017/TCP app=mongodb ← "headless" (§5) mongodb-external NodePort 10.106.38.9 27017:30017/TCP app=mongodb orders-backend ClusterIP 10.97.145.114 8000/TCP app=orders,tier=backend orders-frontend NodePort 10.108.87.195 80:30082/TCP app=orders,tier=frontend
| Type | Reachable from | Real example here |
|---|---|---|
| ClusterIP (default) | Only inside the cluster | orders-backend — the frontend nginx proxies API calls to it by DNS name |
| NodePort | Any node IP, port 30000–32767 | orders-frontend on 30082, Grafana on 30300 |
Headless (clusterIP: None) | DNS returns pod IPs directly | mongodb — required by the StatefulSet, explained in Section 5 |
<service>.<namespace>.svc.cluster.local — the orders backend finds its
database via mongodb.orders-prod.svc.cluster.local (that exact string is in the
orders-config ConfigMap; go look).
NodePorts work but nobody wants :30082 in a URL. The Ingress object maps
hostnames to Services, and the ingress-nginx controller (a pod!) does the actual routing:
$ kubectl get ingress -A
NAMESPACE NAME CLASS HOSTS ADDRESS PORTS
orders-prod orders-ingress nginx orders.quickbites.io 192.0.2.11 80
riders-prod riders-ingress nginx riders.quickbites.io 192.0.2.11 80
The rule inside orders-ingress reads: Host orders.quickbites.io,
path / → Service orders-frontend port 80. Outside the cluster,
orders.quickbites.io is just a DNS record pointing at the ingress controller's
address — DNS gets the request to the right door, the Ingress rules route it inside.
Plain key-value settings, injected as env vars via envFrom.
The real one:
MONGO_HOST: "mongodb.orders-prod.svc.cluster.local" MONGO_PORT: "27017" MONGO_DATABASE: "orders_db" APP_ENV: "prod" LOG_LEVEL: "info"
Same idea, but base64-encoded and access-controlled. The backend pods pull
DB credentials from mongodb-secret / mysql-secret like this:
env:
- name: MONGO_PASSWORD
valueFrom:
secretKeyRef:
name: mongodb-secret
key: MONGO_PASSWORD
The values themselves are in your Secret / values.yaml — never in this doc, never in git, never in chat.
kubectl get secret mongodb-secret -o yaml shows base64 text that anyone can decode with
base64 -d. Secrets protect against casual exposure and enable RBAC — treat cluster
access itself as the real security boundary.
Containers lose their filesystem when they restart. Databases obviously can't accept that, so Kubernetes splits storage into the PV (the actual disk — here, a directory on a specific node) and the PVC (a pod's request to use one):
$ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM grafana-pv 2Gi RWO Retain Bound monitoring/grafana-pvc loki-pv 5Gi RWO Retain Bound monitoring/loki-pvc mongodb-pv 5Gi RWO Retain Bound orders-prod/mongodb-pvc mysql-pv 5Gi RWO Retain Bound riders-prod/mysql-pvc prometheus-pv 5Gi RWO Retain Bound monitoring/prometheus-pvc
hostPath-style local disks (storage class manual) —
e.g. MySQL's data sits in /data/mysql on node-2. That's why the DB pods are
pinned to specific nodes (Section 5).Some things must run on every node. This cluster has two perfect examples in
monitoring: node-exporter (hardware metrics) and promtail
(log shipping). Count them — three of each, one per node:
$ kubectl get pods -n monitoring -o wide | grep -E 'node-exporter|promtail' node-exporter-m9db4 1/1 Running 192.0.2.10 control-plane node-exporter-pn54r 1/1 Running 192.0.2.12 node-2 node-exporter-xb9bj 1/1 Running 192.0.2.11 node-1 promtail-256k9 1/1 Running 10.244.126.29 node-2 promtail-fs6qx 1/1 Running 10.244.194.71 node-1 promtail-kzkk4 1/1 Running 10.244.235.215 control-plane
Nothing in Kubernetes holds a list of pods. Everything selects pods by labels.
The orders frontend Service says selector: app=orders, tier=frontend — any pod
wearing those two labels instantly starts receiving traffic. That is the entire mechanism behind
Services, Deployments and rollouts. Try it:
$ kubectl get pods -n orders-prod -l tier=backend
NAME READY STATUS RESTARTS AGE
orders-backend-84d8667cdc-hwtgx 1/1 Running 1 46d
orders-backend-84d8667cdc-zm2xs 1/1 Running 1 46d
orders-backend-84d8667cdc-hwtgx. What happens?mongodb-0, which comes back with the same name — that's Section 5.mongodb.orders-prod.svc.cluster.local. Where is that hostname configured?Retain. You delete mongodb-pvc by accident. Is the data gone?Retain, the data survives at /data/… on the node.
(With reclaim policy Delete — common in clouds — it would be gone.)