Help Center/ Cloud Container Engine/ User Guide/ Networking/ Gateway API/ Using Envoy Gateway for Service Access
Updated on 2026-06-16 GMT+08:00

Using Envoy Gateway for Service Access

Gateway API is the Kubernetes API for routing and load balancing. CCE provides Envoy Gateway, an Envoy-based implementation of the Gateway API. This add-on is fully compatible with the upstream Kubernetes Gateway API and supports flexible traffic routing configurations. This section describes the core capabilities and configuration methods supported by Envoy Gateway.

Precautions

  • When you create a Gateway, a load balancer is automatically created based on the Envoy Gateway configuration. Automatically created load balancers are billed on a pay-per-use basis.
  • After Envoy Gateway is installed, a GatewayClass named envoy-gateway is automatically created. After the add-on is uninstalled, the GatewayClass is retained. Do not manually delete it. To delete a GatewayClass after the add-on is uninstalled, manually remove its finalizers if no Gateway is associated with it. Otherwise, resources may disappear or functionality may be abnormal after the add-on is reinstalled.
  • Before deleting a custom GatewayClass, ensure the Gateway associated with it is deleted.
  • Uninstalling the add-on does not delete the created Gateway and its related resources (including Deployments, Services, ConfigMaps, and ServiceAccounts). Manually delete the Gateway resources managed by the add-on before uninstalling it. After the add-on is uninstalled, whether deleting a Gateway also deletes its associated resources depends on the deployment mode of the Envoy Gateway data plane. With the add-on's default configuration, deleting a Gateway will also delete its associated resources.
  • In CCE standard clusters, when Envoy Gateway is bound to a dedicated load balancer, the node hosting the add-on controller pod (with the same Gateway name in the same namespace), and any other containers on that same node, cannot use the load balancer's private IP to access ingresses.
  • After the add-on is configured, the associated load balancer configuration is persisted in the add-on's ConfigMap. The add-on pods use a built-in watch mechanism to monitor ConfigMap changes in real time and automatically synchronize the latest configuration. Therefore, it usually takes tens of seconds for the updated load balancing configuration to take effect.

Prerequisites

  • Envoy Gateway has been installed in the current cluster and is running.
  • A backend application and its Service are running in the cluster.
    In this example, the backend application is Nginx, the Service name is backend, and the Service port is 3000. Save the following YAML as backend.yaml and run kubectl apply -f backend.yaml:
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: backend
      namespace: default
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: backend
          version: v1
      template:
        metadata:
          labels:
            app: backend
            version: v1
        spec:
          containers:
            - name: container-1
              image: nginx:latest
          imagePullSecrets:
            - name: default-secret
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: backend
      labels:
        app: backend
        version: v1
      namespace: default
    spec:
      selector:
        app: backend
        version: v1
      ports:
        - name: cce-service-0
          targetPort: 80
          port: 3000
          protocol: TCP
      type: ClusterIP

Step 1: Create a Gateway

  1. Log in to the CCE console and click the cluster name to access the cluster console.
  2. In the navigation pane, choose Services and Ingresses. In the right pane, click the Gateway API tab.
  3. On the Gateways tab page, click Create from YAML and configure the Gateway YAML.

    For complete Gateway parameter definitions, see Gateway. Example:
    apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
      name: my-gateway              # Gateway name
      namespace: default
    spec:
      gatewayClassName: envoy-gateway  # GatewayClass name
      listeners:
        - name: http                                     
          protocol: HTTP               # Protocol
          port: 80                     # Listener port

    In this example, the Gateway automatically creates a load balancer based on the ELB configuration defined in the add-on, and creates an HTTP listener on port 80 for that load balancer.

  4. Click Submit. After the load balancer and listener are created, the Gateway status changes to Normal.

