Getting Started
Commands follow
README.mdanddocs/local.mdat commit34b4535c. The playground was brought up on 2026-09-08 to check them; see the arm64 note below.
Global load balancing needs at least two clusters and a delegated DNS zone, so there is no single-command install that demonstrates anything. The project solves this with a local playground: three k3s clusters in Docker, one acting as the parent DNS, two running k8gb. That is the fastest way to see the mechanism from Internals actually work, so it is what this page uses.
Prerequisites
From docs/local.md:
kubectl- Helm 3
k3d, version 5.3.0 or newer, and a working Docker- Go and
make, to run the repository's targets
Install
Clone the repository and bring up the playground:
git clone https://github.com/k8gb-io/k8gb
cd k8gb
make deploy-full-local-setupThis creates three k3d clusters. k3d-edgedns runs BIND and holds the parent zone that delegates to the other two. k3d-test-gslb1 and k3d-test-gslb2 each run k8gb, a CoreDNS exposed for UDP DNS on ports 5053 and 5054 respectively, a test application, and sample Gslb resources.
On Apple Silicon and other arm64 hosts the playground comes up but answers no DNS. The edgedns cluster runs internetsystemsconsortium/bind9:9.21, published as a single amd64 image; under emulation it crashes on startup with qemu: uncaught target signal 11 (Segmentation fault), so BIND never listens. That failure cascades in a way worth understanding, because it traces the same path as Architecture: external-dns cannot write the NS delegation into BIND over RFC 2136 (RFC2136 create record failed ... connection reset by peer), so the zone is never delegated, so the CoreDNS instances serve nothing for those hosts, and every dig below comes back empty.
The operator itself is unaffected and its logs still show the mechanism working, which is what the "Verify it works" section falls back to on arm64.
Size the VM too. Three k3s clusters do not fit in a 2 CPU, 4 GB container VM; running out shows up as API server TLS handshake timeouts during the Helm install, and as too many open files in the k3s logs when fs.inotify.max_user_instances is left at its default.
For a real deployment the operator is a Helm chart instead:
helm install k8gb oci://ghcr.io/k8gb-io/charts/k8gb --version <version>That path also requires exposing each cluster's CoreDNS for external DNS traffic and delegating a zone to those addresses, which is the part the playground has already done for you. docs/exposing_dns.md and the provider pages under docs/deploy_*.md cover it.
A first working setup
The playground ships with Gslb resources already applied, so the interesting work is observing rather than creating.
- Confirm all three clusters came up.
kubectl cluster-info --context k3d-edgedns \
&& kubectl cluster-info --context k3d-test-gslb1 \
&& kubectl cluster-info --context k3d-test-gslb2- Ask the parent DNS for the round robin hostname. This is the query a real client would make.
dig @localhost -p 1053 roundrobin.cloud.example.com +short +tcpYou should get A records from both clusters, one per node, in an order that varies. docs/local.md shows this output for the default setup:
172.20.0.2
172.20.0.5
172.20.0.4
172.20.0.6- Check those addresses against the actual cluster nodes.
for c in k3d-test-gslb{1,2}; do
kubectl get no --context "$c" \
-o custom-columns="NAME:.metadata.name,IP:status.addresses[0].address"
doneThe IPs in the DNS answer should be exactly the node IPs across both clusters. One hostname, endpoints from two independent clusters, and no component that knows about both.
Verify it works
To see the cross-cluster mechanism rather than only its result, query the internal record that clusters publish for each other, described in Internals:
dig @localhost -p 5053 localtargets-roundrobin.cloud.example.com +short +tcp
dig @localhost -p 5054 localtargets-roundrobin.cloud.example.com +short +tcpEach cluster's CoreDNS answers with only its own healthy endpoints. That is the record the peer operator fetches on every reconcile, and putting the two answers side by side shows why the public record contains both sets.
To see the operator's own view:
kubectl get gslb --context k3d-test-gslb1 -n test-gslb -o wide
kubectl get dnsendpoint --context k3d-test-gslb1 -n test-gslbThe Gslb status carries the hosts, the health it computed, and the targets it settled on. The DNSEndpoint is the object CoreDNS is serving from.
The operator's own log is the most direct view, and it is what still works when DNS does not. Each reconcile prints the list it settled on, from the line quoted in Internals:
kubectl logs --context k3d-test-gslb1 -n k8gb deploy/k8gb --tail=200 | grep "Final target list"INF .../k8gbendpoint/applicationDNSEndpoint.go:167 > Final target list gslb=multiservice-gslb-all targets=["172.19.0.4","172.19.0.5"]To watch a failover, scale the test application to zero in one cluster and re-run the dig from step 2. The addresses belonging to that cluster should disappear once the records expire.
Where to go next
The playground avoids the two things a real deployment has to solve: exposing CoreDNS so other clusters and resolvers can reach it (docs/exposing_dns.md), and delegating a zone from a DNS provider you actually run (docs/deploy_route53.md and its siblings). Strategy choice, geo tags, and split brain handling are covered in the project documentation. Use the k8gb.io/v1beta1 API group for anything new; k8gb.absa.oss/v1beta1 still works but is the legacy group being migrated away from, as described in History.