Customize GKE Gateway traffic using Service Extensions


This page describes how Google Kubernetes Engine (GKE) uses Service Extensions to add custom logic into Cloud Load Balancing.

This page is intended for GKE Identity and account admins and Developers who need to configure custom traffic management logic using Service Extensions.

Before reading this page, ensure that you're familiar with the following:

Overview

GKE uses Service Extensions to add custom logic into Cloud Load Balancing. An extension attaches to a Gateway and references a Service or a GoogleAPIServiceName. The GoogleAPIServiceName is supported for GCPTrafficExtensions only.

You can modify HTTP headers and payloads for requests and responses, or control traffic routing, without impacting backend service selection or security policies. You can use Service Extensions for tasks such as advanced traffic splitting, custom authentication, or request logging.

The GKE Gateway controller supports the following Service Extensions:

  • GCPRoutingExtension: this extension adds custom logic into Cloud Load Balancing to control traffic routing. It is supported for regional external Application Load Balancer and regional internal Application Load Balancer.

    The `GCPRoutingExtension` resource is attached to a Gateway and
        references a Service. The extension
        controls traffic routing.
    Figure: How `GCPRoutingExtension` works with Gateways
  • GCPTrafficExtension: this extension inserts custom logic to Cloud Load Balancing. It lets an extension service change the headers and payloads of requests and responses. The GCPTrafficExtension does not affect backend service selection or backend service security policies.

    The `GCPTrafficExtension` resource is attached to a Gateway and
        references a Service or a `GoogleAPIServiceName`. The extension
        changes the headers and payloads of requests and responses.
    Figure: How `GCPTrafficExtension` works with Gateways

Google Cloud Service Extension compatibility with GatewayClasses

The following table describes the compatibility of Google Cloud service extensions with different GatewayClasses:

GatewayClass GCPRoutingExtension GCPTrafficExtension
gke-l7-rilb Supported Supported
gke-l7-regional-external-managed Supported Supported
gke-l7-global-external-managed Not supported Supported

Before you begin

Before you start, make sure you have performed the following tasks:

  • Enable the Google Kubernetes Engine API.
  • Enable Google Kubernetes Engine API
  • If you want to use the Google Cloud CLI for this task, install and then initialize the gcloud CLI. If you previously installed the gcloud CLI, get the latest version by running gcloud components update.

GKE Gateway controller requirements

Restrictions and limitations

The following table lists the restrictions associated with the configuration of Gateway Service Extensions in GKE:

Category Restrictions and limitations
Load Balancer The GCPRoutingExtension is supported for only regional external Application Load Balancer and regional internal Application Load Balancer (gke-l7-regional-external-managed and gke-l7-rilb Gateway Classes) and is not supported by the gke-l7-global-external-managed Gateway Class.
Extension chain and specification
  • For a GCPTrafficExtension, each ExtensionChain can have a maximum of 3 Extensions.
  • For a GCPRoutingExtension, each ExtensionChain is limited to 1 Extension.
  • A GCPTrafficExtensionSpec and a GCPRoutingExtensionSpec can each have a maximum of 5 ExtensionChains.
Timing and matching
  • The timeout for each individual message on the stream within an Extension must be between 10 and 1,000 milliseconds. This one-second limit applies to Route and Traffic extensions.
  • Each MatchCondition within an ExtensionChain is limited to a maximum of 10 CELExpressions.
  • The resulting MatchCondition string that is sent to the GCE has a character limit of 512.
  • The CELMatcher string within a CELExpression has a maximum length of 512 characters and must adhere to a specific pattern. We don't support the BackendRefs field from CELExpression.
Header and metadata
  • The ForwardHeaders list in an Extension can contain a maximum of 50 HTTP header names.
  • The Metadata map in an Extension can have a maximum of 16 properties.
  • Keys within the Metadata map must be between 1 and 63 characters long.
  • Values within the Metadata map must be between 1 and 1,023 characters long.