Step 2: Create an HTTPRoute

  1. Log in to the CCE console and click the cluster name to access the cluster console.
  2. In the navigation pane, choose Services and Ingresses. In the right pane, click the Gateway API tab.
  3. On the HTTPRoutes tab page, click Create from YAML and configure the HTTPRoute YAML.

    For complete HTTPRoute parameter definitions, HTTPRoute. Example:
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute                # The resource type is HTTPRoute.
    metadata:
      name: backend                # HTTPRoute name
      namespace: default
    spec:
      parentRefs:
        - name: my-gateway  # Gateway name
      hostnames:
        - "www.example.com"    # Domain name
      rules:
        - backendRefs:
            - group: ""
              kind: Service
              name: backend       # Name of the existing Service
              port: 3000          # Service port
              weight: 1

    In this example, HTTP traffic from the Gateway for the domain name www.example.com is routed to Service port 3000.

  4. Click Submit. After the HTTPRoute is created, return to the Gateways tab page and click the name of the associated Gateway. The added route is displayed.

Step 3: Test Accessibility

  1. View the public IP address of the load balancer associated with the Gateway.

    You can also run the following command to obtain the public IP address, replacing my-gateway with the Gateway name:

    kubectl get gateway/my-gateway -o jsonpath='{.status.addresses[0].value}'

  2. Run the following command to test access, replacing xx.xx.xx.xx with the public IP address of the load balancer:

    curl -I -H "Host: www.example.com" http://xx.xx.xx.xx

    Information similar to the following is displayed:

    HTTP/1.1 200 OK
    server: nginx/1.23.2
    date: Thu, 28 May 2026 01:47:02 GMT
    content-type: text/html
    content-length: 615
    last-modified: Wed, 19 Oct 2022 07:56:21 GMT
    etag: "634fada5-267"
    accept-ranges: bytes

More Scenarios

Adding HTTPS Access to a Gateway

  1. Use a certificate to create a kubernetes.io/tls secret. For details, see Creating a Secret.

    In this example, a self-signed certificate is used, and the created secret is named example-cert.

    openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example.com.key -out example.com.crt
    openssl req -out www.example.com.csr -newkey rsa:2048 -nodes -keyout www.example.com.key -subj "/CN=www.example.com/O=example organization"
    openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in www.example.com.csr -out www.example.com.crt

    Use the generated certificate to create the secret.

    kubectl create secret tls example-cert --key=www.example.com.key --cert=www.example.com.crt

  2. Add HTTPS access to the Gateway and configure the certificate.

    Example:
    apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
      name: my-gateway              # Gateway name
      namespace: default
    spec:
      gatewayClassName: envoy-gateway  # GatewayClass name
      listeners:
        - name: http                                     
          protocol: HTTP               # Protocol
          port: 80                     # Listener port
        - name: https
          protocol: HTTPS              # Protocol
          port: 443                    # Listener port
          tls:
            certificateRefs:           # TLS certificate
            - group: ""
              kind: Secret
              name: example-cert       # Secret name
            mode: Terminate

  3. After the load balancer and listener are automatically created, the Gateway transitions to the Normal state. Click the Gateway name to view the added HTTPS listener.

  4. Run the following command to test access, replacing xx.xx.xx.xx with the public IP address of the load balancer:

    curl -I --cacert example.com.crt --resolve www.example.com:443:xx.xx.xx.xx https://www.example.com

    Information similar to the following is displayed:

    HTTP/2 200 
    server: nginx/1.23.2
    date: Thu, 28 May 2026 02:32:57 GMT
    content-type: text/html
    content-length: 615
    last-modified: Wed, 19 Oct 2022 07:56:21 GMT
    etag: "634fada5-267"
    accept-ranges: bytes

