Kubernetes Operator

Kubernetes Operator

nexus-gslb-operator is a separate controller-runtime-based controller-manager (repo: nexus-gslb-operator, not part of this repo's build) that watches Service and Ingress objects in a Kubernetes cluster and keeps a matching Nexus GSLB pool/service/members in sync automatically. There are no CRDs — everything is driven by annotations, the same model as external-dns.

Annotate a Service or Ingress with nexus.gslb/hostname and the operator:

  1. Find-or-creates a Nexus Pool (nexus.gslb/pool, default <namespace>-<name>). Pools are shared across objects/clusters that use the same pool name — a LoadBalancer Service in one cluster and another in a second cluster can both feed members into one pool for true multi-cluster failover.
  2. Find-or-creates a Nexus Service (DNS record) per listed hostname, bound to that pool.
  3. Resolves the object's externally-reachable address(es) — LB IP/hostname for type: LoadBalancer Services and Ingress frontends, or NodePort + node IPs for type: NodePort Services with Ready backing pods — and keeps the pool's members in sync.
  4. On deletion, removes the members and DNS records it created. The pool itself is never auto-deleted, since other objects/clusters may still reference it.

Full annotation reference (nexus.gslb/algorithm, filter-chain, ttl, min-healthy, fallback-cname, weight, priority, region, target-port, endpoint-mode, node-address-type, …) lives in the operator repo's README.

Install

helm upgrade --install nexus-gslb-operator ./charts/nexus-gslb-operator \
  --namespace nexus-system --create-namespace \
  --set nexus.apiUrl=http://gslbd.nexus-system.svc.cluster.local:8080 \
  --set nexus.apiKey=<tenant-or-system-api-key>

The API key is a normal Nexus tenant API key or the system break-glass key (/etc/gslb/envGSLB_API_KEY on each node) — same auth path as gslbctl/Terraform, sent as a Bearer token.

RBAC

The operator needs cluster-wide read on Service/Node/EndpointSlice/ Ingress (to resolve member addresses) and write access to the specific objects it manages (to set finalizers/annotations). The Helm chart generates this; the equivalent raw manifest:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nexus-gslb-operator
rules:
  - apiGroups: [""]
    resources: ["services"]
    verbs: ["get", "list", "watch", "update", "patch"]
  - apiGroups: [""]
    resources: ["services/finalizers"]
    verbs: ["update"]
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "patch"]
  - apiGroups: ["discovery.k8s.io"]
    resources: ["endpointslices"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses"]
    verbs: ["get", "list", "watch", "update", "patch"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses/finalizers"]
    verbs: ["update"]

Set rbac.clusterScoped: false (Helm) for a namespaced Role/RoleBinding instead, if you restrict watchNamespace and only ever use LoadBalancer-backed Services/Ingresses (NodePort resolution needs cluster-wide Node visibility).

Runs under OpenShift's default restricted/restricted-v2 SCC — the pod spec only requests runAsNonRoot: true, no fixed UID.

Known limitations

  • ClusterIP Services don't get members (not externally routable); the Nexus Service/Pool are still created so DNS resolves via the pool's fallback CNAME once members do appear.
  • Node IP changes for NodePort members are only picked up via EndpointSlice events plus the manager's cache resync, not a direct Node watch.
  • Pools are never auto-deleted by the operator; prune unused ones via gslbctl or the API.

Surface parity

API ✅ (existing pools/members/services endpoints) WebUI N/A (k8s-native, annotation-driven) TUI N/A Terraform N/A (separate operator/Helm install path, not a Terraform-managed resource) Kubernetes ✅ (this page) Docs ✅


Was this article helpful?
© 2026