What is Kubernetes? The Ultimate Guide to K8s Basics

The smell of ozone is the only thing that’s stayed the same. In 2008, it meant a capacitor had finally given up the ghost on a Dell PowerEdge R710. It was a sharp, metallic scent that cut through the recycled air of the data center, usually accompanied by the frantic, high-pitched whine of a 15k RPM SAS drive trying to spin up for the last time. I remember the cold. Not the “turn up the thermostat” cold, but the bone-deep chill of a room designed for machines, where the floor tiles rattled under the weight of racks filled with Xeon E5-2600 series processors.

I was standing in Row 4, Tile 12, when the PERC 6/i controller decided it didn’t want to live anymore. The amber light on the front of the chassis wasn’t just a status indicator; it was a personal insult. I had to physically pull the drive, feel the heat radiating off the metal casing, and slide in a replacement. There was a click. A mechanical, tactile confirmation that the hardware was seated. I watched the rebuild progress in the BIOS—not through a web-based dashboard with rounded corners and “user-friendly” progress bars, but through white text on a blue background. It took fourteen hours. I stayed for all of them. I knew exactly where my data lived. I knew which copper trace it was traveling across. I owned the uptime because I could touch the failure.

Now? Now I’m staring at a YAML file that’s four hundred lines long, trying to figure out why a virtual network interface that doesn’t exist on a physical plane can’t talk to a database that’s actually just a distributed consensus algorithm running on someone else’s hardware in Northern Virginia. We’ve traded the soldering iron for the abstraction layer, and we’re all poorer for it.

The Great Virtualization Swindle

The rot started with the hypervisor. At first, it seemed like a good deal. “Consolidate your workloads,” they said. “Stop wasting those Xeon cycles.” So we took our perfectly functional bare-metal installs and shoved them into VMDKs. We thought we were being efficient. What we were actually doing was signing a suicide pact with complexity.

In the old days, if a process went rogue, I’d top it, find the PID, and kill it. If the machine was sluggish, I’d check the physical RAM sticks. With virtualization, we introduced the “noisy neighbor.” Suddenly, my application’s performance was dependent on some other guy’s poorly written SQL query three virtual machines over. We lost the one-to-one mapping of soul to silicon. We started talking about “vCPUs” as if they were real things. They aren’t. They’re just time-slices on a scheduler that’s trying to juggle too many balls.

I remember the first time I saw a VM migration fail. The “vMotion” magic trick. The idea that you could move a running OS across a network link without dropping a packet. It worked, until it didn’t. A slight hiccup in the 10GbE switch, a minor latency spike on the SAN, and suddenly you had a corrupted filesystem and a kernel panic that left no logs because the virtual disk was in limbo. We traded the reliability of a physical backplane for the “flexibility” of a software-defined nightmare.

The Death of the Script and the Rise of the Monolith

Then came the transition from init.d to systemd. This was the first real step toward the “black box” philosophy that dominates the industry today. I used to write my own init scripts. They were bash. They were ugly, sure, but I could read them. I knew exactly what happened when I typed /etc/init.d/httpd start. I knew which environment variables were being set, which user was being su-ed into, and where the PID file was being written.

Then systemd arrived and told me that I didn’t need to know those things. It told me to write a .service file and let the binary take care of the rest. Logs moved from plain text files in /var/log—files I could grep, awk, and sed until my heart was content—into a binary format that required journalctl just to read. If the journal was corrupted? Good luck. You’re not reading those logs. You’re just staring at a screen hoping the “magic” fixes itself.

This was the beginning of the end for the systems engineer. We stopped being mechanics and started being librarians for opaque binaries. We stopped understanding how the process interacted with the kernel and started worrying about whether our “unit file” had the right dependencies. It was the first layer of the shroud that would eventually become the cloud.

The Networking Stack: A Descent into CNI Hell

If you want to see a grown man cry, ask him to explain his Container Network Interface (CNI) setup in a production environment. In the bare-metal days, networking was simple. You had a NIC. You had an IP. You had a gateway. You had iptables. If a packet didn’t get where it was going, you used tcpdump on the interface and you saw exactly where it died.

Now, we have layers of encapsulation that would make a Russian nesting doll blush. We have VXLAN, Geneve, and GRE tunnels wrapped inside each other. When a packet leaves a container, it doesn’t just go to a wire. It goes to a virtual bridge, which hits an iptables NAT rule, which might get encapsulated into a VXLAN packet, which then travels across a physical network to another host, where it’s decapsulated, run through another set of iptables rules, and finally delivered to a pod.

