Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

## Unreleased

### Added

- **netbird**: Dedicated gRPC and relay Services with `appProtocol`, so
Envoy-based Gateway API controllers (Cilium, Envoy Gateway) configure the
correct upstream protocol. `server.grpcService` renders
`<release>-server-grpc` with `appProtocol: kubernetes.io/h2c` (HTTP/2) and
`server.relayService` renders `<release>-server-relay` with
`appProtocol: kubernetes.io/ws` (WebSocket). `server.grpcRoute` and
`server.relayHttpRoute` now auto-fill omitted `backendRefs` to these
Services. Both are enabled by default and only render when their route is
enabled; set `enabled: false` to fall back to the main Service. Previously
all routes shared the main Service (no `appProtocol`), which forced an
HTTP/1.1 upstream and broke gRPC and WebSocket on those controllers.
Fixes #104.

### Changed

- **netbird**: Bump appVersion from 0.72.3 to 0.73.2. Upstream changes are
internal (management/signal/relay stability and performance, posture-check
hardening); no config options, env vars, ports, or protocols changed, and
no database migration steps are required. Dashboard image stays at v2.39.0.
See [v0.73.2 release notes](https://github.com/netbirdio/netbird/releases/tag/v0.73.2) (#112).

## [0.5.0] — 2026-06-11

### Changed
Expand Down
2 changes: 1 addition & 1 deletion charts/netbird/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: netbird
description: A Helm chart for deploying NetBird VPN management, signal, dashboard, and relay services on Kubernetes
type: application
version: 0.5.0
appVersion: "0.72.3"
appVersion: "0.73.2"
keywords:
- netbird
- vpn
Expand Down
97 changes: 68 additions & 29 deletions charts/netbird/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,16 +289,47 @@ dashboard:
- path: { type: PathPrefix, value: / }
```

Rules that omit `backendRefs` get the netbird server / dashboard `Service`
auto-filled on port 80. Specify `backendRefs` explicitly for traffic
splitting or non-default ports.
Rules that omit `backendRefs` get a netbird server `Service` auto-filled on
port 80. The target depends on the traffic class:

- `server.httpRoute` → the main server `Service` (`<release>-server`).
- `server.grpcRoute` → the dedicated gRPC `Service` (`<release>-server-grpc`).
- `server.relayHttpRoute` → the dedicated relay `Service`
(`<release>-server-relay`).

Specify `backendRefs` explicitly for traffic splitting or non-default ports.

### Dedicated gRPC and relay Services (`appProtocol`)

gRPC and relay (WebSocket) traffic cannot share the plain main `Service`
when the Gateway controller is Envoy-based (**Cilium**, **Envoy Gateway**):
those controllers read a Service port's `appProtocol` to pick the upstream
codec, and a Service with no `appProtocol` is treated as HTTP/1.1 — which
breaks gRPC (needs an HTTP/2 / h2c upstream) and WebSocket (needs upgrade
handling). The chart therefore renders two extra `ClusterIP` Services with
the same selector as the main Service but distinct `appProtocol`:

| Service | appProtocol | Rendered when | Consumed by |
| ------------------------ | ------------------- | -------------------------- | ----------------------- |
| `<release>-server-grpc` | `kubernetes.io/h2c` | `server.grpcRoute` on | `server.grpcRoute` |
| `<release>-server-relay` | `kubernetes.io/ws` | `server.relayHttpRoute` on | `server.relayHttpRoute` |

These are enabled by default (`server.grpcService.enabled`,
`server.relayService.enabled`) and only render when their route is enabled.
Set either to `false` to fall back to the main Service. You can also
reference the Services by name in your own `backendRefs`.

> **Cilium:** set the Cilium Helm value `gatewayAPI.enableAppProtocol=true`
> so Cilium honours `appProtocol`. For Envoy Gateway, ensure the
> `EnvoyProxy`/`BackendTLSPolicy` config respects `appProtocol`.

For deployments that expose relay as a raw TCP listener (no HTTP path
matching), use `server.relayTcpRoute` — apiVersion
`gateway.networking.k8s.io/v1alpha2`. TCPRoute ships in the Gateway API
**experimental channel**; make sure its CRDs are installed
(`experimental-install.yaml` from the Gateway API release) before
enabling it.
enabling it. Raw TCP carries no `appProtocol`, so `relayTcpRoute` keeps
defaulting its backendRefs to the main Service.

## STUN Networking

Expand Down Expand Up @@ -789,31 +820,39 @@ Gateway API alternatives to the Ingress blocks above. Enabling both an
Ingress and its matching route block is a template-time error. TLS is
terminated at the referenced Gateway's listeners, not in these values.

| Key | Type | Default | Description |
| ----------------------------------- | ------ | ------- | ------------------------------------------------------------------------------------- |
| `server.httpRoute.enabled` | bool | `false` | Create `HTTPRoute` for HTTP (API + OAuth2). Requires `parentRefs`. |
| `server.httpRoute.parentRefs` | list | `[]` | Gateways to attach to (`name`, `namespace`, optional `sectionName`). |
| `server.httpRoute.hostnames` | list | `[]` | HTTPRoute hostnames |
| `server.httpRoute.rules` | list | `[]` | `HTTPRoute.spec.rules`. Omitted `backendRefs` default to server Service on port 80. |
| `server.httpRoute.annotations` | object | `{}` | Route annotations |
| `server.httpRoute.labels` | object | `{}` | Extra labels |
| `server.grpcRoute.enabled` | bool | `false` | Create `GRPCRoute` for Signal + Management. Works with plaintext h2c. |
| `server.grpcRoute.parentRefs` | list | `[]` | Gateway parent refs |
| `server.grpcRoute.hostnames` | list | `[]` | GRPCRoute hostnames |
| `server.grpcRoute.rules` | list | `[]` | `GRPCRoute.spec.rules` (method or header matches) |
| `server.grpcRoute.annotations` | object | `{}` | Route annotations |
| `server.grpcRoute.labels` | object | `{}` | Extra labels |
| `server.relayHttpRoute.enabled` | bool | `false` | Create `HTTPRoute` for relay + WebSocket (default Gateway API path). |
| `server.relayHttpRoute.parentRefs` | list | `[]` | Gateway parent refs |
| `server.relayHttpRoute.hostnames` | list | `[]` | HTTPRoute hostnames |
| `server.relayHttpRoute.rules` | list | `[]` | `HTTPRoute.spec.rules` |
| `server.relayHttpRoute.annotations` | object | `{}` | Route annotations |
| `server.relayHttpRoute.labels` | object | `{}` | Extra labels |
| `server.relayTcpRoute.enabled` | bool | `false` | Create `TCPRoute` (`v1alpha2`) for raw-TCP relay listeners. |
| `server.relayTcpRoute.parentRefs` | list | `[]` | Gateway parent refs |
| `server.relayTcpRoute.rules` | list | `[]` | `TCPRoute.spec.rules`. Defaults to a single rule targeting server Service on port 80. |
| `server.relayTcpRoute.annotations` | object | `{}` | Route annotations |
| `server.relayTcpRoute.labels` | object | `{}` | Extra labels |
| Key | Type | Default | Description |
| ----------------------------------- | ------ | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `server.httpRoute.enabled` | bool | `false` | Create `HTTPRoute` for HTTP (API + OAuth2). Requires `parentRefs`. |
| `server.httpRoute.parentRefs` | list | `[]` | Gateways to attach to (`name`, `namespace`, optional `sectionName`). |
| `server.httpRoute.hostnames` | list | `[]` | HTTPRoute hostnames |
| `server.httpRoute.rules` | list | `[]` | `HTTPRoute.spec.rules`. Omitted `backendRefs` default to server Service on port 80. |
| `server.httpRoute.annotations` | object | `{}` | Route annotations |
| `server.httpRoute.labels` | object | `{}` | Extra labels |
| `server.grpcRoute.enabled` | bool | `false` | Create `GRPCRoute` for Signal + Management. Works with plaintext h2c. |
| `server.grpcRoute.parentRefs` | list | `[]` | Gateway parent refs |
| `server.grpcRoute.hostnames` | list | `[]` | GRPCRoute hostnames |
| `server.grpcRoute.rules` | list | `[]` | `GRPCRoute.spec.rules` (method or header matches) |
| `server.grpcRoute.annotations` | object | `{}` | Route annotations |
| `server.grpcRoute.labels` | object | `{}` | Extra labels |
| `server.grpcService.enabled` | bool | `true` | Render a dedicated gRPC `Service` (`appProtocol: kubernetes.io/h2c`) when `grpcRoute` is on. False falls back to the main Service. |
| `server.grpcService.appProtocol` | string | `kubernetes.io/h2c` | `appProtocol` on the gRPC Service port. |
| `server.grpcService.port` | int | `80` | gRPC Service port (targets the `http` container port). |
| `server.grpcService.annotations` | object | `{}` | gRPC Service annotations |
| `server.relayHttpRoute.enabled` | bool | `false` | Create `HTTPRoute` for relay + WebSocket (default Gateway API path). |
| `server.relayHttpRoute.parentRefs` | list | `[]` | Gateway parent refs |
| `server.relayHttpRoute.hostnames` | list | `[]` | HTTPRoute hostnames |
| `server.relayHttpRoute.rules` | list | `[]` | `HTTPRoute.spec.rules` |
| `server.relayHttpRoute.annotations` | object | `{}` | Route annotations |
| `server.relayHttpRoute.labels` | object | `{}` | Extra labels |
| `server.relayService.enabled` | bool | `true` | Render a dedicated relay `Service` (`appProtocol: kubernetes.io/ws`) when `relayHttpRoute` is on. False falls back to the main Service. |
| `server.relayService.appProtocol` | string | `kubernetes.io/ws` | `appProtocol` on the relay Service port. |
| `server.relayService.port` | int | `80` | Relay Service port (targets the `http` container port). |
| `server.relayService.annotations` | object | `{}` | Relay Service annotations |
| `server.relayTcpRoute.enabled` | bool | `false` | Create `TCPRoute` (`v1alpha2`) for raw-TCP relay listeners. |
| `server.relayTcpRoute.parentRefs` | list | `[]` | Gateway parent refs |
| `server.relayTcpRoute.rules` | list | `[]` | `TCPRoute.spec.rules`. Defaults to a single rule targeting server Service on port 80. |
| `server.relayTcpRoute.annotations` | object | `{}` | Route annotations |
| `server.relayTcpRoute.labels` | object | `{}` | Extra labels |

#### Server Pod

Expand Down
25 changes: 25 additions & 0 deletions charts/netbird/templates/server-grpc-service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{{- if and .Values.server.grpcService.enabled .Values.server.grpcRoute.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "netbird.server.fullname" . }}-grpc
namespace: {{ .Release.Namespace }}
labels:
{{- include "netbird.server.labels" . | nindent 4 }}
{{- with .Values.server.grpcService.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: ClusterIP
ports:
- name: grpc
port: {{ .Values.server.grpcService.port }}
targetPort: http
protocol: TCP
{{- with .Values.server.grpcService.appProtocol }}
appProtocol: {{ . }}
{{- end }}
selector:
{{- include "netbird.server.selectorLabels" . | nindent 4 }}
{{- end }}
6 changes: 5 additions & 1 deletion charts/netbird/templates/server-grpcroute.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
{{- if .Values.server.grpcRoute.enabled }}
{{- $serviceName := include "netbird.server.fullname" . }}
{{- $backendName := $serviceName }}
{{- if .Values.server.grpcService.enabled }}
{{- $backendName = printf "%s-grpc" $serviceName }}
{{- end }}
{{- $rules := list }}
{{- range .Values.server.grpcRoute.rules }}
{{- $rule := deepCopy . }}
{{- if not (hasKey $rule "backendRefs") }}
{{- $_ := set $rule "backendRefs" (list (dict "name" $serviceName "port" 80)) }}
{{- $_ := set $rule "backendRefs" (list (dict "name" $backendName "port" 80)) }}
{{- end }}
{{- $rules = append $rules $rule }}
{{- end }}
Expand Down
6 changes: 5 additions & 1 deletion charts/netbird/templates/server-relay-httproute.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
{{- if .Values.server.relayHttpRoute.enabled }}
{{- $serviceName := include "netbird.server.fullname" . }}
{{- $backendName := $serviceName }}
{{- if .Values.server.relayService.enabled }}
{{- $backendName = printf "%s-relay" $serviceName }}
{{- end }}
{{- $rules := list }}
{{- range .Values.server.relayHttpRoute.rules }}
{{- $rule := deepCopy . }}
{{- if not (hasKey $rule "backendRefs") }}
{{- $_ := set $rule "backendRefs" (list (dict "name" $serviceName "port" 80)) }}
{{- $_ := set $rule "backendRefs" (list (dict "name" $backendName "port" 80)) }}
{{- end }}
{{- $rules = append $rules $rule }}
{{- end }}
Expand Down
25 changes: 25 additions & 0 deletions charts/netbird/templates/server-relay-service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{{- if and .Values.server.relayService.enabled .Values.server.relayHttpRoute.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "netbird.server.fullname" . }}-relay
namespace: {{ .Release.Namespace }}
labels:
{{- include "netbird.server.labels" . | nindent 4 }}
{{- with .Values.server.relayService.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: ClusterIP
ports:
- name: relay
port: {{ .Values.server.relayService.port }}
targetPort: http
protocol: TCP
{{- with .Values.server.relayService.appProtocol }}
appProtocol: {{ . }}
{{- end }}
selector:
{{- include "netbird.server.selectorLabels" . | nindent 4 }}
{{- end }}
2 changes: 1 addition & 1 deletion charts/netbird/tests/server-deployment_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ tests:
asserts:
- equal:
path: spec.template.spec.containers[0].image
value: "netbirdio/netbird-server:0.72.3"
value: "netbirdio/netbird-server:0.73.2"

- it: should use custom image tag when set
set:
Expand Down
Loading
Loading