亚马逊AWS官方博客

云原生游戏网关架构:EKS + APISIX + Graviton 构建高性能游戏服务网关

前言

在现代游戏运营环境中,随着游戏服务器规模的不断扩大,传统的服务器代理方案面临着诸多挑战。本文将介绍如何使用 API Six 这一高性能网关来解决大规模游戏服务器代理的问题,特别是针对需要使用多个 Network Load Balancer (NLB) 的场景,提供一个更加优雅和高效的解决方案。
在游戏服务架构中,我们经常遇到以下几个关键挑战:

  1. 服务器规模问题
    • 随着游戏的成功运营,服务器数量可能从最初的几台扩展到成百上千台
    • 传统的使用多个 NLB 进行代理的方案在管理和维护上变得越来越复杂
    • 成本问题:每增加一个 NLB 都会带来额外的费用支出
  2. 安全性考虑
    • 游戏服务器需要防护各种网络攻击
    • 传统的 TCP 协议缺乏足够的安全保护机制
    • 需要在不影响性能的前提下提供安全保障
  3. 运维复杂性
    • 多个 NLB 的配置管理较为繁琐
    • 服务器扩缩容时需要频繁调整负载均衡配置
    • 监控和故障排查的难度随着规模增加而增加

面对这些挑战,我们需要一个更现代化的解决方案。API Six 作为一个高性能、可扩展的网关,结合 TLS 加密,能够很好地解决这些问题。在接下来的内容中,我们将详细介绍如何使用 API Six 构建一个高效、安全、易于管理的游戏服务网关系统。

架构介绍

1. 架构整体说明

APIsix核心组件运行于 Amazon EKS(Elastic Kubernetes Service)集群内部。整个系统主要分为两大访问入口:运维(Ops)和玩家(Players),分别通过独立的 ELB(Elastic Load Balancer)接入.(在此建议咱们在部署环境前可以先手动创建ELB, 在EKS中通过TargetGroupBinding的方式来进行绑定服务,这样可以保证后续服务变更时前端接入ELB为同一个.)

2. 流量入口

  • Ops(运维)入口
    运维人员通过 ELB 访问 EKS 集群中的 Admin API,实现对平台的管理和监控。
  • Players(玩家)入口
    玩家流量同样通过独立的 ELB 进入 EKS 集群,主要访问 API Gateway,进而路由到具体的游戏服务(Game Server)或平台服务(Platform Service)。

3. EKS 集群内部结构

  • Admin API 层
    提供管理接口,供运维人员操作和管理整个系统。
  • etcd 层
    作为分布式键值存储,负责服务发现、配置管理等核心功能。Admin API 会将变更写入 etcd,API Gateway 通过 watch 机制实时感知服务变化。
  • API Gateway 层
    这一层是玩家访问的主要入口,API Gateway 负责根据 etcd 的服务发现信息,将玩家请求路由到后端的具体服务(如 Platform Service 或 Game Server)。
  • 业务服务层
    包含平台服务(Platform Service)和多个游戏服(Game Server1、Game Server2 等),这些服务是最终处理玩家请求的核心业务组件。

方案部署

下面我们将逐步来验证整个方案, 方案中我们将采用模拟TCP协议的游戏服务,通过ELB来实现不同游戏服的路由功能.首先我们需要创建一个实验EKS集群, 参考 EKS文档 创建EKS.

创建好EKS后, 添加用户权限

然后创建Access Entry

使用Helm来安装部署APISix

本文采用的部署目标服务器为亚马逊云科技Graviton机型,可以帮助我们发挥APISix的最大性能. 参考步骤如下:

  1. 添加相关helm库
helm repo add apisix https://charts.apiseven.com
helm repo update

  1. 整理apisix-values.yaml
service:
  type: LoadBalancer
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
    service.beta.kubernetes.io/aws-load-balancer-type: nlb
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp
  # Stream proxy configuration (L4 proxy TCP/UDP)
  stream:
    enabled: true
    only: true # Disable HTTP proxy and only enable stream proxy
    tcp:
      - addr: 8888
        tls: true
    udp: []
