Kubernetes Deployment

Nexus GSLB installs on Kubernetes with kustomize manifests in deploy/kubernetes/. The manifests use a StatefulSet where each pod runs a co-located rqlited datastore (a native sidecar) and gets its own PVC. With replicas: 1 you get a standalone single node; with 3+ (odd) replicas the pods form one rqlite Raft cluster (ordinal-0 bootstraps; higher ordinals join it via the headless service) that replicates all config and data-plane objects across pods. NATS (optional) carries only ephemeral health/membership state, not config. See Datastore.

The manifests include full production hardening: NetworkPolicy, PodDisruptionBudget, seccompProfile, non-root uid, and automountServiceAccountToken: false.

Architecture

┌─────────────────────────────────────────────────┐
│  Kubernetes cluster                             │
│                                                 │
│  ┌──────────────┐   ┌──────────────┐            │
│  │  gslbd-0     │   │  gslbd-1     │  ...       │
│  │  PVC: data-0 │   │  PVC: data-1 │            │
│  └──────┬───────┘   └──────┬───────┘            │
│         │                  │                    │
│         └────────┬─────────┘                    │
│                  │ NATS JetStream (optional)     │
│  ┌───────────────▼──────────────────┐           │
│  │  LoadBalancer: gslbd-dns :5353   │           │
│  └──────────────────────────────────┘           │
└─────────────────────────────────────────────────┘

Manifest overview

File Resource(s)
namespace.yaml Namespace: nexus-gslb
serviceaccount.yaml ServiceAccount: gslbd (no token automount)
configmap.yaml ConfigMap: gslbd-configconfig.yaml
secret.yaml Secret: gslbd-credentials — license keys, optional DNSSEC PEMs
statefulset.yaml StatefulSet: gslbd with per-pod 1 Gi PVC
services.yaml 4 services: headless, DNS LoadBalancer, API ClusterIP, metrics ClusterIP
networkpolicy.yaml Ingress/egress policy restricting pod traffic
poddisruptionbudget.yaml maxUnavailable: 1
kustomization.yaml Kustomize entry point

Quick start

0. Registry access

The images are published to public GitHub Container Registry — no authentication or pull secret is required:

ghcr.io/star-storm-development/nexus-gslb/gslbd
ghcr.io/star-storm-development/nexus-gslb/webui

Verify with an anonymous pull: docker pull ghcr.io/star-storm-development/nexus-gslb/gslbd:latest.

If you mirror the images to a private registry, create a pull secret and uncomment the imagePullSecrets block in serviceaccount.yaml.

1. Set credentials
kubectl create secret generic gslbd-credentials \
  --namespace nexus-gslb \
  --from-literal=GSLB_LICENSE_KEY=your-key \
  --from-literal=GSLB_LICENSE_SECRET=your-secret

Free-tier deployments can skip this; the daemon enforces unlicensed RPS limits automatically.

2. Edit the configuration

Edit deploy/kubernetes/configmap.yaml to set your DNS domain, load-balancer endpoints, and any other options. The ConfigMap is mounted read-only at /etc/gslb/config.yaml inside each pod.

3. Deploy
# With kustomize (recommended)
kubectl apply -k deploy/kubernetes/

# Or apply manifests directly (skip kustomization.yaml)
kubectl apply -f deploy/kubernetes/ --ignore-not-found
4. Verify
# Pod status
kubectl -n nexus-gslb get pods -w

# Logs
kubectl -n nexus-gslb logs statefulset/gslbd -f

# API health
kubectl -n nexus-gslb port-forward svc/gslbd-api 8080:8080
curl http://localhost:8080/api/v1/health

