Kubernetes · Live Demo · Card Bots

I put Kubernetes on a server that didn't need it. On purpose.

Every job posting wants Kubernetes in production, and "I ran it in minikube once" doesn't count. So I gave it a real job: my Flip7 game already has bots - I made them play millions of games against each other as computing load, put an autoscaler in charge, and wired the controls to a public page. You crank the demand, the cluster spawns pods; you kill a pod, it comes back.

Try the demo Spawn load, watch pods multiply, kill them mid-game. The cluster doesn't mind.
Bots VS Kubernetes →

Why bots playing a card game

Scaling a static website is theater; a plain web server handles ten thousand visitors from one instance without noticing. To demo autoscaling honestly you need real computation that grows with demand. Simulating card games is exactly that: every game is thousands of decisions, and an 18-bot table costs several times a 4-bot one. Side effect: eleven million games later, I have statistics on which strategy actually wins.

What Kubernetes does, using this demo as the example

Companies run software as many small copies of the same program - Kubernetes calls them pods. Kubernetes is the manager standing over them with three jobs. It watches the workload: when demand rises past a threshold, it starts more copies; when demand falls, it retires them - nobody gets paid to stand around. It replaces the fallen: kill a pod on the demo page and a replacement appears within seconds, not because someone reacted, but because the system's only goal is "six should be running, five are, fix it." And it enforces budgets: the whole demo is capped to a fixed slice of resources, so no amount of clicking can starve anything else - the limit is a rule in the system, not a hope.

That is the entire pitch of Kubernetes in one screen: the right number of copies, self-healing, spending limits - running by themselves, day and night. One honest note: real deployments spread this across many machines, so more pods also means more horsepower. This demo is a single-node cluster - what you are watching is the management, not extra muscle.

What eleven million games taught me

Three bot personalities play: Tactician banks early at three number cards, Gambler chases all seven, Wildcard flips a coin on every decision. The strategy question turns out to depend on everything. Here is each bot's share of all wins, by table size, under official freeze rules (a frozen player keeps their points):

freeze normal · share of wins4 bots8 bots18 bots
Tactician64.4%68.7%61.5%
Gambler20.9%25.0%35.4%
Wildcard14.7%6.3%3.2%

5.0M games at 4 bots, 1.4M at 8, 1.1M at 18.

Playing it safe wins everywhere - but watch the trend. As the table grows, greed scales: Gambler climbs from 20.9% to 35.4% of all wins, while coin-flip play collapses from 14.7% to 3.2%. Big tables end in fast, chaotic rounds where a cautiously banked 30 points stops being enough; committing to the chase starts paying. Random play, meanwhile, just finds more ways to die.

Then change one rule - the house variant where a frozen player scores zero instead of keeping their points:

freeze no points · share of wins4 bots8 bots18 bots
Tactician67.3%74.9%74.1%
Gambler13.7%15.7%20.7%
Wildcard19.0%9.3%5.2%

1.2M games at 4 bots, 1.0M at 8, 1.2M at 18.

The house rule works as an anti-greed tax. Gambler's share drops at every table size, because greedy players stay active the longest - they are exactly the ones still holding cards when a freeze lands, and now it costs them everything. And the strangest result in the whole dataset: at a 4-bot table under house rules, playing randomly beats playing greedy - 19.0% against 13.7%, the only configuration anywhere in which Wildcard is not last. Same bots, same deck; the meta belongs to the table, not the strategy.

One more number for flavor: flip7s per game climb from 1.3 to 3.2 as tables grow, and an average 18-bot game contains around fifty busts. All of it is verifiable live - pick a table size and a rule on the demo and watch the shares converge to these numbers.

For those who want the whole story

The stack: k3s, a simulation service under a Horizontal Pod Autoscaler running one to six pods, a load generator controlled by visitors, and a dashboard whose permissions are scoped so it can only touch the demo's own namespace - a bug in the demo cannot reach anything beyond it, by permission design rather than by luck.

Two bugs from the build worth remembering. First: Node.js reuses HTTP connections by default, and Kubernetes balances load per connection, not per request - so all traffic pinned itself to one pod while freshly scaled ones idled at zero. One line fixed it; finding the line is the lesson. Second: killed pods do not overlap with their replacements. Kubernetes counts a dying pod as alive until it is fully gone, so a kill is a gap and then a heal - the graceful overlap people expect exists only for rolling updates. Both are the kind of thing you only learn by watching a live cluster misbehave.

New to Flip7?