Gateway API (NLB / L4)¶
Phase 2 extends Gateway API support to VNGCloud Network Load Balancers (L4). A Gateway under the vngcloud-nlb GatewayClass with TCPRoute/UDPRoute produces a Layer-4 LoadBalancerConfig — the same CR the Service type=LoadBalancer path emits — so the existing LBC controller drives the cloud NLB.
| GatewayClass | vngcloud-nlb |
| Controller name | gateway.vks.vngcloud.vn/nlb |
| Listener protocols | TCP, UDP |
| Supported routes | TCPRoute, UDPRoute |
Experimental-channel CRDs are a prerequisite
TCPRoute and UDPRoute live only in the Gateway API experimental channel and are not installed by the standard-channel manifests used for the ALB path. Install them before enabling:
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.0/experimental-install.yaml
Because of this, the NLB controller is disabled by default. The controller does not embed or install these CRDs.
Phase 2 scope
TCP and UDP routing are supported. TLSRoute (SNI passthrough) is planned for a follow-up. Mixed L4+L7 on one Gateway is not supported — HTTP/HTTPS listeners on an vngcloud-nlb Gateway are reported UnsupportedProtocol and skipped (use the vngcloud-alb class for L7).
Enabling¶
# values.yaml
gatewayApi:
nlb:
enabled: true # installs the vngcloud-nlb GatewayClass + enables the controller
This sets --disable-nlb-gateway-controller=false. Only enable it on clusters that have the experimental CRDs installed.
Quick start¶
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: tcp-gateway
namespace: default
spec:
gatewayClassName: vngcloud-nlb
listeners:
- name: redis
protocol: TCP
port: 6379
allowedRoutes:
namespaces:
from: Same
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TCPRoute
metadata:
name: redis-route
namespace: default
spec:
parentRefs:
- name: tcp-gateway
sectionName: redis # optional: pin to a listener (also matchable by port)
rules:
- backendRefs:
- name: redis
port: 6379
A UDPRoute is identical with kind: UDPRoute and a UDP listener.
kubectl get gateway tcp-gateway
NAME CLASS ADDRESS PROGRAMMED AGE
tcp-gateway vngcloud-nlb 203.0.113.50 True 3m
How routes map to listeners¶
Each Gateway TCP/UDP listener gets one default pool, built from the backendRefs of the route attached to it:
- A
TCPRouteattaches to aTCPlistener; aUDPRouteto aUDPlistener. - A
parentRefattaches to a listener when itssectionName(if set) names the listener and itsport(if set) equals the listener's port. With neither, it attaches to every compatible listener. - If several routes target one listener, the oldest (by creationTimestamp) wins — an L4 listener has a single default pool.
- A listener with no attached route produces no cloud listener (an L4 listener has nothing to forward without a backend).
Weighted backendRefs across multiple Services are aggregated into one pool with scaled member weights, same as the ALB path.
Policy CRDs¶
The L4 path honors three of the four policy CRDs:
| CRD | Targets | L4 effect |
|---|---|---|
VKSGatewayPolicy |
Gateway |
LB creation spec (scheme, package, name, subnet/zone, InterVPC private subnet + zone, BYO-LB adoption, tags), listener timeouts, allowed CIDRs |
VKSBackendPolicy |
Service |
Pool algorithm, stickiness, member target type (instance/ip), node-label selection, proxyProtocol (PROXY protocol to the backend — see below) |
VKSHealthCheckPolicy |
Service |
Health monitor — TCP (default), PING-UDP (UDP pools), or an HTTP/HTTPS probe; interval/timeout/thresholds/port |
VKSRoutePolicy does not apply to L4 — there are no L7 actions (Reject/Redirect) or path/host matching on a TCP/UDP listener. insertHeaders, certificates, and TLS termination are also L7-only and ignored here.
Health-check defaults: a TCP listener probes its members over TCP; a UDP listener uses PING-UDP (mirrors the Service controller). A VKSHealthCheckPolicy can override the protocol (including an HTTP/HTTPS probe over a TCP pool) and the thresholds/interval/timeout/port.
One app behind an external and internal NLB
VKSBackendPolicy attaches to a Service, so its targetType is global to that Service across every Gateway. To front one app with both an Internet and an Internal NLB — especially with a different targetType per LB — see Policy scope & multi-Gateway exposure.
PROXY protocol (real client IP)¶
VKSBackendPolicy.proxyProtocol: true switches a TCP pool to the vngcloud PROXY pool protocol, so the NLB prepends a PROXY header and an L4 backend (an HAProxy/nginx ingress controller, a TCP proxy, ...) can recover the real client IP behind the NLB. UDP pools ignore it. Mirrors the Service controller's vks.vngcloud.vn/enable-proxy-protocol annotation.
apiVersion: gateway.vks.vngcloud.vn/v1alpha1
kind: VKSBackendPolicy
metadata: {name: backend-proxyproto, namespace: default}
spec:
targetRefs: [{group: "", kind: Service, name: my-l4-backend}]
proxyProtocol: true
Set proxyProtocol before the Gateway is created
The cloud pool protocol is fixed at creation. Apply the VKSBackendPolicy before the Gateway so the pool is created as PROXY from the start; toggling it on a live pool requires recreating the Gateway. The backend must be configured to accept PROXY protocol (e.g. HAProxy ingress proxy-protocol config), otherwise it will reject connections.
See Real client IP with NLB + HAProxy for an end-to-end example.
InterVPC scheme¶
An InterVPC LB is reachable from a client subnet in a different VPC. Set scheme: InterVPC plus the client subnet's ID and zone — privateZoneId is required because the client subnet lives in another VPC. Mirrors the Service controller's private-subnet-id / private-zone-id annotations.
apiVersion: gateway.vks.vngcloud.vn/v1alpha1
kind: VKSGatewayPolicy
metadata: {name: intervpc-lb, namespace: default}
spec:
targetRefs: [{group: gateway.networking.k8s.io, kind: Gateway, name: my-nlb}]
loadBalancerSpec:
scheme: InterVPC
privateSubnetId: subnet-xxxxxxxx # client subnet in the other VPC
privateZoneId: HCM03-1A # zone of that client subnet
Like the other LB-creation fields, these are applied when the LB is first created.
Status¶
- Gateway —
Accepted(always, we own the class) andProgrammed(reflects LBC readiness);status.addressescarries the NLB IP. Per-listenerAcceptedisUnsupportedProtocolfor any HTTP/HTTPS listener. - TCPRoute / UDPRoute —
Accepted(attached to a listener) andResolvedRefs(ResolvedRefs,BackendNotFound, orRefNotPermittedfor a cross-namespace backend without aReferenceGrant).
Relationship to Service type=LoadBalancer¶
Both produce a Layer-4 LoadBalancerConfig and reconcile through the same LBC controller. The Gateway path is declarative routing config (Gateway + routes + policies) instead of a Service plus vks.vngcloud.vn/* annotations. Pick one per workload; they don't share an LB.