Using Path Prefix Matching in an HTTPRoute

  1. Update the sample application to add the /test access path.

    Path matching requires that the corresponding path exists in the backend application. Otherwise, a 404 error is returned.

    For example, the default Nginx access path is /usr/share/nginx/html. When using /test for prefix matching, ensure /usr/share/nginx/html/test exists in your Nginx application. Otherwise, a 404 error is returned.

    The following example uses a startup command to write content to /usr/share/nginx/html/test. The response body is "This is a test."
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: backend
      namespace: default
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: backend
          version: v1
      template:
        metadata:
          labels:
            app: backend
            version: v1
        spec:
          containers:
            - name: container-1
              image: nginx:latest
              command: ["/bin/sh", "-c"]
              args:
              - echo "This is a test." > /usr/share/nginx/html/test && nginx -g "daemon off;"  # Add the test access path.
          imagePullSecrets:
            - name: default-secret

  2. Update the HTTPRoute resource.

    The following example configures the HTTPRoute to match the /test prefix:

    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute                # The resource type is HTTPRoute.
    metadata:
      name: backend                # HTTPRoute name
      namespace: default
    spec:
      parentRefs:
        - name: my-gateway  # Gateway name
      hostnames:
        - "www.example.com"    # Domain name
      rules:
        - backendRefs:
            - group: ""
              kind: Service
              name: backend       # Name of the existing Service
              port: 3000          # Service port
              weight: 1
          matches:
            - path:
                type: PathPrefix  # Prefix match
                value: /test       # Path prefix

  3. Run the following command to test access, replacing xx.xx.xx.xx with the public IP address of the load balancer:

    curl -i -H "Host: www.example.com" http://xx.xx.xx.xx/test

    Information similar to the following is displayed:

    HTTP/1.1 200 OK
    server: nginx/1.23.2
    date: Thu, 28 May 2026 03:22:10 GMT
    content-type: application/octet-stream
    content-length: 16
    last-modified: Thu, 28 May 2026 03:15:51 GMT
    etag: "6a17b367-10"
    accept-ranges: bytes
    
    This is a test.

Configuring Proportional Request Distribution in an HTTPRoute

In this example, an HTTPRoute is created with routing rules for backend1 and backend2, and weights are assigned to each backend.

All request traffic is distributed according to the ratio of each backend's weight to the total weight of all backends.

  1. Create two sample applications, backend1 and backend2.

    Example:
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: backend1
      namespace: default
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: backend1
          version: v1
      template:
        metadata:
          labels:
            app: backend1
            version: v1
        spec:
          containers:
            - name: container-1
              image: nginx:latest
              command: ["/bin/sh", "-c"]
              args:
              - echo "This is backend1." > /usr/share/nginx/html/index.html && nginx -g "daemon off;"  # By default, "This is backend1" is returned for requests to this backend.
          imagePullSecrets:
            - name: default-secret
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: backend1
      labels:
        app: backend1
        version: v1
      namespace: default
    spec:
      selector:
        app: backend1
        version: v1
      ports:
        - name: cce-service-0
          targetPort: 80
          port: 3000
          protocol: TCP
      type: ClusterIP
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: backend2
      namespace: default
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: backend2
          version: v1
      template:
        metadata:
          labels:
            app: backend2
            version: v1
        spec:
          containers:
            - name: container-1
              image: nginx:latest
              command: ["/bin/sh", "-c"]
              args:
              - echo "This is backend2." > /usr/share/nginx/html/index.html && nginx -g "daemon off;"   # By default, "This is backend2" is returned for requests to this backend.
          imagePullSecrets:
            - name: default-secret
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: backend2
      labels:
        app: backend2
        version: v1
      namespace: default
    spec:
      selector:
        app: backend2
        version: v1
      ports:
        - name: cce-service-0
          targetPort: 80
          port: 3000
          protocol: TCP
      type: ClusterIP

  2. Update the HTTPRoute to add routes for backend1 and backend2, and set their weights to 2 and 8, respectively.

    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute                # The resource type is HTTPRoute.
    metadata:
      name: backend                # HTTPRoute name
      namespace: default
    spec:
      parentRefs:
        - name: my-gateway  # Gateway name
      hostnames:
        - "www.example.com"    # Domain name
      rules:
        - backendRefs:
            - group: ""
              kind: Service
              name: backend1       # Name of the existing Service
              port: 3000           # Service port
              weight: 2            # Set the weight to 2.
            - group: ""
              kind: Service
              name: backend2       # Name of the existing Service
              port: 3000           # Service port
              weight: 8            # Set the weight to 8.

  3. Initiate 20 consecutive requests to test traffic distribution, replacing xx.xx.xx.xx with the public IP address of the load balancer.

    for i in $(seq 1 20); do curl -sS -H "Host: www.example.com" http://xx.xx.xx.xx/; done

    The expected output shows that backend1 receives approximately 20% of traffic and backend2 receives approximately 80%.

    This is backend2.
    This is backend2.
    This is backend2.
    This is backend1.
    This is backend1.
    This is backend2.
    This is backend2.
    This is backend2.
    This is backend2.
    This is backend2.
    This is backend2.
    This is backend2.
    This is backend1.
    This is backend2.
    This is backend2.
    This is backend2.
    This is backend2.
    This is backend1.
    This is backend2.
    This is backend2.

