在中国区EKS集群使用 kgateway 代理 Lambda 函数的实践过程

参考资料

kgateway 是一个基于 Kubernetes Gateway API 的控制平面,底层数据平面使用 Envoy 代理。本文尝试在 AWS 中国区(cn-north-1)EKS 集群上部署 kgateway,并通过它将流量路由到 AWS Lambda 函数的完整过程,包括遇到的问题和解决方案。

kgateway环境部署和测试

环境信息:

  • EKS 集群 1.33:cn-north-1 区域
  • kgateway 版本:v2.2.0(Helm chart)
  • Gateway API 版本:v1.4.0
  • Lambda 函数:arn:aws-cn:lambda:cn-north-1:000000000000:function:py314

安装 kgateway

首先安装 Gateway API CRD 和 kgateway 控制平面:

shell 复制代码
# 安装 Gateway API 标准 CRD
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml

# 安装 kgateway CRD
helm install kgateway-crds oci://cr.kgateway.dev/kgateway-dev/charts/kgateway-crds \
  --namespace kgateway-system --create-namespace --version v2.2.0

# 安装 kgateway 控制平面
helm upgrade -i kgateway oci://cr.kgateway.dev/kgateway-dev/charts/kgateway \
  --namespace kgateway-system \
  --version v2.2.0

安装完成后,kgateway-system 命名空间中会运行一个 kgateway 控制平面 Pod。

部署 httpbin 示例应用

kgateway 官方示例中的 httpbin 使用了 docker.io/mccutchen/go-httpbin:v2.6.0curlimages/curl:7.83.1,这两个镜像托管在 Docker Hub 上。在中国区 EKS 节点上,由于网络原因无法直接拉取 Docker Hub 镜像,Pod 会一直处于 ImagePullBackOff 状态。这里提前将镜像同步到自己的 ECR 仓库,修改 httpbin.yaml 中的镜像地址:

yaml 复制代码
containers:
  - image: 000000000000.dkr.ecr.cn-north-1.amazonaws.com.cn/go-httpbin:v2.6.0
    name: httpbin
    # ...
  - image: 000000000000.dkr.ecr.cn-north-1.amazonaws.com.cn/curl:7.83.1
    name: curl
    # ...

创建 Gateway 和 HTTPRoute

yaml 复制代码
# Gateway:创建一个监听 8080 端口的 HTTP 网关
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
metadata:
  name: http
  namespace: kgateway-system
spec:
  gatewayClassName: kgateway
  listeners:
  - protocol: HTTP
    port: 8080
    name: http
    allowedRoutes:
      namespaces:
        from: All
        
# HTTPRoute:将 www.example.com 的流量路由到 httpbin
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: httpbin
  namespace: httpbin
spec:
  parentRefs:
    - name: http
      namespace: kgateway-system
  hostnames:
    - "www.example.com"
  rules:
    - backendRefs:
        - name: httpbin
          port: 8000

Gateway 资源的运行原理

当创建一个 Gateway 资源(指定gatewayClassName为kgateway)后,kgateway 控制平面会自动完成以下操作:

  1. 创建 Envoy 代理 Deployment:一个名为 http(与 Gateway 同名)的 Deployment,运行 Envoy 代理 Pod
  2. 创建 ServiceAccount:名为 http 的 ServiceAccount
  3. 创建 LoadBalancer Service:名为 http 的 Service,类型为 LoadBalancer

这个自动创建的 Service 中包含了 loadBalancerClass: service.k8s.aws/nlb,因此Load Balancer Controller 会接管它并自动创建一个 NLB。整个流程如下

shell 复制代码
kubectl apply Gateway(name=http)
  → kgateway 控制平面检测到新 Gateway
  → 自动创建 Deployment(http) + ServiceAccount(http) + Service(type=LoadBalancer, loadBalancerClass=service.k8s.aws/nlb)
  → Load Balancer Controller 检测到带有 loadBalancerClass 的 Service
  → 自动创建 NLB

