Gateway API cho Multi-Cluster Routing
Tại sao Gateway API Thay Thế MCI Custom Resources
MCI (Multi-Cluster Ingress) là GCP-specific resource. Nó hoạt động, nhưng:
# MCI (GCP-only)
apiVersion: cloud.google.com/v1
kind: MultiClusterIngress
metadata:
name: app
# Gateway API (Kubernetes standard)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: app-gatewayGateway API: Kubernetes standard abstraction cho ingress/routing. Nó replace MCI custom resources & cung cấp:
- Portability: Same YAML works trên GKE, EKS, AKS, on-prem Kubernetes
- Rich routing: HTTPRoute (path/host/header-based), TCPRoute, TLSRoute, UDPRoute
- Resource model: GatewayClass → Gateway → Routes (better separation of concerns)
- Multi-namespace: Route từ multiple namespaces bisa bind ke single Gateway
Mental model:
MCI = "Ingress v1 para multi-cluster"
Gateway API = "Ingress v2++ with routing DSL"Resource Model
GatewayClass: Infrastructure Provider Abstraction
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: gke-l7
spec:
controllerName: gke.io/l7-gxlb # GCP Global HTTP(S) LB controller
description: GKE Global HTTP(S) Load BalancerÝ nghĩa: "Khi tôi tạo Gateway dengan gatewayClassName=gke-l7, hãy sử dụng GCP's global LB".
Khác với ingress.class, GatewayClass rõ ràng khai báo controller provider.
Gateway: Ingress Entry Point
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: app-gateway
namespace: default
spec:
gatewayClassName: gke-l7
listeners:
- name: http
port: 80
protocol: HTTP
- name: https
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: app-cert
namespace: default
status:
addresses:
- type: IPAddress
value: 1.2.3.4 # Global anycast IP (same as MCI)
listeners:
- name: http
supportedKinds:
- group: gateway.networking.k8s.io
kind: HTTPRouteCơ chế:
- Gateway resource tạo global LB (cùng ip như MCI)
- Controller watches để HTTPRoute resources
- HTTPRoute define routing rules (path/host/header)
- Routes bind to backendServices
HTTPRoute: Routing Rules
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: app-routes
namespace: backend
spec:
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: app-gateway
namespace: default
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api/v1
backendRefs:
- group: ""
kind: Service
name: api-service
port: 8080
weight: 100 # 100% traffic to this service
- matches:
- path:
type: PathPrefix
value: /frontend
backendRefs:
- group: ""
kind: Service
name: frontend-service
port: 3000
weight: 100Routing logic:
Client: GET http://api.example.com/api/v1/users
1. Gateway matches hostname: api.example.com ✓
2. HTTPRoute checks path: /api/v1 ✓
3. Backend: api-service (port 8080)
4. Service discovery: MCS ServiceImport untuk multi-cluster
5. LB route to endpoints (via NEG)Multi-Cluster Configuration
Cross-Cluster BackendRef
# Config Cluster: HTTPRoute yang target services ở member clusters
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: global-app-routes
namespace: default
spec:
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: app-gateway
rules:
- backendRefs:
- group: ""
kind: Service
name: api-service
namespace: backend # Cross-namespace
weight: 50
- group: net.gke.io
kind: ServiceExport # Cross-cluster!
name: api-service
namespace: backend
weight: 50Cơ chế:
HTTPRoute backendRef:
→ kind: Service (local)
→ kind: ServiceExport (via MCS, target multi-cluster)
When backendRef specifies ServiceExport:
→ GKE Gateway controller looks for matching ServiceImport
→ Finds MCS endpoints từ cluster A, B, C
→ Creates NEG entries
→ LB route to multi-cluster endpointsTraffic Splitting Across Clusters
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: canary-split
namespace: default
spec:
rules:
- backendRefs:
- group: ""
kind: Service
name: api-v1 # Stable version
weight: 90 # 90% traffic
- group: ""
kind: Service
name: api-v2 # Canary version
weight: 10 # 10% trafficCanary deployment:
Cluster A (v1.0): 90% traffic
Cluster B (v1.1): 10% traffic
Monitor metrics:
- Error rate: v1.1 < 0.5%? OK
- Latency p99: v1.1 < 50ms? OK
If OK: increase weight 10→50→100
If FAIL: decrease weight 10→0 (drain)Advanced Routing: Host, Path, Header-Based
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: advanced-routing
spec:
rules:
# Rule 1: Exact host
- matches:
- hostname: "internal.example.com"
backendRefs:
- name: internal-service
port: 8080
# Rule 2: Path + Method
- matches:
- path:
type: PathPrefix
value: /api/admin
method: POST
backendRefs:
- name: admin-service
port: 9000
# Rule 3: Header-based routing (canary by user)
- matches:
- headers:
- name: X-Canary
value: "true"
backendRefs:
- name: api-v2-canary
port: 8080
weight: 100
# Rule 4: Default
- backendRefs:
- name: api-v1-stable
port: 8080
weight: 100Request flow:
GET /api/admin (POST)
Header: X-Canary: true
1. Check hostname: default (no match)
2. Check /api/admin + POST: ✓ Match rule 2
3. Route to: admin-serviceConstraints & Best Practices
Gateway API Maturity
- GA (General Availability): HTTPRoute, TCPRoute (v1)
- Experimental: Service binding, policy attachment
- Not yet: Dynamic weight changes (use headless service + MCS locality instead)
Cross-Cluster Limitations
- Same project requirement: All clusters must be in same GCP project
- VPC peering must be configured: Gateway controller needs reach pod endpoints
- Service name consistency: Service di cluster A & B harus same name di same namespace
- No cross-namespace ServiceExport: ServiceExport hanya visible di same namespace
Production Patterns
✅ Pattern: Canary dengan Gateway API + ServiceExport
# Stable version (Cluster A)
apiVersion: v1
kind: Service
metadata:
name: app
namespace: default
spec:
selector:
app: app
version: v1.0
ports:
- port: 8080
name: http
---
apiVersion: net.gke.io/v1
kind: ServiceExport
metadata:
name: app
namespace: default
---
# Canary version (Cluster B)
apiVersion: v1
kind: Service
metadata:
name: app
namespace: default
spec:
selector:
app: app
version: v1.1
ports:
- port: 8080
name: http
---
apiVersion: net.gke.io/v1
kind: ServiceExport
metadata:
name: app
namespace: default
---
# Config Cluster: Gateway + routing
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: app-canary
namespace: default
spec:
rules:
- backendRefs:
- kind: Service
name: app
namespace: default
weight: 90
- kind: Service
name: app # Same name, different cluster via MCS
namespace: default
weight: 10Workflow:
1. Deploy v1.1 to Cluster B
2. ServiceExport immediately visible via MCS
3. Gateway controller adds to NEG (weight: 10)
4. Monitor error rate: if OK, increase weight
5. Gradually: 10→25→50→100
6. Promote: v1.1 becomes stable✅ Pattern: Path-Based Routing to Services
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: app-router
spec:
rules:
# /api/* → API service
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api-service
port: 8080
# /static/* → Frontend service
- matches:
- path:
type: PathPrefix
value: /static
backendRefs:
- name: frontend-service
port: 3000
# Default → Homepage
- backendRefs:
- name: homepage-service
port: 8080❌ Anti-Pattern: Mixing Gateway API + MCI
# WRONG: Using both Gateway API AND MultiClusterIngress
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
spec:
gatewayClassName: gke-l7
---
apiVersion: cloud.google.com/v1
kind: MultiClusterIngress
spec:
backend:
serviceName: appProblem: Dua resources compete untuk same global LB → undefined behavior.
Fix: Use ONLY Gateway API (modern, recommended).
Debugging Gateway API
Check Gateway Status
kubectl get gateway -n default
NAME CLASS ADDRESS READY
app-gateway gke-l7 1.2.3.4 True
# Detailed status
kubectl describe gateway app-gateway -n default
Status:
Addresses:
- Type: IPAddress
Value: 1.2.3.4
Listeners:
- Name: http
Conditions:
- Type: Accepted
Status: "True"
Reason: Accepted
- Type: Programmed
Status: "True"
Reason: ProgrammedCheck HTTPRoute Status
kubectl get httproute -n default
NAME HOSTNAMES STATUS
app-routes ["api.example.com"] Accepted
# Check if route is accepted
kubectl describe httproute app-routes -n default
Status:
Parents:
- Name: app-gateway
Conditions:
- Type: Accepted
Status: "True"
- Type: ResolvedRefs
Status: "True"Verify Backend Resolution
# Check if ServiceExport is visible from config cluster
kubectl get serviceexport -A
NAMESPACE NAME AGE
backend api-service 5m
# Check MCS ServiceImport (auto-created)
kubectl get serviceimport -n backend
NAME CLUSTER-IPS AGE
api-service [172.30.1.100] 5m
# Verify NEG created
gcloud compute network-endpoint-groups list | grep appComparison: MCI vs Gateway API
| Feature | MCI | Gateway API |
|---|---|---|
| Portability | GCP only | K8s standard |
| Routing rules | Basic (service only) | Advanced (HTTPRoute) |
| Resource model | Flat (MCIngress + MCSvc) | Hierarchical (GatewayClass → Gateway → Routes) |
| Multi-namespace routes | No | Yes (HTTPRoute in different ns) |
| Traffic splitting | NEG weights | HTTPRoute weights |
| Status visibility | Limited | Rich (conditions, ready status) |
| Adoption | Declining | Rising (GA in K8s 1.30+) |
Summary
Gateway API adalah modern replacement untuk MCI custom resources. Nó provide:
- Standard Kubernetes abstraction (gateway.networking.k8s.io)
- Rich routing rules (path, host, method, header-based)
- Cross-namespace support (routes from multiple namespaces bind to single gateway)
- Better visibility (status conditions, debugging)
- Future-proof (evolving K8s standard, not GCP-specific)
Recommendation: Use Gateway API cho new multi-cluster deployments. MCI masih supported, tapi Gateway API adalah future direction.