A single-node Kubernetes cluster can teach you quite a lot.
You can learn how workloads are deployed, how services find each other, how ingress works, where configuration lives and what happens during an ordinary restart. You can build something useful enough that Kubernetes stops feeling experimental.
Then you add another machine and discover how much of what you learned depended on there being only one.
That's why I like adding the second node relatively early.
I'm not doing it primarily for capacity. If the first machine isn't overloaded, another sixteen gigabytes of memory may accomplish nothing immediately. The value is that two machines give the cluster somewhere else for work to exist, which makes a whole category of assumptions visible for the first time.
Before adding it, I want the first node reasonably understood.
In this example I'm using k3s on Ubuntu. The existing server boots cleanly, its services come back without intervention, certificates renew, ingress works and I can explain enough of the installation that rebuilding it wouldn't require archaeology.
Then I prepare another machine.
I generally prefer similar hardware and the same operating system because variation creates additional questions, and right now I'm interested in Kubernetes rather than whether two different Linux configurations behave differently under load. The machines don't have to be identical, but predictable addressing, synchronized time, compatible networking and a common approach to updates remove variables that aren't particularly interesting yet.
There will be plenty of opportunities to introduce differences later.
Joining the machine to k3s is mechanically simple. The existing cluster provides the information and credentials necessary for another node to join, and the new machine is configured to contact the existing server.
A short time later, kubectl get nodes shows two nodes.
That is not redundancy.
It is two nodes.
The distinction is worth preserving because Kubernetes doesn't automatically make every dependency redundant merely because another machine has appeared.
If the second machine joined as an agent, for example, the original k3s server may still contain control-plane responsibilities that the agent can't replace. An ingress controller may still have only one replica. Persistent data may still exist on storage associated with one machine. Applications may have only one running instance.
We've increased the number of places where some workloads could run.
Now we need to discover what actually can.
I wouldn't start by deploying a pile of new applications. I'd give the second node something deliberately unimportant to do.
Run a disposable workload there. Delete its pod. Watch Kubernetes recreate it. Drain the node and see what moves. Bring it back and watch what returns.
Then inspect what didn't move.
This is where the second node starts paying for itself.
A workload may be constrained to the original machine because of an unnoticed node selector. A volume may be local. An application may have a dependency reachable only through the first node. A service that looked like part of the cluster may turn out to be something running beside Kubernetes on one particular server.
None of these discoveries means the design is wrong.
Some components should be tied to particular machines.
The important part is knowing which ones are.
Then I start removing machines.
Initially I do it politely.
Drain the second node and verify that workloads eligible to run elsewhere actually do. Reboot it. Let it rejoin. Watch the events Kubernetes produces and make sure I understand what happened.
After that, I become less polite.
Turn it off.
Disconnect its network.
Don't give Kubernetes the graceful sequence it received during the previous test.
Real failures don't usually begin with kubectl drain.
The result may look almost identical from the application's perspective, or it may reveal assumptions the graceful test never