Forwarding Requests by Header in an HTTPRoute

Envoy Gateway supports request forwarding based on headers. In this example, the following rules are configured for demonstration:

  • When the request header is x-env: test, the request is forwarded to backend1.
  • When the request header is x-env: prod, the request is forwarded to backend2.
  • All other requests are forwarded to the default backend.
  1. Create two sample applications, backend1 and backend2.

    Example:
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: backend1
      namespace: default
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: backend1
          version: v1
      template:
        metadata:
          labels:
            app: backend1
            version: v1
        spec:
          containers:
            - name: container-1
              image: nginx:latest
              command: ["/bin/sh", "-c"]
              args:
              - echo "This is backend1." > /usr/share/nginx/html/index.html && nginx -g "daemon off;"  # By default, "This is backend1" is returned for requests to this backend.
          imagePullSecrets:
            - name: default-secret
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: backend1
      labels:
        app: backend1
        version: v1
      namespace: default
    spec:
      selector:
        app: backend1
        version: v1
      ports:
        - name: cce-service-0
          targetPort: 80
          port: 3000
          protocol: TCP
      type: ClusterIP
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: backend2
      namespace: default
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: backend2
          version: v1
      template:
        metadata:
          labels:
            app: backend2
            version: v1
        spec:
          containers:
            - name: container-1
              image: nginx:latest
              command: ["/bin/sh", "-c"]
              args:
              - echo "This is backend2." > /usr/share/nginx/html/index.html && nginx -g "daemon off;"   # By default, "This is backend2" is returned for requests to this backend.
          imagePullSecrets:
            - name: default-secret
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: backend2
      labels:
        app: backend2
        version: v1
      namespace: default
    spec:
      selector:
        app: backend2
        version: v1
      ports:
        - name: cce-service-0
          targetPort: 80
          port: 3000
          protocol: TCP
      type: ClusterIP

  2. Update the HTTPRoute to forward requests based on headers.

    Example:

    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: backend
      namespace: default
    spec:
      parentRefs:
        - name: my-gateway
      hostnames:
        - "www.example.com"
      rules:
      # Rule 1: Header x-env = test -> backend1 in the test environment
      - matches:
        - headers:
          - name: x-env
            value: test  # Exact match value
            type: Exact  # Match type. Options: Exact (default), Prefix, and RegularExpression
          path:
            type: PathPrefix
            value: /
        backendRefs:
        - group: ""
          kind: Service
          name: backend1
          port: 3000
      # Rule 2: Header x-env = prod -> backend2 in the production environment
      - matches:
        - headers:
          - name: x-env
            value: prod  # Exact match value
            type: Exact  # Match type. Options: Exact (default), Prefix, and RegularExpression
          path:
            type: PathPrefix
            value: /
        backendRefs:
        - group: ""
          kind: Service
          name: backend2
          port: 3000
      # Rule 3: Default route (when no header is matched)
      - matches:
        - path:
            type: PathPrefix
            value: /
        backendRefs:
        - group: ""
          kind: Service
          name: backend
          port: 3000

  3. Test access using the request header x-env: test.

    curl -i -H "Host: www.example.com" -H "x-env: test" http://xx.xx.xx.xx/

    Expected output:

    HTTP/1.1 200 OK
    server: nginx/1.23.2
    date: Sat, 30 May 2026 10:09:03 GMT
    content-type: text/html
    content-length: 18
    last-modified: Thu, 28 May 2026 06:49:53 GMT
    etag: "6a17e591-12"
    accept-ranges: bytes
    
    This is backend1.

    The output shows that backend1 was accessed, as expected.

  4. Test access using the request header x-env: prod.

    curl -i -H "Host: www.example.com" -H "x-env: prod" http://xx.xx.xx.xx

    Expected output:

    HTTP/1.1 200 OK
    server: nginx/1.23.2
    date: Sat, 30 May 2026 10:09:01 GMT
    content-type: text/html
    content-length: 18
    last-modified: Thu, 28 May 2026 07:07:17 GMT
    etag: "6a17e9a5-12"
    accept-ranges: bytes
    
    This is backend2.

    The output shows that backend2 was accessed, as expected.