Envoy 代理通过 xDS 协议从 kgateway 控制平面获取路由配置。当创建 HTTPRoute 资源时,kgateway 将其转换为 Envoy 能理解的路由规则,通过 xDS 推送给 Envoy。整个数据流:用户请求 → NLB → Envoy Pod(通过 xDS 从 kgateway 获取路由配置)→ 后端服务。

由于 NLB 在中国区可能需要较长时间就绪,这里直接通过 Envoy Pod IP 验证

shell 复制代码
PROXY_IP=$(kubectl get pods -n kgateway-system -l app.kubernetes.io/name=http -o jsonpath='{.items[0].status.podIP}')

curl -i http://$PROXY_IP:8080/headers -H "host: www.example.com:8080"
# 返回 HTTP/1.1 200 OK,验证成功

配置 Lambda 后端

kgateway 通过 Backend CRD 定义 Lambda 后端。注意 type: AWS 字段虽然文档标记为 deprecated,但 CRD 的 CEL validation 仍然要求它存在:

yaml 复制代码
apiVersion: gateway.kgateway.dev/v1alpha1
kind: Backend
metadata:
  name: lambda-backend
  namespace: kgateway-system
spec:
  type: AWS
  aws:
    accountId: "000000000000"
    region: cn-north-1
    lambda:
      functionName: py314
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: lambda-route
  namespace: kgateway-system
spec:
  parentRefs:
    - name: http
      namespace: kgateway-system
  hostnames:
    - "lambda.example.com"
  rules:
    - backendRefs:
        - name: lambda-backend
          group: gateway.kgateway.dev
          kind: Backend

Envoy 代理需要 AWS 凭证来签名 Lambda 调用请求。使用 eksctl 为 Gateway 自动创建的 http ServiceAccount 绑定 IRSA:

shell 复制代码
aws iam create-policy --policy-name kgateway-lambda-invoke --policy-document '{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["lambda:InvokeFunction"],
    "Resource": [
      "arn:aws-cn:lambda:cn-north-1:000000000000:function:py314",
      "arn:aws-cn:lambda:cn-north-1:000000000000:function:py314:*"
    ]
  }]
}'

eksctl create iamserviceaccount \
  --name http --namespace kgateway-system \
  --cluster test127 --region cn-north-1 \
  --attach-policy-arn arn:aws-cn:iam::000000000000:policy/kgateway-lambda-invoke \
  --override-existing-serviceaccounts --approve

注意 IAM Policy 的 Resource 需要同时包含不带 qualifier 和带 * qualifier 的 ARN,因为 kgateway 生成的 Lambda ARN 会自动附加 :$LATEST

遇到的问题和分析

配置完成后,请求 Lambda 返回 503。通过逐层排查,发现了三个层面的问题。

凭证获取:Envoy 不走 IRSA

Envoy 日志只显示 IMDS 获取凭证失败,完全没有尝试 IRSA 的 Web Identity Token 路径:

复制代码
[warning][aws] Token fetch failed, falling back to IMDSv1. Reason: Network
[error][aws] AWS Instance metadata fetch failure: Network

虽然 Pod 中已经注入了 AWS_ROLE_ARNAWS_WEB_IDENTITY_TOKEN_FILE 环境变量,token 文件也正确挂载,但 Envoy 内置的 credential provider chain 实现与 AWS SDK 不同,在某些情况下会跳过 Web Identity 路径直接尝试 IMDS。

同时 EKS 节点默认配置为 IMDSv2(HttpTokens: required)且 hop limit 为 1,容器内无法访问 IMDS。临时解决方案是将节点改为 IMDSv1 并调整 hop limit:

shell 复制代码
aws ec2 modify-instance-metadata-options \
  --instance-id i-075fed9073360dcbd \
  --http-tokens optional \
  --http-put-response-hop-limit 2 \
  --region cn-north-1
Lambda Endpoint 和 ARN 错误

凭证问题解决后,请求仍然 503。通过 Envoy admin API 查看 config dump,发现 kgateway 生成的 Envoy 配置中有三处错误:

配置项 kgateway 生成的值(错误) 正确值
Lambda endpoint lambda.cn-north-1.amazonaws.com lambda.cn-north-1.amazonaws.com.cn
TLS SNI lambda.cn-north-1.amazonaws.com lambda.cn-north-1.amazonaws.com.cn
Lambda ARN arn:aws:lambda:cn-north-1:... arn:aws-cn:lambda:cn-north-1:...

lambda.cn-north-1.amazonaws.com 在中国区 DNS 中根本无法解

endpointURL 字段的 Panic Bug

尝试通过 Backend 的 endpointURL 字段手动指定正确的 Lambda endpoint:

yaml 复制代码
lambda:
  functionName: py314
  endpointURL: https://lambda.cn-north-1.amazonaws.com.cn

结果导致 kgateway 控制平面 panic 并进入 CrashLoopBackOff:

go 复制代码
panic: runtime error: invalid memory address or nil pointer dereference
goroutine 141 [running]:
.../plugins/backend/plugin.go:158

原因是 configureLambdaEndpoint 函数在解析 URL 时,对 https://lambda.cn-north-1.amazonaws.com.cn 这样没有显式端口的 URL 调用了 strconv.ParseUint(parsedURL.Port(), 10, 32),而 parsedURL.Port() 返回空字符串,导致解析失败。

但是,根据envoy官方以及官方博客中给出的结果, 在中国区将lambda作为Envoy后端静态配置是可用的

从源码出发修复问题

kgateway 是 Go 语言编写的开源项目(Apache 2.0),代码仓库在 github.com/kgateway-dev/kgateway。根据 panic 堆栈和 config dump 分析,问题集中在一个文件:

复制代码
pkg/kgateway/extensions2/plugins/backend/aws.go

kgateway 控制平面在这个过程中的角色是:监听 Gateway、HTTPRoute、Backend 等 Kubernetes 资源的变化,将它们翻译为 Envoy 的 xDS 配置(包括 cluster、route、listener 等),通过 gRPC 推送给 Envoy 代理。Lambda 相关的配置翻译逻辑在 pkg/kgateway/extensions2/plugins/backend/aws.go 中完成。

其中三个函数需要修改:

  • getLambdaHostname --- 拼接 Lambda 服务 endpoint:
go 复制代码
// 原始代码:硬编码 .amazonaws.com
func getLambdaHostname(in *kgateway.AwsBackend) string {
    if in.Lambda.EndpointURL != nil {
        return *in.Lambda.EndpointURL
    }
    return fmt.Sprintf("lambda.%s.amazonaws.com", in.Region)
}
  • buildLambdaARN --- 构造 Lambda ARN:
go 复制代码
// 原始代码:硬编码 arn:aws:
arnStr := fmt.Sprintf("arn:aws:lambda:%s:%s:function:%s:%s",
    region, in.AccountId, in.Lambda.FunctionName, in.Lambda.Qualifier)
  • configureLambdaEndpoint --- 解析自定义 endpoint URL:
go 复制代码
// 原始代码:未处理 port 为空的情况
port, err := strconv.ParseUint(parsedURL.Port(), 10, 32)  // parsedURL.Port() 可能返回 ""

克隆源码并切换到 v2.2.0 tag:

shell 复制代码
git clone --depth 1 --branch v2.2.0 https://github.com/kgateway-dev/kgateway.git

新增 partition 判断函数

go 复制代码
// awsPartitionForRegion returns the AWS partition for the given region.
// China regions (cn-*) use "aws-cn" partition.
// GovCloud regions (us-gov-*) use "aws-us-gov" partition.
// All other regions use the standard "aws" partition.
func awsPartitionForRegion(region string) string {
    if strings.HasPrefix(region, "cn-") {
        return "aws-cn"
    }
    if strings.HasPrefix(region, "us-gov-") {
        return "aws-us-gov"
    }
    return "aws"
}

getLambdaHostname 支持中国区后缀