# Enable APISIX Ingress Controller
ingress-controller:
  enabled: false
# Enable APISIX Dashboard
dashboard:
  enabled: true
  config:
    conf:
      etcd:
        endpoints:
          - apisix-etcd:2379
        prefix: "/apisix"
    authentication:
      secret: Admin@2025
      expire_time: 3600
      users:
        - username: admin # dashboard 用户名
          password: Admin@2025 # dashboard 密码
  ingress:
    enabled: true
    className: "alb"
    annotations:
      alb.ingress.kubernetes.io/scheme: internet-facing
      alb.ingress.kubernetes.io/target-type: ip
      alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
      alb.ingress.kubernetes.io/healthcheck-path: "/dashboard"
      alb.ingress.kubernetes.io/healthcheck-protocol: HTTP
      alb.ingress.kubernetes.io/healthcheck-port: traffic-port
      alb.ingress.kubernetes.io/healthcheck-interval-seconds: '10'
      alb.ingress.kubernetes.io/healthcheck-timeout-seconds: '5'
      alb.ingress.kubernetes.io/success-codes: '200'
      alb.ingress.kubernetes.io/healthy-threshold-count: '2'
      alb.ingress.kubernetes.io/unhealthy-threshold-count: '2'
    hosts:
      - host: ""
        paths:
          - "/*"
# Basic APISIX configuration
apisix:
  image:
    repository: apache/apisix
    tag: 3.7.0-debian
    pullPolicy: IfNotPresent
  replicaCount: 2
  admin:
    enabled: true
    service:
      type: ClusterIP
# etcd configuration
etcd:
  image:
    repository: bitnami/etcd
    tag: 3.5.9
    pullPolicy: IfNotPresent
  persistence:
    storageClass: efs-sc
  replicaCount: 3
  service:
    port: 2379
  prefix: "/apisix"
  timeout: 30

# Resource settings
resources:
  limits:
    cpu: 1000m
    memory: 2Gi
  requests:
    cpu: 500m
    memory: 1Gi

# Timezone setting
timezone: "UTC"

# AWS EKS specific settings
nodeSelector:
  kubernetes.io/os: linux
  kubernetes.io/arch: arm64
  
# Tolerations for Graviton nodes (if needed)
tolerations:
  - key: "kubernetes.io/arch"
    operator: "Equal"
    value: "arm64"
    effect: "NoSchedule"

# Affinity to prefer Graviton nodes
affinity:
  nodeAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
    - weight: 100
      preference:
        matchExpressions:
        - key: kubernetes.io/arch
          operator: In
          values:
          - arm64
  1. 执行命令更新服务
helm install apisix apisix/apisix --create-namespace --namespace ingress-apisix \
--values apisix-values.yaml
  1. 如果此处部署有问题,一定要关注一下当前的storageclass是否存在.
etcd:
  persistence:
    storageClass: efs-sc # 请格外注意此处,否则可能方案部署失败.

另推荐一个小技巧,如果部署出现问题,可以使用Amazon Q CLI来做诊断,整个过程完全自动化,下面是我的步骤截图.

部署 游戏服务

模拟游戏服代码

#!/usr/bin/env python3
import socket
import sys
import threading

# Get server name from command line argument
server_name = sys.argv[1] if len(sys.argv) > 1 else "Unknown Server"

def handle_client(client_socket, addr):
    print(f"[{server_name}] Connection from {addr}")
    try:
        # Keep connection alive and echo back data
        while True:
            data = client_socket.recv(1024)
            if not data:
                break
                
            print(f"[{server_name}] Received: {data}")
            
            # Echo back the data with server name prefix
            response = f"[{server_name}] {data.decode('utf-8', errors='ignore')}".encode()
            client_socket.send(response)
    except Exception as e:
        print(f"[{server_name}] Error handling client: {e}")
    finally:
        print(f"[{server_name}] Connection closed: {addr}")
        client_socket.close()

