Introduction
This article demonstrates how to run Knative Serving in combination with Envoy Gateway and how to extend its functionality.
Prerequisites
The following tools must be installed:
- kind
- kubectl
- helm
- ko
- Go
Installing cloud-provider-kind
cloud-provider-kind is a tool that enables LoadBalancer Services in kind clusters. Install it with the following command:
go install sigs.k8s.io/cloud-provider-kind@latest
Installing ko
ko is a tool for building and deploying applications written in Go as container images. Install it with the following command:
go install github.com/google/ko@latest
Setup Steps
1. Create a kind Cluster
First, create a local Kubernetes cluster with kind.
kind create cluster
2. Install Knative Serving
Install the Knative Serving CRDs and core components.
kubectl apply -f https://github.com/knative/serving/releases/latest/download/serving-crds.yaml
kubectl apply -f https://github.com/knative/serving/releases/latest/download/serving-core.yaml
3. Configure Knative Serving for Gateway API
Modify the Knative Serving network configuration to use Gateway API.
kubectl patch configmap/config-network \
-n knative-serving \
--type merge \
-p '{"data":{"ingress.class":"gateway-api.ingress.networking.knative.dev"}}'
4. Create values File for Envoy Gateway
Create a configuration file for Envoy Gateway. This configuration deploys Envoy Proxy in the same namespace where the Gateway is deployed.
cat <<'YAML' > values-eg.yaml
config:
envoyGateway:
provider:
type: Kubernetes
kubernetes:
deploy:
type: GatewayNamespace
YAML
5. Install Envoy Gateway
Install Envoy Gateway using Helm.
export ENVOY_GATEWAY_VERSION=v1.6.1
helm install eg oci://docker.io/envoyproxy/gateway-helm \
--version $ENVOY_GATEWAY_VERSION \
-n envoy-gateway-system --create-namespace \
-f ./values-eg.yaml
6. Start cloud-provider-kind
To enable LoadBalancer Services, open a separate terminal and start cloud-provider-kind. This process needs to remain running.
# Run in a separate terminal
sudo cloud-provider-kind
Leave this terminal running and return to the original terminal to continue with the following steps.
7. Clone the net-gateway-api Repository
Clone the repository containing resources for integrating Knative Serving with Gateway API.
git clone https://github.com/knative-extensions/net-gateway-api.git
cd net-gateway-api
8. Deploy Gateway API Integration Components
Use ko to deploy the components that integrate Knative Serving with Gateway API.
export KO_DOCKER_REPO=kind.local
ko apply -f config/
Note: If you created your KinD cluster with the --name option (e.g., kind create cluster --name my-cluster), you need to additionally set the KIND_CLUSTER_NAME environment variable as follows:
export KO_DOCKER_REPO=kind.local
export KIND_CLUSTER_NAME=my-cluster
ko apply -f config/
9. Deploy Gateway API Resources
Deploy the Gateway resources for Knative Serving.
kubectl apply -f ./third_party/envoy-gateway
10. Apply Gateway Configuration ConfigMap
Add configuration so that Knative Serving recognizes Envoy Gateway.
cat <<'YAML' | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: config-gateway
namespace: knative-serving
labels:
app.kubernetes.io/component: net-gateway-api
app.kubernetes.io/name: knative-serving
serving.knative.dev/release: devel
data:
external-gateways: |
- class: eg-external
gateway: eg-external/eg-external
service: eg-external/knative-external
supported-features:
- HTTPRouteRequestTimeout
# local-gateways defines the Gateway to be used for cluster local traffic
local-gateways: |
- class: eg-internal
gateway: eg-internal/eg-internal
service: eg-internal/knative-internal
supported-features:
- HTTPRouteRequestTimeout
YAML
11. Configure Domain
Set up a test domain configuration.
kubectl patch configmap config-domain -n knative-serving --type merge -p '{"data":{"example.com":""}}'
12. Deploy Sample Application
Deploy a sample application for testing.
cat <<'YAML' | kubectl apply -f -
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: helloworld-go
spec:
template:
spec:
containers:
- image: gcr.io/knative-samples/helloworld-go
env:
- name: TARGET
value: Go Sample v1
YAML
Verification
1. Get LoadBalancer IP and Configure /etc/hosts
Get the LoadBalancer IP address.
export LB_IP=$(kubectl -n eg-external get svc knative-external -o jsonpath='{.status.loadBalancer.ingress[*].ip}{"\n"}')
echo $LB_IP
Add the obtained IP address to /etc/hosts.
echo "$LB_IP helloworld-go.default.example.com" | sudo tee -a /etc/hosts
2. Access Test
curl http://helloworld-go.default.example.com
If deployed successfully, you should receive a response like:
Hello Go Sample v1!
Advanced: Extending Knative Service with Envoy Gateway
One advantage of using Envoy Gateway is the ability to add functionality using resources like SecurityPolicy without modifying the Knative Service itself. Here's an example of adding Basic authentication.
Adding Basic Authentication
1. Create Authentication Credentials
First, create an authentication file with the htpasswd command (username: foo, password: bar).
htpasswd -cbs .htpasswd foo bar
2. Create Secret
Register the created authentication file as a Kubernetes Secret.
kubectl -n default create secret generic basic-auth --from-file=.htpasswd
3. Apply SecurityPolicy
Use Envoy Gateway's SecurityPolicy resource to add Basic authentication to the HTTPRoute.
cat <<'YAML' | kubectl apply -f -
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
name: helloworld-go-basic-auth
namespace: default
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: helloworld-go.default.example.com
basicAuth:
users:
name: basic-auth
YAML
4. Verification
Accessing without authentication returns a 401 error.
curl http://helloworld-go.default.example.com
# 401 Unauthorized
Accessing with authentication credentials returns a successful response.
curl -u foo:bar http://helloworld-go.default.example.com
# Hello Go Sample v1!
This demonstrates how Basic authentication can be easily added using Envoy Gateway's SecurityPolicy without modifying the Knative Service or HTTPRoute manifests at all. Similarly, various features such as rate limiting, CORS configuration, JWT verification, etc., can be added at the Gateway layer, so please refer to Security | Envoy Gateway.
Summary
This article introduced how to run Knative Serving in combination with Envoy Gateway.
Envoy Gateway is a high-performance official Envoy gateway implementation, and its combination with Knative Serving enables flexible control and extension of serverless application routing.
Reference Links
- Knative Serving
- Basic Authentication | Envoy Gateway
- knative-extensions/net-gateway-api
- Kubernetes Gateway API
Notes
HTTPRoute Output When Traffic Splitting is Enabled
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
annotations:
networking.internal.knative.dev/rollout: '{"configurations":[{"configurationName":"helloworld-go","percent":50,"revisions":[{"revisionName":"helloworld-go-00005","percent":50}],"stepParams":{}},{"configurationName":"helloworld-go","tag":"qa","percent":100,"revisions":[{"revisionName":"helloworld-go-00005","percent":100}],"stepParams":{}}]}'
networking.knative.dev/ingress.class: gateway-api.ingress.networking.knative.dev
serving.knative.dev/creator: kubernetes-admin
serving.knative.dev/lastModifier: kubernetes-admin
creationTimestamp: "2025-11-09T05:41:51Z"
generation: 33
labels:
networking.knative.dev/visibility: ""
serving.knative.dev/route: helloworld-go
serving.knative.dev/routeNamespace: default
serving.knative.dev/service: helloworld-go
name: helloworld-go.default.example.com
namespace: default
ownerReferences:
- apiVersion: networking.internal.knative.dev/v1alpha1
blockOwnerDeletion: true
controller: true
kind: Ingress
name: helloworld-go
uid: f15718d7-91ba-482f-8322-27968a218697
resourceVersion: "220984"
uid: 07fca6bb-1e7a-4a53-8e09-107df56fcb25
spec:
hostnames:
- helloworld-go.default.example.com
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: eg-external
namespace: eg-external
rules:
- backendRefs:
- filters:
- requestHeaderModifier:
set:
- name: Knative-Serving-Namespace
value: default
- name: Knative-Serving-Revision
value: helloworld-go-00003
type: RequestHeaderModifier
group: ""
kind: Service
name: helloworld-go-00003
port: 80
weight: 50
- filters:
- requestHeaderModifier:
set:
- name: Knative-Serving-Namespace
value: default
- name: Knative-Serving-Revision
value: helloworld-go-00005
type: RequestHeaderModifier
group: ""
kind: Service
name: helloworld-go-00005
port: 80
weight: 50
filters:
- requestHeaderModifier:
set:
- name: K-Network-Hash
value: f3149388225bc6465417757efc25ea3b739741e2037cf7a5fc49772260fc882f
type: RequestHeaderModifier
matches:
- headers:
- name: K-Network-Hash
type: Exact
value: override
path:
type: PathPrefix
value: /
timeouts:
request: 0s
- backendRefs:
- filters:
- requestHeaderModifier:
set:
- name: Knative-Serving-Namespace
value: default
- name: Knative-Serving-Revision
value: helloworld-go-00003
type: RequestHeaderModifier
group: ""
kind: Service
name: helloworld-go-00003
port: 80
weight: 50
- filters:
- requestHeaderModifier:
set:
- name: Knative-Serving-Namespace
value: default
- name: Knative-Serving-Revision
value: helloworld-go-00005
type: RequestHeaderModifier
group: ""
kind: Service
name: helloworld-go-00005
port: 80
weight: 50
matches:
- path:
type: PathPrefix
value: /
timeouts:
request: 0s
status:
parents:
- conditions:
- lastTransitionTime: "2025-11-10T04:00:42Z"
message: Route is accepted
observedGeneration: 33
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: "2025-11-10T04:00:42Z"
message: Resolved all the Object references for the Route
observedGeneration: 33
reason: ResolvedRefs
status: "True"
type: ResolvedRefs
controllerName: gateway.envoyproxy.io/gatewayclass-controller
parentRef:
group: gateway.networking.k8s.io
kind: Gateway
name: eg-external
namespace: eg-external
HTTPRoute Output When Using DomainMapping
Apply the following:
apiVersion: networking.internal.knative.dev/v1alpha1
kind: ClusterDomainClaim
metadata:
name: test.com
spec:
namespace: default
---
apiVersion: serving.knative.dev/v1beta1
kind: DomainMapping
metadata:
name: test.com
namespace: default
spec:
ref:
name: helloworld-go
kind: Service
apiVersion: serving.knative.dev/v1
The following is output:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
annotations:
networking.knative.dev/ingress.class: gateway-api.ingress.networking.knative.dev
serving.knative.dev/creator: kubernetes-admin
serving.knative.dev/lastModifier: kubernetes-admin
creationTimestamp: "2025-11-10T04:48:55Z"
generation: 1
labels:
networking.knative.dev/visibility: ""
serving.knative.dev/domainMappingNamespace: default
serving.knative.dev/domainMappingUID: 88954e71-03c4-4004-81e1-2916c6ba976c
name: test.com
namespace: default
ownerReferences:
- apiVersion: networking.internal.knative.dev/v1alpha1
blockOwnerDeletion: true
controller: true
kind: Ingress
name: test.com
uid: d7351ea1-9e67-4ebc-80cb-4227195362c1
resourceVersion: "231722"
uid: 45f70e96-0f30-4cda-b1a2-f6f1a25dad0b
spec:
hostnames:
- test.com
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: eg-external
namespace: eg-external
rules:
- backendRefs:
- filters:
- requestHeaderModifier:
set:
- name: K-Original-Host
value: test.com
type: RequestHeaderModifier
group: ""
kind: Service
name: helloworld-go
port: 80
weight: 100
filters:
- requestHeaderModifier:
set:
- name: K-Network-Hash
value: 244b84148c16d15938fd7f3ab2d377b0b40f6418229edee9af3fe0d961fd152a
type: RequestHeaderModifier
- type: URLRewrite
urlRewrite:
hostname: helloworld-go.default.svc.cluster.local
matches:
- headers:
- name: K-Network-Hash
type: Exact
value: override
path:
type: PathPrefix
value: /
timeouts:
request: 0s
- backendRefs:
- filters:
- requestHeaderModifier:
set:
- name: K-Original-Host
value: test.com
type: RequestHeaderModifier
group: ""
kind: Service
name: helloworld-go
port: 80
weight: 100
filters:
- type: URLRewrite
urlRewrite:
hostname: helloworld-go.default.svc.cluster.local
matches:
- path:
type: PathPrefix
value: /
timeouts:
request: 0s
status:
parents:
- conditions:
- lastTransitionTime: "2025-11-10T04:48:55Z"
message: Route is accepted
observedGeneration: 1
reason: Accepted
status: "True"
type: Accepted
- lastTransitionTime: "2025-11-10T04:48:55Z"
message: Resolved all the Object references for the Route
observedGeneration: 1
reason: ResolvedRefs
status: "True"
type: ResolvedRefs
controllerName: gateway.envoyproxy.io/gatewayclass-controller
parentRef:
group: gateway.networking.k8s.io
kind: Gateway
name: eg-external
namespace: eg-external
This helloworld-go.default.svc.cluster.local points to the Envoy Proxy of the internal Envoy Gateway.
apiVersion: v1
kind: Service
metadata:
annotations:
serving.knative.dev/creator: kubernetes-admin
serving.knative.dev/lastModifier: kubernetes-admin
creationTimestamp: "2025-11-09T05:41:51Z"
labels:
serving.knative.dev/route: helloworld-go
serving.knative.dev/service: helloworld-go
name: helloworld-go
namespace: default
ownerReferences:
- apiVersion: serving.knative.dev/v1
blockOwnerDeletion: true
controller: true
kind: Route
name: helloworld-go
uid: 24cc3f82-f7b3-40d9-89ce-ea516e46382f
resourceVersion: "56240"
uid: c6ea6306-abf9-49c0-8a28-9a71c9a3a99a
spec:
externalName: knative-internal.eg-internal.svc.cluster.local
ports:
- appProtocol: kubernetes.io/h2c
name: http2
port: 80
protocol: TCP
targetPort: 80
sessionAffinity: None
type: ExternalName
status:
loadBalancer: {}
Therefore, for example, when exposed via ALB, the path from external traffic would be as follows, which includes one additional hop through the Internal Envoy Proxy compared to the typical path exposed through External Envoy:
flowchart LR
A["ALB"]
subgraph K8S["Kubernetes"]
direction LR
subgraph EXT["Namespace: eg-external"]
B["External Envoy Proxy"]
end
subgraph INT["Namespace: eg-internal"]
C["Internal Envoy Proxy"]
end
subgraph KS["Namespace: knative-serving"]
D["Knative Activator"]
end
subgraph P["Knative Revision Pod"]
direction LR
E["Queue Proxy (sidecar)"]
F["User App Container"]
end
end
A e1@==> B
B e2@==> C
C e3@==> D
D e4@==> E
E e5@==> F
e1@{ animate: true }
e2@{ animate: true }
e3@{ animate: true }
e4@{ animate: true }
e5@{ animate: true }

Top comments (0)