Demystifying the Beast: My Journey Through Kubernetes The Hard Way
My experience building a Kubernetes cluster from scratch using Kubernetes The Hard Way, exploring PKI, kubeconfig, etcd encryption, networking, and real-world debugging.
Kubernetes is often treated as a "black box." You run a command, and magic happens. But what happens when the magic breaks? To truly understand the engine under the hood, I decided to pull it apart and build it back up piece by piece following the legendary "Kubernetes The Hard Way."
The Mission: What is "The Hard Way"?
Most developers interact with Kubernetes through managed services like GKE or EKS. "The Hard Way" strips all of that away. No automation scripts, no installers. Just raw binaries, configuration files, and a lot of certificates.
The goal was to create a production-grade cluster across three distinct functional layers:
- The Jumpbox (The Orchestrator): The root of trust. This is where I generated the PKI (Public Key Infrastructure) and pushed configurations to the rest of the cluster.
- The Control Plane (The Brain): Where the "Source of Truth" lives in etcd, managed by the API Server, Scheduler, and Controller Manager.
- The Worker Nodes (The Muscle): The boots on the ground running containerd, kubelet, and kube-proxy.
Step 1: Building the Nervous System (PKI & Certificates)
Security in Kubernetes isn't optional; it's baked into the binary communication. Every single component must prove its identity via TLS certificates.
I spent a significant amount of time in the Jumpbox, acting as a manual Certificate Authority. I had to generate unique keys for every "person" in the cluster (the admin, the nodes, the proxy, etc.).
The Logic: If the certificate doesn't have the right Common Name (CN) or Organization (O), the Node Authorizer will reject the request. For example, a worker node must identify as
system:node:<nodeName>in thesystem:nodesgroup to be trusted.
The Distribution Loop
Once generated, these weren't just files; they were the "passports" for my servers. I used a loop to securely ship them to their respective homes:
# Shipping passports to the workers
for host in node-0 node-1; do
ssh root@${host} mkdir -p /var/lib/kubelet/
scp ca.crt ${host}.crt ${host}.key root@${host}:/var/lib/kubelet/
done
Step 2: The Secret Language of kubeconfig
A common trap I fell into was thinking kubeconfig was just for me (the user). It's not. In a manual cluster, every component—the Kubelet, the Proxy, the Scheduler—is a client. They all need their own kubeconfig to tell them:
- Where the API server is.
- Who they are (their certificate).
- How to prove it.
Deep Dive: The etcd Encryption Myth
One of the most eye-opening parts of this project was dismantling the myth that etcd handles data encryption.
The Reality: etcd is a simple, "dumb" key-value store. It stores exactly what you give it. If you send it plain text, it stores plain text.
Encryption actually happens at the API Server level before the data ever reaches the database. I had to manually define an EncryptionConfiguration to tell the API Server:
"Hey, before you send Secrets to etcd, encrypt them using AES-CBC with this specific key."
The Data Flow
Write
Client → API Server (Encrypts) → etcd
Read
etcd → API Server (Decrypts) → Client
Key Insight: The API Server is the "Gatekeeper" of security; etcd is just the "Vault" where the encrypted boxes are kept.
The Debugging Odyssey: Where Things Got Real
Manual setup is unforgiving. I hit two major "walls" that taught me more than any tutorial ever could.
1. The Connectivity Trap (127.0.0.1)
My first kubectl get nodes failed instantly.
- The Error: Connection refused.
- The Lesson: My kubeconfig defaults were pointing to
localhost:6443. In a distributed cluster, the Jumpbox needs to be told the explicit Public IP or Load Balancer address of the API Server.
2. The "ContainerCreating" Deadlock (The Typo of Doom)
I had a Pod stuck in ContainerCreating for an hour. kubectl told me nothing. I had to go deeper, SSHing into the worker node and checking the logs:
journalctl -u kubelet -f
- The Culprit: A single extra period in
/etc/cni/net.d/10-bridge.conf10.200.0.0./24. - The Fix: Fixing that CIDR range allowed the CNI (Container Network Interface) to finally create the Network Sandbox.
- The Realization: Kubernetes cannot start your application until it creates a "Pause" container (the Sandbox). If the network isn't perfect, the foundation never sets.
Tooling: Knowing When to Leave kubectl
One of my biggest takeaways was learning the hierarchy of tools.
| Tool | Focus | When to Use |
|---|---|---|
kubectl | Orchestration | To see the desired state (the what). |
crictl | Runtime | To see the actual state on a specific node (the why). |
When the sandbox was failing, sudo crictl pods gave me the raw truth that the API Server was too high-level to see.
Final Thoughts: From Black Box to Glass Box
Building Kubernetes this way was grueling, but it transformed the cluster from a "black box" into a transparent machine.
- Networking isn't magic: It's a series of bridges and CIDR rules.
- Security is absolute: If the TLS handshake fails, the cluster is dead.
- etcd is just a vault: It doesn't encrypt itself; the API Server must do the heavy lifting before the data even touches the disk.
By the time I saw those Nginx pods in a Running state, I didn't just have a cluster—I had the "autopsy" skills to fix it when it inevitably breaks again.
NAME READY STATUS RESTARTS AGE
nginx-pod 1/1 Running 0 54m
Mission accomplished.