Event
  • For a GCPRoutingExtension, if requestBodySendMode is not set, the supportedEvents list can contain only RequestHeaders events.
  • For a GCPRoutingExtension, if requestBodySendMode is set to FullDuplexStreamed, the supportedEvents list can contain only RequestHeaders, RequestBody, and RequestTrailers events.
GCPTrafficExtension
  • responseBodySendMode field is supported for GCPTrafficExtension only.
  • googleAPIServiceName field is supported for GCPTrafficExtension only.
googleAPIServiceName and backendRef When you reference a Service that uses the backendRef in an Extension, you must meet the following conditions:
  • Must use HTTP2 as its appProtocol.
  • Must be in the same namespace as the Extension and the Gateway that's referenced by the Extension.
  • Cannot use IAP.
  • Cannot use Google Cloud Armor security policies (securityPolicy field from GCPBackendPolicyConfig.
  • Cannot use Cloud CDN.
  • Must set exactly one of either backendRef or googleAPIServiceName for an Extension.
  • Must set authority, if backendRef is set.
  • Must set authority, if googleAPIServiceName is set.
  • Configure requestBodySendMode for extensions by using backendRef only.
  • Configure responseBodySendMode for extensions by using backendRef only.

Configure GKE Service Extensions

You can customize traffic routing, modify request or response payloads, and integrate with external services by configuring GKE Service Extensions. Gateways don't have Service Extensions by default.

To configure GKE Service Extensions:

  1. Deploy a Gateway: to configure a GKE Service extension, you must first deploy a Gateway, which directs the external traffic to your cluster. This can be a global external Application Load Balancer, regional external Application Load Balancer, or regional internal Application Load Balancer Gateway.

    For more information about deploying Gateways, see Deploying Gateways.

  2. Deploy a backend callout Service: create a Kubernetes Service that represents the backend service for custom logic execution. The load balancer invokes this service.

  3. Configure the Service Extensions: configure the appropriate Service Extensions based on your load balancer type and requirements.

    1. GCPRoutingExtension for regional Gateways: use this extension for regional external Application Load Balancer and regional internal Application Load Balancer to implement custom routing logic within the region.

    2. GCPTrafficExtension for global external, regional external, and internal Gateways: use this extension for global external Application Load Balancer, regional external Application Load Balancer, and regional internal Application Load Balancer to perform traffic manipulation, such as header modification or payload inspection, across various load balancer types.

Deploy a backend callout service

A callout service implements custom logic for Gateway Service Extensions in GKE. The Gateway invokes these backend applications, based on GCPTrafficExtension or GCPRoutingExtension configurations, to modify or route traffic.

You deploy a callout service to add custom logic to your Gateway. This separate service handles custom processing, such as header manipulation, payload transformations, or traffic routing.

To deploy a backend service that can function as a callout for your Gateway, perform the following steps:

  1. (Optional) Create a secret for TLS: This command creates a Kubernetes secret of type TLS that contains your TLS certificate and private key.

    To create the TLS secret for your callout service, replace the following:

    • SECRET_NAME: the secret name for your callout service
    • path-to-cert: the file paths to your certificate
    • path-to-key: the file paths to your key
  2. To verify that the secret was added, run the following command:

    kubectl get secrets SECRET_NAME
    

    Replace SECRET_NAME with the secret name for your callout service.

    The output should be similar to the following:

    NAME            TYPE                DATA   AGE
    SECRET_NAME     kubernetes.io/tls   2      12s
    
  3. Define Deployment and Service resources.

    You must define the following:

    • Deployment: to manage the application pods that contain the custom logic for your Service Extensions.
    • Service: to expose the application pods that are managed by the Deployment as a network service.
    1. Create a sample manifest extension-service-app.yaml that has Deployment and Service definitions:

      apiVersion: apps/v1
      kind: Deployment
      metadata:
      name: extension-service-app
      spec:
      selector:
          matchLabels:
            app: store
        replicas: 1
        template:
          metadata:
            labels:
              app: store
          spec:
            containers:
            - name: serviceextensions
              image: us-docker.pkg.dev/service-extensions-samples/callouts/python-example-basic:main
              ports:
              - containerPort: 8080
              - containerPort: 443
              volumeMounts:
              - name: certs
                mountPath: "/etc/certs/"
                readOnly: true
              env:
              - name: POD_NAME
                valueFrom:
                  fieldRef:
                    fieldPath: metadata.name
              - name: NAMESPACE
                valueFrom:
                  fieldRef:
                    fieldPath: metadata.namespace
              - name: TLS_SERVER_CERT
                value: "/etc/certs/path-to-cert"
              - name: TLS_SERVER_PRIVKEY
                value: "/etc/certs/path-to-key"
                resources:
                requests:
                  cpu: 10m
            volumes:
            - name: certs
              secret:
                secretName: SECRET_NAME
                optional: false
      ---
      apiVersion: v1
      kind: Service
      metadata:
        name: extension-service
      spec:
        ports:
        - port: 443
          targetPort: 443
          appProtocol: HTTP2
        selector:
          app: store
      
    2. Apply the extension-service-app.yaml manifest:

      kubectl apply -f extension-service-app.yaml
      
  4. Verify your configuration:

    1. Verify that the application was deployed:

      kubectl get pod --selector app=store
      

      After the application starts running, the output is similar to the following:

      NAME                                     READY   STATUS    RESTARTS   AGE
      extension-service-app-85f466bc9b-b5mf4   1/1     Running   0          7s
      
    2. Verify that the Service was deployed:

      kubectl get service extension-service
      

      The output is similar to the following, which shows a Service for each store Deployment:

      NAME                TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
      extension-service   ClusterIP   34.118.225.9   <none>        443/TCP   2m40s
      

Configure the Service Extensions

You can configure either a GCPRoutingExtension or a GCPTrafficExtension to customize your traffic flow.

Configure the GCPRoutingExtension for regional Gateways

You can reroute traffic by using a GCPRoutingExtension. To configure a GCPRoutingExtension, update the HTTPRoute to specify the requests for the service-extensions.com host.

  1. Update HTTPRoute. Modify your HTTPRoute to include hostnames or paths that will trigger the routing extension.

    1. Save the following sample manifest as the store-route.yaml file:

      kind: HTTPRoute
      apiVersion: gateway.networking.k8s.io/v1
      metadata:
        name: store
      spec:
        parentRefs:
        - kind: Gateway
          name:GATEWAY_NAME
        hostnames:
        - "store.example.com"
        - "service-extensions.example.com"
        rules:
        - backendRefs:
          - name: store-v1
            port: 8080
        - matches:
          - headers:
            - name: env
              value: canary
          backendRefs:
          - name: store-v2
            port: 8080
        - matches:
          - path:
              value: /de
          backendRefs:
          - name: store-german
            port: 8080
      

      Replace GATEWAY_NAME with the name of your Gateway.

    2. Apply the store-route.yaml manifest:

      kubectl apply -f store-route.yaml
      
  2. Define the GCPRoutingExtension.

    1. Save the GCPRoutingExtension configuration in the sample gcp-routing-extension.yaml file:

      kind: GCPRoutingExtension
      apiVersion: networking.gke.io/v1
      metadata:
        name: my-gateway-extension
        namespace: default
      spec:
        targetRefs:
        - group: "gateway.networking.k8s.io"
          kind: Gateway
          name: GATEWAY_NAME
        extensionChains:
        - name: chain1
          matchCondition:
            celExpressions:
            - celMatcher: request.path.contains("serviceextensions")
          extensions:
          - name: ext1
            authority: "myext.com"
            timeout: 1s
              backendRef:
                group: ""
              kind: Service
              name: extension-service
              port: 443
      

      Replace GATEWAY_NAME with the name of your Gateway.

    2. Apply the sample manifest to your cluster:

      kubectl apply -f gcp-routing-extension.yaml
      
  3. Verify the configuration of the GCPRoutingExtension and its binding to the Gateway.

    1. Check the GCPRoutingExtension deployment:

      kubectl describe gcproutingextension my-gateway-extension
      

      The output is similar to the following:

      Name:         my-gateway-extension
      Namespace:    default
      Labels:       <none>
      Annotations:  <none>
      API Version:  networking.gke.io/v1
      Kind:         GCPRoutingExtension
      Metadata:
        Creation Timestamp:  2025-03-02T17:12:30Z
        Generation:        1
        Resource Version:  31283253
        UID:               ec8efaa0-d8e7-4e1b-9fd4-0ae0ef3c74d0
      Spec:
        Extension Chains:
          Extensions:
            Authority:    myext.com
            Backend Ref:
              Group:
              Kind: Service
              Name: extension-service
              Port: 443
            Name:       ext1
            Timeout:    1s
          Match Condition:
            Cel Expressions:
              Cel Matcher: request.path.contains("serviceextensions")
          Name:  chain1
        Target Refs:
          Group: gateway.networking.k8s.io
          Kind: Gateway
          Name: GATEWAY_NAME
      Events:  <none>
      

      The output displays the details of the GCPRoutingExtension, which is named my-gateway-extension, within the default namespace. The output shows the Spec field, which contains the definition of how the extension should behave.

    2. Verify the Gateway binding:

      1. Confirm that the GCPRoutingExtension is bound to the Gateway. This might take a few minutes:

        kubectl describe gateway GATEWAY_NAME
        

        The output is similar to the following:

        Name:         GATEWAY_NAME
        Namespace:    default
        Labels:       none
        Annotations:  networking.gke.io/addresses: /projects/1234567890/regions/us-central1/addresses/test-hgbk-default-internal-http-5ypwen3x2gcr
                      networking.gke.io/backend-services:
                        /projects/1234567890/regions/us-central1/backendServices/test-hgbk-default-extension-service-443-rduk21fwhoj0, /projects/1234567890/re...
                      networking.gke.io/firewalls: /projects/1234567890/global/firewalls/test-hgbk-l7-default-us-central1
                      networking.gke.io/forwarding-rules: /projects/1234567890/regions/us-central1/forwardingRules/test-hgbk-default-internal-http-qn7dk9i9zm73
                      networking.gke.io/health-checks:
                        /projects/1234567890/regions/us-central1/healthChecks/test-hgbk-default-extension-service-443-rduk21fwhoj0, /projects/1234567890/regio...
                      networking.gke.io/last-reconcile-time: 2025-03-02T17:15:02Z
                      networking.gke.io/lb-route-extensions:
                        /projects/1234567890/locations/us-central1/lbRouteExtensions/test-hgbk-default-internal-http-lwh0op4qorb0
                      networking.gke.io/lb-traffic-extensions:
                      networking.gke.io/ssl-certificates:
                      networking.gke.io/target-http-proxies:
                        /projects/1234567890/regions/us-central1/targetHttpProxies/test-hgbk-default-internal-http-2jzr7e3xclhj
                      networking.gke.io/target-https-proxies:
                      networking.gke.io/url-maps: /projects/1234567890/regions/us-central1/urlMaps/test-hgbk-default-internal-http-2jzr7e3xclhj
        API Version:  gateway.networking.k8s.io/v1
        Kind:         Gateway
        Metadata:
          Creation Timestamp:  2025-03-02T16:37:50Z
          Finalizers:
          gateway.finalizer.networking.gke.io
          Generation:        1
          Resource Version:  31284863
          UID:               fd512611-bad2-438e-abfd-5619474fbf31
        ...
        

        The output shows the annotations, which GKE uses to store the links between the Gateway and the underlying Google Cloud resources. The networking.gke.io/lb-route-extensions annotation confirms the binding of the gateway to the GCPRoutingExtension.

      2. Check the extension status by confirming that the GCPRoutingExtension has a Reconciled status with the ReconciliationSucceeded reason. This command might take a few minutes.

        kubectl describe gcproutingextension my-gateway-extension
        

        The output is similar to the following:

        Name:         my-gateway-extension
        Namespace:    default
        Labels:       <none>
        Annotations:  <none>
        API Version:  networking.gke.io/v1
        Kind:         GCPRoutingExtension
        Metadata:
          Creation Timestamp:  2025-03-02T17:12:30Z
          Generation:          1
          Resource Version:    31284378
          UID:                 ec8efaa0-d8e7-4e1b-9fd4-0ae0ef3c74d0
        Spec:
          Extension Chains:
            Extensions:
              Authority:  myext.com
              Backend Ref:
                Group:
                Kind:   Service
                Name:   extension-service
                Port:   443
              Name:     ext1
              Timeout:  1s
            Match Condition:
              Cel Expressions:
                Cel Matcher:  request.path.contains("serviceextensions")
            Name:             chain1
          Target Refs:
            Group:  gateway.networking.k8s.io
            Kind:   Gateway
            Name:   GATEWAY_NAME
        Status:
          Ancestors:
            Ancestor Ref:
              Group:      gateway.networking.k8s.io
              Kind:       Gateway
              Name:       GATEWAY_NAME
              Namespace:  default
            Conditions:
              Last Transition Time:  2025-03-02T17:14:15Z
              Message:
              Reason:                Accepted
              Status:                True
              Type:                  Accepted
              Last Transition Time:  2025-03-02T17:14:15Z
              Message:
              Reason:                ReconciliationSucceeded
              Status:                True
              Type:                  Reconciled
            Controller Name:         networking.gke.io/gateway
        Events:
          Type    Reason  Age                From                   Message
          ----    ------  ----               ----                   -------
          Normal  ADD     2m31s              sc-gateway-controller  default/my-gateway-extension
        Normal  SYNC    51s (x2 over 98s)  sc-gateway-controller  Attachment of GCPRoutingExtension "default/my-gateway-extension" to AncestorRef {Group:       "gateway.networking.k8s.io",
        Kind:        "Gateway",
        Namespace:   "default",
        Name:        "GATEWAY_NAME",
        SectionName: nil,
        Port:        nil} was a success
          Normal  SYNC    23s           sc-gateway-controller  Reconciliation of GCPRoutingExtension "default/my-gateway-extension" to AncestorRef {Group:       "gateway.networking.k8s.io",
        Kind:        "Gateway",
        Namespace:   "default",
        Name:        "GATEWAY_NAME",
        SectionName: nil,
        Port:        nil} was a success
        

        The Status.Conditions field shows a Reconciled condition with Status: True and Reason: ReconciliationSucceeded.

        True and Reason: ReconciliationSucceeded. This information confirms that the extension was successfully applied.

  4. Send traffic to your application.

    After your Gateway, Route, and application are deployed in your cluster, you can pass traffic to your application.

    1. To access your application, you need to find the IP address of your Gateway.

      In your terminal, use the following command:

      kubectl get gateways.gateway.networking.k8s.io GATEWAY_NAME -o=jsonpath="{.status.addresses[0].value}"
      

      Replace GATEWAY_NAME with the name of your Gateway.

      This command outputs the Gateway's IP address. In the follow-up commands, replace GATEWAY_IP_ADDRESS with the IP address from the output.

    2. Test the path update by going to the serviceextensions version of the store service at store.example.com/serviceextensions:

      curl http://store.example.com/serviceextensions --resolve store.example.com:80:GATEWAY_IP_ADDRESS -v
      

      The output is similar to the following:

      {
      "cluster_name": "gke1",
      "host_header": "service-extensions.com",
      "metadata": "store-v1",
      "pod_name": "store-v1-5d9554f847-cvxpd",
      "pod_name_emoji": "💇🏼‍♀️",
      "project_id": "gateway-demo",
      "timestamp": "2025-03-15T12:00:00",
      "zone": "us-central1-c"
      }
      

Configure the GCPTrafficExtension

You can use a GCPTrafficExtension to use advanced traffic management capabilities within your Google Cloud environment. You can configure this extension across global external Application Load Balancers, regional external Application Load Balancers, and regional internal Application Load Balancers. You can use GCPTrafficExtension to implement custom request and response logic, sophisticated routing, transformations, and security policies.

  1. Update HTTPRoute. Modify your HTTPRoute to include hostnames or paths that will trigger the traffic extension.

    1. Save the following sample manifest as the store-route.yaml file:

      kind: HTTPRoute
      apiVersion: gateway.networking.k8s.io/v1
      metadata:
        name: store
      spec:
        parentRefs:
        - kind: Gateway
          name: GATEWAY_NAME
        hostnames:
        - "store.example.com"
        - "service-extensions.example.com"
        rules:
        - backendRefs:
          - name: store-v1
            port: 8080
        - matches:
          - headers:
            - name: env
              value: canary
          backendRefs:
          - name: store-v2
            port: 8080
        - matches:
          - path:
              value: /de
          backendRefs:
          - name: store-german
            port: 8080
      

      Replace GATEWAY_NAME with the name of your Gateway, such as internal-http, external-http, or global-external-http.

    2. Apply the store-route.yaml manifest to your cluster:

      kubectl apply -f store-route.yaml
      
  2. Define the GCPTrafficExtension.

    1. Save the GCPTrafficExtension configuration to the sample gcp-traffic-extension.yaml file:

      kind: GCPTrafficExtension
      apiVersion: networking.gke.io/v1
      metadata:
        name: my-traffic-extension
        namespace: default
      spec:
        targetRefs:
        - group: "gateway.networking.k8s.io"
          kind: Gateway
          name: GATEWAY_NAME
        extensionChains:
        - name: chain1
          matchCondition:
            celExpressions:
            - celMatcher: request.path.contains("serviceextensions")
          extensions:
          - name: ext1
            authority: "myext.com"
            timeout: 1s
            backendRef:
              group: ""
              kind: Service
              name: extension-service
              port: 443
      

      Replace GATEWAY_NAME with the name of your Gateway, as internal-http, external-http, or global-external-http.

    2. Apply the sample manifest to your cluster:

      kubectl apply -f gcp-traffic-extension.yaml
      
  3. Verify the configuration of the GCPTrafficExtension and its binding to the Gateway.

    1. Check the GCPTrafficExtension deployment:

      kubectl describe gcptrafficextension my-traffic-extension
      

      The output is similar to the following:

      Name:         my-traffic-extension
      Namespace:    default
      Labels:       <none>
      Annotations:  <none>
      API Version:  networking.gke.io/v1
      Kind:         GCPTrafficExtension
      Metadata:
        Creation Timestamp:  2025-03-02T17:12:30Z
        Generation:        1
        Resource Version:  31283253
        UID:               ec8efaa0-d8e7-4e1b-9fd4-0ae0ef3c74d0
      Spec:
        Extension Chains:
          Extensions:
            Authority:    myext.com
            Backend Ref:
              Group:
              Kind: Service
              Name: extension-service
              Port: 443
            Name:       ext1
            Timeout:    1s
          Match Condition:
            Cel Expressions:
              Cel Matcher: request.path.contains("serviceextensions")
          Name:  chain1
        Target Refs:
          Group: gateway.networking.k8s.io
          Kind: Gateway
          Name: GATEWAY_NAME
      Events:  <none>
      

      The output displays the details of the GCPTrafficExtension named my-traffic-extension within the default namespace. It shows the Spec field, which contains the definition of how the extension should behave.

    2. Verify the Gateway binding:

      Confirm that the GCPTrafficExtension is bound to the Gateway. This command might take a few minutes to complete:

      kubectl describe gateway GATEWAY_NAME
      

      The output is similar to the following:

      Name:         GATEWAY_NAME
      Namespace:    default
      Labels:       <none>
      Annotations:  networking.gke.io/addresses: /projects/1234567890/regions/us-central1/addresses/test-hgbk-default-internal-http-5ypwen3x2gcr
                    networking.gke.io/backend-services:
                      /projects/1234567890/regions/us-central1/backendServices/test-hgbk-default-extension-service-443-rduk21fwhoj0, /projects/1234567890/re...
                    networking.gke.io/firewalls: /projects/1234567890/global/firewalls/test-hgbk-l7-default-us-central1
                    networking.gke.io/forwarding-rules: /projects/1234567890/regions/us-central1/forwardingRules/test-hgbk-default-internal-http-qn7dk9i9zm73
                    networking.gke.io/health-checks:
                      /projects/1234567890/regions/us-central1/healthChecks/test-hgbk-default-extension-service-443-rduk21fwhoj0, /projects/1234567890/regio...
                    networking.gke.io/last-reconcile-time: 2025-03-02T17:15:02Z
                    networking.gke.io/lb-traffic-extensions:
                      /projects/1234567890/locations/us-central1/lbTrafficExtensions/test-hgbk-default-internal-http-lwh0op4qorb0
                    networking.gke.io/ssl-certificates:
                    networking.gke.io/target-http-proxies:
                      /projects/1234567890/regions/us-central1/targetHttpProxies/test-hgbk-default-internal-http-2jzr7e3xclhj
                    networking.gke.io/target-https-proxies:
                    networking.gke.io/url-maps: /projects/1234567890/regions/us-central1/urlMaps/test-hgbk-default-internal-http-2jzr7e3xclhj
      API Version:  gateway.networking.k8s.io/v1
      Kind:         Gateway
      Metadata:
        Creation Timestamp:  2025-03-02T16:37:50Z
        Finalizers:
          gateway.finalizer.networking.gke.io
        Generation:        1
        Resource Version:  31284863
        UID:               fd512611-bad2-438e-abfd-5619474fbf31
      ...
      

      The output shows the annotations, which GKE uses to store the links between the Gateway and the underlying Google Cloud resources. The networking.gke.io/lb-traffic-extensions annotation confirms the binding.

    3. Check the extension status:

      Confirm that the GCPTrafficExtension has a Reconciled status with the ReconciliationSucceeded reason. This command might take a few minutes to complete.

      kubectl describe gcptrafficextension my-traffic-extension
      

      The output is similar to the following:

      Name:         my-traffic-extension
      Namespace:    default
      Labels:       <none>
      Annotations:  <none>
      API Version:  networking.gke.io/v1
      Kind:         GCPTrafficExtension
      Metadata:
        Creation Timestamp:  2025-03-02T17:12:30Z
        Generation:          1
        Resource Version:    31284378
        UID:                 ec8efaa0-d8e7-4e1b-9fd4-0ae0ef3c74d0
      Spec:
        Extension Chains:
          Extensions:
            Authority:  myext.com
            Backend Ref:
              Group:
              Kind:   Service
              Name:   extension-service
              Port:   443
            Name:     ext1
            Timeout:  1s
          Match Condition:
            Cel Expressions:
              Cel Matcher:  request.path.contains("serviceextensions")
          Name:             chain1
        Target Refs:
          Group:  gateway.networking.k8s.io
          Kind:   Gateway
          Name:   GATEWAY_NAME
      Status:
        Ancestors:
          Ancestor Ref:
            Group:      gateway.networking.k8s.io
            Kind:       Gateway
            Name:       GATEWAY_NAME
            Namespace:  default
          Conditions:
            Last Transition Time:  2025-03-02T17:14:15Z
            Message:
            Reason:                Accepted
            Status:                True
            Type:                  Accepted
            Last Transition Time:  2025-03-02T17:14:15Z
            Message:
            Reason:                ReconciliationSucceeded
            Status:                True
            Type:                  Reconciled
          Controller Name:         networking.gke.io/gateway
      Events:
        Type    Reason  Age                From                   Message
        ----    ------  ----               ----                   -------
        Normal  ADD     2m31s              sc-gateway-controller  default/my-traffic-extension
        Normal  SYNC    51s (x2 over 98s)  sc-gateway-controller  Attachment of GCPTrafficExtension "default/my-gateway-extension" to AncestorRef {Group:       "gateway.networking.k8s.io",
        Kind:        "Gateway",
        Namespace:   "default",
        Name:        "GATEWAY_NAME",
        SectionName: nil,
        Port:        nil} was a success
        Normal  SYNC    23s           sc-gateway-controller  Reconciliation of GCPTrafficExtension "default/my-traffic-extension" to AncestorRef {Group:       "gateway.networking.k8s.io",
        Kind:        "Gateway",
        Namespace:   "default",
        Name:        "GATEWAY_NAME",
        SectionName: nil,
        Port:        nil} was a success
      

      The Status.Conditions field shows a Reconciled condition with Status: True and Reason: ReconciliationSucceeded. This information confirms that the extension was successfully applied.

  4. Send traffic to your application.

    After your Gateway, Route, and application are deployed in your cluster, you can pass traffic to your application.

    1. To access your application, you need to find the IP address of your Gateway.

      In your terminal, use the following command:

      kubectl get gateways.gateway.networking.k8s.io GATEWAY_NAME -o=jsonpath="{.status.addresses[0].value}"
      

      Replace GATEWAY_NAME with the name of your Gateway.

      This command outputs the Gateway's IP address. In the follow-up commands, replace GATEWAY_IP_ADDRESS with the IP address from the output.

    2. Test the path update by going to the serviceextensions version of the store service at store.example.com/serviceextensions:

      curl http://store.example.com/serviceextensions --resolve store.example.com:80:GATEWAY_IP_ADDRESS -v
      

      The output is similar to the following:

      {
      *   Request completely sent off
      < HTTP/1.1 200 OK
      < server: Werkzeug/2.3.7 Python/3.11.3
      < date: Sun, 02 Mar 2025 16:58:10 GMT
      < content-type: application/json
      < access-control-allow-origin: *
      < hello: service-extensions
      < via: 1.1 google
      < transfer-encoding: chunked
      }
      

Troubleshoot traffic extensions on Gateways

This section provides troubleshooting tips for configuring traffic extensions on Gateways.

Gateway not found

The following error indicates that the Gateway resource specified in the targetRefs field of the GCPTrafficExtension or GCPRoutingExtension resource does not exist:

error: failed to create resource: GCPTrafficExtension.networking.gke.io "my-traffic-extension" is invalid: spec.gatewayRef: gateway "my-gateway" not found in namespace "default"

To resolve this issue, ensure that the Gateway resource specified in the targetRefs field of the GCPTrafficExtension or GCPRoutingExtension resource exists in the specified namespace.

Service or service port not found

The following error indicates that the Service or Service port specified in the backendRef field of the GCPTrafficExtension or GCPRoutingExtension resource does not exist:

error: failed to create resource: GCPTrafficExtension.networking.gke.io "my-traffic-extension" is invalid: spec.service: service "callout-service" not found in namespace "default"

To resolve this issue, ensure that the Service and Service port specified in the backendRef field of the GCPTrafficExtension or GCPRoutingExtension resource exist in the specified namespace.

No network endpoints in the NEG

The following error indicates that there are no network endpoints in the NEG are associated with the Service specified in the backendRef field of the GCPTrafficExtension or GCPRoutingExtension resource:

error: failed to create resource: GCPTrafficExtension.networking.gke.io "my-traffic-extension" is invalid: spec.service: no network endpoints found for service "callout-service"

To resolve this issue, ensure that the Service specified in the backendRef field of the GCPTrafficExtension or GCPRoutingExtension resource has network endpoints.

No reply or reply with an error when sending the request

If you don't receive a reply, or if you receive a reply with an error when you send a request, it might indicate that the callout Service is not working correctly.

To resolve this issue, check the logs of the callout Service for any errors.

Error code 404 in the JSON payload

The following error indicates that the callout Service is not found or is not responding to the request:

{
  "error": {
    "code": 404,
    "message": "Requested entity was not found.",
    "status": "NOT_FOUND"
  }
}

To resolve this issue, ensure that the callout Service is running, that it is listening on the correct port, and that the service is correctly configured in the GCPTrafficExtension or GCPRoutingExtension resource.

Error code 500 in the JSON payload

The following error indicates that the callout Service is experiencing an internal server error:

{
  "error": {
    "code": 500,
    "message": "Internal server error.",
    "status": "INTERNAL"
  }
}

To resolve this issue, check the logs of the callout Service to identify the cause of the internal server error.

What's next