Operator
Octopus runs its controller in-process: the same binary that serves traffic also watches the cluster and programs its own router. There is no sidecar and no separate control plane. The operator reconciles two resource models — the standard Gateway API and the Octopus CRDs — into one merged route table that it applies to the live router.
Enabling it01
The operator is off by default. Enable it in the gateway config:
kubernetes:
enabled: true
gateway_class: octopus # GatewayClass name this instance reconciles
watch_namespaces: [] # empty = all namespaces (needs cluster-scoped RBAC)
leader_election: true
terminate_tls: falseThe controllerName Octopus claims for GatewayClasses is gateway.octopus.io/gateway-controller;
the Octopus CRD API group is gateway.octopus.io. See Gateway API
for the GatewayClass wiring.
What it watches02
For each watch scope (see namespace scoping) the operator spawns watchers for:
Gateway API routes —
HTTPRouteandGRPCRoute(gateway.networking.k8s.io/v1).Gateway— used to resolve listener hostnames so routes can intersect theirhostnameswith the parent listener's, and (withterminate_tls) to source listener TLS Secrets.Octopus CRDs —
OctopusRoute,OctopusUpstream,OctopusPolicy.ConfigMap— to resolve anOctopusRouteconvention'sscriptRefto an inline script.ReferenceGrant(gateway.networking.k8s.io/v1beta1) — to authorize cross-namespace references (a convention'sscriptRef, or a Gateway's TLS Secret).
When terminate_tls is enabled, an additional set of watchers (Gateway, TLS Secret,
ReferenceGrant) drives the TLS reconciler — see TLS & cert-manager.
The controller watches Gateway resources to read their listeners, but it does not currently
reconcile GatewayClass objects or write status back to any resource. Treat the GatewayClass as a
selector/marker (Octopus matches on its own gateway_class) rather than something the operator
acknowledges via status conditions.
The reconcile → merge → apply pipeline03
The reconciler keeps a RouteStore keyed by source. Every watch event upserts or removes that
source's contribution and then re-applies the whole table:
Translate. Each resource is translated into an intermediate, source-agnostic route representation (plus any upstream clusters it implies). For example an
HTTPRouterule becomes one upstream cluster and a set of per-host, per-method, per-path routes; anOctopusRoutebecomes one route per method referencing its upstream by name. See Gateway API and Octopus CRDs for the exact field mappings.Merge. The store merges every source into one routing table.
Overlay policies.
OctopusPolicyoverlays are applied to the merged routes they target (GEP-713 policy attachment) — enriching them with auth/rate-limit, either filling gaps or overriding inline settings.Apply. The resulting table is programmed into the live
Router. The router is swapped atomically, so requests continue to be served against the previous table until the new one is in place.
Re-translation is incremental but dependency-aware: changing a parent Gateway re-translates the
routes attached to it (their hostname intersection may have changed), and changing a ConfigMap or
ReferenceGrant re-resolves the OctopusRoutes that depend on it.
Source precedence and merge04
Routes carry their originating source: Static (the gateway config file), GatewayApi
(HTTPRoute/GRPCRoute), or OctopusRoute. Static config routes are seeded into the store at
startup so they survive every subsequent merge — reconciling Gateway API or Octopus resources adds
to, and never erases, your static configuration. Each source is stored under its own key, so
upserting or deleting one resource only replaces that resource's slice of the table.
When two routes collide on the same host/method/path, OctopusRoute.priority (higher wins) breaks
the tie; Gateway API routes do not set an explicit priority.
Namespace scoping05
watch_namespaces controls the watch scope:
Empty (default) — watch all namespaces. This requires cluster-scoped RBAC (
rbac.clusterScoped=true).A list — one set of watchers is spawned per listed namespace, and the operator only sees resources in those namespaces.
kubernetes:
enabled: true
watch_namespaces: [team-a, team-b]Leader election06
leader_election defaults to true. It is intended so that, when the gateway is scaled to multiple
replicas, only one replica owns status writes (via a coordination.k8s.io/v1 Lease). Each replica
still watches and programs its own in-process router, so routing works on every replica
regardless — leader election concerns shared cluster-side writes, not the data path.
RBAC07
The operator needs get/list/watch on the resources it reconciles. The chart's cluster-scoped
RBAC grants:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["httproutes", "grpcroutes", "gateways", "gatewayclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: ["gateway.octopus.io"]
resources: ["octopusgateways", "octopusroutes", "octopusupstreams", "octopuspolicies"]
verbs: ["get", "list", "watch"]
# Only needed for operator-managed TLS termination:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["referencegrants"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"](Discovery additionally needs services/endpoints/endpointslices, and configmaps access is
required for convention scriptRef resolution.) See Discovery for the
discovery RBAC and the Helm chart page for the full role.