Simple Kubernetes App: Deep Dive
This document explains what actually happens when you deploy a simple app to Kubernetes. Not just the steps, but why each piece exists and what breaks when you remove it.
What a Deployment Actually Does
Section titled “What a Deployment Actually Does”A Deployment is not just “a way to run pods.” It is a declaration of intent. You tell Kubernetes: “I want 2 replicas of nginx running at all times.” Kubernetes figures out how to make that happen.
The magic is the reconciliation loop. A controller inside the cluster constantly compares the desired state (your YAML) with the actual state (what is running). If they do not match, it takes action.
spec: replicas: 2 selector: matchLabels: app: simple-nginxWhen you apply this, the Deployment controller creates a ReplicaSet. The ReplicaSet is the thing that actually manages pods. The Deployment manages the ReplicaSet.
Why the extra layer? ReplicaSets track revisions. When you update the container image, the Deployment creates a new ReplicaSet, scales it up, and scales the old one down. This is how rolling updates work. The old ReplicaSet sticks around (with zero replicas) so you can roll back.
Try it yourself:
kubectl get replicasets -n simple-appYou will see one ReplicaSet with 2 pods. Update the image and you will see two ReplicaSets, one scaling down and one scaling up.
What happens when you delete a pod
Section titled “What happens when you delete a pod”Delete a pod and watch:
kubectl delete pod -l app=simple-nginx -n simple-app --wait=falsekubectl get pods -n simple-app -wThe ReplicaSet notices the actual count (1) is below the desired count (2). It creates a new pod immediately. This is the reconciliation loop in action. The new pod gets a new name, a new IP, and possibly runs on a different node. Nothing about the old pod carries over.
This is the core insight: pods are disposable. Kubernetes treats them like cattle, not pets.
Why Services Exist
Section titled “Why Services Exist”Pods get IP addresses, but those addresses are useless for long-term communication. Every time a pod restarts, it gets a new IP. Every time the Deployment scales, new IPs appear and old ones vanish.
A Service provides a stable endpoint. It gets a fixed ClusterIP that never changes for the lifetime of the Service. Behind the scenes, kube-proxy maintains routing rules that forward traffic from the Service IP to whichever pods are currently healthy.
apiVersion: v1kind: Servicemetadata: name: simple-nginx-servicespec: selector: app: simple-nginx ports: - port: 80 targetPort: 80The Service does not know about the Deployment. It does not know about the ReplicaSet. It only knows about pods that match its selector. This loose coupling is deliberate. You can swap out the entire Deployment behind a Service without any consumer noticing.
Other pods in the cluster reach nginx by calling simple-nginx-service. CoreDNS resolves that name to the ClusterIP. kube-proxy routes the traffic to a healthy pod. If one pod is down, traffic goes to the other. No client-side logic required.
The Label and Selector System
Section titled “The Label and Selector System”Labels are the glue that connects everything in Kubernetes. They are just key-value pairs on objects. The power comes from selectors that filter objects by those labels.
In the simple app, one label ties everything together: app: simple-nginx.
Here is how it flows:
- The Deployment has
selector.matchLabels: app: simple-nginx. It manages pods with this label. - The Pod template has
labels: app: simple-nginx. Every pod created by the Deployment gets this label. - The Service has
selector: app: simple-nginx. It routes traffic to pods with this label.
The Deployment and Service never reference each other directly. They both independently select pods by label. This is why you can create the Service before the Deployment, or delete and recreate the Deployment without touching the Service. As long as the labels match, traffic flows.
Get it wrong and things break silently. If the Service selector says app: nginx but your pods are labeled app: simple-nginx, the Service will have zero endpoints. No error, no warning. Traffic goes nowhere.
Check your endpoints:
kubectl get endpoints simple-nginx-service -n simple-appIf the ENDPOINTS column is empty, your labels do not match.
Resource Requests and Limits
Section titled “Resource Requests and Limits”Every container in this demo has resource constraints:
resources: requests: memory: "32Mi" cpu: "25m" limits: memory: "64Mi" cpu: "50m"These two settings do very different things.
Requests are what the scheduler uses to place pods. When a pod asks for 32Mi of memory, the scheduler finds a node with at least 32Mi available. Requests are a guarantee. If the node has 32Mi free, your pod will get it.
Limits are what the kubelet enforces at runtime. If your container tries to use more than 64Mi of memory, Kubernetes kills it with an OOMKilled status. If it tries to use more than 50m of CPU, the kernel throttles it. The container does not get killed for exceeding CPU limits, it just runs slower.
What happens when you set them wrong
Section titled “What happens when you set them wrong”No requests or limits: The scheduler places the pod anywhere. Under memory pressure, your pod is the first to be evicted because Kubernetes considers it “best effort” quality of service. In a shared cluster, this is asking for trouble.
Requests too high: Your pod reserves resources it never uses. A pod requesting 4Gi of memory that only uses 200Mi wastes 3.8Gi that other pods could use. The node looks full when it is actually mostly idle.
Limits too low: Your container keeps getting OOMKilled or CPU-throttled. You will see pods in CrashLoopBackOff because they restart, hit the limit, get killed, restart again.
Requests equal to limits: This gives your pod “guaranteed” quality of service. It will not be evicted under memory pressure unless the node itself is failing. This is the safest setting for production workloads but uses resources less efficiently.
For this demo, the values are intentionally small. Nginx is lightweight and 64Mi is plenty. In your own apps, start with reasonable estimates, monitor actual usage with kubectl top pods, and adjust from there.
Putting It All Together
Section titled “Putting It All Together”The simple app has just three files, but they demonstrate four core Kubernetes concepts:
- Deployments keep your app running through the reconciliation loop
- Services provide stable networking for ephemeral pods
- Labels connect resources without tight coupling
- Resource constraints prevent noisy neighbors and keep the cluster stable
Every other Kubernetes feature builds on these primitives. StatefulSets, DaemonSets, Ingresses, HorizontalPodAutoscalers: they all use the same label selectors, the same resource model, and the same reconciliation pattern. Get comfortable with these four concepts and the rest of Kubernetes becomes variations on a theme.