核心摘要
- 通过单个NLB配合APISIX的TLS SNI路由能力,替代传统多NLB架构,大幅降低成本与运维复杂度
- 基于Amazon EKS部署APISIX,结合Graviton ARM架构实例可获得至少两倍的性能提升
- 完整涵盖从集群创建、Helm部署、证书配置到路由测试的全流程实操步骤
- 提供生产环境最佳实践,包括组件分离、水平扩展、安全加固与监控告警策略
EKS + APISIX + Graviton 云原生游戏网关架构实战指南
游戏服务网关面临的核心挑战
现代游戏运营环境中,随着玩家规模持续增长,游戏服务器数量往往从初期的几台快速扩展至成百上千台。这种规模化增长给传统服务器代理方案带来了严峻考验,尤其是依赖多个 Network Load Balancer (NLB) 进行流量分发的架构,正面临越来越突出的管理瓶颈。
服务器规模扩展困境
- 游戏成功运营后,服务器数量呈指数级增长,传统多NLB方案的配置管理日益繁琐
- 每新增一个NLB都意味着额外的费用支出,成本压力随规模线性上升
- 服务器扩缩容时需要频繁调整负载均衡配置,人工操作容易出错
安全防护需求
- 游戏服务器需要抵御DDoS、CC攻击等多种网络威胁
- 传统TCP协议缺乏足够的安全保护机制,数据传输存在被窃听风险
- 安全加固不能以牺牲性能为代价,玩家体验必须得到保障
运维复杂性激增
- 多个NLB的配置分散管理,缺乏统一视图
- 监控指标分布在不同资源上,故障排查难度随规模增加而倍增
- 配置变更缺乏版本控制,回滚操作困难重重
面对这些挑战,Apache APISIX 作为一款高性能、可扩展的云原生API网关,结合TLS加密与SNI路由能力,能够优雅地解决上述问题。通过将APISIX部署在 Amazon EKS 集群中,并运行于 Graviton ARM架构实例上,可以构建一套高效、安全且易于管理的游戏服务网关系统。
整体架构设计解析
架构全景
APISIX核心组件运行于 Amazon EKS (Elastic Kubernetes Service) 集群内部。整个系统划分为两大访问入口:运维(Ops)和玩家(Players),分别通过独立的ELB接入。这里有一个值得注意的部署技巧:建议在部署环境前先手动创建ELB,然后在EKS中通过 TargetGroupBinding 方式绑定服务,这样可以确保后续服务变更时前端接入的ELB地址保持不变,避免DNS切换带来的影响。
流量入口设计
运维入口:运维人员通过专用ELB访问EKS集群中的Admin API,实现对整个平台的管理、监控和配置操作。这一入口应当配置严格的访问控制策略,仅允许内网或VPN访问。
玩家入口:玩家流量通过独立的ELB进入EKS集群,主要访问API Gateway层,进而根据SNI信息路由到具体的游戏服务(Game Server)或平台服务(Platform Service)。
EKS集群内部组件
Admin API层:提供RESTful管理接口,供运维人员操作和管理整个系统,包括路由配置、证书管理、插件启停等功能。
etcd层:作为分布式键值存储,负责服务发现、配置管理等核心功能。Admin API将配置变更写入etcd,API Gateway通过watch机制实时感知服务变化,实现配置热更新。
API Gateway层:这是玩家访问的主要入口,APISIX网关负责根据etcd中的服务发现信息和路由规则,将玩家请求精准路由到后端的具体服务。
业务服务层:包含平台服务(Platform Service)和多个游戏服(Game Server1、Game Server2等),这些服务是最终处理玩家请求的核心业务组件。
方案部署完整流程
下面将逐步验证整个方案。方案中采用模拟TCP协议的游戏服务,通过ELB配合APISIX实现不同游戏服的路由功能。
创建EKS集群
参考 EKS官方文档 创建EKS集群。集群创建完成后,需要添加用户权限并创建Access Entry,确保后续操作具备足够的权限。
使用Helm部署APISIX
本方案的部署目标服务器采用 Graviton 机型,可以充分发挥APISIX的性能优势。根据社区Benchmark测试,Graviton机型相对于x86机型可提供至少两倍的性能提升。
添加Helm仓库:
helm repo add apisix https://charts.apiseven.com
helm repo update
准备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
password: Admin@2025
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
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
执行Helm安装命令:
helm install apisix apisix/apisix --create-namespace --namespace ingress-apisix \
--values apisix-values.yaml
关键注意事项:如果部署过程中遇到问题,务必检查当前集群的 storageClass 是否存在。etcd的持久化存储配置 storageClass: efs-sc 必须指向有效的存储类,否则可能导致部署失败。
这里分享一个实用技巧:如果部署出现问题,可以使用 Amazon Q CLI 进行自动化诊断,整个排查过程高度自动化,能够快速定位问题根源。
部署模拟游戏服务
模拟游戏服代码(tcp-echo-server.py):
#!/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:
while True:
data = client_socket.recv(1024)
if not data:
break
print(f"[{server_name}] Received: {data}")
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)
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)
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证书
使用TLS的 SNI(Server Name Indication) 功能时,每个需要使用SNI的域名都必须配置有效的证书。SNI允许从同一个IP地址和端口为多个主机名提供服务,证书用于验证服务器身份并建立加密连接。
生成自签名证书:
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"
上传证书到APISIX:
kubectl port-forward -n ingress-apisix svc/apisix-admin 9180:9180 &
sleep 3
# 上传gs-1.com证书
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"]
}'
# 上传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
验证证书上传结果:
curl -X GET http://127.0.0.1:9180/apisix/admin/ssls \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1'
配置路由规则
基于已配置的证书,通过Admin API配置相关路由信息。在实际生产环境中,平台服配置好证书后,可以调用这些API动态配置路由:
kubectl port-forward -n ingress-apisix svc/apisix-admin 9180:9180 &
sleep 3
# 配置gs-1路由
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"
}'
# 配置gs-2路由
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
通过SNI测试不同游戏服访问:
# 访问gs-1游戏服
openssl s_client -connect k8s-ingressa-apisixga-xxxxx.xxxx.elb.ap-northeast-1.amazonaws.com:8888 \
-servername gs-1.com -quiet
# 访问gs-2游戏服
openssl s_client -connect k8s-ingressa-apisixga-xxxx.xxxx.elb.ap-northeast-1.amazonaws.com:8888 \
-servername gs-2.com -quiet
通过不同的SNI即可访问到不同的游戏服,成功实现了使用同一个NLB配合APISIX访问不同游戏服的目标。
Dashboard访问(可选)
可以通过Dashboard可视化界面访问APISIX系统,查看和管理相关配置数据。需要确认ALB的 certificate ARN 配置正确:
kubectl get ingress -n ingress-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负载均衡将流量分发至APISIX,避免在L7层引入过多额外延迟
- TLS终端:如需HTTPS支持,推荐在边缘层终端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机型可提供至少两倍的性能提升
- 插件开关粒度:仅在需要的路由上启用插件,避免全局加载过多插件导致请求路径冗余执行
- 缓存与限流:利用 proxy-cache 插件对静态或可缓存响应进行本地缓存,减轻后端压力;结合 limit-req、limit-count 插件防止流量突发与恶意攻击
- 日志与追踪:启用skywalking、zipkin或opentelemetry插