All Products
Search
Document Center

Container Service for Kubernetes:Expose a service with Gateway API and ALB

Last Updated:Mar 26, 2026

The Gateway API is an official Kubernetes project and the next-generation routing API that replaces Ingress. This topic shows how to use the Gateway API with the ALB Ingress Controller to expose services in an ACK cluster through an Application Load Balancer (ALB) instance.

Resource model

The Gateway API models service networking through three resource types, each owned by a different role:

Resource Owner Purpose
GatewayClass Infrastructure provider (Alibaba Cloud) Defines a class of gateways backed by a specific controller — in this case, the ALB Ingress Controller
Gateway Cluster operator Provisions an ALB instance and configures listeners (ports, protocols, hostnames)
HTTPRoute Application developer Defines routing rules that map incoming requests to backend Services

This separation lets platform teams control infrastructure while application teams manage their own routing rules independently.

The ALB Ingress Controller has supported the Gateway API since version 2.17.0.

Prerequisites

Before you begin, ensure that you have:

  • An ACK managed cluster running Kubernetes 1.24 or later

  • The ALB Ingress Controller version 2.17.0 or later installed in the cluster

  • The Gateway API version 1.1.0 or later installed in the cluster

  • Two vSwitches that support ALB in the VPC where your cluster is deployed

Verify the environment

When all prerequisites are met, the ALB Ingress Controller automatically creates a GatewayClass resource named alb. Verify this before proceeding.

kubectl

Run the following command:

kubectl get gatewayclass

Example output:

NAME   CONTROLLER                         ACCEPTED   AGE
alb    gateways.alibabacloud.com/alb/v1   True       1m

The ACCEPTED: True status confirms the GatewayClass is ready.

Deploy a sample application

Create an httpbin.yaml file with the following content.

The Service uses ClusterIP by default. If your cluster uses the Flannel network plug-in, change spec.type to NodePort.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-httpbin
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: go-httpbin
  template:
    metadata:
      labels:
        app: go-httpbin
        version: v1
    spec:
      containers:
        - image: registry.cn-hangzhou.aliyuncs.com/mse/go-httpbin
          args:
            - "--port=8090"
            - "--version=v1"
          imagePullPolicy: Always
          name: go-httpbin
          ports:
            - containerPort: 8090
---
apiVersion: v1
kind: Service
metadata:
  name: go-httpbin
  namespace: default
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 8090
      protocol: TCP
  selector:
    app: go-httpbin

Apply the manifest:

kubectl apply -f httpbin.yaml

Deploy the Gateway and routing rule

Important

Creating a Gateway provisions an ALB instance and incurs fees. ALB instances created by a Gateway are not automatically deleted when you delete the cluster. Delete the Gateway resource before deleting the cluster to avoid unexpected charges.

Step 1: Create the Gateway and HTTPRoute

Create a gateway.yaml file:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: alb
  namespace: default
spec:
  gatewayClassName: alb
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      hostname: "*.ingress.top"
      allowedRoutes:
        namespaces:
          from: Same
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
  name: demo-route
spec:
  parentRefs:            # Attach this route to the Gateway above
    - group: gateway.networking.k8s.io
      kind: Gateway
      name: alb
  hostnames:
    - demo.ingress.top
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:       # Forward matched traffic to go-httpbin on port 80
        - kind: Service
          name: go-httpbin
          port: 80

The Gateway creates an ALB instance with an HTTP listener on port 80 for *.ingress.top. The HTTPRoute attaches to that Gateway and forwards all traffic for demo.ingress.top to the go-httpbin Service.

Apply the manifest:

kubectl apply -f gateway.yaml

Step 2: Wait for the Gateway to be ready

kubectl wait --for=condition=programmed gateway/alb --timeout=120s

Once the command returns, verify the Gateway address:

kubectl get gateway alb

Example output:

NAME   CLASS   ADDRESS                                                  PROGRAMMED   AGE
alb    alb     alb-0mwhq4ck6xxxxxxxxx.cn-hangzhou.alb.aliyuncsslb.com   True         2m12s

