AWS vs GCP vs Hetzner: Six Clouds, One Kubernetes Benchmark
On this page
Yesterday we published UpCloud vs OVH vs DigitalOcean, three identical clusters under one in-cluster suite: k6 HTTP load with SLO thresholds, iperf3 across nodes, fio and sysbench on every worker. The obvious question arrived within hours: fine, but what about the clouds most people actually run on?
So we ran the identical suite on three more clusters: EKS on AWS, GKE on Google Cloud, and k3s on Hetzner, each with two workers in the 2 vCPU / 4 GB class. Same phases, same thresholds, same extraction. Six clouds now sit on one axis, and the axis is not kind to sticker specs.
The usual caveat carries over from part one and applies to every number here: one day per provider, two workers each, one size class. Hyperscaler entry tiers are burstable or shared by design where UpCloud and OVH sell dedicated-share tiers, and that asymmetry is inherent to comparing like-priced entry plans. It is also, again, most of the story.
The contenders, rounds one and two
| Hetzner | GKE | EKS | |
|---|---|---|---|
| Region and plan | hel1, cx23 | europe-north1-a, e2-medium | eu-west-1, t3.medium |
| CPU tier | shared vCPU | shared core | burstable (unlimited mode) |
| Storage | local NVMe | pd-balanced | EBS gp3 baseline |
| Control plane | self-managed k3s | GKE, zonal | EKS |
| Worker cost | 2 x €5.49 | 2 x ~$24 + $73 fee | 2 x ~$34 + $73 fee |
Round one, for reference: UpCloud 2xCPU-4GB in fi-hel1, OVH c3-4 in Paris, DigitalOcean s-2vcpu-4gb in fra1, all detailed in the original post.
Hetzner runs the same self-managed k3s shape as round one’s clusters. The two hyperscalers bring managed control planes, which is genuinely worth money, and also bring the management fee that makes their per-node economics worse before the first pod starts.
What you bought vs what you can schedule
The first result arrived before a single benchmark ran. The suite’s stock manifests, which fit trivially on every other cluster in both rounds, would not schedule on GKE at all: podinfo asks for a modest 250 millicores and no e2-medium node had it. The arithmetic: e2-medium exposes 940m allocatable CPU, and GKE’s own system pods request 753m of it.
We had to fork the manifests down to 50m requests to get the GKE run scheduled at all. Once running, to its credit, it performed: the pods burst fine and the latency below is real. But if you size a GKE entry-class node pool from the spec sheet, Kubernetes will refuse most of what you try to put on it. You are buying a fifth of the node you think you are.
HTTP latency: the budget tier holds the line
All three new clusters passed all eight k6 SLO thresholds with 0.00% error rates, exactly like round one. The percentiles land in a tight band between round one’s extremes:
| p95 / p99 in ms | Hetzner | GKE | EKS |
|---|---|---|---|
| smoke (unloaded) | 1.89 / 3.67 | 1.16 / 2.44 | 1.33 / 3.09 |
| baseline, 50 rps | 2.33 / 4.15 | 1.32 / 2.17 | 1.53 / 4.33 |
| stress, ramp to 300 rps | 1.90 / 3.40 | 1.27 / 2.25 | 1.36 / 3.82 |
| spike, 400 rps | 1.85 / 3.39 | 1.49 / 3.93 | 1.30 / 3.66 |
For calibration: UpCloud and OVH sat below 1 ms at p95 in every scenario, and DigitalOcean’s shared tier climbed to 5.6 / 13.7 under spike. The new trio sits at 1.2 to 2.3 ms at p95 with none of DigitalOcean’s load-dependent tail growth. GKE’s constrained nodes posted the cleanest numbers of the three, which says something good about its network path and something uncomfortable about how little the guaranteed-CPU number correlates with real latency at this load.
Network: EKS crashes the podium
| Hetzner | GKE | EKS | |
|---|---|---|---|
| Forward | 1.85 Gbit/s | 1.73 Gbit/s | 4.97 Gbit/s |
| Reverse | 1.41 Gbit/s | 1.82 Gbit/s | 4.76 Gbit/s |
| What limits it | receiver CPU at ~70% | provider policer, 40k+ retransmits | receiver CPU at ~48% |
EKS is the story here: within 8% of UpCloud’s round-one best, with CPU nowhere near saturated. One honest asterisk: t3.medium’s network is burst-rated. A 30 second benchmark rides the burst window; a sustained streaming workload eventually meets the much lower baseline. GKE shows the same policer signature DigitalOcean did in round one, high retransmit counts with idle CPUs, which means no bigger node fixes it at this tier. Hetzner’s ceiling is its small CPU, the benign kind of limit: buy a bigger node and the pipe widens.
Storage: one of these things is not like the others
Round one taught us to read twin numbers as fingerprints: caps produce identical values, physics produces merely close ones. Round two has both, and a ringer.
The full per-worker table for round two:
| Per worker | HZ w0 | HZ w1 | GKE w0 | GKE w1 | EKS w0 | EKS w1 |
|---|---|---|---|---|---|---|
| Seq write MB/s | 3,609 | 2,452 | 175 | 175 | 135 | 135 |
| Seq read MB/s | 2,742 | 2,853 | 176 | 177 | 132 | 132 |
| 4k rand read IOPS | 35,769 | 33,742 | 3,769 | 3,759 | 3,099 | 3,099 |
| 4k rand write IOPS | 29,489 | 28,612 | 3,572 | 3,558 | 3,033 | 3,034 |
| fsync ops/s | 1,535 | 1,547 | 468 | 466 | 483 | 481 |
| fdatasync p99 ms | 2.21 | 2.47 | 3.39 | 3.46 | 3.52 | 3.52 |
GKE’s 175 next to 175 and EKS’s 3,099 next to 3,099 are the cap fingerprint again: pd-balanced and gp3 baseline doing exactly what their documentation says, no more, ever. Predictable, plannable, and roughly a tenth of what round one’s budget IOPS leader delivered.
Then there is the Hetzner column. €5.49 buys local NVMe posting 2.5 to 3.6 GB/s sequential, 30k random IOPS, and a 2.5 ms fsync p99, on the cheapest node in either round by a factor of four. It is instance-local storage, so as with OVH in round one, durability is your replication story, and the k3s cluster running it is self-managed. But as raw price-per-performance, nothing else in this test is on the same chart.
CPU: two classes, clearly labeled
Every shared or burstable tier in both rounds lands between 1,069 and 1,370 events per second; every dedicated-share tier lands between 7,970 and 9,666. There is no middle. The spec sheets all say “2 vCPU”; the benchmark says there are two products with a 7x gap wearing the same label. EKS’s t3.medium tops the budget class thanks to unlimited-mode bursting, and it also posts the best memory bandwidth of the trio at 4.9 GB/s, but remember that sustained bursting on t3 bills you for the privilege.
One difference from round one worth celebrating: all three round-two pairs are twins. Hetzner’s workers differ by 5%, GKE’s by 2%, EKS’s by 1%. Nobody drew DigitalOcean’s 2.8x lottery this time.
What to place where, six clouds edition
UpCloud keeps the overall crown from round one: the IOPS headroom, the network ceiling that scales with node size, and dedicated-class CPU still make it our pick for control planes and stateful workloads.
OVH still owns everything latency-shaped: its 0.43 ms fsync p99 is one nothing in round two came within 5x of.
Hetzner is the value verdict of round two. It gives up two thirds of the network and the dedicated CPU class, and in exchange costs a quarter of anything else here while carrying storage that embarrasses nodes six times its price. For dev clusters, CI runners, batch work, or any replicated data layer where you control durability, it is the obvious answer. Do the math against the price-hike post before assuming, but at these numbers the math is short.
EKS is the best hyperscaler showing: budget-class-best network and CPU, honest caps on disk, tight twins, and a managed control plane. The fee and the burst-billing asterisks are the price of admission, and its provisioning was the rockiest of the six (more on that below).
GKE performed better than its scheduling story deserves: cleanest budget-tier latency, perfectly consistent disks. But 187 schedulable millicores on a 2 vCPU node makes the entry class a demo tier, not a workload tier. Size up a class or plan around the reservation.
DigitalOcean keeps its round-one verdict: smoothest onboarding, honest SLO passes, and the only etcd-bar breach plus the twin-node lottery at the entry tier.
Full disclosure: the sweep found our bugs too
A benchmark that exercises real provisioning paths finds real bugs, and three of them were ours. Running the AWS leg surfaced a missing credential mount in our scheduler deployment, a missing iam:GetRole in the CloudFormation template we hand to customers for EKS onboarding, and a subnet configuration case our preflight should have caught before AWS did. All three fixes shipped before this post did, which is exactly the argument for benchmarking through the same path your users take.
Run it yourself
The suite is unchanged from the methodology post: three Kubernetes Jobs and a target deployment, about fifteen minutes per cluster, mechanical pass/fail verdicts. The six-cloud loop was the same Ankra motion as round one: declare the same cluster shape per provider, deploy the suite as a stack, collect logs, tear down. The marginal cost of adding a cloud to the comparison was one YAML declaration and a coffee. When a spec sheet and a benchmark disagree, believe the benchmark, especially about what “2 vCPU” means.
Get started: Create a free account on Ankra.
Join our community: Slack
Follow us on: LinkedIn | GitHub
Contact us: [email protected]
Get the next post in your inbox
Related Posts
UpCloud vs OVH vs DigitalOcean: One Kubernetes Benchmark, Three Different Clouds
We ran the same k6, iperf3 and fio suite on identical Kubernetes clusters on UpCloud, OVH and DigitalOcean. The spec sheets matched; the numbers did not.
The Spec Sheet Is Not a Benchmark: Testing Kubernetes Clusters in 15 Minutes
Two identical cloud VMs measured 2.8x apart on CPU; one failed etcd's fsync bar. A 15-minute in-cluster benchmark suite catches this before production does.