def start_server(port=9000):
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
    # Bind to all interfaces
    server.bind(('0.0.0.0', port))
    server.listen(5)
    
    print(f"[*] {server_name} listening on 0.0.0.0:{port}")
    
    try:
        while True:
            client, addr = server.accept()
            client_handler = threading.Thread(target=handle_client, args=(client, addr))
            client_handler.daemon = True
            client_handler.start()
    except KeyboardInterrupt:
        print(f"[{server_name}] Shutting down server")
        server.close()

if __name__ == "__main__":
    start_server(9000)

模拟EKS中的服务部署代码: test-server-1.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-server-1
  namespace: game
  labels:
    app: test-server-1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-server-1
  template:
    metadata:
      labels:
        app: test-server-1
    spec:
      containers:
      - name: tcp-server
        image: python:3.9-slim
        command: ["python"]
        args: ["-u", "/app/tcp-echo-server.py", "test-server-1"]
        ports:
        - containerPort: 9000
        volumeMounts:
        - name: script-volume
          mountPath: /app
      volumes:
      - name: script-volume
        configMap:
          name: tcp-echo-server
          defaultMode: 0777
---
apiVersion: v1
kind: Service
metadata:
  name: gs-1
  namespace: game
  labels:
    app: test-server-1
spec:
  selector:
    app: test-server-1
  ports:
  - port: 9000
    targetPort: 9000
    protocol: TCP
  type: ClusterIP 
test-server-2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-server-2
  namespace: game
  labels:
    app: test-server-2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-server-2
  template:
    metadata:
      labels:
        app: test-server-2
    spec:
      containers:
      - name: tcp-server
        image: python:3.9-slim
        command: ["python"]
        args: ["-u", "/app/tcp-echo-server.py", "test-server-2"]
        ports:
        - containerPort: 9000
        volumeMounts:
        - name: script-volume
          mountPath: /app
      volumes:
      - name: script-volume
        configMap:
          name: tcp-echo-server
          defaultMode: 0777
---
apiVersion: v1
kind: Service
metadata:
  name: gs-2
  namespace: game
  labels:
    app: test-server-2
spec:
  selector:
    app: test-server-2
  ports:
  - port: 9000
    targetPort: 9000
    protocol: TCP
  type: ClusterIP

部署服务

kubectl create namespace game
kubectl create configmap tcp-echo-server --from-file=tcp-echo-server.py --namespace game
kubectl apply -f test-server-1.yaml
kubectl apply -f test-server-2.yaml

配置证书

当使用TLS的SNI功能时,每个你想要使用SNI的域名或主机名都需要一个有效的证书。这是因为SNI允许从同一个IP地址和端口提供多个主机名服务,而证书用于验证服务器的身份并与客户端建立加密连接。使用OpenSSL为2个Game Server服务生成证书文件和密钥文件。

  1. 生成证书
    • openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout gs-1.key -out gs-1.crt -subj "/CN=gs-1.com"
      openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout gs-2.key -out gs-2.crt -subj "/CN=gs-2.com"
      
  2. 上传证书到apisix
    • kubectl port-forward -n ingress-apisix svc/apisix-admin 9180:9180 &
      sleep 3
      curl  -X POST http://127.0.0.1:9180/apisix/admin/ssls -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -d '
      {
          "cert": "'"$(cat gs-1.crt)"'",
          "key": "'"$(cat gs-1.key)"'",
          "snis": ["gs-1.com"]
      }'
      # Create SSL certificate for gs-2.com
      curl -X POST http://127.0.0.1:9180/apisix/admin/ssls -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1'  -d '
      {
          "cert": "'"$(cat gs-2.crt)"'",
          "key": "'"$(cat gs-2.key)"'",
          "snis": ["gs-2.com"]
      }'
      kill %1
      
  3. 验证证书上传
    • curl -X GET http://127.0.0.1:9180/apisix/admin/ssls -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1'

配置路由

下面我们基于已经配置好的证书来配置相关的路由信息, 也就是通常我们在平台服配置好证书后,可以调用相关API来配置路由,命令信息如下:

kubectl port-forward -n ingress-apisix svc/apisix-admin 9180:9180 &
sleep 3
curl -i -X POST http://127.0.0.1:9180/apisix/admin/stream_routes -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -d '{
  "upstream": {
    "nodes": {
      "gs-1.game.svc.cluster.local:9000": 1
    },
    "type": "roundrobin"
  },
  "sni": "gs-1.com"
}'

curl -i -X POST http://127.0.0.1:9180/apisix/admin/stream_routes -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -d '{
  "upstream": {
    "nodes": {
      "gs-2.game.svc.cluster.local:9000": 1
    },
    "type": "roundrobin"
  },
  "sni": "gs-2.com"
}'

测试基于SNI的访问

首先获取对应APIsix服务的ALB地址

> kubectl get svc -n ingress-apisix apisix-gateway
NAME             TYPE           CLUSTER-IP      EXTERNAL-IP                                                                     PORT(S)                       AGE
apisix-gateway   LoadBalancer   10.100.xxxx.12   k8s-ingressa-apisixga-xxxxxxx-xxx.elb.us-east-1.amazonaws.com   80:30496/TCP,8888:30694/TCP   3d2h

通过上面返回获取的ALB的地址

openssl s_client -connect k8s-ingressa-apisixga-xxxxx.xxxx.elb.ap-northeast-1.amazonaws.com:8888 \
-servername gs-1.com -quiet
openssl s_client -connect k8s-ingressa-apisixga-xxxx.xxxx.elb.ap-northeast-1.amazonaws.com:8888 \
-servername gs-2.com -quiet

至此可以看到通过不同的SNI我就可以访问到不同的游戏服了,也就解决了使用同一个NLB+APIsix的访问不同的游戏服了.

APISix dashboard访问 (Optional)

我们也可以通过Dashboard来访问当前的APIsix系统,查看相关的配置数据. 不过这里需要我们确认一下ALB的certificate ARN 是不是正确.

kubectl get ingress -n ingress-apisix

APISix 部署亚马逊云科技最佳实践

在生产环境中部署 Apache APISIX 时的关键最佳实践,帮助提升稳定性、性能与可维护性。

核心架构与组件分离

为了保证系统可扩展与高可用,推荐将 APISIX 各核心组件解耦部署:

  • 控制平面(etcd):使用单独部署的 etcd 集群存储路由与插件配置,建议在部署APISix的时候直接指向预先部署好的etcd,至少部署 3 节点,开启数据持久化与快照备份,防止单点故障。
  • 数据平面(APISIX 节点):外部请求由多个 APISIX 实例处理,按需水平扩容。每个实例仅负责流量转发与插件执行,配置从 etcd 动态拉取。
  • 运维监控(Prometheus & Grafana):部署专用的监控系统,采集 APISIX 及 etcd 的指标与日志,实时告警与可视化。

部署模式与扩展策略

  • 无状态部署
    APISIX 实例本身应保持无状态,所有动态配置均存储在 etcd。容器化或虚拟机化均可,借助 Kubernetes 等平台实现自动伸缩与滚动升级。
  • 水平扩展
    根据 QPS 与响应延迟指标,动态扩缩容。建议在 Kubernetes 中配置 HPA(Horizontal Pod Autoscaler),结合自定义指标(如 CPU、内存或请求速率)自动调整实例数。
  • 灰度发布与回滚
    配合 Kubernetes Deployment 或其它发布工具,利用 canary 发布策略逐步下发新版本。在出现问题时,可快速回滚至稳定版本,且不中断大部分流量。
  • 优雅退出, 需要保证apisix pod退出时请求都已经处理完毕
lifecycle:
  preStop:
    exec:
      command: ["sh", "-c", "sleep 15 && apisix quit"]

网络与安全

  • 高性能网络
    采用 L4 负载均衡(如 Nginx Stream、LVS)将流量分发至 APISIX,避免在 L7 层引入过多额外延迟。
  • TLS 终端
    如需 HTTPS 支持,推荐在边缘层(L4)终端 TLS,再以 HTTP 通信至 APISIX;或直接在 APISIX 上使用 ssl 插件终端 TLS,并结合 Cert-Manager 自动续签证书。
  • 访问控制与认证
    启用 IP 黑白名单、ACL 插件,并根据业务需求接入 JWT、OAuth2 等认证插件,确保后端服务安全。

