Smart annotations for Gateway routes
Smart annotations let you configure NetScaler® entities directly from your Gateway and route objects, using annotations of the form
gateway.citrix.com/<nsresource>. A single, registry-driven engine handles every supported resource — LB monitor, load balancing virtual server, service group, and content switching virtual server — so you tune the NetScaler configuration alongside your Kubernetes manifests, without any CLI or GUI configuration on the NetScaler instance.
This is a cross-cutting feature. It applies to every route kind that the Gateway Controller programs (Gateway, HTTPRoute, TCPRoute, UDPRoute, AIGatewayRoute, and mcproute), not to MCP routes alone.
Annotation form
The annotation key is
gateway.citrix.com/<nsresource>. Place it on any Gateway or route object the Gateway Controller programs. The target NetScaler entity depends on whether the object is a Gateway (front end) or a route (back end).
| Object | Target NetScaler entity |
|---|---|
Gateway |
The Gateway's front-end content switching (CS) virtual server |
HTTPRoute (Gateway API) |
The route's back-end service group / LB virtual server |
AIGatewayRoute |
The route's back-end service group / LB virtual server |
TCPRoute (Gateway API) |
The route's back-end service group / LB virtual server |
UDPRoute (Gateway API) |
The route's back-end service group / LB virtual server |
mcproute |
The route's back-end service group / LB virtual server |
Note:
There is no
TLSRoute carrier — to configure the HTTPS front end, place gateway.citrix.com/csvserver on the Gateway object.
Modes
Each supported resource is applied in one of two modes.
-
create_bind— The controller creates a standalone NetScaler object and binds it to the target entity. For example, anlbmonitoris created and bound to the route's back-end service group. When the value is a bare string, that string names a preconfigured object that already exists on the NetScaler, and the controller only binds it (it never creates or deletes it). -
set— The controller sets NITRO attributes on an entity that it already owns. This is how you tune the controller-createdlbvserver,servicegroup, orcsvserver.
Value model
An annotation value is parsed as YAML or JSON, and can take three shapes:
-
String — Names a preconfigured NetScaler object. This is valid for
create_bindresources only (bind-only; the controller never creates or removes the named object). -
Mapping — A flat set of NITRO attributes for the resource, plus the reserved schema keys below. Any attribute the NetScaler resource supports is accepted.
-
List — A list of strings and/or mappings, applied in order.
The reserved keys in a mapping are consumed by the annotation engine and are never sent to the NetScaler as attributes:
| Reserved key | Meaning |
|---|---|
name |
For a preconfigured item (preconfigured: true), the name of the existing NetScaler object to bind. For a controller-created object, name is ignored — the controller derives the name from the route. |
preconfigured |
true marks the item as bind-only against an existing object. Requires name. Valid for create_bind resources only. |
backendRefs |
Scopes the entry to specific back ends. Each entry is a {name, namespace, port} mapping matching a backendRef of the route, so the setting applies only to those back ends' service groups or LB virtual servers. When omitted, the entry applies to all of the route's back ends. Not applied to lbmonitor entries: a monitor is bound to the service groups of all the route's back ends. On an mcproute, servicegroup and sslservicegroup entries also apply to all of the route's back ends; only lbvserver entries honor backendRefs there. |
Note:
The mapping is a flat set of attributes — it is not keyed by service name. This differs from the NetScaler Ingress Controller annotations (for example,
ingress.citrix.com/servicegroup: '{"appname": {...}}'). To target one back end rather than all of them, use the backendRefs reserved key. This does not apply to lbmonitor entries, or to servicegroup and sslservicegroup entries on an mcproute.
Supported resources
| Resource | Mode | Target entity | Description |
|---|---|---|---|
lbmonitor |
create_bind |
service group | Creates an LB monitor and binds it to the route's back-end service group. A bare string binds a preconfigured monitor. |
lbvserver |
set |
LB virtual server | Sets attributes on the route's back-end LB virtual server (for example, session persistence). |
servicegroup |
set |
service group | Sets attributes on the route's back-end service group (for example, client IP insertion). |
sslservicegroup |
set |
service group | Sets SSL parameters on the back-end service group. Intended for SSL service groups; a non-SSL back end tolerates the setting rather than failing the route. |
csvserver |
set |
CS virtual server | Sets attributes on the Gateway (or route) front-end CS virtual server. |
Configure CS, LB, and service group settings
CS virtual server on a Gateway
Place
gateway.citrix.com/csvserver on the Gateway object to set attributes on its front-end CS virtual server.
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: mcp-gw
namespace: default
annotations:
gateway.citrix.com/csvserver: |
l2conn: "ON"
spec:
gatewayClassName: mcpgwclass
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
EOF
LB virtual server on a route
Place
gateway.citrix.com/lbvserver on a route to set attributes on its back-end LB virtual server. The following example enables rule-based session persistence keyed on an MCP session header, so all requests in a session pin to the same back end.
kubectl apply -f - <<EOF
apiVersion: citrix.com/v1
kind: mcproute
metadata:
name: mcp-github
namespace: default
annotations:
gateway.citrix.com/lbvserver: |
persistenceType: RULE
rule: 'http.req.header("Mcp-Session-Id")'
resRule: 'http.res.header("Mcp-Session-Id")'
spec:
parentRefs:
- name: mcp-gw
namespace: default
rules:
- matches:
- headers:
- name: x-netscaler-target-mcp-server
type: MCPTargetServer
value: github.mcp.example
backendRefs:
- name: mcp-github
port: 443
EOF
Service group on a route
Place
gateway.citrix.com/servicegroup on a route to set attributes on its back-end service group. The following example turns on client IP insertion and sets the header the client IP is carried in.
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: mcp-github
namespace: default
annotations:
gateway.citrix.com/servicegroup: |
cip: ENABLED
cipHeader: X-Forwarded-For
spec:
parentRefs:
- name: mcp-gw
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: mcp-github
port: 80
EOF
For an SSL back end, use
gateway.citrix.com/sslservicegroup to set SSL parameters on the back-end service group. The following annotation turns off SSL session reuse, for example for MCP streaming:
metadata:
annotations:
gateway.citrix.com/sslservicegroup: |
sessReuse: DISABLED
Configure a load balancing monitor
Attach a load balancing monitor to a route's back-end service group with the
gateway.citrix.com/lbmonitor annotation. The Gateway API routes (HTTPRoute, TCPRoute, UDPRoute), AIGatewayRoute, and mcproute all use it. The annotation value is one of two kinds:
-
A monitor that the controller creates. Give a mapping of NITRO
lbmonitorattributes, such astype,httpRequest,interval, andrespTimeout. The controller creates the monitor and binds it to the route's back-end service group. The controller derives the monitor name from the service group, so anynamethat you supply is ignored. -
A monitor that you already created on NetScaler. Give only the name of the monitor, as a plain string. The controller binds this monitor to the route's back-end service group. It never creates, changes, or deletes the monitor.
Example 1: The controller creates the monitor. The following example creates an HTTP monitor that sends
HEAD /healthz to the back-end service every 10 seconds.
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web-route
namespace: default
annotations:
gateway.citrix.com/lbmonitor: |
type: HTTP
httpRequest: "HEAD /healthz"
interval: 10
respTimeout: 3
spec:
parentRefs:
- name: mcp-gw
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: web-svc
port: 80
EOF
Example 2: You bind a monitor that already exists on NetScaler. First, create the monitor on NetScaler. For example:
add lb monitor my-http-monitor HTTP -respCode 200 -httpRequest "HEAD /healthz"
Then, set the annotation to the name of the monitor.
my-http-monitor is only an example. Replace it with the name of your own monitor.
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web-route
namespace: default
annotations:
gateway.citrix.com/lbmonitor: my-http-monitor
spec:
parentRefs:
- name: mcp-gw
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: web-svc
port: 80
EOF
The monitor must exist on NetScaler before you apply the route. The controller does not create it, and the binding fails if the monitor is missing.
Note:
You can also write an existing monitor as a mapping, with
name: my-http-monitor and preconfigured: true. Without preconfigured: true, the controller ignores name and does not bind your monitor.
For a TCP back end, use
type: TCP in the mapping. To bind more than one monitor, give a YAML list. The list can contain both kinds of monitor.
When you remove a monitor from the annotation, remove the whole annotation, or delete the route, the controller deletes a monitor that it created (with its binding), but for a monitor that you created on NetScaler, it removes only the binding and leaves the monitor in place.
Modify and unset behavior
A NetScaler
set changes only the attributes it is given and never clears the rest, so the engine tracks changes across updates:
-
Attribute removed on an update (
setandcreate_bind) — An attribute that was present on the previous version of the object but is absent now is explicitly unset back to its NetScaler default. A droppedrespTimeoutorsessReuse, for example, does not linger as configuration drift. -
Whole annotation removed — Removing the annotation entirely tears down the objects the controller created for it (for example, a controller-created monitor and its binding), in reverse order. Preconfigured objects referenced by name are left intact — only their binding is removed.
Note:
A controller-created object is named from the route, and a per-item name is derived per target. If an entry names an object the route does not own, points an entity at another controller-owned object, or renames the controller-created object, the engine drops that attribute and logs a warning; the rest of the entry still applies.
Reference
-
NetScaler Ingress Controller annotations — the Ingress Controller analog (
ingress.citrix.com/,service.citrix.com/,route.citrix.com/) of these Gateway Controller annotations.