Understanding Kubernetes Basics

The ultimate aim of Kubernetes is to deploy applications as container on worker nodes.

The typical kubernetes cluster would look as this

EliteBook 840 G3 — Ubuntu 24.04 — VMware Workstation Pro 25
    ├── k3s-cp-01      172.16.77.10  (control plane)
    ├── k3s-worker-01  172.16.77.11  (worker)
    └── k3s-worker-02  172.16.77.12  (worker)

Control Plane — k3s-cp-01

The brain of the cluster and the components are

kube-apiserver

Every single kubectl command goes through here and nothing in the cluster happens without going through the API server.

# every command hits port 6443 on cp-01
kubectl get pods        # → https://172.16.77.10:6443/api/v1/pods

The service that runs on the control plane is  k3s.service

etcd / SQLite (kine)

Stores all our cluster state so every deployment, pod, secret, configmap, namespace. SQLite as the default for our lab cluster as its small enviornment but the moment you need HA or are in production like vKS we need etcd

# our state lives here on cp-01
/var/lib/rancher/k3s/server/db/state.db

# we can query it directly
sudo sqlite3 /var/lib/rancher/k3s/server/db/state.db 
 sudo sqlite3 /var/lib/rancher/k3s/server/db/state.db "SELECT name FROM kine WHERE name LIKE '%mealie%';"

kube-scheduler

When we apply a deployment, the scheduler decides which node the pod runs on. It looks at available resources on each node and picks the best fit when its first created. This is what picked k3s-worker-01 for Mealie. Descheduler is another service that balances workloads after a pod is deployed so this is the DRS.

controller-manager

Watches the cluster state and makes sure reality matches what we declared. If a Mealie pod dies, the controller-manager notices the ReplicaSet has 0/1 pods and tells the scheduler to create a new one.


Worker Nodes — k3s-worker-01 and k3s-worker-02

Worker nodes is where we actual workloads run — Mealie, Forgejo, Grafana, everything.

kubelet

The agent that runs on every worker node. It receives pod specs from the apiserver and tells the container runtime to run them. It also reports back pod status and that’s how kubectl get pods knows if a pod is Running or not.

# running as k3s-agent on workers
sudo systemctl status k3s-agent

kube-proxy

Handles networking rules on each node. When you create a Service, kube-proxy sets up iptables rules so traffic gets routed to the right pod IP. This is how 172.16.77.10:9000 reaches Mealie at 10.42.2.197:9000.

Container Runtime

The thing that actually pulls images and runs containers.

Container

Kubernetes doesnt deploy containers directly on worker nodes. The containers are encapsulated into something known as pods.

Pod

A single instance of an application is called a pod. To scale applications, we just increasse the replicas so its another pod eg: 2 web application

Pod
└── container (mealie)

Multi-Container Pod

A single pod can have multiple containers in it.

Pod
├── container (main app)
└── helper container (sidecar — helper)


Container Runtime — containerd and runc

containerd

Manages the container lifecycle — pulls images from registry, stores them on disk, creates and deletes containers. Kubernetes talks to containerd via the CRI (Container Runtime Interface).

runc

Does the actual low-level work — creates Linux namespaces (network, PID, mount) so each container is isolated from others. Sets up cgroups for CPU and memory limits. Starts the container process.

EliteBook (Docker)         →  docker build + docker push
cluster nodes (containerd) →  pull and run what was already built

CLI Tools for containerd

crictl is what you use on k3s nodes and it has the same structure as docker

sudo crictl ps                    # running containers
sudo crictl images                # pulled images
sudo crictl logs <container-id>   # container logs
sudo crictl exec -it <id> sh      # exec into container
sudo crictl stats                 # resource usage

The Full Flow — kubectl apply -f deployment.yaml

What happens when you deploy Mealie:

1. kubectl
       → sends request to kube-apiserver (cp-01:6443)

2. kube-apiserver
       → validates the YAML
       → stores it in SQLite/kine

3. kube-scheduler
       → sees a new pod needs scheduling
       → checks available resources on each node
       → picks k3s-worker-01

4. kubelet on k3s-worker-01
       → receives pod spec from apiserver
       → tells containerd to run it

5. containerd
       → pulls ghcr.io/mealie-recipes/mealie:v3.13.0
       → mounts PVCs (mealie-data, mealie-logs, mealie-temp)
       → calls runc

6. runc
       → creates Linux namespaces
       → sets up cgroups
       → starts the Mealie process

7. kube-proxy
       → sets up iptables rules
       → 172.16.77.10:9000 → 10.42.2.197:9000

8. controller-manager
       → watches the ReplicaSet
       → if pod dies, triggers a new one

Result:

k get pods -n mealie
NAME                     READY   STATUS    RESTARTS   AGE
mealie-dcd97746f-5hpn4   1/1     Running   0          39s

Where Data Lives

WhatWhereNode
Cluster state/var/lib/rancher/k3s/server/db/state.dbcp-01
PVC data/var/lib/rancher/k3s/storage/pvc-{uid}_{ns}_{name}worker-02
Container images/var/lib/rancher/k3s/agent/containerd/each node
Manifests~/homelab/cp-01 + GitHub

(Visited 3 times, 1 visits today)

By Ash Thomas

Ash Thomas is a seasoned IT professional with extensive experience as a technical expert, complemented by a keen interest in blockchain technology.