go 复制代码
func getLambdaHostname(in *kgateway.AwsBackend) string {
    if in.Lambda.EndpointURL != nil {
        return *in.Lambda.EndpointURL
    }
    // China regions use .amazonaws.com.cn suffix
    if strings.HasPrefix(in.Region, "cn-") {
        return fmt.Sprintf("lambda.%s.amazonaws.com.cn", in.Region)
    }
    return fmt.Sprintf("lambda.%s.amazonaws.com", in.Region)
}

buildLambdaARN 使用正确的 partition

go 复制代码
func buildLambdaARN(in *kgateway.AwsBackend, region string) (string, error) {
    partition := awsPartitionForRegion(region)
    arnStr := fmt.Sprintf("arn:%s:lambda:%s:%s:function:%s:%s",
        partition, region, in.AccountId, in.Lambda.FunctionName, in.Lambda.Qualifier)
    // ...
}

configureLambdaEndpoint 处理空端口

go 复制代码
portStr := parsedURL.Port()
if portStr == "" {
    // use default port based on scheme (443 for https, 80 for http)
    return config, nil
}
port, err := strconv.ParseUint(portStr, 10, 32)

编译和部署

shell 复制代码
# 编译(需要 Go 1.25.5+,GOTOOLCHAIN=auto 会自动下载)
export GOTOOLCHAIN=auto GOSUMDB="sum.golang.org"
go build -C /path/to/kgateway -o kgateway-bin ./cmd/kgateway/

# 基于原始镜像构建修复版
cat > Dockerfile.cn-fix <<EOF
FROM 000000000000.dkr.ecr.cn-north-1.amazonaws.com.cn/kgateway:v2.2.0
COPY kgateway-bin /usr/local/bin/kgateway
EOF

docker build -f Dockerfile.cn-fix -t 000000000000.dkr.ecr.cn-north-1.amazonaws.com.cn/kgateway:v2.2.0-cn-fix .
docker push 000000000000.dkr.ecr.cn-north-1.amazonaws.com.cn/kgateway:v2.2.0-cn-fix

# 更新控制平面镜像
kubectl set image deployment/kgateway -n kgateway-system \
  controller=000000000000.dkr.ecr.cn-north-1.amazonaws.com.cn/kgateway:v2.2.0-cn-fix

# 重启 Envoy 代理以获取新的 xDS 配置
kubectl rollout restart deployment/http -n kgateway-system

确认 Envoy 配置修正

通过 Envoy admin API 查看 config dump,确认三个关键值已修正:

shell 复制代码
Endpoint: lambda.cn-north-1.amazonaws.com.cn : 443
SNI: lambda.cn-north-1.amazonaws.com.cn
ARN: arn:aws-cn:lambda:cn-north-1:000000000000:function:py314:$LATEST

使用如下命令再次测试,Lambda 函数成功调用,返回 200

shell 复制代码
PROXY_IP=$(kubectl get pods -n kgateway-system -l app.kubernetes.io/name=http \
  -o jsonpath='{.items[0].status.podIP}')
curl -i http://$PROXY_IP:8080/ -H "host: lambda.example.com"
HTTP/1.1 200 OK
content-type: application/json
x-amzn-requestid: ffaf07b3-06a0-4dac-9a44-b1f6f379d917
x-amz-executed-version: $LATEST
x-envoy-upstream-service-time: 302
server: envoy

{"message": "Hello from kgateway + Lambda in China region!", "runtime": "python3.14", "region": "cn-north-1"}
相关推荐
SelectDB14 小时前
Litefuse 开源并推出单进程轻量模式,25 秒就能跑起来的 Agent 可观测与评估平台
运维·后端·自动化运维
XIAOHEZIcode2 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
用户0328472220703 天前
如何搭建本地yum源(上)
运维
大树886 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠6 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质6 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
Inhand陈工6 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智6 天前
ARP代理--工作原理
运维·网络·arp·arp代理
shushangyun_6 天前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
施努卡机器视觉6 天前
SNK施努卡侧滑门锁上滑轮总成自动化装配线,从零件到组件,全流程精密制造方案
运维·自动化·制造