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

<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">&quot;&quot;</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">&quot;services&quot;</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">&quot;get&quot;</span><span class="kw">,</span><span class="at"> </span><span class="st">&quot;list&quot;</span><span class="kw">,</span><span class="at"> </span><span class="st">&quot;watch&quot;</span><span class="kw">,</span><span class="at"> </span><span class="st">&quot;update&quot;</span><span class="kw">,</span><span class="at"> </span><span class="st">&quot;patch&quot;</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">&quot;&quot;</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">&quot;services/finalizers&quot;</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">&quot;update&quot;</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">&quot;&quot;</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">&quot;nodes&quot;</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">&quot;get&quot;</span><span class="kw">,</span><span class="at"> </span><span class="st">&quot;list&quot;</span><span class="kw">,</span><span class="at"> </span><span class="st">&quot;watch&quot;</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">&quot;&quot;</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">&quot;events&quot;</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">&quot;create&quot;</span><span class="kw">,</span><span class="at"> </span><span class="st">&quot;patch&quot;</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">&quot;discovery.k8s.io&quot;</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">&quot;endpointslices&quot;</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">&quot;get&quot;</span><span class="kw">,</span><span class="at"> </span><span class="st">&quot;list&quot;</span><span class="kw">,</span><span class="at"> </span><span class="st">&quot;watch&quot;</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">&quot;networking.k8s.io&quot;</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">&quot;ingresses&quot;</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">&quot;get&quot;</span><span class="kw">,</span><span class="at"> </span><span class="st">&quot;list&quot;</span><span class="kw">,</span><span class="at"> </span><span class="st">&quot;watch&quot;</span><span class="kw">,</span><span class="at"> </span><span class="st">&quot;update&quot;</span><span class="kw">,</span><span class="at"> </span><span class="st">&quot;patch&quot;</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">&quot;networking.k8s.io&quot;</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">&quot;ingresses/finalizers&quot;</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">&quot;update&quot;</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

  • 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?