A TCP or UDP service can be working perfectly behind a Kubernetes Service while its first Gateway API manifest still fails at the API boundary. A route may refer to the wrong listener, use an old experimental API version, or contain a value with the wrong type. Those are inexpensive mistakes to find before a controller, load balancer, DNS record, or production backend is involved.
Gateway API 1.6 makes TCPRoute and UDPRoute standard v1 resources. That gives operators a portable resource model for raw layer-4 traffic, but it does not make every controller support every deployment pattern. The useful first check is therefore deliberately narrow: validate the YAML against the current resource schemas, then use the controller and a controlled test listener to establish attachment and traffic behavior.
This guide builds that first check with the Gateway API 1.6 standard installation manifest and kubeconform. The validation run used Gateway API 1.6.0 CRDs and kubeconform 0.8.0 on Linux AMD64. A three-resource example containing a Gateway, TCPRoute, and UDPRoute passed strict schema validation. Changing only a backend port from the integer 5432 to the string not-a-port made the same validator fail. That paired result is useful in CI because it proves the check can reject malformed input instead of merely reporting success on one file.
Separate manifest validity from data-plane readiness
A Gateway controller owns the data plane. Gateway API defines the objects it reads, their status contract, and portable capabilities; it does not itself allocate an address or proxy a packet. A schema check cannot determine whether the GatewayClass exists, whether the controller accepts TCP or UDP listeners, whether the named Service has ready endpoints, or whether a network policy permits the eventual connection.
That limitation is a feature of the check’s scope. A static validation job can run without credentials for a cluster and without reading a real namespace. It catches YAML and API-shape mistakes early, while cluster admission and an authorized connectivity test answer the later operational questions.
Use the standard API version for new 1.6 manifests:
apiVersion: gateway.networking.k8s.io/v1
kind: TCPRoute
The 1.6 release moved TCPRoute and UDPRoute to standard v1 and deprecated their v1alpha2 versions. Do not change an installed cluster merely because a file validates locally. First confirm the cluster’s installed Gateway API CRDs and controller release support the version and route type you intend to use.
Download a validator with an integrity check
Keep validation tooling in a temporary CI directory or an approved tool cache, not beside a workload manifest that might be deployed. kubeconform publishes a checksum manifest with its release archives. Download the Linux AMD64 archive and verify it before extraction:
version=v0.8.0
base="https://github.com/yannh/kubeconform/releases/download/${version}"
curl -fsSLO "${base}/CHECKSUMS"
curl -fsSLO "${base}/kubeconform-linux-amd64.tar.gz"
grep 'kubeconform-linux-amd64.tar.gz$' CHECKSUMS | sha256sum -c -
tar -xzf kubeconform-linux-amd64.tar.gz kubeconform
./kubeconform -v
The checksum command must print OK before the archive is unpacked or executed. The validation run used kubeconform v0.8.0. Record the release URL, checksum result, and tool version in a build log; a package-managed or preinstalled copy may behave differently.
Next, obtain the standard Gateway API CRD manifest that corresponds to the API release under review:
curl -fsSL \
https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.0/standard-install.yaml \
-o gateway-api-v1.6.0-standard-install.yaml
sha256sum gateway-api-v1.6.0-standard-install.yaml
Keep the manifest as review evidence. Do not apply it to a cluster from a content-validation job. Installing or replacing CRDs is a platform change that needs controller compatibility and an approved rollout plan.
Define one listener per L4 protocol
The following example has separate listener names and ports for a TCP application and a UDP DNS-like service. The allowedRoutes.kinds entries make the intended attachment type explicit. Names and ports are illustrative; replace them with values owned by the cluster team.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: l4-gateway
namespace: demo
spec:
gatewayClassName: <gateway-class>
listeners:
- name: tcp-app
protocol: TCP
port: 15432
allowedRoutes:
kinds:
- kind: TCPRoute
- name: udp-dns
protocol: UDP
port: 1053
allowedRoutes:
kinds:
- kind: UDPRoute
A GatewayClass is controller-specific. The placeholder is intentional: copying a class name from another cluster can produce an object that is syntactically valid but never accepted by the local controller. Query the authorized cluster’s GatewayClass objects and controller documentation before choosing it.
The listener port is the port exposed by the Gateway, not the Service port. Keeping a sectionName on every route below avoids attaching a raw route to another compatible listener added later. The Gateway API release notes specifically call out that omitting sectionName and port in a parent reference can attach a route to every matching TCP listener.
Attach routes to the named listener
A layer-4 route does not contain HTTP host or path matching. It forwards traffic selected by the listener’s protocol and port to a backend reference. Add a TCP route and UDP route in the same review file:
---
apiVersion: gateway.networking.k8s.io/v1
kind: TCPRoute
metadata:
name: tcp-app
namespace: demo
spec:
parentRefs:
- name: l4-gateway
sectionName: tcp-app
rules:
- backendRefs:
- name: tcp-service
port: 5432
---
apiVersion: gateway.networking.k8s.io/v1
kind: UDPRoute
metadata:
name: udp-dns
namespace: demo
spec:
parentRefs:
- name: l4-gateway
sectionName: udp-dns
rules:
- backendRefs:
- name: dns-service
port: 53
The backend port is an integer. It must match a Service port exposed in the same namespace unless an authorized cross-namespace reference policy is in place. Schema validation can catch a string in this field, but it cannot prove that tcp-service or dns-service exists. Treat a clean schema result as an input gate, not a deployment approval.
Run a strict local schema gate
kubeconform can use the maintained CRD schema catalog in addition to its built-in Kubernetes schemas. Run strict mode so unknown fields are not silently accepted:
./kubeconform -strict -summary \
-schema-location default \
-schema-location \
'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{ .Group }}/{{ .ResourceKind }}_{{ .ResourceAPIVersion }}.json' \
routes.yaml
The successful validation run reported three valid resources and no errors:
Summary: 3 resources found in 1 file - Valid: 3, Invalid: 0, Errors: 0, Skipped: 0
To verify that the gate is meaningful, make a disposable negative fixture. Change only the TCP backend port:
backendRefs:
- name: tcp-service
port: not-a-port
The tested validator rejected that object and returned a nonzero status:
routes-invalid.yaml - TCPRoute broken-tcp-route is invalid:
... at '/spec/rules/0/backendRefs/0/port': got string, want integer
Summary: 1 resource found in 1 file - Valid: 0, Invalid: 1, Errors: 0, Skipped: 0
Make that nonzero result a CI assertion. Do not append || true to a validation command: an unavailable schema location, malformed YAML, or unsupported resource must fail the job rather than look like a clean route. Pin the schema source or mirror it through an approved dependency process if the build environment cannot fetch it reliably.
Continue with controller status and a bounded traffic test
After the static gate passes, apply the reviewed manifest only to an authorized test namespace and inspect the controller’s status conditions:
kubectl get gateway l4-gateway -n demo -o yaml
kubectl get tcproute tcp-app -n demo -o yaml
kubectl get udproute udp-dns -n demo -o yaml
Look for controller acceptance and programming conditions, plus resolved backend references. Exact condition names and messages can vary with the controller. A resource accepted by the Kubernetes API but not programmed by the controller is not ready for a DNS or firewall change.
Finally, test TCP and UDP separately from an approved client against a disposable endpoint. Record only the result, protocol, and non-sensitive timing data. Do not publish real load-balancer addresses, internal service names, credentials, DNS zones, or packet captures. A TCP connection alone does not prove UDP behavior, and neither protocol test replaces application-level health checks.
Gateway API 1.6 gives raw TCP and UDP services stable route resources, but stable APIs still need a staged validation path. Check the manifest shape offline, make the gate prove it rejects a bad value, then use controller conditions and a controlled traffic test to prove the route works where it will run. That sequence keeps a small YAML mistake out of the same change window as an exposed listener.
Sources
- Gateway API v1.6: TCPRoute and UDPRoute Graduate to Standard, Kubernetes, 3 August 2026.
- Gateway API v1.6.0 release assets, Kubernetes SIG Network.
- kubeconform v0.8.0 release, kubeconform project.
- CRDs catalog, Datree.