Taming Multi-Cloud Kubernetes Networking with Topology Aware Routing
Behrouz Hassanbeygi
Oct. 27, 2025
/
Building a multi-cloud Kubernetes cluster is a fascinating challenge. The goal is a single, unified control plane spanning multiple providers (like AWS, Azure, and GCP), but the real hurdle is networking. How do you make pods in different clouds talk to each other securely and efficiently?
I recently tackled this for a presentation, and the journey was... educational. I started with a simple, lightweight stack but quickly ran into deep networking issues, specifically with Kubernetes Services. Here’s how I built it, what broke, and how I ultimately fixed it.
My plan was to keep things simple.
With the cluster up, it was time to test. I used the k8s-netperf benchmark to see how well the inter-cluster communication performed.
The initial k8s-netperf results were a mixed bag.
This meant the overlay network (WireGuard) was working, but Kubernetes's service discovery and load balancing (managed by kube-proxy) was not playing nice.
My first instinct was to fix kube-proxy's behavior.
I tried enabling Kubernetes's built-in Topology-Aware Hints. I patched my Service with:
The goal was to tell kube-proxy to prioritize endpoints in the same zone (i.e., the same cloud). This did nothing. It seemed kube-proxy was either ignoring my changes or was incapable of implementing them in this setup.
K3s, like many distros, defaults to iptables mode for kube-proxy. I thought switching to ipvs mode might be more intelligent.
This was a disaster. As soon as I enabled ipvs, it immediately conflicted with my WireGuard network and completely disrupted the tunnel. Back to square one.
At this point, I was convinced kube-proxy was the problem. The solution? Get rid of it.
This created a new problem: K3s's default CNI, Flannel, doesn't support running without kube-proxy. I needed to replace both. The two main candidates were Calico and Cilium.
I decided to go all-in on Cilium.
I removed Flannel and installed Cilium using its Helm chart, making sure to enable the kube-proxy replacement:
# Define our K3s API server info
API_SERVER_IP=10.200.0.1
API_SERVER_PORT=6443
helm install --upgrade cilium cilium/cilium --version 1.18.3 \
--namespace kube-system \
--set kubeProxyReplacement=true \
--set k8sServiceHost=${API_SERVER_IP} \
--set k8sServicePort=${API_SERVER_PORT}
The result? A massive improvement! The service traffic was stable. The wild performance swings were gone.
However, it still wasn't perfect. The k8s-netperf benchmark showed that traffic was still being routed to pods in other zones (clouds), even when a local pod was available. Cilium was balancing the traffic fairly, but not smartly.
After digging through the Cilium documentation, I found the magic flag I was missing. I ran a cilium upgrade command to enable its native service topology awareness:
cilium upgrade --set loadBalancer.serviceTopology=true
This was it. 🚀
The result was flawless. The k8s-netperf benchmarks now showed exactly what I wanted:
Setting up a multi-cloud overlay with WireGuard is surprisingly straightforward. The real complexity lies in making Kubernetes's internal networking (specifically Service routing) aware of your underlying topology.
While kube-proxy struggled, Cilium's kubeProxyReplacement mode combined with its loadBalancer.serviceTopology feature proved to be the perfect solution. It intelligently routes traffic, respects network zones, and finally made my multi-cloud cluster performant and predictable.
Address
Level 8
11-17 York Street
Sydney NSW 2000
Phone Number
+61 2 8294 8067
Email
[email protected]
By Behrouz Hassanbeygi
By Behrouz Hassanbeygi
© 2017-2025 Darumatic Pty Ltd. All Rights Reserved.