Gendi Notes

Kubernetes Basics

You describe what you want running; Kubernetes keeps making reality match that description. Everything else is detail.

Level 1 guide kubernetesk8sorchestration

What is it?#

Kubernetes (often "K8s") runs containers across many machines for you. You hand it a description — "I want 3 copies of this image, reachable on port 80" — and it decides where they run, restarts them when they crash, and replaces them when a machine dies.

Docker runs a container on one machine. Kubernetes runs containers across many, and keeps them running without you watching.

Why it exists#

With a handful of containers on one server, docker compose is enough. The problems appear at scale:

ProblemWhat you'd do by handKubernetes
A container crashes at 3amGet paged, SSH in, restartRestarts it automatically
A whole server diesManually move everythingReschedules onto healthy nodes
Traffic tripledManually start more copiesAutoscales
Deploy a new versionStop old, start new, hopeRolling update, automatic rollback
Where is there room for this?Track capacity in a spreadsheetThe scheduler decides

What it is made of#

A cluster has two halves.

┌───────────────────── CONTROL PLANE (the brain) ──────────────────────┐ │ │ │ ┌────────┐ ┌──────────────┐ ┌───────────┐ ┌──────────────┐ │ │ │ etcd │←→ │ API SERVER │←→ │ Scheduler │ │ Controllers │ │ │ └────────┘ └──────────────┘ └───────────┘ └──────────────┘ │ │ stores the front door picks a keep desired │ │ all state for everything node = actual │ └──────────────────────────┬───────────────────────────────────────────┘ │ ┌──────────────────┼──────────────────┐ ↓ ↓ ↓ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ NODE 1 │ │ NODE 2 │ │ NODE 3 │ ← workers │ kubelet │ │ kubelet │ │ kubelet │ │ kube-proxy │ │ kube-proxy │ │ kube-proxy │ │ [pods...] │ │ [pods...] │ │ [pods...] │ └──────────────┘ └──────────────┘ └──────────────┘

Control plane#

ComponentJobIf it stops
API serverThe only way in. Everything talks to itNo changes possible; running pods keep running
etcdDatabase holding all cluster stateThe cluster loses its memory. Back this up
SchedulerChooses which node a new pod goes onNew pods stay Pending; existing ones fine
ControllersReconcile desired vs actualDeploys stall silently

Worker nodes#

ComponentJob
kubeletThe agent on each node. Starts and watches containers
kube-proxySets up networking so Services work
Container runtimeActually runs containers (containerd)

The objects you will use#

You describe everything in YAML. These are the ones you actually need:

ObjectWhat it isAnalogy
PodOne or more containers that run togetherA single running instance
DeploymentKeeps N identical pods running; handles updatesA manager of pods
ServiceA stable address for a group of podsA receptionist
IngressRoutes external HTTP traffic to ServicesThe front door
ConfigMapNon-secret configurationA settings file
SecretPasswords, keys, tokensA locked drawer
NamespaceA folder for grouping objectsA project folder
internet │ ↓ Ingress "example.com/api → the api Service" │ ↓ Service stable IP + DNS name; picks a healthy pod │ ├──→ Pod ─┐ ├──→ Pod ├─ all created and watched by one Deployment └──→ Pod ─┘

You almost never create a Pod directly. You create a Deployment, and it creates the Pods.

How to use it#

Talking to the cluster#

kubectl is the command-line tool. Everything goes through it.

Orientation — run these first on any new cluster
kubectl cluster-info            # is the cluster reachable?
kubectl get nodes               # the machines. All should be Ready
kubectl get pods                # pods in the CURRENT namespace
kubectl get pods -A             # every namespace
kubectl get all                 # most object types in this namespace

Deploying something#

Write a manifest:

deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3                  # desired state: three pods
  selector:
    matchLabels:
      app: web                 # which pods this Deployment owns
  template:                    # the recipe for each pod
    metadata:
      labels:
        app: web               # MUST match the selector above
    spec:
      containers:
        - name: web
          image: nginx:1.27-alpine
          ports:
            - containerPort: 80
          resources:
            requests:          # what it is guaranteed
              memory: "64Mi"
              cpu: "100m"      # 100m = 0.1 of a CPU core
            limits:            # what it may never exceed
              memory: "128Mi"
              cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web                   # sends traffic to pods with this label
  ports:
    - port: 80
      targetPort: 80

Apply it:

kubectl apply -f deployment.yaml     # create or update. Use this, not `create`
kubectl get pods -l app=web          # see the three pods
kubectl rollout status deploy/web    # wait for the rollout to finish

kubectl apply is declarative — run it repeatedly and you converge on the file's contents. kubectl create fails if the object exists. Always use apply.

Labels are the glue#

Nothing in Kubernetes is linked by name. Objects find each other by labels.

Service selector: app=web │ │ "give me every pod labelled app=web" ↓ Pod app=web ✓ Pod app=web ✓ Pod app=api ✗

Day-to-day commands#