I’ve spent weeks debugging iptables vs IPVS in large clusters. iptables was never meant to handle twenty thousand rules. When you get to that scale, the sequential evaluation of rules turns your CPU into a space heater. So we move to IPVS, which uses hash tables. Great. Now, instead of a readable list of rules, you have a kernel-level load balancer that is virtually impossible to inspect without specialized tools.

Listen, kid, if you’re asking me what is a CNI, you’ve already lost the plot. It’s a desperate attempt to give an IP address to something that shouldn’t exist for more than five minutes. It’s a way to pretend that we still have a network when all we really have is a series of software hacks held together by hope and bad documentation. You’re not routing packets; you’re managing a hallucination.

The YAML Purgatory

And then there’s Kubernetes. The “operating system of the cloud.” Or, as I call it, the world’s most expensive way to run a web server. We’ve reached a point where we don’t even write code anymore; we just write YAML. We spend our days indenting lines and praying that we didn’t accidentally use a tab instead of two spaces.

I’m currently looking at a cluster running version 1.29.2. It’s supposed to be the “stable” release. Yet, here I am, at 3:00 AM, looking at a pod that refuses to start. The developer tells me “it worked on my machine.” Of course it did. His machine isn’t trying to pull a 2GB image over a throttled internal registry link while the CNI is having a stroke.

Here is what my life looks like now. This is the “modern” experience:

$ kubectl get events -n kube-system
LAST SEEN   TYPE      REASON             OBJECT                                 MESSAGE
4m22s       Warning   Failed             pod/coredns-78fcdf6894-jm28n           Error: ImagePullBackOff
2m15s       Normal    BackOff            pod/coredns-78fcdf6894-jm28n           Back-off pulling image "registry.k8s.io/coredns/coredns:v1.11.1"
12s         Warning   Failed             pod/coredns-78fcdf6894-jm28n           Failed to pull image "registry.k8s.io/coredns/coredns:v1.11.1": rpc error: code = Unknown desc = failed to pull and unpack image "registry.k8s.io/coredns/coredns:v1.11.1": failed to resolve reference "registry.k8s.io/coredns/coredns:v1.11.1": failed to do request: Head "https://registry.k8s.io/v2/coredns/coredns/manifests/v1.11.1": dial tcp 142.250.190.48:443: i/o timeout

I run a describe because I love punishing myself:

$ kubectl describe pod coredns-78fcdf6894-jm28n -n kube-system
Name:                 coredns-78fcdf6894-jm28n
Namespace:            kube-system
Priority:             2000000000
Priority Class:       system-cluster-critical
Service Account:      coredns
Node:                 node-01/10.0.0.15
Start Time:           Tue, 14 May 2024 02:10:45 -0400
Labels:               k8s-app=kube-dns
                      pod-template-hash=78fcdf6894
Annotations:          <none>
Status:               Pending
IP:                   
IPs:                  <none>
Controlled By:        ReplicaSet/coredns-78fcdf6894
Containers:
  coredns:
    Container ID:  
    Image:         registry.k8s.io/coredns/coredns:v1.11.1
    Image ID:      
    Port:          53/UDP
    Host Port:     0/UDP
    Args:
      -conf
      /etc/coredns/Corefile
    State:          Waiting
      Reason:       ImagePullBackOff
    Ready:          False
    Restart Count:  0
    Limits:
      memory:  170Mi
    Requests:
      cpu:        100m
      memory:     70Mi
...
Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  5m                 default-scheduler  Successfully assigned kube-system/coredns-78fcdf6894-jm28n to node-01
  Normal   Pulling    3m (x3 over 5m)    kubelet            Pulling image "registry.k8s.io/coredns/coredns:v1.11.1"
  Warning  Failed     3m (x3 over 5m)    kubelet            Failed to pull image "registry.k8s.io/coredns/coredns:v1.11.1": rpc error: code = Unknown desc = failed to pull and unpack image "registry.k8s.io/coredns/coredns:v1.11.1": failed to resolve reference "registry.k8s.io/coredns/coredns:v1.11.1": failed to do request: Head "https://registry.k8s.io/v2/coredns/coredns/manifests/v1.11.1": dial tcp 142.250.190.48:443: i/o timeout
  Warning  Failed     3m (x3 over 5m)    kubelet            Error: ErrImagePull
  Normal   BackOff    2m (x6 over 5m)    kubelet            Back-off pulling image "registry.k8s.io/coredns/coredns:v1.11.1"
  Warning  Failed     15s (x20 over 5m)  kubelet            Error: ImagePullBackOff

Look at that. ImagePullBackOff. A timeout. Why? Is the registry down? No. Is the node’s DNS broken? Maybe, but wait—this is the DNS pod. We have a circular dependency in our infrastructure. The thing that resolves names can’t start because it can’t resolve the name of the place where its image lives. This is the “intelligence” of the modern orchestrator. It’s a dog chasing its own tail in a dark room, and I’m the one who has to clean up the mess.