# DNS query through the LoadBalancer
LB=$(kubectl -n nexus-gslb get svc gslbd-dns \
  -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
dig @$LB -p 5353 A gslb.example.com

Image

The statefulset.yaml defaults to nexus-gslb/gslbd:latest. Update via kustomize without editing the StatefulSet directly:

# In deploy/kubernetes/kustomization.yaml:
images:
  - name: nexus-gslb/gslbd
    newName: ghcr.io/star-storm-development/nexus-gslb/gslbd
    newTag: 0.3.0

Or with the CLI:

cd deploy/kubernetes
kustomize edit set image nexus-gslb/gslbd=ghcr.io/star-storm-development/nexus-gslb/gslbd:0.3.0

Scaling

Config and data-plane objects replicate automatically: the per-pod rqlited sidecars form one Raft cluster (use an odd replica count; a 3-node Raft cluster tolerates one lost pod). NATS is optional and carries only ephemeral state — without it each pod still serves the same config but only observes health from its own probes.

# Scale to 3 replicas (each gets its own PVC)
kubectl -n nexus-gslb scale statefulset/gslbd --replicas=3

Optionally enable ephemeral state sync in configmap.yaml:

state:
  nats:
    servers: ["nats://your-nats:4222"]
  healthPolicy: "prefer-local"

See docs/user/StateSyncSetup.md for full NATS configuration.

DNS on port 53

The default DNS port is 5353 (no privileged port required). Most cloud load balancers can map port 53 → 5353 transparently. To run natively on port 53:

  1. In configmap.yaml set dns.port: 53
  2. In statefulset.yaml add to the container securityContext:
    capabilities:
      add: ["NET_BIND_SERVICE"]
      drop: ["ALL"]
  3. Update services.yaml gslbd-dns port from 5353 to 53

DNSSEC

Pass DNSSEC key PEM content via the Secret (recommended over file mounts):

kubectl create secret generic gslbd-credentials \
  --namespace nexus-gslb \
  --from-literal=GSLB_LICENSE_KEY=your-key \
  --from-literal=GSLB_LICENSE_SECRET=your-secret \
  --from-file=GSLB_KSK_PEM=./keys/ksk.pem \
  --from-file=GSLB_ZSK_PEM=./keys/zsk.pem

Then in configmap.yaml:

dnssec:
  enabled: true
  zone: "gslb.example.com."
  ksk:
    envVar: "GSLB_KSK_PEM"
  zsk:
    envVar: "GSLB_ZSK_PEM"
  signatureValidityDays: 7

Uncomment the GSLB_KSK_PEM / GSLB_ZSK_PEM env var blocks in statefulset.yaml.

Export the DS record:

kubectl -n nexus-gslb exec statefulset/gslbd -- \
  gslbctl dnssec ds --zone gslb.example.com. --ksk-env GSLB_KSK_PEM

Prometheus metrics

The gslbd-metrics Service exposes /metrics on port 9090 with standard prometheus.io/scrape annotations. For Prometheus Operator:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: gslbd
  namespace: nexus-gslb
spec:
  selector:
    matchLabels:
      app.kubernetes.io/component: metrics
  endpoints:
    - port: metrics
      interval: 15s

Secrets management

For production, replace secret.yaml with your preferred secrets operator:

  • External Secrets Operator: create an ExternalSecret projecting from Vault/AWS SM/GCP SM into gslbd-credentials
  • Sealed Secrets: seal with kubeseal and commit the SealedSecret
  • Vault Agent: inject secrets as environment variables via annotation

The Secret name (gslbd-credentials) and key names (GSLB_LICENSE_KEY, GSLB_LICENSE_SECRET) must match what the StatefulSet references.

Upgrade

# Update image tag in kustomization.yaml, then:
kubectl apply -k deploy/kubernetes/

# Watch the rolling update
kubectl -n nexus-gslb rollout status statefulset/gslbd

The StatefulSet uses RollingUpdate strategy. With maxUnavailable: 1 (PDB), a single-replica deployment will briefly be unavailable — scale to 2+ for zero-downtime upgrades.

Uninstall

kubectl delete -k deploy/kubernetes/

# PVCs are not deleted by kustomize — remove manually if needed:
kubectl -n nexus-gslb delete pvc -l app.kubernetes.io/name=gslbd

Was this article helpful?
© 2026