DEV Community

Kahiro Okina
Kahiro Okina

Posted on

Extending Knative Service with Envoy Gateway Integration

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Setup Steps

1. Create a kind Cluster

First, create a local Kubernetes cluster with kind.

kind create cluster
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"}}'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

9. Deploy Gateway API Resources

Deploy the Gateway resources for Knative Serving.

kubectl apply -f ./third_party/envoy-gateway
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

11. Configure Domain

Set up a test domain configuration.

kubectl patch configmap config-domain -n knative-serving --type merge -p '{"data":{"example.com":""}}'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Add the obtained IP address to /etc/hosts.

echo "$LB_IP helloworld-go.default.example.com" | sudo tee -a /etc/hosts
Enter fullscreen mode Exit fullscreen mode

2. Access Test

curl http://helloworld-go.default.example.com
Enter fullscreen mode Exit fullscreen mode

If deployed successfully, you should receive a response like:

Hello Go Sample v1!
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

2. Create Secret

Register the created authentication file as a Kubernetes Secret.

kubectl -n default create secret generic basic-auth --from-file=.htpasswd
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

4. Verification

Accessing without authentication returns a 401 error.

curl http://helloworld-go.default.example.com
# 401 Unauthorized
Enter fullscreen mode Exit fullscreen mode

Accessing with authentication credentials returns a successful response.

curl -u foo:bar http://helloworld-go.default.example.com
# Hello Go Sample v1!
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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: {}
Enter fullscreen mode Exit fullscreen mode

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 }
Enter fullscreen mode Exit fullscreen mode

Top comments (0)