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:
- Find-or-creates a Nexus Pool
(
nexus.gslb/pool, default<namespace>-<name>). Pools are shared across objects/clusters that use the same pool name — aLoadBalancerService in one cluster and another in a second cluster can both feed members into one pool for true multi-cluster failover. - Find-or-creates a Nexus Service (DNS record) per listed hostname, bound to that pool.
- Resolves the object's externally-reachable address(es) — LB
IP/hostname for
type: LoadBalancerServices and Ingress frontends, or NodePort + node IPs fortype: NodePortServices withReadybacking pods — and keeps the pool's members in sync. - 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
<span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">helm</span> upgrade <span class="at">--install</span> nexus-gslb-operator ./charts/nexus-gslb-operator <span class="dt">\</span></span><span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> <span class="at">--namespace</span> nexus-system <span class="at">--create-namespace</span> <span class="dt">\</span></span><span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> <span class="at">--set</span> nexus.apiUrl=http://gslbd.nexus-system.svc.cluster.local:8080 <span class="dt">\</span></span><span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> <span class="at">--set</span> nexus.apiKey=<span class="op"><</span>tenant-or-system-api-key<span class="op">></span></span>
The API key is a normal Nexus tenant API key or the system
break-glass key (/etc/gslb/env → GSLB_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:
<span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="fu">apiVersion</span><span class="kw">:</span><span class="at"> rbac.authorization.k8s.io/v1</span></span><span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="fu">kind</span><span class="kw">:</span><span class="at"> ClusterRole</span></span><span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="fu">metadata</span><span class="kw">:</span></span><span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> nexus-gslb-operator</span></span><span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="fu">rules</span><span class="kw">:</span></span><span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> </span><span class="fu">apiGroups</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">""</span><span class="kw">]</span></span><span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">resources</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">"services"</span><span class="kw">]</span></span><span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">verbs</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">"get"</span><span class="kw">,</span><span class="at"> </span><span class="st">"list"</span><span class="kw">,</span><span class="at"> </span><span class="st">"watch"</span><span class="kw">,</span><span class="at"> </span><span class="st">"update"</span><span class="kw">,</span><span class="at"> </span><span class="st">"patch"</span><span class="kw">]</span></span><span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> </span><span class="fu">apiGroups</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">""</span><span class="kw">]</span></span><span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">resources</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">"services/finalizers"</span><span class="kw">]</span></span><span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">verbs</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">"update"</span><span class="kw">]</span></span><span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> </span><span class="fu">apiGroups</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">""</span><span class="kw">]</span></span><span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">resources</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">"nodes"</span><span class="kw">]</span></span><span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">verbs</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">"get"</span><span class="kw">,</span><span class="at"> </span><span class="st">"list"</span><span class="kw">,</span><span class="at"> </span><span class="st">"watch"</span><span class="kw">]</span></span><span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> </span><span class="fu">apiGroups</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">""</span><span class="kw">]</span></span><span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">resources</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">"events"</span><span class="kw">]</span></span><span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">verbs</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">"create"</span><span class="kw">,</span><span class="at"> </span><span class="st">"patch"</span><span class="kw">]</span></span><span id="cb2-18"><a href="#cb2-18" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> </span><span class="fu">apiGroups</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">"discovery.k8s.io"</span><span class="kw">]</span></span><span id="cb2-19"><a href="#cb2-19" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">resources</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">"endpointslices"</span><span class="kw">]</span></span><span id="cb2-20"><a href="#cb2-20" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">verbs</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">"get"</span><span class="kw">,</span><span class="at"> </span><span class="st">"list"</span><span class="kw">,</span><span class="at"> </span><span class="st">"watch"</span><span class="kw">]</span></span><span id="cb2-21"><a href="#cb2-21" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> </span><span class="fu">apiGroups</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">"networking.k8s.io"</span><span class="kw">]</span></span><span id="cb2-22"><a href="#cb2-22" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">resources</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">"ingresses"</span><span class="kw">]</span></span><span id="cb2-23"><a href="#cb2-23" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">verbs</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">"get"</span><span class="kw">,</span><span class="at"> </span><span class="st">"list"</span><span class="kw">,</span><span class="at"> </span><span class="st">"watch"</span><span class="kw">,</span><span class="at"> </span><span class="st">"update"</span><span class="kw">,</span><span class="at"> </span><span class="st">"patch"</span><span class="kw">]</span></span><span id="cb2-24"><a href="#cb2-24" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> </span><span class="fu">apiGroups</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">"networking.k8s.io"</span><span class="kw">]</span></span><span id="cb2-25"><a href="#cb2-25" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">resources</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">"ingresses/finalizers"</span><span class="kw">]</span></span><span id="cb2-26"><a href="#cb2-26" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">verbs</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">"update"</span><span class="kw">]</span></span>
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
ClusterIPServices 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
gslbctlor 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 ✅