Specifying a Single Gateway Listener for an HTTPRoute

In an HTTPRoute, you can use the sectionName or port field to bind the forwarding rule to a specific listener in the parent Gateway.

  1. Update the HTTPRoute resource.

    In the following example, the HTTPRoute binds only to the Gateway listener named http on port 80.

    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: my-route
      namespace: default
    spec:
      parentRefs:
      - name: my-gateway
        sectionName: http  # Bind only the listener named http.
        port: 80  # Bind only the listener on port 80.
      hostnames:
        - "www.example.com"    # Domain name
      rules:
        - backendRefs:
            - group: ""
              kind: Service
              name: backend       # Name of the existing Service
              port: 3000          # Service port
              weight: 1
    • sectionName and port are optional. If they are left blank, the HTTPRoute binds to all listeners in the Gateway by default.
    • sectionName="http" binds the HTTPRoute to the listener named http in the Gateway.
    • port="80" binds the HTTPRoute to the listener on port 80 in the Gateway.
    • sectionName="http" and port="80" together bind the HTTPRoute to the listener named http on port 80 in the Gateway.

  2. After the HTTPRoute is created, return to the Gateways tab page and click the associated Gateway name. The route is attached only to listener port 80.

Customizing an EnvoyProxy to Configure an Existing Load Balancer for a Gateway

  1. Create a custom EnvoyProxy.

  2. Associate with the custom EnvoyProxy.

    • Method 1: Associate a GatewayClass with the EnvoyProxy.
      1. Create a GatewayClass.
        apiVersion: gateway.networking.k8s.io/v1
        kind: GatewayClass
        metadata:
          name: my-gateway-class
        spec:
          controllerName: gateway.envoyproxy.io/gatewayclass-controller
          parametersRef:        
            group: gateway.envoyproxy.io
            kind: EnvoyProxy     
            name: dev-proxy-config   # Name of the created custom EnvoyProxy
            namespace: default  # EnvoyProxy namespace
      2. Create a Gateway.
        apiVersion: gateway.networking.k8s.io/v1
        kind: Gateway
        metadata:
          name: my-gateway              # Gateway name
          namespace: default
        spec:
          gatewayClassName: my-gateway-class  # Name of the created GatewayClass
          listeners:
            - name: http                                     
              protocol: HTTP               # Protocol
              port: 80                     # Listener port
            - name: http-2                                     
              protocol: HTTP               # Protocol
              port: 81                     # Listener port
    • Method 2: Associate a Gateway with the EnvoyProxy.
      apiVersion: gateway.networking.k8s.io/v1
      kind: Gateway
      metadata:
        name: my-gateway
        namespace: default
      spec:
        gatewayClassName: envoy-gateway   # Name of the GatewayClass created by the add-on
        infrastructure:
          parametersRef:
            group: gateway.envoyproxy.io
            kind: EnvoyProxy
            name: dev-proxy-config  # Name of the created custom EnvoyProxy
        listeners:
          - name: http
            protocol: HTTP
            port: 82

  3. Log in to the CCE console. On the Gateways tab page, verify that the load balancer associated with the new Gateway is the existing one.

    If an existing load balancer is used, different Gateways cannot share the same port. Otherwise, the Gateway status will become abnormal.