{"id":29305,"date":"2026-09-12T10:02:01","date_gmt":"2026-09-12T08:02:01","guid":{"rendered":"https:\/\/blog.mi.hdm-stuttgart.de\/?p=29305"},"modified":"2026-09-12T10:12:12","modified_gmt":"2026-09-12T08:12:12","slug":"leased-cloud-machines-teardown-guarantee","status":"publish","type":"post","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/","title":{"rendered":"Leased cloud machines: making a teardown guarantee observable"},"content":{"rendered":"\n<p class=\"has-small-font-size wp-block-paragraph\"><em>Written as the course project for Software Development for Cloud Computing (113479a) at HdM Stuttgart. The source code for <a href=\"https:\/\/github.com\/lucawalz\/horizon\">horizon<\/a> and <a href=\"https:\/\/github.com\/lucawalz\/bedrock\">bedrock<\/a> is on GitHub.<\/em><\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">&#8220;This raises the question of who notices when the alarm system is not working properly.&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lisanne Bainbridge, <a href=\"https:\/\/ckrybus.com\/static\/papers\/Bainbridge_1983_Automatica.pdf\">Ironies of Automation<\/a>, 1983<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">A machine you have to give back<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Some jobs want a machine I don&#8217;t own. Not often, and never for long. An hour of eight cores for a build that would otherwise run overnight, on hardware I would happily own and can&#8217;t justify buying right now, with RAM and storage priced the way they are.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Renting one is a single API call. The machine appears in about a minute and charges a few cents an hour from the moment it exists. Giving it back reliably took four months.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That sounds like the wrong way round, so here&#8217;s the part that makes it true. A server bills for as long as it exists, whether or not anything is running on it, and nothing in the cluster notices when one is left behind. A cluster with a forgotten machine in it looks exactly like a cluster without one. Both report healthy. The difference turns up on an invoice, about a hundred euros later.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/lucawalz\/horizon\">horizon<\/a> is what I built for that. It&#8217;s a <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/extend-kubernetes\/operator\/\">Kubernetes operator<\/a>: you write down how much capacity you want and until when. Then it rents the machines, joins them to the cluster, and destroys them when the time is up. It runs on <a href=\"https:\/\/github.com\/lucawalz\/bedrock\">bedrock<\/a>, which is three small Lenovo desktops at home running <a href=\"https:\/\/nixos.org\/\">NixOS<\/a> and <a href=\"https:\/\/docs.k3s.io\/\">K3s<\/a>, reconciled from a git repository by <a href=\"https:\/\/fluxcd.io\/\">Flux<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So the whole project is one requirement with a lot of machinery under it. When the deadline passes, the machine is gone.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It&#8217;s easy to state, easy to implement badly, and surprisingly hard to know you have. The hard part was never deleting a machine. It was knowing, at any given moment, that it would be deleted. I thought I knew more than once, and I was wrong every time.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What was pitched, and what survived<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The original proposal was one sentence, and I&#8217;ve still got it:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">a Go CLI and lightweight controller that lets any K3s homelab extend into the cloud, temporarily bursting overflow workloads, running permanent cloud workers, or federating independent clusters, using Terraform, NixOS, and Tailscale.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Three products in one sentence. Bursting, permanent cloud workers, and cluster federation aren&#8217;t one tool with three settings; they are three tools that happen to rent machines. Bursting is about deadlines and teardown. Permanent workers are a different job made of upgrades, drain policy and drift over months. Federation is a third job again, whose hard parts are identity, trust between control planes, and what happens when the link goes down. I built parts of all three, and they kept getting in each other&#8217;s way.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Only bursting survived, so horizon adds temporary capacity to a cluster that already exists.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The named technologies fared no better. <a href=\"https:\/\/developer.hashicorp.com\/terraform\">Terraform<\/a> was the first version&#8217;s entire provisioning layer. horizon shelled out to a module living in the bedrock repository, ran <code class=\"\" data-line=\"\">init<\/code> against it, and selected a workspace per burst before applying and reading the server outputs back. NixOS is still how bedrock&#8217;s machines are declared, and horizon knows nothing about it; the tool generates a <a href=\"https:\/\/docs.cloud-init.io\/\">cloud-init<\/a> document and boots whatever image the operator points it at. <a href=\"https:\/\/tailscale.com\/docs\/concepts\/what-is-tailscale\">Tailscale<\/a>, the third name in the sentence, is configured entirely in the other repository.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Most of the original scope ended up deleted. A Cluster API integration, a Velero-based workload migration and a full terminal interface were each built, used for a while, and taken out again.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/three-programs.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"498\" data-attachment-id=\"29306\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/three-programs\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/three-programs.png\" data-orig-size=\"1400,681\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}\" data-image-title=\"three-programs\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/three-programs-1024x498.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/three-programs-1024x498.png\" alt=\"Six responsibilities against the three programs horizon has been. Five drain to nothing across the three. Only giving the machine back is held by all three.\" class=\"wp-image-29306\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/three-programs-1024x498.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/three-programs-300x146.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/three-programs-768x374.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/three-programs.png 1400w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">That sounds like a waste of time, and yes, some of it was. But the deletions weren&#8217;t random, because most of what I cut turned out to belong somewhere else. Cluster creation belongs in the git tree that Flux reconciles, image building is the job of whoever owns the image, and the network overlay is the estate&#8217;s problem. What was left after every one of those cuts was the same requirement that had been in the sentence from the very beginning, buried in the middle of it: give the machine back.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Everything after this point is an attempt to make that requirement true, and then an attempt to find out whether it actually is.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why the provider won&#8217;t do this for you<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The obvious thing to do with an idle machine is switch it off. It buys nothing. A Hetzner server bills for as long as the server object exists, because a powered-off server keeps its capacity reserved; <a href=\"https:\/\/docs.hetzner.com\/cloud\/billing\/faq\/\">Hetzner&#8217;s billing documentation<\/a> is explicit that it keeps costing, and that any fraction of an hour is billed as a whole hour. Deletion is the only thing that stops the meter.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Providers differ on whether a machine can arrange its own deletion, and they differ in a way that changes the architecture rather than the configuration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">AWS will terminate an instance when the guest shuts itself down, if <a href=\"https:\/\/docs.aws.amazon.com\/AWSEC2\/latest\/UserGuide\/Using_ChangingInstanceInitiatedShutdownBehavior.html\"><code class=\"\" data-line=\"\">InstanceInitiatedShutdownBehavior<\/code><\/a> is set to terminate. <a href=\"https:\/\/docs.aws.amazon.com\/AWSEC2\/latest\/UserGuide\/terminating-instances.html\">Billing ends<\/a> when the state reaches shutting-down. The machine needs no credential at all; <code class=\"\" data-line=\"\">poweroff<\/code> is the whole mechanism. Google Compute Engine goes further and enforces a server-side <a href=\"https:\/\/cloud.google.com\/compute\/docs\/instances\/limit-vm-runtime\"><code class=\"\" data-line=\"\">maxRunDuration<\/code><\/a>, which can delete the instance when the timer expires. The provider holds that timer, where nothing on the machine can reach it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Hetzner has neither. There is no server-side deadline, and shutting down stops nothing. If a Hetzner machine is going to destroy itself, it has to call the delete API, and to call the delete API it has to hold a token that can delete servers. That token has to live on the ephemeral machine, which is also the machine running migrated workloads, and Hetzner tokens are <a href=\"https:\/\/docs.hetzner.com\/cloud\/api\/getting-started\/generating-api-token\/\">project-scoped<\/a> with no per-resource permissions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I picked Hetzner for three reasons. It&#8217;s cheap. Cheap enough that <a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2021\/02\/27\/migrating-from-heroku-to-hetzner-achieving-scalability-with-docker-kubernetes-and-rancher\/\">another project written up on this blog<\/a> moved a startup&#8217;s backend off Heroku and onto Hetzner servers, and worked out that comparable capacity on Heroku would have cost five to six times as much. It is the hardest of the three, so building against it forces the compensating machinery to be real. And I wanted the mechanism to be mine and to work on any provider, instead of leaning on a feature that only a bigger provider offers and being tied to that provider to keep it. A design that works on Google Compute Engine may only be working because Google is holding the timer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The provider differences matter less than what they all have in common, which is that the failure mode is money, and money is silent. A crashed process pages someone, but a machine nobody deleted just keeps billing until whoever reads the invoice weeks later asks what that line is.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">First answer: the guarantee lived inside the program<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">At first the guarantee lived inside a single function, and for a while I couldn&#8217;t see anything wrong with that.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code class=\"\" data-line=\"\">core.Burst<\/code> did the whole job linearly. It reserved capacity from the provider and waited for the new node to register with the cluster, then rewrote the target workload&#8217;s <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/scheduling-eviction\/assign-pod-node\/#affinity-and-anti-affinity\">affinity<\/a> so the pods would land on it, and returned. Two <a href=\"https:\/\/go.dev\/ref\/spec#Defer_statements\">deferred<\/a> rollbacks guarded it: an outer one that returned the reserved server count to its previous value if anything after the scale step failed, and an inner one that restored the saved affinity and <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/scheduling-eviction\/taint-and-toleration\/\">tolerations<\/a> if migration failed. Both ran on a fresh context, so a cancelled burst still cleaned up after itself.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I read it twice, looking for the bug, and didn&#8217;t find one. Within its own assumptions it is correct.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So I stopped reading and tried to break it instead. The experiment ran against the real <code class=\"\" data-line=\"\">core.Burst<\/code> and the real node image, one rented server per run, split into a treatment arm, where the process got killed, and a control arm, where it didn&#8217;t. In the treatment arm I sent the process <a href=\"https:\/\/man7.org\/linux\/man-pages\/man7\/signal.7.html\"><code class=\"\" data-line=\"\">SIGKILL<\/code><\/a> while it was waiting for the node to register: the kill a program cannot catch, refuse or clean up after, which is what a hard power-off or an out-of-memory kill looks like from inside the process. Five runs, five servers left running and billing, with nothing anywhere that knew they existed. In the control arm I let the node-ready timeout expire normally, so the function returned through its own error path. That run leaked nothing: the rollback fired and put the reserved count back where it had been.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The control arm is what makes the result mean anything. Without it, five leaks look like five bugs in the rollback, and the fix looks like reading the rollback more carefully. With it, the rollback is demonstrably fine. It handles the failure it was written for, and it is absent for the failure it was not, because a deferred call runs when a function returns and a killed process never returns from anything.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That is true in Go, in C, and in any other language, because the handling happens in the kernel and the process is never consulted. There is no amount of care inside a linear function that fixes this, and no signal handler to write. The cleanup is in the wrong place.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The replacement<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The replacement is a <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/extend-kubernetes\/api-extension\/custom-resources\/\">custom resource<\/a> and a controller. A <code class=\"\" data-line=\"\">CapacityLease<\/code> states how much capacity, from which provider and region, for how long, and for which workload. The controller compares that statement against observed reality on every pass and acts on the difference, so compensation stops being a deferred call and becomes the ordinary business of a loop that, by construction, runs again after a crash. Around it sit layers that do not share a failure mode: a <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/overview\/working-with-objects\/finalizers\/\">finalizer<\/a> persisted before any provider call, ownership and deadline labels written atomically in the create call so an instance is never anonymous even for an instant, a collector that lists every configured provider on a slow tick and deletes anything no live lease claims once its deadline has passed, and launch deadlines that release a machine that never boots or never joins.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I copied most of that shape from <a href=\"https:\/\/karpenter.sh\/\">Karpenter<\/a>. I went through its published failure history expecting to find the bug I&#8217;d just written, and didn&#8217;t: the documented leaks there came from identity mismatch, not from missing cleanup code. That was a relief, and it was also a fairly direct instruction about where to look.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Deletion counts as complete only when the provider reports the instance absent. A successful delete call is not evidence.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How a machine in a datacentre joins a cluster in a house<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before any of that can be tested, a rented machine in Helsinki has to become a node in a cluster sitting on a residential line in Germany. These two things share no network, and the hard requirement is that nothing on the home line accepts an inbound connection.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That requirement isn&#8217;t paranoia. Opening a port to a cluster&#8217;s API server from the public internet, on a connection whose address is already published in DNS, means the first line of defence for the whole estate is whatever the API server&#8217;s authentication does that day. I wasn&#8217;t going to open that port, but the rented machine still had to reach in.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The way out is that both ends dial out. A coordination service, reachable from anywhere, learns where each peer currently is. The peers ask it about each other, then attempt to connect directly, punching through their respective <a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc3022.html\">network address translation<\/a> using the techniques described in Tailscale&#8217;s <a href=\"https:\/\/tailscale.com\/blog\/how-nat-traversal-works\">write-up of NAT traversal<\/a>. Once that succeeds the traffic is peer to peer and the coordination service is out of the path. Nothing points inward. The home firewall never gets an inbound rule, because there&#8217;s no inbound connection to permit: both sides opened outbound ones and met.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/join-path.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"439\" data-attachment-id=\"29307\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/join-path\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/join-path.png\" data-orig-size=\"1400,600\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}\" data-image-title=\"join-path\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/join-path-1024x439.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/join-path-1024x439.png\" alt=\"A home region and a rented region, each with one arrow up to a coordination server above them. Nothing points back down into either region.\" class=\"wp-image-29307\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/join-path-1024x439.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/join-path-300x129.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/join-path-768x329.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/join-path.png 1400w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">There&#8217;s a second thing that had to happen first, which I hadn&#8217;t accounted for and which took over the project for a while.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">My cluster didn&#8217;t really have a network of its own. It was three machines on a flat home LAN, behind a consumer router from the telephone company. That router can&#8217;t express VLANs at all, and it can&#8217;t express a rule between two of its own devices. Everything I wanted to say about the boundary between a rented machine and my home could not be said on that hardware.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So a Raspberry Pi became the router. It runs NixOS, declared as another host in the same repository as the cluster, with <a href=\"https:\/\/netfilter.org\/projects\/nftables\/index.html\">nftables<\/a> for the firewall, <a href=\"https:\/\/kea.readthedocs.io\/en\/stable\/\">kea<\/a> for DHCP and <a href=\"https:\/\/github.com\/AdguardTeam\/AdGuardHome\">AdGuard<\/a> for DNS. On top of that went a documented address plan. The cluster sits in a trusted servers zone of its own, the home devices are somewhere else entirely, and traffic between zones is denied by default. Changing the firewall is now an edit to a file and an apply, reviewed the same way as everything else.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The alternative was an off-the-shelf firewall appliance, which does all of this behind a polished interface and keeps its configuration in its own XML on its own disk, outside the repository. It doesn&#8217;t run on the ARM hardware I already owned, so it meant buying another old PC. That is the same spend I could not justify at the top of this post. I picked the Pi and hand-assembled what the appliance bundles, which was more work and more networking than I had done before.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Five overlay networks, one search<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The join path above sounds like a design decision. In practice it was one requirement discovered slowly, at the cost of four wrong answers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The requirement is this. The coordination service is the thing every node has to reach before it can reach anything else. A rented machine that can&#8217;t reach it never learns where the cluster is, and a machine that never learns where the cluster is never becomes a node. So the coordination service has to be reachable from outside the house, unconditionally, before anything else works.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I resisted that for a long time, because it meant giving up on hosting everything myself.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The five attempts, in order<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/netbird.io\/\">NetBird<\/a> went in first and lasted about five hours. It&#8217;s WireGuard with a management server and a signalling server on top, handing out keys and introducing peers to each other, and you can run both yourself. It cost a <a href=\"https:\/\/helm.sh\/\">Helm<\/a> release, a dashboard behind the ingress, a management gRPC service on a node port and an encryption key in the secret store, and all of that came back out the same evening, when I replaced it with <a href=\"https:\/\/github.com\/juanfont\/headscale\">Headscale<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Everything in Tailscale is open except the control server, so Headscale is a rewrite of that one piece: the normal Tailscale clients point at your box instead of theirs. That one ran for a few days and failed on the requirement directly, rather than on any defect of its own. I had deployed it into the cluster, so the coordination server every node needed in order to find the cluster was itself inside the cluster, behind the residential line, where reaching it from outside meant opening exactly the inbound path I had ruled out. A rented machine booting in Helsinki couldn&#8217;t get its first packet anywhere.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/www.zerotier.com\/\">ZeroTier<\/a> came next, on a suggestion from my professor, and held for about six weeks, the longest of the four that lost. It doesn&#8217;t use WireGuard at all, and it isn&#8217;t really a VPN in the sense the others are. It&#8217;s a virtual Ethernet switch: joined machines behave as though they&#8217;re plugged into the same LAN, and they find each other through root servers ZeroTier runs. Joins are one command, so rented nodes came up on it. What it didn&#8217;t do properly was remote access, and there were things I could not route through its tunnels. That is what took it out.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then I went the other way entirely and self-hosted <a href=\"https:\/\/www.wireguard.com\/\">WireGuard<\/a> on the router. WireGuard is the protocol most of the others are built on, and it&#8217;s only the tunnel, which was the appeal: it&#8217;s in the kernel, it&#8217;s declarative, it answers only authenticated peers, and it puts nobody else in the path.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That lasted under two days, and it failed on a design position rather than a bug. WireGuard does no peer discovery, deliberately: a peer is a public key and an endpoint that somebody typed in. That&#8217;s a virtue when the peer list is a fixed set of machines. It is fatal when the entire point of the system is that a machine which didn&#8217;t exist five minutes ago has to join by itself. Every rented node needed key material injected at provisioning time and a <a href=\"https:\/\/man7.org\/linux\/man-pages\/man5\/systemd.service.5.html\">one-shot unit<\/a> to bring the interface up before K3s could start, and the home line had to publish a stable endpoint whose address couldn&#8217;t go in the repository.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Tailscale is WireGuard plus a coordination server, which swaps public keys between machines and walks them through NAT so neither side needs an open inbound port. It solves discovery, and it solves it the one way I had spent weeks refusing: by letting a company run that server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The requirement that ruled out four of the five is the one I was slowest to accept, and accepting it cost me a position I had already defended in writing. Earlier I had turned down a small VPS on the grounds that it reintroduced somebody else&#8217;s computer into a setup meant to be self-hosted. Then I handed the same job to a company. Once somebody else&#8217;s computer was in the picture either way, a VPS running Headscale would only have added one more server for me to patch and keep online. It isn&#8217;t a flattering ending, but it bought something real. I&#8217;m not paying Tailscale anything, and if Cloudflare goes down again I can still reach my cluster from anywhere in the world.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Second answer: the guarantee moved onto the machine<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">All four of the operator&#8217;s own layers have something in common, and it took me a while to see it. The finalizer, the ownership labels, the launch deadlines and the collector each close a different race. Every one of them needs the operator process, the API server and the network alive at the same moment. That combination is the one most likely to fail together, and it is the combination a leaked server usually implies. So a fifth cluster-side layer would have improved the ordinary case and done nothing at all for the case that matters.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So the guarantee had to move off the cluster and onto the rented machine itself. What runs there is a dead man&#8217;s switch: it arms itself when the machine boots, and it destroys the machine when whichever of two deadlines comes first runs out.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The first clock is a time of day. Kubernetes keeps a record for every machine in the cluster, and the controller writes the deadline onto that machine&#8217;s own record, as an <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/overview\/working-with-objects\/annotations\/\">annotation<\/a>, which is a small piece of text attached to it. The agent reads its own <code class=\"\" data-line=\"\">Node<\/code> using the <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/architecture\/#kubelet\">kubelet<\/a> credential the machine already has, so it needs no account of its own. Kubernetes keeps that credential weak on purpose, and when I tried it, it couldn&#8217;t read any other node, couldn&#8217;t list nodes, and couldn&#8217;t even delete its own. The machine asks for its deadline, and the cluster never has to reach in and tell it. So nothing needs a way into a firewalled machine, and no new credential has to exist. A refreshed annotation can push this deadline later, but never past the second one.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The second clock doesn&#8217;t care what time it is. It starts when the agent starts and counts forward from there, and nothing can extend it. It uses <a href=\"https:\/\/pkg.go.dev\/time#hdr-Monotonic_Clocks\">Go&#8217;s monotonic clock<\/a>, which a change to the system time can&#8217;t move. Setting the clock back, by accident or on purpose, buys a machine nothing.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/teardown-coverage.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"452\" data-attachment-id=\"29308\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/teardown-coverage\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/teardown-coverage.png\" data-orig-size=\"1400,618\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}\" data-image-title=\"teardown-coverage\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/teardown-coverage-1024x452.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/teardown-coverage-1024x452.png\" alt=\"Five teardown layers against five failure modes. The four inside the operator cover only normal expiry. The fifth, on the rented machine, covers four of the five. The last column, where the node agent is gone too, is empty.\" class=\"wp-image-29308\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/teardown-coverage-1024x452.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/teardown-coverage-300x132.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/teardown-coverage-768x339.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/teardown-coverage.png 1400w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The agent fires if either clock has run out. That is the whole decision, and the function that makes it has no third case:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-go\" data-line=\"\">func fired(startedAt time.Time, maxLifetime time.Duration, wall *time.Time, now time.Time) (string, bool) {\n\tif now.Sub(startedAt) &gt;= maxLifetime {\n\t\treturn reasonMaxLifetimeElapsed, true\n\t}\n\tif wall != nil &amp;&amp; !now.Before(*wall) {\n\t\treturn reasonWallClockDeadline, true\n\t}\n\treturn &quot;&quot;, false\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A third case would let one clock speak for the other, and neither can be trusted alone. The time of day can be defeated by editing the deadline or moving the clock. The counter can&#8217;t express a lease that gets extended, only a fixed maximum. More to the point, the controller writes the annotation, and the controller is precisely the component whose failure this layer exists to survive. A machine that trusts the annotation to tell it when not to fire is trusting a dead process.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">horizon treats a node that can&#8217;t reach the cluster as dead, on purpose. It isn&#8217;t running scheduled work and it&#8217;s pure cost, so the switch simply fires, whether the cluster is down or just unreachable. The deadline always sits a margin beyond the renewal interval, and the cluster refuses a provider configuration where that margin is too small to be useful. The margin is what a missed renewal is allowed to cost before the switch fires.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When the switch fires, the delete keeps retrying: first after five seconds, then doubling, then once a minute for as long as the process is alive. The retry count is set to the largest 32-bit integer, which at that spacing is a few thousand years. Giving up would leave a server billing, and a server that is billing is the thing this code exists to prevent, so there is no failure branch worth taking.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The turn: the second clock was not running<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Either clock is enough on its own to destroy the machine, the one the controller keeps refreshing or the one the agent starts at boot.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;d tested that logic hard. It never occurred to me to test whether the second clock was running at all.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A leased node came up, joined, and reported Ready. It took work. Its watchdog had never started.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The cloud-init horizon writes is supposed to download the agent and install a unit to run it. But on NixOS that script runs with a PATH that misses the system profile. <a href=\"https:\/\/www.busybox.net\/about.html\">busybox<\/a> was supplying most of what the script reached for, and curl wasn&#8217;t there at all, so the download failed and the unit was never written. The rest of the script carried on as if it had worked.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nothing noticed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Neither Kubernetes nor horizon had an opinion. A node is <a href=\"https:\/\/kubernetes.io\/docs\/reference\/node\/node-status\/#condition\">Ready<\/a> when its kubelet tells the API server it&#8217;s healthy, which says nothing about whether the machine can still destroy itself, and the lease showed instances ready and a deadline in the future.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So for a while the guarantee ran on one clock, and the clock it lost was the one that exists for when the control plane is gone.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The fix in the image was one line of PATH, putting the real tools ahead of busybox on all four cloud-init stage units. The boot script had been looking up its tools in the wrong order. So most of what it asked for came back as a stripped-down stand-in instead of the real program, and it carried on regardless. It only failed where anyone could see it, at the one tool the stand-in has no version of.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The more important fix was in horizon, where the agent annotates its own node once the watchdog is armed and the controller turns that into a condition on the lease. Now the cluster can answer the question directly. Before, the only way to find out was to take a machine away and watch.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The same shape, the same week<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">That same week I hit the identical failure with different parts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A leased node came up Ready and schedulable. Pods scheduled onto it. Those pods could reach nothing at all: a probe pod on that node couldn&#8217;t resolve <code class=\"\" data-line=\"\">kubernetes.default.svc<\/code>, and couldn&#8217;t reach a pod on a home node, timing out after twelve seconds. From the other direction, a home worker pinging the leased node&#8217;s overlay address got a hundred percent packet loss.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The cluster reported the node healthy the entire time.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/node-ready.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"405\" data-attachment-id=\"29309\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/node-ready\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/node-ready.png\" data-orig-size=\"1400,554\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}\" data-image-title=\"node-ready\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/node-ready-1024x405.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/node-ready-1024x405.png\" alt=\"The node reported Ready, with one arrow down to the only thing Ready tested, that the kubelet reached the API server. The watchdog and the pod network sit either side with no arrow reaching them, and both were false.\" class=\"wp-image-29309\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/node-ready-1024x405.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/node-ready-300x119.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/node-ready-768x304.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/node-ready.png 1400w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The router&#8217;s firewall forwards a narrow list of ports from the overlay into the trusted zone, including the Kubernetes API port. So the kubelet on the rented machine reached the API server, registered, and kept its lease renewed, which was all the Ready condition asked for. But the pod network is a different interface with a different address range, and its traffic hit a drop rule a few lines below the allow.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Kubernetes checked the one path the firewall permitted, found it working, and reported what it found, without ever looking at the pod network.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The fix wasn&#8217;t on the rented machine. The three home nodes had to join the overlay themselves, and the pod network had to move onto it fleet-wide, so that every node in the cluster advertises an address from the same range and any peer is directly reachable from any other. That reverses two earlier decisions in the repository: one that pinned the home pod-network MTU on the explicit assumption that the home nodes had no overlay interface to bind to, and one that had removed the last home node from the overlay and concluded that no home node was a member any more.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The rollout had to run in two separate phases, every node onto the overlay first and then the pod network node by node, control plane last, because the intermediate state is the actual hazard. A migrated node advertises an overlay address while an unmigrated peer still advertises a LAN address, and the two directions never agree on a route. That asymmetry had already produced a pod network passing zero traffic between two nodes, down to the smallest packets. That earlier incident is the reason the phases are separate at all. Stopping halfway makes it permanent.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I didn&#8217;t trust the new setup until I&#8217;d checked by hand that a pod in Helsinki could reach the API server and talk directly to a pod at home.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The price is that two machines on the same switch, a metre apart, now encrypt their pod traffic and send it through a tunnel to reach each other. <a href=\"https:\/\/github.com\/flannel-io\/flannel\">Flannel<\/a> binds one interface per node, so there&#8217;s no configuration in which home-to-home traffic takes the LAN and home-to-rented traffic takes the tunnel. I measured the direct path between two home nodes at under a millisecond, so the overhead is small. But it&#8217;s still overhead paid on every packet for a machine that exists a few times a month.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Third answer: the guarantee reports itself<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Both failures had the same shape, and so did the fix. A guarantee that cannot be checked is a guarantee taken on faith, and faith is exactly what got proved wrong twice in a week.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So the agent annotates its own <code class=\"\" data-line=\"\">Node<\/code> once its watchdog is armed, and refreshes that annotation on every poll tick that does not fire the switch. The controller reads the annotation for every joined instance and raises a <code class=\"\" data-line=\"\">WatchdogArmed<\/code> condition on the lease. A condition is Kubernetes&#8217; standard way for an object to report a yes-or-no fact about itself. It&#8217;s true when every joined machine reports a fresh value, and false otherwise, with a warning naming the machines at fault. It shows up as a column in <code class=\"\" data-line=\"\">kubectl get capacityleases<\/code>, and because it&#8217;s an ordinary condition, <a href=\"https:\/\/github.com\/kubernetes\/kube-state-metrics\">kube-state-metrics<\/a> can turn it into a metric with a few lines of configuration. The cluster can alert on it without a line of code that knows anything about horizon.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It&#8217;s an annotation rather than a label because the value is a timestamp, and label values can&#8217;t contain the colons a timestamp needs. A machine is already allowed to update its own record, under the <a href=\"https:\/\/kubernetes.io\/docs\/reference\/access-authn-authz\/node\/\">node authorizer<\/a>, and that&#8217;s the same permission the deadline read depends on.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The condition is purely a report, so it doesn&#8217;t gate scheduling, block a lease, or trigger teardown. Losing the watchdog is a loss of the layer that survives the control plane being unreachable, which is a loss of defence in depth. It is not a loss of teardown, and wiring the report into the enforcement would create a dependency between two things whose independence is the entire design.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Because the agent keeps refreshing the annotation, a unit that keeps crashing, a script that never started, and an install that died before writing anything all look the same to the controller, which sees no fresh annotation and sets the condition to false.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Measuring it<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To find out whether any of it actually worked, I rented dozens of machines and drove them from a harness outside the cluster. It had to be outside, because the most interesting measurements meant killing the operator, and once the operator is dead, neither it nor <a href=\"https:\/\/prometheus.io\/\">Prometheus<\/a> can record anything about it. The harness polls the provider API directly every five seconds and records the exact instant a server disappears, which is the authoritative clock for teardown because it observes the disappearance instead of inferring it from a controller that may be dead. The harness reports everything the operator says about itself against that.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Every machine in those runs was released and independently verified gone at the provider. Every run ends with a row written only after the harness queries the provider and sees no server carrying that run&#8217;s label. No run needed manual intervention, including six that failed for unrelated reasons and five in which the operator was deliberately scaled to zero.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/predicted-window.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"507\" data-attachment-id=\"29310\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/predicted-window\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/predicted-window.png\" data-orig-size=\"1400,693\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}\" data-image-title=\"predicted-window\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/predicted-window-1024x507.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/predicted-window-1024x507.png\" alt=\"The window predicted before the run, between two and three minutes after the operator stopped, with the three firings drawn beneath it as an interval that falls inside it.\" class=\"wp-image-29310\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/predicted-window-1024x507.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/predicted-window-300x149.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/predicted-window-768x380.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/predicted-window.png 1400w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Where the controller had a working path, the operator&#8217;s recorded release time equalled the lease deadline to the second in every one of twenty-five runs, and the provider reported the server absent between zero and five seconds later, median three. That gap is at or below the harness&#8217;s own poll interval in every run, so the split between &#8220;the controller noticed&#8221; and &#8220;the machine went away&#8221; is not resolvable by this instrument. I&#8217;d designed the runs to measure that split. The honest answer is that it is smaller than I can see.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The result I care about is the arm where the control plane was removed. Three runs took a twenty-minute lease, waited for readiness, then scaled the operator to zero replicas. With the operator gone the wall-clock deadline stopped being refreshed, so the last value it had written was the one that stood. In all three runs the machine deleted itself five, thirteen and nine seconds after that deadline, and roughly fifteen minutes before the lease deadline it was supposedly running to. The harness&#8217;s own log confirms the operator came back only after the server was already gone, so nothing cluster-side can account for the deletion.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">No control plane took part in those three deletions. The machines destroyed themselves from a clock they were carrying.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This doesn&#8217;t prove the system can run without an operator, since both the deadline the node fired on and the token it deleted with were placed on the machine by the operator before it died. It only shows that teardown survives the operator going away.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The instruments misled too<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">My own tests fooled me twice more. Thirty boots split cleanly into two groups by how long they took to become ready. One group of values sat just above one minute, the other just above ninety seconds, and there was an empty stretch in the middle with nothing in it at all. Almost every value also landed within a second above a multiple of thirty seconds. A physical process that produces two groups with nothing between them is interesting, and I spent a while being interested.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The controller&#8217;s requeue interval, while those runs happened, was thirty seconds. It had no watch on <code class=\"\" data-line=\"\">Node<\/code> objects, so node readiness couldn&#8217;t reach the lease&#8217;s status between ticks. The split was the sampling grid.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/readiness-observed.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"681\" data-attachment-id=\"29311\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/readiness-observed\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/readiness-observed.png\" data-orig-size=\"1400,931\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;,&quot;alt&quot;:&quot;&quot;}\" data-image-title=\"readiness-observed\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/readiness-observed-1024x681.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/readiness-observed-1024x681.png\" alt=\"Thirty boots, with nineteen near sixty seconds and the rest near ninety. The empty stretch between them is twenty nine seconds wide, drawn beneath the thirty second polling interval of almost the same width.\" class=\"wp-image-29311\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/readiness-observed-1024x681.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/readiness-observed-300x200.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/readiness-observed-768x511.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/readiness-observed.png 1400w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The provider-side timings from the same runs do not land on that grid at all, which is what settles it: create latency is tight and forms a single group, and the gap from running to recorded ready is thirty-four to forty-five seconds in the fast group and sixty-two to sixty-nine in the slow one, with nothing between. So what I can defend is a range and not a number. The controller now wakes on <code class=\"\" data-line=\"\">Node<\/code> events and takes readiness from the node&#8217;s own timestamp, which fixes the instrument.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Concurrent boots<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The second one cost machines.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;d scheduled several measurement arms to run at once, on the correct reasoning that a provider which bills a whole hour per machine charges nothing extra for concurrency. What I hadn&#8217;t verified was that concurrency worked at all, because every boot until that point had been strictly sequential, which I confirmed afterwards by checking that no two runs overlapped.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Under concurrency, joins became unreliable in two different-looking ways. Some machines sat at created and never joined, while others joined and reached Ready, only for the earlier of the two to lose readiness half a minute later while the later one carried on.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Two deliberate concurrent boots and an SSH session gave the answer. Both machines reported the same overlay device identity, the same overlay address, and the same <a href=\"https:\/\/man7.org\/linux\/man-pages\/man5\/machine-id.5.html\"><code class=\"\" data-line=\"\">\/etc\/machine-id<\/code><\/a>. They weren&#8217;t two devices sharing an address, they were one device. The node image, a snapshot built from the bedrock repository, had baked a machine identity into itself, so every clone booted from it presented the same identity to the overlay&#8217;s coordination service. The later registration took the identity, and the earlier machine lost its route, which explains both signatures at once: lose the route before joining and the node never joins, lose it after joining and the node goes NotReady.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The image had booted dozens of times without a hint of the problem. In sequential tests each machine was gone before the next one took the same identity, so there was never anything to conflict with.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The fix is in bedrock&#8217;s image build and not in horizon: one provisioner that clears the machine identity and the overlay&#8217;s saved state immediately before the snapshot is taken, and then asserts both are cleared, so a build that fails to clear identity fails the build. The absence of that assertion is what let the defect hide for so long. I verified the fix against the exact scenario that had failed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What still cannot be seen<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The guarantee survives the failure of either clock, but not the failure of both. With the operator scaled to zero and the node agent stopped, two machines outlived their lease deadlines by thirteen minutes and were collected five and six seconds after I scaled the operator back up. The sweeper that collects an abandoned machine lives inside the operator, so removing the operator removes the sweeper. That is a real hole, and it&#8217;s where the layering stops.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Workload migration ships too, and naming one or more namespaces moves their workloads onto the leased nodes and restores them at teardown, with the saved placement written onto the workloads themselves so a restore survives the operator being removed entirely. There is also a replication mode that leaves the originals where they are and runs extra copies on the leased nodes until the lease ends, which works for Deployments but not StatefulSets, though neither mode was part of the measurement campaign.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So far every machine the finished version rented has been deleted within the limits I set before the first run, all fifty-five of them. That doesn&#8217;t promise anything about the next one, but it&#8217;s the best answer I have.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If I keep one thing from all of this, it&#8217;s that every failure here was a system telling the truth about something next to what I wanted to know. The deferred rollback handled the errors it was written for, the Ready condition described the API path, the lease reported a deadline for a machine that couldn&#8217;t enforce it, the sampling grid reported what it sampled, and dozens of sequential boots reported a working image. In each case the fix was to add a signal that checks the thing itself, instead of inferring it from something next to it.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">&#8220;If some host holding a lease for this file is unreachable, the delay continues until the lease expires.&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Cary G. Gray and David R. Cheriton, <a href=\"https:\/\/www.cs.cmu.edu\/~15712\/papers\/gray89.pdf\">Leases: An Efficient Fault-Tolerant Mechanism for Distributed File Cache Consistency<\/a>, 1989<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">References<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">[1] L. Bainbridge, &#8220;Ironies of automation,&#8221; Automatica, vol. 19, no. 6, pp. 775-779, 1983. [Online]. Available: <a href=\"https:\/\/ckrybus.com\/static\/papers\/Bainbridge_1983_Automatica.pdf\">https:\/\/ckrybus.com\/static\/papers\/Bainbridge_1983_Automatica.pdf<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[2] The Kubernetes Authors, &#8220;Operator pattern,&#8221; Kubernetes Documentation. [Online]. Available: <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/extend-kubernetes\/operator\/\">https:\/\/kubernetes.io\/docs\/concepts\/extend-kubernetes\/operator\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[3] NixOS contributors, &#8220;Nix &amp; NixOS | Declarative builds and deployments.&#8221; [Online]. Available: <a href=\"https:\/\/nixos.org\/\">https:\/\/nixos.org\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[4] The K3s Project Authors, &#8220;K3s &#8211; Lightweight Kubernetes.&#8221; [Online]. Available: <a href=\"https:\/\/docs.k3s.io\/\">https:\/\/docs.k3s.io\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[5] The Flux authors, &#8220;Flux &#8211; the GitOps family of projects.&#8221; [Online]. Available: <a href=\"https:\/\/fluxcd.io\/\">https:\/\/fluxcd.io\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[6] HashiCorp, &#8220;Terraform,&#8221; HashiCorp Developer. [Online]. Available: <a href=\"https:\/\/developer.hashicorp.com\/terraform\">https:\/\/developer.hashicorp.com\/terraform<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[7] Canonical Group Ltd, &#8220;Cloud-init documentation.&#8221; [Online]. Available: <a href=\"https:\/\/docs.cloud-init.io\/\">https:\/\/docs.cloud-init.io\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[8] Tailscale, &#8220;What is Tailscale?&#8221; Tailscale Docs. [Online]. Available: <a href=\"https:\/\/tailscale.com\/docs\/concepts\/what-is-tailscale\">https:\/\/tailscale.com\/docs\/concepts\/what-is-tailscale<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[9] Hetzner Online GmbH, &#8220;FAQ,&#8221; Hetzner Docs, Cloud billing. [Online]. Available: <a href=\"https:\/\/docs.hetzner.com\/cloud\/billing\/faq\/\">https:\/\/docs.hetzner.com\/cloud\/billing\/faq\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[10] Amazon Web Services, &#8220;Change instance initiated shutdown behavior,&#8221; Amazon Elastic Compute Cloud User Guide. [Online]. Available: <a href=\"https:\/\/docs.aws.amazon.com\/AWSEC2\/latest\/UserGuide\/Using_ChangingInstanceInitiatedShutdownBehavior.html\">https:\/\/docs.aws.amazon.com\/AWSEC2\/latest\/UserGuide\/Using_ChangingInstanceInitiatedShutdownBehavior.html<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[11] Amazon Web Services, &#8220;Terminate Amazon EC2 instances,&#8221; Amazon Elastic Compute Cloud User Guide. [Online]. Available: <a href=\"https:\/\/docs.aws.amazon.com\/AWSEC2\/latest\/UserGuide\/terminating-instances.html\">https:\/\/docs.aws.amazon.com\/AWSEC2\/latest\/UserGuide\/terminating-instances.html<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[12] Google, &#8220;Limit the run time of a VM,&#8221; Compute Engine, Google Cloud Documentation. [Online]. Available: <a href=\"https:\/\/cloud.google.com\/compute\/docs\/instances\/limit-vm-runtime\">https:\/\/cloud.google.com\/compute\/docs\/instances\/limit-vm-runtime<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[13] Hetzner Online GmbH, &#8220;Generating an API token,&#8221; Hetzner Docs, Cloud API. [Online]. Available: <a href=\"https:\/\/docs.hetzner.com\/cloud\/api\/getting-started\/generating-api-token\/\">https:\/\/docs.hetzner.com\/cloud\/api\/getting-started\/generating-api-token\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[14] The Kubernetes Authors, &#8220;Assigning Pods to Nodes,&#8221; Kubernetes Documentation, sec. Affinity and anti-affinity. [Online]. Available: <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/scheduling-eviction\/assign-pod-node\/#affinity-and-anti-affinity\">https:\/\/kubernetes.io\/docs\/concepts\/scheduling-eviction\/assign-pod-node\/#affinity-and-anti-affinity<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[15] The Go Authors, &#8220;Defer statements,&#8221; The Go Programming Language Specification, version go1.27, 2026. [Online]. Available: <a href=\"https:\/\/go.dev\/ref\/spec\">https:\/\/go.dev\/ref\/spec<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[16] The Kubernetes Authors, &#8220;Taints and Tolerations,&#8221; Kubernetes Documentation. [Online]. Available: <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/scheduling-eviction\/taint-and-toleration\/\">https:\/\/kubernetes.io\/docs\/concepts\/scheduling-eviction\/taint-and-toleration\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[17] Linux man-pages project, &#8220;signal(7): overview of signals,&#8221; release 6.19, 2026. [Online]. Available: <a href=\"https:\/\/man7.org\/linux\/man-pages\/man7\/signal.7.html\">https:\/\/man7.org\/linux\/man-pages\/man7\/signal.7.html<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[18] The Kubernetes Authors, &#8220;Custom Resources,&#8221; Kubernetes Documentation. [Online]. Available: <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/extend-kubernetes\/api-extension\/custom-resources\/\">https:\/\/kubernetes.io\/docs\/concepts\/extend-kubernetes\/api-extension\/custom-resources\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[19] The Kubernetes Authors, &#8220;Finalizers,&#8221; Kubernetes Documentation. [Online]. Available: <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/overview\/working-with-objects\/finalizers\/\">https:\/\/kubernetes.io\/docs\/concepts\/overview\/working-with-objects\/finalizers\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[20] Amazon Web Services, &#8220;Karpenter: Just-in-time Nodes for Any Kubernetes Cluster.&#8221; [Online]. Available: <a href=\"https:\/\/karpenter.sh\/\">https:\/\/karpenter.sh\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[21] P. Srisuresh and K. Egevang, &#8220;Traditional IP network address translator (Traditional NAT),&#8221; RFC 3022, IETF, Jan. 2001. [Online]. Available: <a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc3022.html\">https:\/\/www.rfc-editor.org\/rfc\/rfc3022.html<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[22] D. Anderson, &#8220;How NAT traversal works,&#8221; Tailscale, 2020. [Online]. Available: <a href=\"https:\/\/tailscale.com\/blog\/how-nat-traversal-works\">https:\/\/tailscale.com\/blog\/how-nat-traversal-works<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[23] The netfilter project, &#8220;The netfilter.org &#8216;nftables&#8217; project.&#8221; [Online]. Available: <a href=\"https:\/\/netfilter.org\/projects\/nftables\/index.html\">https:\/\/netfilter.org\/projects\/nftables\/index.html<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[24] Internet Systems Consortium, &#8220;Kea Administrator Reference Manual,&#8221; Kea 3.2.0. [Online]. Available: <a href=\"https:\/\/kea.readthedocs.io\/en\/stable\/\">https:\/\/kea.readthedocs.io\/en\/stable\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[25] AdGuard, &#8220;AdGuard Home: Network-wide ads &amp; trackers blocking DNS server.&#8221; [Online]. Available: <a href=\"https:\/\/github.com\/AdguardTeam\/AdGuardHome\">https:\/\/github.com\/AdguardTeam\/AdGuardHome<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[26] NetBird, &#8220;NetBird &#8211; Open Source Zero Trust Networking.&#8221; [Online]. Available: <a href=\"https:\/\/netbird.io\/\">https:\/\/netbird.io\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[27] The Helm Authors, &#8220;Helm: The package manager for Kubernetes.&#8221; [Online]. Available: <a href=\"https:\/\/helm.sh\/\">https:\/\/helm.sh\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[28] J. Font, &#8220;headscale: An open source, self-hosted implementation of the Tailscale control server.&#8221; [Online]. Available: <a href=\"https:\/\/github.com\/juanfont\/headscale\">https:\/\/github.com\/juanfont\/headscale<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[29] ZeroTier, Inc., &#8220;ZeroTier | Next-Generation Connectivity and Cybersecurity.&#8221; [Online]. Available: <a href=\"https:\/\/www.zerotier.com\/\">https:\/\/www.zerotier.com\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[30] J. A. Donenfeld, &#8220;WireGuard: fast, modern, secure VPN tunnel.&#8221; [Online]. Available: <a href=\"https:\/\/www.wireguard.com\/\">https:\/\/www.wireguard.com\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[31] The systemd project, &#8220;systemd.service(5): Service unit configuration,&#8221; systemd 261. [Online]. Available: <a href=\"https:\/\/man7.org\/linux\/man-pages\/man5\/systemd.service.5.html\">https:\/\/man7.org\/linux\/man-pages\/man5\/systemd.service.5.html<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[32] The Kubernetes Authors, &#8220;Annotations,&#8221; Kubernetes Documentation. [Online]. Available: <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/overview\/working-with-objects\/annotations\/\">https:\/\/kubernetes.io\/docs\/concepts\/overview\/working-with-objects\/annotations\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[33] The Kubernetes Authors, &#8220;Cluster Architecture,&#8221; Kubernetes Documentation, sec. kubelet. [Online]. Available: <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/architecture\/#kubelet\">https:\/\/kubernetes.io\/docs\/concepts\/architecture\/#kubelet<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[34] The Go Authors, &#8220;time package,&#8221; Go Packages, sec. Monotonic Clocks. [Online]. Available: <a href=\"https:\/\/pkg.go.dev\/time#hdr-Monotonic_Clocks\">https:\/\/pkg.go.dev\/time#hdr-Monotonic_Clocks<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[35] The BusyBox project, &#8220;BusyBox: The Swiss Army Knife of Embedded Linux.&#8221; [Online]. Available: <a href=\"https:\/\/www.busybox.net\/about.html\">https:\/\/www.busybox.net\/about.html<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[36] The Kubernetes Authors, &#8220;Node Status,&#8221; Kubernetes Documentation, sec. Conditions. [Online]. Available: <a href=\"https:\/\/kubernetes.io\/docs\/reference\/node\/node-status\/#condition\">https:\/\/kubernetes.io\/docs\/reference\/node\/node-status\/#condition<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[37] The flannel authors, &#8220;flannel: flannel is a network fabric for containers, designed for Kubernetes.&#8221; [Online]. Available: <a href=\"https:\/\/github.com\/flannel-io\/flannel\">https:\/\/github.com\/flannel-io\/flannel<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[38] The Kubernetes Authors, &#8220;kube-state-metrics: Add-on agent to generate and expose cluster-level metrics.&#8221; [Online]. Available: <a href=\"https:\/\/github.com\/kubernetes\/kube-state-metrics\">https:\/\/github.com\/kubernetes\/kube-state-metrics<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[39] The Kubernetes Authors, &#8220;Using Node Authorization,&#8221; Kubernetes Documentation. [Online]. Available: <a href=\"https:\/\/kubernetes.io\/docs\/reference\/access-authn-authz\/node\/\">https:\/\/kubernetes.io\/docs\/reference\/access-authn-authz\/node\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[40] The Prometheus Authors, &#8220;Prometheus &#8211; Monitoring system &amp; time series database.&#8221; [Online]. Available: <a href=\"https:\/\/prometheus.io\/\">https:\/\/prometheus.io\/<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[41] The systemd project, &#8220;machine-id(5): Local machine ID configuration file,&#8221; systemd 261. [Online]. Available: <a href=\"https:\/\/man7.org\/linux\/man-pages\/man5\/machine-id.5.html\">https:\/\/man7.org\/linux\/man-pages\/man5\/machine-id.5.html<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[42] C. G. Gray and D. R. Cheriton, &#8220;Leases: An efficient fault-tolerant mechanism for distributed file cache consistency,&#8221; in Proc. 12th ACM Symp. Operating Systems Principles (SOSP), 1989, pp. 202-210. [Online]. Available: <a href=\"https:\/\/www.cs.cmu.edu\/~15712\/papers\/gray89.pdf\">https:\/\/www.cs.cmu.edu\/~15712\/papers\/gray89.pdf<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[43] L. Walz, &#8220;bedrock: Bare-metal Kubernetes homelab on NixOS, reconciled with Flux GitOps,&#8221; 2025. [Online]. Available: <a href=\"https:\/\/github.com\/lucawalz\/bedrock\">https:\/\/github.com\/lucawalz\/bedrock<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[44] L. Walz, &#8220;horizon: Kubernetes operator for job-shaped burst capacity on Hetzner Cloud, torn down by construction,&#8221; 2026. [Online]. Available: <a href=\"https:\/\/github.com\/lucawalz\/horizon\">https:\/\/github.com\/lucawalz\/horizon<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">[45] E. Ngo, N. Brocker, B. Reuter, and M. Koch, &#8220;Migrating from Heroku to Hetzner: Achieving scalability with Docker, Kubernetes and Rancher,&#8221; Computer Science Blog @ HdM Stuttgart, Feb. 2021. [Online]. Available: <a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2021\/02\/27\/migrating-from-heroku-to-hetzner-achieving-scalability-with-docker-kubernetes-and-rancher\/\">https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2021\/02\/27\/migrating-from-heroku-to-hetzner-achieving-scalability-with-docker-kubernetes-and-rancher\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Renting a cloud machine is a single API call. Giving it back reliably took four months. This post follows horizon through three rewrites, the teardown layers that survived them, and the measurements that show whether the guarantee actually holds.<\/p>\n","protected":false},"author":1238,"featured_media":29317,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false,"_ppma_block_editor_authors":""},"categories":[120,659,22,21],"tags":[7,332,421,338,154,1267,1266],"ppma_author":[1065],"class_list":["post-29305","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cloud-technologies","category-devops","category-student-projects","category-system-architecture","tag-cloud","tag-go","tag-hetzner","tag-k3s","tag-kubernetes","tag-nixos","tag-tailscale"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"Renting a cloud machine is one API call. Giving it back reliably took four months. How horizon makes a Kubernetes teardown guarantee observable.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Luca Walz\"\/>\n\t<meta name=\"keywords\" content=\"cloud,go,hetzner,k3s,kubernetes,nixos,tailscale\" \/>\n\t<link rel=\"canonical\" href=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"Computer Science Blog\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"Leased cloud machines: making a teardown guarantee observable\" \/>\n\t\t<meta property=\"og:description\" content=\"Renting a cloud machine is one API call. Giving it back reliably took four months. How horizon makes a Kubernetes teardown guarantee observable.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/lighthouse.png\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/lighthouse.png\" \/>\n\t\t<meta property=\"og:image:width\" content=\"1672\" \/>\n\t\t<meta property=\"og:image:height\" content=\"941\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2026-09-12T08:02:01+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2026-09-12T08:12:12+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n\t\t<meta name=\"twitter:title\" content=\"Leased cloud machines: making a teardown guarantee observable\" \/>\n\t\t<meta name=\"twitter:description\" content=\"Renting a cloud machine is one API call. Giving it back reliably took four months. How horizon makes a Kubernetes teardown guarantee observable.\" \/>\n\t\t<meta name=\"twitter:image\" content=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/lighthouse.png\" \/>\n\t\t<script type=\"application\/ld+json\" class=\"aioseo-schema\">\n\t\t\t{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/2026\\\/09\\\/12\\\/leased-cloud-machines-teardown-guarantee\\\/#article\",\"name\":\"Leased cloud machines: making a teardown guarantee observable\",\"headline\":\"Leased cloud machines: making a teardown guarantee observable\",\"author\":{\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/author\\\/luca_walz\\\/#author\"},\"publisher\":{\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/#organization\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/lighthouse-banner-1-scaled.png\",\"width\":2560,\"height\":427},\"datePublished\":\"2026-09-12T10:02:01+02:00\",\"dateModified\":\"2026-09-12T10:12:12+02:00\",\"inLanguage\":\"en-US\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/2026\\\/09\\\/12\\\/leased-cloud-machines-teardown-guarantee\\\/#webpage\"},\"isPartOf\":{\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/2026\\\/09\\\/12\\\/leased-cloud-machines-teardown-guarantee\\\/#webpage\"},\"articleSection\":\"Cloud Technologies, DevOps, Student Projects, System Architecture, Cloud, go, Hetzner, k3s, Kubernetes, NixOS, Tailscale, Luca Walz\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/2026\\\/09\\\/12\\\/leased-cloud-machines-teardown-guarantee\\\/#breadcrumblist\",\"itemListElement\":[{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de#listItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/category\\\/system-designs\\\/#listItem\",\"name\":\"System Designs\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/category\\\/system-designs\\\/#listItem\",\"position\":2,\"name\":\"System Designs\",\"item\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/category\\\/system-designs\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/category\\\/system-designs\\\/system-architecture\\\/#listItem\",\"name\":\"System Architecture\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de#listItem\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/category\\\/system-designs\\\/system-architecture\\\/#listItem\",\"position\":3,\"name\":\"System Architecture\",\"item\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/category\\\/system-designs\\\/system-architecture\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/2026\\\/09\\\/12\\\/leased-cloud-machines-teardown-guarantee\\\/#listItem\",\"name\":\"Leased cloud machines: making a teardown guarantee observable\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/category\\\/system-designs\\\/#listItem\",\"name\":\"System Designs\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/2026\\\/09\\\/12\\\/leased-cloud-machines-teardown-guarantee\\\/#listItem\",\"position\":4,\"name\":\"Leased cloud machines: making a teardown guarantee observable\",\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/category\\\/system-designs\\\/system-architecture\\\/#listItem\",\"name\":\"System Architecture\"}}]},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/#organization\",\"name\":\"Computer Science Blog @ HdM Stuttgart\",\"description\":\"on computer science and media topics\",\"url\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/author\\\/luca_walz\\\/#author\",\"url\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/author\\\/luca_walz\\\/\",\"name\":\"Luca Walz\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/2026\\\/09\\\/12\\\/leased-cloud-machines-teardown-guarantee\\\/#authorImage\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/e49f866dedd3a9424a6180c9a8f4c74fe47f46e9243ef5112072eaa96650c046?s=96&d=mm&r=g\",\"width\":96,\"height\":96,\"caption\":\"Luca Walz\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/2026\\\/09\\\/12\\\/leased-cloud-machines-teardown-guarantee\\\/#webpage\",\"url\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/2026\\\/09\\\/12\\\/leased-cloud-machines-teardown-guarantee\\\/\",\"name\":\"Leased cloud machines: making a teardown guarantee observable\",\"description\":\"Renting a cloud machine is one API call. Giving it back reliably took four months. How horizon makes a Kubernetes teardown guarantee observable.\",\"inLanguage\":\"en-US\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/#website\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/2026\\\/09\\\/12\\\/leased-cloud-machines-teardown-guarantee\\\/#breadcrumblist\"},\"author\":{\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/author\\\/luca_walz\\\/#author\"},\"creator\":{\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/author\\\/luca_walz\\\/#author\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/lighthouse-banner-1-scaled.png\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/2026\\\/09\\\/12\\\/leased-cloud-machines-teardown-guarantee\\\/#mainImage\",\"width\":2560,\"height\":427},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/index.php\\\/2026\\\/09\\\/12\\\/leased-cloud-machines-teardown-guarantee\\\/#mainImage\"},\"datePublished\":\"2026-09-12T10:02:01+02:00\",\"dateModified\":\"2026-09-12T10:12:12+02:00\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/#website\",\"url\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/\",\"name\":\"Computer Science Blog @ HdM Stuttgart\",\"description\":\"on computer science and media topics\",\"inLanguage\":\"en-US\",\"publisher\":{\"@id\":\"https:\\\/\\\/blog.mi.hdm-stuttgart.de\\\/#organization\"}}]}\n\t\t<\/script>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"Leased cloud machines: making a teardown guarantee observable","description":"Renting a cloud machine is one API call. Giving it back reliably took four months. How horizon makes a Kubernetes teardown guarantee observable.","canonical_url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/","robots":"max-image-preview:large","keywords":"cloud,go,hetzner,k3s,kubernetes,nixos,tailscale","webmasterTools":{"miscellaneous":""},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/#article","name":"Leased cloud machines: making a teardown guarantee observable","headline":"Leased cloud machines: making a teardown guarantee observable","author":{"@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/author\/luca_walz\/#author"},"publisher":{"@id":"https:\/\/blog.mi.hdm-stuttgart.de\/#organization"},"image":{"@type":"ImageObject","url":"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/lighthouse-banner-1-scaled.png","width":2560,"height":427},"datePublished":"2026-09-12T10:02:01+02:00","dateModified":"2026-09-12T10:12:12+02:00","inLanguage":"en-US","mainEntityOfPage":{"@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/#webpage"},"isPartOf":{"@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/#webpage"},"articleSection":"Cloud Technologies, DevOps, Student Projects, System Architecture, Cloud, go, Hetzner, k3s, Kubernetes, NixOS, Tailscale, Luca Walz"},{"@type":"BreadcrumbList","@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/#breadcrumblist","itemListElement":[{"@type":"ListItem","@id":"https:\/\/blog.mi.hdm-stuttgart.de#listItem","position":1,"name":"Home","item":"https:\/\/blog.mi.hdm-stuttgart.de","nextItem":{"@type":"ListItem","@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/#listItem","name":"System Designs"}},{"@type":"ListItem","@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/#listItem","position":2,"name":"System Designs","item":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/","nextItem":{"@type":"ListItem","@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/system-architecture\/#listItem","name":"System Architecture"},"previousItem":{"@type":"ListItem","@id":"https:\/\/blog.mi.hdm-stuttgart.de#listItem","name":"Home"}},{"@type":"ListItem","@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/system-architecture\/#listItem","position":3,"name":"System Architecture","item":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/system-architecture\/","nextItem":{"@type":"ListItem","@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/#listItem","name":"Leased cloud machines: making a teardown guarantee observable"},"previousItem":{"@type":"ListItem","@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/#listItem","name":"System Designs"}},{"@type":"ListItem","@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/#listItem","position":4,"name":"Leased cloud machines: making a teardown guarantee observable","previousItem":{"@type":"ListItem","@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/system-architecture\/#listItem","name":"System Architecture"}}]},{"@type":"Organization","@id":"https:\/\/blog.mi.hdm-stuttgart.de\/#organization","name":"Computer Science Blog @ HdM Stuttgart","description":"on computer science and media topics","url":"https:\/\/blog.mi.hdm-stuttgart.de\/"},{"@type":"Person","@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/author\/luca_walz\/#author","url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/author\/luca_walz\/","name":"Luca Walz","image":{"@type":"ImageObject","@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/#authorImage","url":"https:\/\/secure.gravatar.com\/avatar\/e49f866dedd3a9424a6180c9a8f4c74fe47f46e9243ef5112072eaa96650c046?s=96&d=mm&r=g","width":96,"height":96,"caption":"Luca Walz"}},{"@type":"WebPage","@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/#webpage","url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/","name":"Leased cloud machines: making a teardown guarantee observable","description":"Renting a cloud machine is one API call. Giving it back reliably took four months. How horizon makes a Kubernetes teardown guarantee observable.","inLanguage":"en-US","isPartOf":{"@id":"https:\/\/blog.mi.hdm-stuttgart.de\/#website"},"breadcrumb":{"@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/#breadcrumblist"},"author":{"@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/author\/luca_walz\/#author"},"creator":{"@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/author\/luca_walz\/#author"},"image":{"@type":"ImageObject","url":"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/lighthouse-banner-1-scaled.png","@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/#mainImage","width":2560,"height":427},"primaryImageOfPage":{"@id":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/#mainImage"},"datePublished":"2026-09-12T10:02:01+02:00","dateModified":"2026-09-12T10:12:12+02:00"},{"@type":"WebSite","@id":"https:\/\/blog.mi.hdm-stuttgart.de\/#website","url":"https:\/\/blog.mi.hdm-stuttgart.de\/","name":"Computer Science Blog @ HdM Stuttgart","description":"on computer science and media topics","inLanguage":"en-US","publisher":{"@id":"https:\/\/blog.mi.hdm-stuttgart.de\/#organization"}}]},"og:locale":"en_US","og:site_name":"Computer Science Blog","og:type":"article","og:title":"Leased cloud machines: making a teardown guarantee observable","og:description":"Renting a cloud machine is one API call. Giving it back reliably took four months. How horizon makes a Kubernetes teardown guarantee observable.","og:url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/","og:image":"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/lighthouse.png","og:image:secure_url":"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/lighthouse.png","og:image:width":1672,"og:image:height":941,"article:published_time":"2026-09-12T08:02:01+00:00","article:modified_time":"2026-09-12T08:12:12+00:00","twitter:card":"summary_large_image","twitter:title":"Leased cloud machines: making a teardown guarantee observable","twitter:description":"Renting a cloud machine is one API call. Giving it back reliably took four months. How horizon makes a Kubernetes teardown guarantee observable.","twitter:image":"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/lighthouse.png"},"aioseo_meta_data":{"post_id":"29305","title":"#post_title","description":"Renting a cloud machine is one API call. Giving it back reliably took four months. How horizon makes a Kubernetes teardown guarantee observable.","keywords":null,"keyphrases":{"focus":{"keyphrase":"teardown","score":0,"analysis":[]},"additional":[]},"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"custom_image","og_image_url":"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/lighthouse.png","og_image_width":"1672","og_image_height":"941","og_image_custom_url":"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/lighthouse.png","og_image_custom_fields":null,"og_video":"","og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":true,"twitter_card":"summary_large_image","twitter_image_type":"custom_image","twitter_image_url":null,"twitter_image_custom_url":"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/lighthouse.png","twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"Article","isEnabled":true},"graphs":[]},"schema_type":"default","schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":"-1","robots_max_videopreview":"-1","robots_max_imagepreview":"large","priority":null,"frequency":"default","local_seo":null,"breadcrumb_settings":null,"limit_modified_date":false,"ai":{"faqs":[],"keyPoints":[],"schemas":[],"titles":[],"descriptions":[],"socialPosts":{"email":{"subject":"","preview":"","content":""},"linkedin":[],"twitter":[],"facebook":[],"instagram":[]}},"created":"2026-09-12 06:54:17","updated":"2026-09-12 08:12:12","seo_analyzer_scan_date":null,"focus_keyword":"teardown","additional_keywords":null,"truseo_locale":null},"aioseo_breadcrumb":"<div class=\"aioseo-breadcrumbs\"><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/blog.mi.hdm-stuttgart.de\" title=\"Home\">Home<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">&raquo;<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/\" title=\"System Designs\">System Designs<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">&raquo;<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/system-architecture\/\" title=\"System Architecture\">System Architecture<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">&raquo;<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\tLeased cloud machines: making a teardown guarantee observable\n\t\t<\/span><\/div>","aioseo_breadcrumb_json":[{"label":"Home","link":"https:\/\/blog.mi.hdm-stuttgart.de"},{"label":"System Designs","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/"},{"label":"System Architecture","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/system-architecture\/"},{"label":"Leased cloud machines: making a teardown guarantee observable","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/09\/12\/leased-cloud-machines-teardown-guarantee\/"}],"jetpack-related-posts":[{"id":1017,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2016\/07\/25\/machine-learning-in-secure-systems\/","url_meta":{"origin":29305,"position":0},"title":"Machine Learning in secure systems","author":"Claudius Messerschmidt","date":"25. July 2016","format":false,"excerpt":"Sadly today's security systems often be hacked and sensitive informations get stolen. To protect a company against cyber-attacks security experts define a \"rule set\" to detect and prevent any attack. This \u201canalyst-driven solutions\u201d are build up from human experts with their domain knowledge. This knowledge is based on experiences and\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2016\/07\/Machine_learning_SeSy_robot_landscape.jpg?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2016\/07\/Machine_learning_SeSy_robot_landscape.jpg?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2016\/07\/Machine_learning_SeSy_robot_landscape.jpg?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2016\/07\/Machine_learning_SeSy_robot_landscape.jpg?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":27565,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2025\/02\/28\/scaling-an-ai-transcription-model-as-a-service\/","url_meta":{"origin":29305,"position":1},"title":"Scaling an AI Transcription Model as a Service","author":"ns144","date":"28. February 2025","format":false,"excerpt":"Ton-Texter is a Software as a service solution that delivers state of the art transcription performance. In this blog post, we will explore how we have improved its scalability to handle high demand.","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/Featured_Image01.jpg?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/Featured_Image01.jpg?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/Featured_Image01.jpg?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/Featured_Image01.jpg?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/Featured_Image01.jpg?resize=1050%2C600&ssl=1 3x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/Featured_Image01.jpg?resize=1400%2C800&ssl=1 4x"},"classes":[]},{"id":308,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2016\/03\/10\/more-is-always-better-building-a-cluster-with-pies\/","url_meta":{"origin":29305,"position":2},"title":"More is always better: building a cluster with Pies","author":"Benjamin Binder","date":"10. March 2016","format":false,"excerpt":"So you have written the uber-pro-web-application with a bazillion of active users. But your requests start to get out of hand and the Raspberry Pi under your desk can't handle all the pressure on its own. Finally,\u00a0the time for rapid expansion has come! If you have already containerized your application,\u2026","rel":"","context":"In &quot;Scalable Systems&quot;","block_context":{"text":"Scalable Systems","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/scalable-systems\/"},"img":{"alt_text":"Raspberry Pi 2","src":"https:\/\/upload.wikimedia.org\/wikipedia\/commons\/3\/3d\/Raspberry_PI.jpeg","width":350,"height":200,"srcset":"https:\/\/upload.wikimedia.org\/wikipedia\/commons\/3\/3d\/Raspberry_PI.jpeg 1x, https:\/\/upload.wikimedia.org\/wikipedia\/commons\/3\/3d\/Raspberry_PI.jpeg 1.5x, https:\/\/upload.wikimedia.org\/wikipedia\/commons\/3\/3d\/Raspberry_PI.jpeg 2x, https:\/\/upload.wikimedia.org\/wikipedia\/commons\/3\/3d\/Raspberry_PI.jpeg 3x, https:\/\/upload.wikimedia.org\/wikipedia\/commons\/3\/3d\/Raspberry_PI.jpeg 4x"},"classes":[]},{"id":2615,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2017\/08\/27\/fooling-the-intelligence\/","url_meta":{"origin":29305,"position":3},"title":"FOOLING THE INTELLIGENCE","author":"Jonas Miederer","date":"27. August 2017","format":false,"excerpt":"Adversarial machine learning and its dangers The world is led by machines, humans are subjected to the robot\u2019s rule. Omniscient computer systems hold the control of the world. The newest technology has outpaced human knowledge, while the mankind is powerless in the face of the stronger, faster, better and almighty\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2017\/08\/AAEAAQAAAAAAAAxmAAAAJDcyNzkyZjkzLTUzZTEtNGU1ZS04OWYxLWU4NDU5Y2QxOTRjYQ.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2017\/08\/AAEAAQAAAAAAAAxmAAAAJDcyNzkyZjkzLTUzZTEtNGU1ZS04OWYxLWU4NDU5Y2QxOTRjYQ.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2017\/08\/AAEAAQAAAAAAAAxmAAAAJDcyNzkyZjkzLTUzZTEtNGU1ZS04OWYxLWU4NDU5Y2QxOTRjYQ.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2017\/08\/AAEAAQAAAAAAAAxmAAAAJDcyNzkyZjkzLTUzZTEtNGU1ZS04OWYxLWU4NDU5Y2QxOTRjYQ.png?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2017\/08\/AAEAAQAAAAAAAAxmAAAAJDcyNzkyZjkzLTUzZTEtNGU1ZS04OWYxLWU4NDU5Y2QxOTRjYQ.png?resize=1050%2C600&ssl=1 3x"},"classes":[]},{"id":149,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2016\/01\/03\/more-docker-more-power-part-1-setting-up-virtualbox\/","url_meta":{"origin":29305,"position":4},"title":"More docker = more power? \u2013 Part 1: Setting up VirtualBox","author":"Tobias Schneider","date":"3. January 2016","format":false,"excerpt":"This series of blogposts will focus on the effects on response times when performing different tasks running on a variable number of docker containers in a virtual machine. What will be the performance differences running a small or large number of containers on the same machine? These posts will function\u2026","rel":"","context":"In &quot;System Designs&quot;","block_context":{"text":"System Designs","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2016\/01\/1429543497dockerimg.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2016\/01\/1429543497dockerimg.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2016\/01\/1429543497dockerimg.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2016\/01\/1429543497dockerimg.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":12751,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2021\/02\/28\/industry-4-0-real-time-data-visualization\/","url_meta":{"origin":29305,"position":5},"title":"Industry 4.0 &#8211; Real time data visualization","author":"Philip Betzler","date":"28. February 2021","format":false,"excerpt":"As part of the lecture \"System Engineering and Management\", we worked on a project in cooperation with IBM to visualize Industry 4.0 data in real time using a configuration-based approach. This project aims to avoid needing a web developer every time there is a modification in the data.","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/03\/config_type_harting_mica.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/03\/config_type_harting_mica.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/03\/config_type_harting_mica.png?resize=525%2C300&ssl=1 1.5x"},"classes":[]}],"jetpack_sharing_enabled":true,"authors":[{"term_id":1065,"user_id":1238,"is_guest":0,"slug":"luca_walz","display_name":"Luca Walz","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/e49f866dedd3a9424a6180c9a8f4c74fe47f46e9243ef5112072eaa96650c046?s=96&d=mm&r=g","author_category":"","user_url":"","last_name":"Walz","first_name":"Luca","job_title":"","description":""}],"jetpack_featured_media_url":"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/09\/lighthouse-banner-1-scaled.png","_links":{"self":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/29305","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/users\/1238"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/comments?post=29305"}],"version-history":[{"count":7,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/29305\/revisions"}],"predecessor-version":[{"id":29321,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/29305\/revisions\/29321"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/media\/29317"}],"wp:attachment":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/media?parent=29305"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/categories?post=29305"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/tags?post=29305"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/ppma_author?post=29305"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}