配置管理与版本控制

  • 基础配置与热更新
    把路由、上游服务、插件配置以 YAML/JSON 格式存储于代码仓库,结合 CI/CD 流水线自动同步至 etcd,实现配置即代码(Configuration as Code)。
  • 版本管理
    每次配置变更都需打 tag 并在流水线中校验(lint、单元测试、灰度发布),确保变更可追溯、可回滚。
  • 选择稳定版本,并及时跟进社区的更新.
  • 升级版本时需要进行完整的回归测试,保证新版本的兼容性问题.

性能优化与插件治理

  • 实例选择
    优先选择Graviton类型主机,经过多轮测试发现Graviton的机型相对于x86机型可以提供至少2两倍的性能提升,具体请参考社区 Benchmark 链接:.
  • 插件开关粒度

仅在需要的路由上启用插件,避免全局加载过多插件导致请求路径冗余执行。

  • 缓存与限流
    利用 proxy-cache 插件对静态或可缓存响应进行本地缓存,减轻后端压力;结合 limit-req、limit-count 插件防止流量突发与恶意攻击。
  • 日志与追踪
    启用 skywalking、zipkin 或 opentelemetry 插件,将请求链路与指标上报至分布式追踪系统,快速定位性能瓶颈。

监控告警与健康检查

  • 健康探针
    在 Kubernetes 中配置 LivenessProbe 与 ReadinessProbe,APISIX 节点异常时可自动剔除。
  • 关键指标
    重点监控请求速率、响应延迟、错误率,以及 etcd 的延迟与 leader 选举状态。根据阈值配置告警规则,保证故障可被及时发现与响应
  • 在实际生产中,如果service数量比较多以及并发大的情况下,需要对netfilter.nf_conntrack_max进行调整。建议结合prometheus和grafana进行告警,及时发现问题并优化相关参数。我们也可以通过采用类似C7gn的机型来提升网络吞吐。

灾备与高可用设计

  • 跨可用区部署
    将 etcd 和 APISIX 实例分布在多个可用区或机房,保证单区故障时仍有服务可用。
  • 定期备份
    对 etcd 数据进行周期性全量与增量备份,并在异地存储;同时验证备份可用性与恢复流程。

通过上述最佳实践,可以构建一套 高可用、可扩展、易运维 的 APISIX 服务部署体系,满足业务在复杂流量下的稳定运行与快速迭代需求。

总结

借助以上方案通过将所有玩家和运维流量先汇聚到单个NLB,再由部署在 EKS 集群内的 Apache APISIX 按 TLS SNI 把请求精准分发到各游戏服,从而用最少的负载均衡实例实现统一路由、动态服务发现和全链路加密,不仅显著降低 NLB 成本和配置复杂度,还能在服务器扩缩容时保持流量无感知切换,成为高并发游戏场景下经济、高效且易维护的网关架构,同时,借助Graviton,APISix能够实现极高的性价比。

参考内容

https://api7.ai/blog/api7-latency

https://apisix.apache.org/blog/2022/08/12/arm-performance-google-aws-azure-with-apisix/

本篇作者

郭俊龙

亚马逊云科技解决方案架构师,主要负责游戏行业客户解决方案设计,比较擅长云原生微服务以及大数据方案设计和实践。

任耀洲

AWS解决方案架构师,负责企业客户应用在AWS的架构咨询和
设计。在微服务架构设计、数据库等领域有丰富的经验

刘幸园

亚马逊云科技客户技术经理。主要负责游戏、互联网行业客户的架构优化、成本管理、技术咨询等工作。拥有 10 年以上的数据库优化、项目管理与技术支持经验。

杨冬冬

亚马逊云科技资深容器解决方案架构师,在云原生领域深耕多年,拥有丰富的行业经验。