From b41564d462e408295e0067da6111974adb834b5e Mon Sep 17 00:00:00 2001
From: jwright
A note on the commands: this site uses oc, - OpenShift's CLI. Everything shown works identically with kubectl - - oc is a superset that adds OpenShift's own verbs. Read + OpenShift's CLI. The Kubernetes commands here work the same + under kubectl; oc is a superset, and the + OpenShift-only parts (SCCs, Routes) are its own. Read kubectl in the docs, type whichever your cluster gives you.
That 1/1 reads as containers-ready over containers-wanted: a pod can hold more than one, which is Course IIIb.
@@ -113,7 +113,15 @@So what does the image provide? The filesystem that process sees. That is the whole job, and it is why an image is a stack of layers rather than a disk image.
-Field note. Prove it on your own box. + podman run -d --name t alpine sleep 300, then on the + host: ps -ef | grep sleep finds the process, + lsns -p <pid> lists the namespaces it was handed, + cat /proc/<pid>/cgroup shows where its limits live, and + mount | grep overlay shows the layers stitched together. None of + it is exotic. It is your kernel, described differently.
+ +An image is not a copy of a machine. It is a stack of read-only layers, each recording only what changed from the one beneath. Layers are content-addressed, so an identical layer is stored once and reused by every image that references it, so a pull only @@ -188,8 +196,9 @@ authorised and is admitted there, and it is the only component allowed to touch etcd - the key-value store holding the entire cluster state. Lose etcd and you have lost the cluster, which is why backing it up is the homework nobody should skip. The scheduler - decides which node a new pod belongs on and writes that decision down; it never starts - anything. The controller-manager runs the reconciliation loops. Nothing talks + decides which node a new pod belongs on - packing by the resources a pod + requests, not by what it currently uses - and writes that decision down; it never + starts anything. The controller-manager runs the reconciliation loops. Nothing talks sideways - every component watches the api-server.
One of those loops is the chain you will debug most: a Deployment creates a ReplicaSet, and the ReplicaSet creates pods. That is why the deploy in the @@ -226,17 +235,19 @@
Every node runs a kubelet - the agent that owns what should be running there. It does - not create containers itself. It speaks CRI to containerd or CRI-O, and that + not create containers itself. It speaks CRI - the Container Runtime Interface - to containerd or CRI-O, and that runtime pulls the image (through the mirror of Course VII) and actually creates and starts - the container, handing the low-level work to runc or crun. A CNI plugin hands the pod - a real IP, and kube-proxy - or an eBPF datapath replacing it - makes Service - addresses route to real pods - and it is the runtime, not the kubelet, that calls the CNI - plugin to wire the pod up.
+ the container, handing the low-level work to runc or crun. A CNI plugin (Container Network Interface) hands the pod + a real IP, and kube-proxy - or an eBPF datapath (code running + inside the kernel itself) replacing it - makes Service + addresses route to real pods. One correction worth carrying: the runtime calls the CNI plugin, + not the kubelet.The kubelet decides what should run. The runtime is what starts it.
-Field note. Node NotReady? Check kubelet-to-API-server - connectivity before anything else - a node that cannot phone home is presumed lost.
+Field note. Node NotReady? The kubelet is a systemd unit + like any other: journalctl -u kubelet on that node, and check it + can still reach the api-server. A node that cannot phone home is presumed lost.
The control plane never touches your workload. It writes intent; the kubelet turns intent into instructions, and the runtime turns instructions into processes. Authority and execution meet exactly here, nowhere else.
@@ -269,8 +280,8 @@Everything inside the jacket shares a network namespace: one IP, localhost between friends. Volumes are declared once on the pod, but each container mounts the ones it needs - - sharing storage is opt-in, not automatic. initContainers run first, in order, to completion - - gatekeepers under everything. Sidecars ride along with their own containers and their + sharing storage is opt-in, not automatic. initContainers run first, in order, each to + completion - nothing else in the pod starts until every one of them has exited successfully. Sidecars ride along with their own containers and their own jobs: proxy, logs, reload.
startup owns warm-up, readiness gates traffic, liveness restarts the @@ -321,10 +332,12 @@
A Service is the fixed point: a ClusterIP inside, a LoadBalancer at the edge, Ingress or the Gateway API doing host- and path-routing above. Clients hold the name; the pods behind it come and go without anyone being told.
-A Service holds no list of pods. It holds a label selector - match app: api - and a controller continuously matches that against every - pod in the namespace, keeping the passing set in an EndpointSlice. Labels are how + pod in the namespace - a Kubernetes namespace this time, a naming boundary for + objects, no relation to the kernel namespaces of Course I - keeping the passing set in an + EndpointSlice. Labels are how everything here finds everything else, from a Service picking pods to a fleet hub picking whole clusters (Course VI). Wear the label and you are eligible; readiness decides whether you stay.
@@ -374,16 +387,18 @@The SCC is admission deciding what a pod may BE. The default, restricted-v2, runs +
The SCC - Security Context Constraint - is admission deciding what a pod may BE, checked + by the api-server when the pod is created and before any node sees it. The default, restricted-v2, runs your container as a random non-root UID - your image has to cope. Workloads that genuinely need privilege get a dedicated ServiceAccount bound to a minimal custom SCC, never the stock one.
Routes predate Ingress and still rule here. Operators run machinery on your behalf - - though the split matters: the platform's own operators are driven by the Cluster Version +
Routes predate Ingress and still rule here. An Operator is a controller paired with a custom + resource: you describe what you want in YAML, and its controller builds it and keeps it true + - the reconciliation loop of C-II, sold as a product. The split matters: the platform's own operators are driven by the Cluster Version Operator, while OLM installs and upgrades the add-on Operators you choose from OperatorHub. The OS underneath is immutable - changed by MachineConfig, never by SSH. - And SNO puts the entire promise on one box at the edge. At fleet scale the labels + And SNO - single-node OpenShift - puts the whole cluster on one box at the edge. At fleet scale the labels from Course VI decide which of these boxes runs what.
On OpenShift, admission is the interview - the SCC is the dress code.
Field note. Deployment stuck at 0/1 with no pod at all? The @@ -440,12 +455,13 @@ and with prune enabled what leaves git leaves the cluster. Both are opt-in: without them the reconciler reports the drift and waits for a human. Rollback is git revert, which is why commit hygiene is an operational skill.
-Field note. Fighting the reconciler over SSH is arm-wrestling a - machine that does not get tired - C-II's warning, now with a face. The fix is always - upstream, in git.
+Field note. A hand-patch on a live object survives exactly until + the next sync. If you must touch production directly to stop the bleeding, open the pull + request in the same hour - otherwise the reconciler quietly undoes your fix, and the outage + returns with nobody able to say why.
Run many OpenShift clusters under a hub - RHACM, with edge clusters arriving through - zero-touch provisioning - and nobody applies apps to clusters by hand. Each app carries a +
Run many OpenShift clusters under a hub - RHACM, Red Hat's fleet manager, with edge clusters + arriving through zero-touch provisioning - and nobody applies apps to clusters by hand. Each app carries a Placement that selects cluster labels; the hub matches placements against the labels a cluster wears, and the chosen cluster is given its assignment - push-style from the hub by default, or in a pull model where each cluster runs its own reconciler, which is the @@ -482,7 +498,9 @@
myapp:latest feels like a name. It is a sticky note - a mutable pointer anyone with push rights can peel off one image and press onto another, and - nothing anywhere records that it moved. The digest - sha256 of the image manifest - is + nothing anywhere records that it moved. The digest - sha256 of the image manifest, the + small JSON index listing an image's layers, and no relation to the YAML manifests you apply + to a cluster - is its actual name: same bytes, same digest, forever. (Careful: the sha256 a build prints is the local image ID, a different hash from the manifest digest the registry mints on push - the pushed one is what you pin.)
@@ -501,7 +519,7 @@Three ways to name an image, in rising order of honesty: :latest - (a moving target), :1.4.2 (a promise somebody keeps, until they + (a moving target), :1.4.2 (a label someone maintains, until they re-push it), and name:1.4.2@sha256:... - a fact. The tag stays for human eyes; the digest does the pulling. Pin by digest and "what is running?" has exactly one answer.
@@ -529,8 +547,8 @@Every rebuild is a new artefact - in practice a different digest (reproducible builds are the deliberate exception), untested by the stages before it. So build once, then promote the same digest through environments by copying, registry - to registry - dev proves the exact bytes prod will run. On a multi-arch image that copy needs - --all (and --preserve-digests to fail + to registry - dev proves the exact bytes prod will run. On a multi-arch image that copy + (skopeo copy) needs --all (and --preserve-digests to fail loudly rather than quietly), or you copy one architecture and the digest you promoted is not the digest that lands. Human tags ride along; the digest is the through-line.