kubectl describe pod <name>     # events + state. FIRST command when something is wrong
kubectl logs <pod>              # its output
kubectl logs <pod> -f           # follow
kubectl logs <pod> --previous   # the CRASHED container's logs, not the new one
kubectl exec -it <pod> -- sh    # shell inside the pod

kubectl scale deploy/web --replicas=5
kubectl rollout restart deploy/web    # restart all pods (picks up new config)
kubectl rollout undo deploy/web       # roll back to the previous version

kubectl delete -f deployment.yaml     # remove what the file created

kubectl describe is the most valuable debugging command. The Events at the bottom usually name the problem outright: image pull failure, insufficient memory, failing probe, missing ConfigMap.

Requests and limits#

These two numbers matter more than almost anything else:

MeaningEffect
requestGuaranteed minimumUsed by the scheduler to pick a node
limitHard ceilingExceeding memory → killed; exceeding CPU → throttled

Setting no requests at all means the scheduler is guessing, and your pods are first to be evicted when a node runs short.

What goes wrong first#

Pod statusMeaningFirst command
PendingNot scheduled — no node has room, or a volume is missingkubectl describe pod → Events
ImagePullBackOffCannot fetch the image: wrong name, tag, or no credentialskubectl describe pod
CrashLoopBackOffStarts, exits, restarts, repeatedlykubectl logs <pod> --previous
OOMKilledExceeded its memory limitRaise the limit, or fix the leak
Running but not workingOften a Service/label problemkubectl get endpoints

Interview corner · الأسئلة اللي بتتسأل#

The control plane is completely down. Is my application down? · الـ control plane وقع، الموقع واقع؟

No — and this is the question that separates people who have operated a cluster from people who have read about one.

Running pods keep serving traffic, because the data path does not go through the control plane:

Still worksBecause
Pods keep runningThe kubelet on each node supervises them locally
A crashed container restartsThe kubelet does this without asking anyone
Service traffic flowsThe forwarding rules are already programmed on each node
DNS resolvesCoreDNS pods are just pods, already running
Stops workingBecause
Deploys, scaling, kubectlEvery write goes through the API server
Rescheduling after a node diesThat is a controller's job
New Service endpointsThe endpoints controller is not running
Why does a memory limit kill the container but a CPU limit does not?

Because memory cannot be time-shared and CPU can.

If a process has written a page of memory, that page is occupied — the kernel cannot give it to someone else and hand it back a millisecond later. The only way to enforce a memory ceiling is to stop the process existing. So the cgroup OOM killer terminates it: OOMKilled, exit code 137.

CPU is a rate, not a quantity. The CFS scheduler enforces a limit by giving the cgroup a quota per 100 ms period and freezing it once spent.

CPU limit 500m = 50ms of every 100ms period |█████████████·············|█████████████·············| 0 50ms 100ms 0 50ms 100ms running THROTTLED running THROTTLED The app is alive the whole time. It is just not scheduled for half of it — which shows up as latency, not as an error.
You delete a Pod and it comes back. You delete it again — same thing. What is happening, and how do you actually remove it?

Nothing is wrong. You are fighting a reconciliation loop and it will always win.

kubectl delete pod changes actual state. The Deployment's replicas: 3 is desired state, stored in etcd and untouched by your delete. The ReplicaSet controller sees 2 of 3, and creates one.

To remove it you must change desired state:

kubectl scale deploy/web --replicas=0   # desired is now 0
kubectl delete deploy web               # desired no longer exists
What is the difference between Running and Ready, and which one does a Service care about?

Running means the container process started. Ready means the readiness probe is passing. A Service only routes to Ready pods.

Pod lifecycle ───────────── Pending → ContainerCreating → Running → Running + Ready │ │ │ └── NOW in the │ EndpointSlice, │ NOW gets traffic └── process is up, but the app may still be loading config, warming a cache, connecting to the DB

This is what readiness probes are for: the gap between "the process exists" and "the application can serve a request". An app with no readiness probe is marked Ready the instant the process starts, so a rolling update sends traffic to pods that are still booting — and you get a burst of 502s on every deploy that nobody can explain.

ProbeFails →Use it for
readinessProbeRemoved from the Service, not restarted"Can I serve traffic right now?"
livenessProbeContainer is restarted"Am I wedged and beyond saving?"
startupProbeHolds off the other twoSlow-starting apps (JVM, big migrations)

Key takeaways#

  • You declare desired state; controllers make reality match. That is the whole model.
  • Deployment → Pods → Service. You create Deployments, not Pods.
  • Labels connect everything. kubectl get endpoints showing <none> means a selector typo.
  • kubectl describe pod first, then kubectl logs --previous for crashes.
  • apply, never create.
  • Memory limits kill; CPU limits throttle and make CPU graphs look deceptively low.
  • Everything is namespaced — -A when you cannot find something.
  • Running is not Ready. Services route only to Ready, and a missing readiness probe is why deploys emit unexplained 502s.

Last updated 2026-09-08