Step 3: Check the HTTPRoute status

kubectl describe httproute demo-route | grep Status -A 20

Example output:

Status:
  Parents:
    Conditions:
      Last Transition Time:  2025-05-23T08:21:25Z
      Message:               Route is accepted.
      Observed Generation:   1
      Reason:                Accepted
      Status:                True
      Type:                  Accepted
      Last Transition Time:  2025-05-23T08:21:25Z
      Message:               Route is resolved.
      Observed Generation:   1
      Reason:                ResolvedRefs
      Status:                True
      Type:                  ResolvedRefs
    Controller Name:         gateways.alibabacloud.com/alb/v1
    Parent Ref:
      Group:  gateway.networking.k8s.io
      Kind:   Gateway
      Name:   alb

Both Accepted: True and ResolvedRefs: True confirm the route is active.

Step 4: Test access

Get the Gateway address and send a test request:

export ALB_DOMAIN=$(kubectl get gateway alb -n default -o jsonpath='{.status.addresses[?(@.type=="Hostname")].value}')
curl -H "Host: demo.ingress.top" http://${ALB_DOMAIN}/version

Example output:

version: v1
hostname: go-httpbin-547xxxxxf6-xxxxx

Scenarios

Modify request headers with a filter

HTTPRoute filters let you rewrite requests or responses in transit. This example adds a custom request header before traffic reaches the backend.

Create an httproute-filter.yaml file:

apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
  name: demo-filter
spec:
  parentRefs:
    - group: gateway.networking.k8s.io
      kind: Gateway
      name: alb
  hostnames:
    - filter.ingress.top
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      filters:
        - type: RequestHeaderModifier
          requestHeaderModifier:
            add:
              - name: my-header
                value: foo
      backendRefs:
        - kind: Service
          name: go-httpbin
          port: 80

This HTTPRoute uses a RequestHeaderModifier filter, which runs before the request reaches the backend. The filter:

  • Adds my-header: foo to every request that matches the route.

  • Uses the add action, which appends the header rather than replacing any existing header with the same name.

Apply the routing rule:

kubectl apply -f httproute-filter.yaml

Send a test request to the /header endpoint, which echoes all request headers:

curl -H "Host: filter.ingress.top" http://${ALB_DOMAIN}/header

Example output:

headers: {
    "Accept": [
      "*/*"
    ],
    "Connection": [
      "close"
    ],
    "Host": [
      "filter.ingress.top"
    ],
    "My-Header": [
      "foo"
    ],
    "Path": [
      "/header"
    ],
    "Protocol": [
      "HTTP/1.1"
    ],
    "Remoteip": [
      "118.xx.xx.91"
    ],
    "URL": [
      "/header"
    ],
    "User-Agent": [
      "curl/8.9.1"
    ]
  }
 query param:
, hostname: go-httpbin-547xxxxxf6-xxxxx

The My-Header: foo entry in the output confirms the filter is working.

Split traffic by weight

Assign weights to multiple backend Services to distribute traffic proportionally. This is useful for canary deployments.

Create an nginx.yaml file with two NGINX deployments:

Expand to view the YAML content

apiVersion: apps/v1
kind: Deployment
metadata:
  name: old-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      run: old-nginx
  template:
    metadata:
      labels:
        run: old-nginx
    spec:
      containers:
      - image: registry.cn-hangzhou.aliyuncs.com/acs-sample/old-nginx
        imagePullPolicy: Always
        name: old-nginx
        ports:
        - containerPort: 80
          protocol: TCP
      restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  name: old-nginx
spec:
  type: ClusterIP
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    run: old-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: new-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      run: new-nginx
  template:
    metadata:
      labels:
        run: new-nginx
    spec:
      containers:
      - image: registry.cn-hangzhou.aliyuncs.com/acs-sample/new-nginx
        imagePullPolicy: Always
        name: new-nginx
        ports:
        - containerPort: 80
          protocol: TCP
      restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  name: new-nginx
spec:
  type: ClusterIP
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    run: new-nginx

