部署前的架构考量
去年团队决定把分散在各处的 LLM 调用收拢到统一入口时,我对比过几种方案。自研网关太重,通用 API 网关又缺少对 Token 计量、模型路由这类 LLM 场景的原生支持。kgateway 吸引我的点在于它把云原生基因和 LLM 场景做了深度结合——基于 Envoy 扩展、原生支持 Kubernetes 部署,同时内置了 OpenAI 协议兼容、多供应商管理和成本管控能力。
这次部署的目标很明确:在 K8s 集群内搭建一个能同时调度云端 OpenAI 和本地 vLLM 实例的统一网关,让上游业务方只认一个地址、一把密钥,背后的模型切换和故障转移对调用方完全透明。
基础环境准备与镜像部署
我们的集群运行的是 Kubernetes 1.28,节点已预先装好 containerd。kgateway 提供官方 Helm Chart,但为了更精细地控制配置,我选择先以 Docker Compose 模式在测试环境验证,再迁移到 K8s。
测试机是一台 8C16G 的 Ubuntu 22.04,Docker 和 Docker Compose 已就绪。先创建目录结构:
mkdir -p /opt/kgateway/{config,data,logs} cd /opt/kgateway核心配置文件config/gateway-config.yaml是整场的关键,后面会逐段拆解。这里先把最小化的 Docker Compose 文件放出来:
# docker-compose.yml version: '3.8' services: kgateway: image: ghcr.io/kgateway/kgateway:v0.7.2 container_name: kgateway restart: unless-stopped ports: - "8080:8080" - "9090:9090" environment: - OPENAI_API_KEY=${OPENAI_API_KEY} - LOCAL_VLLM_KEY=${LOCAL_VLLM_KEY:-EMPTY} volumes: - ./config/gateway-config.yaml:/etc/kgateway/config.yaml:ro - ./data:/data - ./logs:/var/log/kgateway command: ["serve", "--config", "/etc/kgateway/config.yaml"].env文件单独存放敏感信息,已加入.gitignore:
OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx LOCAL_VLLM_KEY=EMPTY启动命令很简洁:
docker-compose up -d docker logs -f kgateway --tail 100看到Server started on :8080和Metrics exposed on :9090两条日志,说明服务已正常拉起。
三段核心配置详解
kgateway 的配置采用 YAML 单文件驱动,结构清晰。我重点展开providers、routing、rate_limiting三段,这也是实际踩坑最多的地方。
providers:双供应商并存配置
providers: - name: "openai-prod" provider_type: "openai" api_key: "${OPENAI_API_KEY}" api_base: "https://api.openai.com/v1" timeout_ms: 30000 models: - name: "gpt-4o" cost_per_token: input: 0.000005 output: 0.000015 - name: "gpt-4o-mini" cost_per_token: input: 0.00000015 output: 0.0000006 - name: "vllm-local" provider_type: "openai" # vLLM 兼容 OpenAI 协议 api_key: "${LOCAL_VLLM_KEY}" api_base: "http://vllm-service.internal:8000/v1" timeout_ms: 60000 models: - name: "llama-3-70b-instruct" cost_per_token: input: 0 output: 0这里有两个细节值得注意。一是 vLLM 的api_key字段不能省略,即使本地服务无鉴权也要填任意非空字符串,否则会触发配置校验失败。二是cost_per_token对本地模型设为 0,便于后续成本报表中区分"真实支出"和"内部核算"。
routing:模型路由与故障转移
routing: default_timeout_ms: 45000 rules: - path: "/v1/chat/completions" allowed_models: - "gpt-4o" - "gpt-4o-mini" - "llama-3-70b-instruct" default_provider: "openai-prod" default_model: "gpt-4o-mini" # 基于请求头的智能路由 header_rules: - header_name: "x-prefer-local" header_value: "true" target_provider: "vllm-local" target_model: "llama-3-70b-instruct" # 模型级故障转移 fallback_chain: - model: "gpt-4o" provider: "openai-prod" - model: "llama-3-70b-instruct" provider: "vllm-local" - path: "/v1/embeddings" allowed_models: - "text-embedding-3-small" default_provider: "openai-prod" default_model: "text-embedding-3-small" cache_enabled: trueheader_rules是我们用得最顺手的特性。业务方在请求头里带x-prefer-local: true,就能强制路由到本地 vLLM,适合处理涉密文档或需要离线运行的场景。fallback_chain则保障了当 OpenAI 返回 429 或超时时的自动降级——实际测试中,我把网络断掉模拟故障,请求在 2.3 秒内切换到了 vLLM,对调用方完全透明。
rate_limiting:Token 级限流与预算拦截
rate_limiting: enabled: true global_rps: 200 # 基于 API Key 的细粒度限流 per_key_limits: - key_pattern: "sk-prod-*" rps: 50 token_budget: daily_input_tokens: 10000000 daily_output_tokens: 5000000 action_on_exceed: "reject_with_429" - key_pattern: "sk-dev-*" rps: 10 token_budget: daily_input_tokens: 500000 daily_output_tokens: 200000 action_on_exceed: "reject_with_429" # 全局 Token 消耗速率保护 token_rate_protection: enabled: true max_tokens_per_minute: 5000000token_budget是老板最关心的功能。我们为生产环境密钥配置了每日 1000 万输入 Token 的上限,超限即返回 429 并附带X-Budget-Exceeded响应头,前端可以据此引导用户联系管理员扩容。这个设计避免了月底收到天价账单的尴尬。
环境变量注入的安全实践
API Key 管理是生产环境的命门。我们的做法分三层:
第一层,运行时注入。如前面的 Compose 文件所示,密钥从不写死在任何配置里,全部通过环境变量传入。K8s 迁移后,改用 Secret 资源挂载:
env: - name: OPENAI_API_KEY valueFrom: secretKeyRef: name: kgateway-secrets key: openai-key第二层,配置分离。gateway-config.yaml中所有敏感字段统一使用${VAR_NAME}占位符,配合 CI/CD 中的环境变量替换,确保同一份配置模板能在 dev、staging、prod 三环境复用。
第三层,密钥轮换。kgateway 支持热加载配置,更新 Secret 后发送 SIGHUP 信号即可生效,无需重启 Pod。我们配合 Vault 的动态 Secret 功能,实现了 24 小时自动轮换。
验证与指标观测
服务启动后的验证分两步走。先用健康检查确认存活:
curl -s http://localhost:8080/health | jq . # 期望输出: {"status":"healthy","version":"v0.7.2"}再用实际请求验证路由和转发:
# 测试云端模型 curl http://localhost:8080/v1/chat/completions \ -H "Authorization: Bearer sk-prod-abc123" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "解释kgateway的routing规则"}], "max_tokens": 200 }' # 测试强制路由到本地 curl http://localhost:8080/v1/chat/completions \ -H "Authorization: Bearer sk-prod-abc123" \ -H "x-prefer-local: true" \ -H "Content-Type: application/json" \ -d '{ "model": "llama-3-70b-instruct", "messages": [{"role": "user", "content": "同样的问题"}] }'Prometheus 指标在 9090 端口暴露,关键指标包括:
| 指标名 | 用途 |
|---|---|
kgateway_request_total | 按模型、状态码分类的请求总量 |
kgateway_token_input_total | 累计输入 Token 数,用于成本核算 |
kgateway_token_output_total | 累计输出 Token 数 |
kgateway_cache_hit_ratio | Embedding 缓存命中率 |
kgateway_fallback_count | 故障转移触发次数 |
我们在 Grafana 中配置了大盘,实时监控各模型的 Token 消耗趋势。上周发现 gpt-4o 的调用量突增,通过kgateway_request_total{model="gpt-4o"}下钻分析,定位到某个新上线的内部工具错误地硬编码了模型名称,修复后成本回归正常。
Embedding 缓存的降本效果
知识库场景的 Embedding 请求有个特点:大量重复文本。kgateway 的缓存层对/v1/embeddings路径做了专门优化:
cache: enabled: true type: "redis" redis_url: "redis://redis.internal:6379/0" ttl_seconds: 86400 embedding_cache: similarity_threshold: 0.98 # 语义相似度阈值 max_entries: 100000开启缓存两周后,Embedding 请求的缓存命中率达到 67%,对应 OpenAI 账单下降约 62%。这个收益远超预期,因为内部知识库的文档更新频率本身不高,大量查询集中在固定的一批 FAQ 上。
从单点到高可用的演进
测试环境验证通过后,向 K8s 生产环境的迁移水到渠成。我们的演进路径分三个阶段:
第一阶段:单实例部署。直接用一个 Deployment + Service 暴露,快速验证业务兼容性。此时配置文件通过 ConfigMap 挂载,Secret 通过环境变量注入。
第二阶段:多实例无状态化。kgateway 本身不保存请求状态,完全适合水平扩展。我们将副本数调到 3,前置 Nginx Ingress 做负载均衡。配置统一迁移到独立的配置服务,支持热更新。
第三阶段:多可用区部署。利用 K8s 的拓扑分布约束,确保 Pod 分散在不同节点和可用区:
topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: kgateway同时,vLLM 后端也做了多副本部署,配合 kgateway 的fallback_chain实现跨可用区的故障转移。最近一次机房网络抖动中,这个架构保证了核心业务的零中断。
回头看这次部署,最大的体会是:LLM 网关的价值不只是"转发请求",而是把模型管理、成本控制、稳定性保障这些分散在各处的工程问题,收敛到一个云原生的统一控制面上。kgateway 的 YAML 驱动配置虽然初期需要仔细打磨,但一旦跑顺,后续的新模型接入、策略调整都变得非常轻量。团队现在新增一个模型供应商,平均只需 15 分钟配置加验证,这在以前是不敢想的效率。