Why the Scheduler is Not Your Friend

In the bare-metal world, I decided where things went. I had a spreadsheet. Server A ran the database. Server B ran the web server. If Server A was getting hammered, I knew exactly which physical CPU was sweating.

In Kubernetes, we have the “Scheduler.” It’s a black box that looks at resource requests and limits and decides where to place your pods. It sounds great on paper. In practice, it’s a disaster. The scheduler doesn’t know that Node 04 has a slightly flaky NIC. It doesn’t know that Node 07 is physically located in a rack that’s currently undergoing a cooling failure. It just sees “available capacity” and dumps the workload there.

Then there are “Requests” and “Limits.” This is the ultimate lie. We tell the scheduler that a pod “requests” 100m of CPU but can “limit” to 2000m. What happens when every pod on the node suddenly decides it needs its limit? The kernel starts throttling. Your application’s latency spikes from 10ms to 500ms, and you have no idea why because the “average CPU usage” on your dashboard looks fine. You’re being suffocated by the abstraction. You’re fighting a ghost in the machine that you didn’t even invite.

The etcd Heart of Darkness

At the center of this whole circus is etcd. It’s a key-value store that uses the Raft consensus algorithm. It is the single point of failure for the entire cluster. If etcd goes down, your cluster is a vegetable. If etcd gets slow, your cluster starts hallucinating.

I have spent more nights than I care to admit trying to restore etcd from a snapshot. Have you ever tried to fix a corrupted db file at 4:00 AM? It’s like performing heart surgery on a patient who is currently exploding. You have to stop all the API servers, find the most recent consistent snapshot, and hope to God that the member IDs match up when you try to bring the cluster back online.

The latency requirements for etcd are insane. If your disk seek time increases by a few milliseconds, etcd starts complaining about “failing to send heartbeats on time.” Then the leader election starts. Then the whole cluster stops accepting writes. All because you’re running on “cloud-optimized” storage that isn’t actually fast enough to handle a consistent write-ahead log. I miss the days when a database was a file on a local disk, not a distributed existential crisis.

Serverless: The Ultimate Lie

If Kubernetes is a circus, “Serverless” is a magic show where the magician just hides the rabbit in his pocket and tells you it disappeared. “No servers to manage!” the marketing fluff screams.

There are always servers. You just don’t have the SSH keys to them.

Serverless is the final admission of defeat. It’s saying, “I give up. I don’t want to know how the computer works. Just take my code and run it.” But when your Lambda function takes 5 seconds to start because of a “cold start” (which is just a fancy way of saying “we had to spin up a container because we weren’t actually ready for you”), you have no recourse. You can’t tune the kernel. You can’t check the connection pool. You can’t do anything but sit there and pay the bill.

It’s the ultimate vendor lock-in. You’re not building a system; you’re building a dependency. You’re writing code that only works in one specific environment, managed by one specific company, and if they decide to change the pricing or the runtime version, you’re stuck. You’ve traded your sovereignty for the illusion of simplicity.

The Sound of the Next Shiny Object

I can hear it coming. The next wave of “innovation.” Maybe it’s “AI-driven infrastructure” or “WebAssembly on the Edge.” It doesn’t matter what they call it. It’ll be another layer of YAML, another set of abstractions, and another way to move us further away from the hardware.

They’ll tell you it’s “faster.” They’ll tell you it’s “more scalable.” They’ll use words that sound like they came out of a brochure for a luxury spa. But it won’t be better. It’ll just be more complicated. It’ll be more things to break in ways you can’t see.

I’m tired. I’m tired of debugging networks that don’t exist. I’m tired of reading logs that are stored in binary formats. I’m tired of explaining to twenty-somethings that you can’t just “scale” your way out of a bad algorithm.

One of these days, the “cloud” is going to have a real bad day. Not a “one region is down” bad day, but a “the underlying abstractions have become so tangled that nobody knows how to untie the knot” bad day. And when that happens, I’ll be in my basement, staring at my old R710. It’s loud, it’s heavy, and it uses more power than a small village. But when I press the power button, I know exactly what’s going to happen.

I can hear the fans spin up. I can smell the ozone. And for a second, I’m back in control.

Don’t get comfortable. The next “shiny object” is already being polished, and it’s designed to make sure you never touch a piece of hardware again. They want you to be a consumer of “compute,” not a master of machines. Resist it. Learn the stack. Understand the kernel. Because when the magic stops working—and it always stops working—you’re the one who’s going to be left in the dark, staring at a “Pending” status on a screen that doesn’t care about your uptime.

Related Articles

Explore more insights and best practices:

Leave a Comment