old-nginx returns the string old; new-nginx returns new. Both are deployed in the default namespace.

Create an httproute-weight.yaml file:

apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
  name: demo-weight
spec:
  parentRefs:
    - group: gateway.networking.k8s.io
      kind: Gateway
      name: alb
  hostnames:
    - weight.ingress.top
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        # Weights are relative, not percentages — they do not need to add up to 100.
        - kind: Service
          name: old-nginx
          port: 80
          weight: 100
        - kind: Service
          name: new-nginx
          port: 80
          weight: 100

Both backends have equal weights (100:100), so traffic splits 1:1.

Apply the manifests:

kubectl apply -f nginx.yaml
kubectl apply -f httproute-weight.yaml

Send 10 requests to observe the distribution:

for i in {1..10}; do curl -H "Host: weight.ingress.top" http://${ALB_DOMAIN}/; done

Example output:

old
new
new
old
new
old
old
new
new
old

Traffic is distributed roughly 1:1 between the two backends.

Configure a TLS certificate

Add HTTPS termination to the Gateway by referencing a Kubernetes Secret that contains your TLS certificate.

Step 1: Create a self-signed certificate

openssl req -subj '/CN=ingress.top' -new -newkey rsa:2048 -sha256 \
  -days 365 -nodes -x509 -keyout server.key -out server.crt \
  -addext "subjectAltName = DNS:ingress.top" \
  -addext "keyUsage = digitalSignature" \
  -addext "extendedKeyUsage = serverAuth" 2> /dev/null; \
  openssl x509 -in server.crt -subject -noout

Step 2: Create a TLS Secret

kubectl create secret tls ingress.top --key server.key --cert server.crt

Step 3: Update the Gateway to add an HTTPS listener

Create a gateway-tls.yaml file:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: alb
  namespace: default
spec:
  gatewayClassName: alb
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      hostname: "*.ingress.top"
      allowedRoutes:
        namespaces:
          from: Same
    - name: https
      protocol: HTTPS
      port: 443
      hostname: "*.ingress.top"
      allowedRoutes:
        namespaces:
          from: Same
      tls:
        mode: Terminate          # ALB terminates TLS and forwards plain HTTP to backends
        certificateRefs:
          - kind: Secret
            name: ingress.top   # The Secret created in the previous step

The Terminate mode means ALB decrypts incoming HTTPS traffic and forwards plain HTTP to your backend Services. The certificateRefs field points to the Secret holding the certificate and private key.

Apply the update:

kubectl apply -f gateway-tls.yaml

Step 4: Verify the certificate

Wait for the Gateway PROGRAMMED status to return to True:

kubectl wait --for=condition=programmed gateway/alb --timeout=120s

Then verify the certificate is served correctly:

openssl s_client -servername ingress.top -connect ${ALB_DOMAIN}:443

Example output:

CONNECTED(00000003)
depth=0 CN = ingress.top
verify error:num=18:self-signed certificate
verify return:1
depth=0 CN = ingress.top
verify return:1
---
Certificate chain
 0 s:CN = ingress.top
   i:CN = ingress.top
   a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
   v:NotBefore: Jun 24 10:49:39 2025 GMT; NotAfter: Jun 24 10:49:39 2026 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
subject=CN = ingress.top
issuer=CN = ingress.top
---
...
New, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
...
Verify return code: 18 (self-signed certificate)

The CN = ingress.top in the certificate chain and TLSv1.2 cipher confirm TLS termination is working. The self-signed certificate error is expected — production deployments should use a CA-signed certificate.

To test HTTPS access to the application, use the -k flag to skip certificate verification (required for self-signed certs):

curl -H "Host: demo.ingress.top" -k https://${ALB_DOMAIN}/version

Example output:

version: v1
hostname: go-httpbin-547xxxxxf6-xxxxx

What's next

  • Gateway API — Learn about the full Gateway API specification, including advanced routing features such as header-based matching and traffic mirroring.

  • ALB Ingress Controller — Explore ALB-specific annotations and configuration options for production workloads.