ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

实战记录:基于 GitHub Actions、ArgoCD 与 Kong 网关将 LiteLLM 部署至 OCI ARM 节点的完整踩坑与落地实践

实战记录:基于 GitHub Actions、ArgoCD 与 Kong 网关将 LiteLLM 部署至 OCI ARM 节点的完整踩坑与落地实践 实战记录基于 GitHub Actions、ArgoCD 与 Kong 网关将 LiteLLM 部署至 OCI ARM 节点的完整踩坑与落地实践1. 架构目标与部署背景在多模型网关系统的建设中我们希望将LiteLLM Proxy作为底层大模型统一接入层Service A部署在已有的跨云 KubernetesTencent Cloud K3s 控制面 OCIfree-arm-vmARM64 工作节点集群中。整个发布与运行链路要求满足以下生产级工程标准不可变 GitOps 交付使用 GitHub Actions 构建多架构容器镜像通过 Digest Pinning内容哈希寻址自动触发 ArgoCD 发布杜绝依赖会漂移的 tag机密全生命周期托管Zero Secrets in Git真实 API Key 和 Redis 密码统一保存在 OCI Vault 中通过 External Secrets OperatorESO自动同步到集群内 Secret代码仓库中仅保留结构映射12-Factor 配置与机密解耦非敏感配置LiteLLM 模型列表、Redis 连接参数、网络代理白名单由ConfigMap承载敏感机密由Secret承载容器通过envFrom与卷挂载解耦消费统一入口网关Kong Gateway API不为服务单独部署额外 Ingress 控制器复用现有 Kong Gateway 暴露公网 IP通过 Kubernetes Gateway API 的HTTPRoute实现跨命名空间转发与路径前缀剥离。最终的系统数据流与部署拓扑如下[ 外部客户端 / 业务系统 ] │ (HTTP :31850 /litellm/v1/...) ▼ [ OCI free-arm-vm: Kong Gateway (NodePort) ] │ (Gateway API HTTPRoute 剥离 /litellm 前缀) ▼ [ LiteLLM Pod (:4000) on free-arm-vm ] ──(DNS)──► [ K3s Redis (:6379) on free-arm-vm ] │ (环境变量解耦注入) ├─► [ ConfigMap: litellm-config (/app/config/config.yaml) ] └─► [ Secret: litellm-secrets (来自 OCI Vault 的 Gemini Key / Master Key / Redis 密码) ] │ ▼ (HTTPS 出站) [ Google Gemini API (gemini-3.6-flash / gemini-3.7-flash) ]2. CI 与镜像构建解决多架构与 Tag 漂移问题2.1 多架构构建与 Digest Pinningfree-arm-vm是 4C24G 的 ARM64 实例而开发机与 CI Runner 多为 AMD64。因此 GitHub Actions 工作流必须生成多架构 Manifest Index# .github/workflows/build-and-push-image.yaml-name:Build and push imageid:builduses:docker/build-push-actionv6with:context:.platforms:linux/amd64,linux/arm64push:truetags:${{steps.meta.outputs.tags}}-name:Record manifest digestrun:|echo Digest: ${{ steps.build.outputs.digest }}2.2 为什么必须使用 Digest Pinning 而非 Commit Tag在初期验证时我们发现相同的 Git Commit SHA 再次触发构建时GHCR 中生成的镜像 Tag 名称虽然相同但由于基础镜像更新或 BuildKit 元数据变化Tag 指向的底层 Manifest Index Digest 发生了改变。如果 ArgoCD 直接跟踪sha-commit当 CI 重跑后Git 仓库中的清单没有任何提交记录但线上拉取的镜像内容已经发生漂移这严重破坏了 GitOps 的可复现性与审计能力。因此我们升级了共享 Helm Chartgeneric-web-service-v2v2.1.0在 Deployment 模板中引入 Digest 优先机制# templates/deployment.yamlimage:{{-if .Values.image.digest}}{{.Values.image.repository}}{{.Values.image.digest}}{{-else}}{{.Values.image.repository}}:{{.Values.image.tag}}{{-end}}CI 构建成功后通过repository_dispatch自动向 GitOps 清单仓库my-argocd-manifests提交具体的image.digest: sha256:...实现内容级别的严格锁定。3. 机密管理OCI Vault 与 External Secrets Operator (ESO) 落地为了实现“零机密进 Git”我们采用了 OCI 托管的 Vault 与 Kubernetes 内的 External Secrets OperatorESO。3.1 架构与 Bootstrap 凭证设计ESO 负责将 OCI Vault 中的机密拉取为 Kubernetes Secret但 ESO 访问 OCI 也需要身份证明。为此我们建立了专用的机器账号User PrincipalOCI IAM 用户litellm-vault-readerOCI IAM 组litellm-vault-readers本地私钥/home/gateman/keys/litellm-vault-reader.pem公钥指纹已上传至 OCI 用户下Bootstrap Secret在集群目标命名空间llm-system中手工注入一次性凭据kubectl create secret generic oci-litellm-vault-reader-nllm-system\--from-fileprivateKey/home/gateman/keys/litellm-vault-reader.pem\--from-literalfingerprintb3:f3:cc:2b:b0:9d:88:c9:75:08:0f:82:e5:b6:e1:a13.2 踩坑排查OCI IAM 权限与密钥解密死锁在配置好SecretStore和ExternalSecret后发现 Secret 始终无法同步ESO 控制器抛出错误Secrets service failed to GetSecretBundleByName, HTTP status code 404: Authorization failed or requested resource not found.排查与原因定位使用 OCI CLI 工具带着litellm-vault-reader凭据复现请求同样复现了404 NotAuthorizedOrNotFound检查 OCI 用户属性发现虽然创建了用户litellm-vault-reader但该用户未被加入litellm-vault-readers组OCI Vault 的机密内容由 KMS 主密钥Master Encryption Key加密存储。调用GetSecretBundleByName时OCI 底层需要解密机密载荷。原 IAM Policy 仅配置了read secret-bundles缺少 KMS 密钥解密权限。修复方案将用户加入专属组oci iam group add-user\--group-idocid1.group.oc1..aaaaaaaajvxytvkmyupwguyzza27dsx2ovbtv26sg2dyppdsnuluxdpe2zja\--user-idocid1.user.oc1..aaaaaaaa3fcoxuuzcdtwav4mcsqwxznzsarmj6ctugtnravseu5i5hst7neq补齐 Policy 权限加入use keys和use key-delegate[Allow group litellm-vault-readers to read secret-family in compartment litellm-prod,Allow group litellm-vault-readers to read vaults in compartment litellm-prod,Allow group litellm-vault-readers to read secrets in compartment litellm-prod,Allow group litellm-vault-readers to use keys in compartment litellm-prod,Allow group litellm-vault-readers to use key-delegate in compartment litellm-prod,Allow group litellm-vault-readers to inspect compartments in tenancy]策略更新生效后ESO 立即完成了密钥抓取llm-system/litellm-secrets状态转为SecretSynced / Ready: True成功生成包含OPENAI_API_KEY_FREE_1、LITELLM_MASTER_KEY和REDIS_PASSWORD的 Kubernetes Secret。3.3 跨云 DNS 抖动优化internalTrafficPolicy: Local由于集群节点分布在腾讯云与 OCI早期 ESO 控制器在跨节点请求 CoreDNS10.43.0.10:53时因 UDP 流量走 Tailscale 叠加网络偶发超时。优化方案将 CoreDNS 扩容至 3 副本确保 OCIfree-arm-vm本地运行一个 CoreDNS Pod将kube-dnsService 的内部流量策略由Cluster切换为Localkubectl patch svc kube-dns-nkube-system--typemerge-p{spec:{internalTrafficPolicy:Local}}此后本节点所有 Pod 的 DNS 解析请求均直接由本地 CoreDNS 承载耗时降至 0ms彻底消除了网络超时告警。4. ArgoCD 控制面排错与 GitOps 自动化发布4.1 控制面假死排查SQLite IO Wait 与跨节点网络在配置好 Application 清单后发现 ArgoCD Web 界面无法打开应用同步状态卡在Unknown。排查过程登录阿里云 Master 控制节点查看 K3s 日志journalctl -u k3s发现系统每隔 30 秒打印vxlan_network.go: external interface not found检查节点负载top发现 CPU100% waI/O Wait内存使用率超过 95%原因为阿里云 Master 为 2C2G 规格底层的 Kine (SQLite) 累计了 78 天共 420 万次历史 revision在执行压缩检查点时产生剧烈磁盘 I/O同时所有 ArgoCD 组件挤在同一节点加剧了内存竞争。修复与拓扑优化重启 Master 节点的 k3s 进程systemctl restart k3s重新初始化虚拟网桥重新编排 ArgoCD Pod 拓扑核心通信后台application-controller、repo-server、redis保留在 Master 节点避免跨云跨节点 gRPC 远程调用较重的 Web 界面argocd-server调度至 OCI 节点调整后Master 节点 I/O Wait 降为 0%CPU 空闲率达到 99%ArgoCD 所有 Application 状态恢复为SyncedHealthy。5. LiteLLM 容器运行时踩坑与 12-Factor 配置重构5.1 踩坑Redis 配置丢失导致容器 CrashLoopBackOffArgoCD 将 Pod 调度到free-arm-vm后Pod 启动失败并反复重启。查看日志发现报错Setting Cache on Proxy File /app/.venv/lib/python3.12/site-packages/litellm/_redis.py, line 475, in _get_redis_client_logic raise ValueError(Either host or url must be specified for redis.) ValueError: Either host or url must be specified for redis.根本原因在config.yaml中Redis 主机配置为host: os.environ/REDIS_HOST。但在 Deployment 的环境变量注入中我们只注入了来自 Secret 的敏感密码遗漏了REDIS_HOST环境变量导致读取为NoneLiteLLM 初始化缓存直接崩溃。5.2 优雅重构拒绝 Deployment Hardcode践行标准 12-Factor为了彻底避免在 Deployment 的 values 中硬编码散乱的环境变量我们将配置进行了三层解耦重构配置文件ConfigMap VolumeRedis 主机和端口属于集群内部非敏感网络属性直接写在 ConfigMap 挂载的config.yaml中litellm_settings:cache:truecache_params:type:redishost:redis.redis.svc.cluster.local# 直接内聚在配置文件中port:6379password:os.environ/REDIS_PASSWORD# 仅密码引用环境变量supported_call_types:[chat_completion]ttl:3600非敏感环境变量ConfigMap envFromNO_PROXY等网络参数通过 ConfigMap 注入敏感机密Secret envFromOPENAI_API_KEY_FREE_1、LITELLM_MASTER_KEY、REDIS_PASSWORD通过 Secret 注入。重构后的 Deployment 变得极其干净移除了所有env:块envFrom:-configMapRef:name:litellm-config-secretRef:name:litellm-secrets5.3 探针冷启动策略调整LiteLLM 在 ARM64 节点启动时加载模型定义与初始化 Redis 连接需要约 30-40 秒。初始配置的initialDelaySeconds: 15会导致 Kubelet 在应用尚未监听端口前判定探针失败并强杀容器。将探针宽限期调整为符合实际冷启动特性的值livenessProbe:initialDelaySeconds:45periodSeconds:15failureThreshold:5readinessProbe:initialDelaySeconds:45periodSeconds:15failureThreshold:5调整后Pod 启动平滑0 重启直接进入1/1 Running。6. 网关层接入Kong Gateway API 路由与超时加固6.1 踩坑 1Gateway 跨命名空间拦截 (NotAllowedByListeners)LiteLLM 部署在llm-system命名空间其创建的HTTPRoute尝试绑定部署在default命名空间的公共网关kong-main-gateway。执行kubectl describe httproute litellm-svc-route -n llm-system发现路由被拒绝Reason: NotAllowedByListeners, Status: False, Type: Accepted原因kong-main-gateway的监听器默认配置了allowedRoutes.namespaces.from: Same仅允许同一命名空间default下的应用绑定。解决在基础清单infrastructure/kong-gateway/Gateway.yaml中将网关策略调整为放行所有命名空间listeners:-name:httpport:80protocol:HTTPallowedRoutes:namespaces:from:All# 允许跨 Namespace 挂载路由6.2 踩坑 2URL 前缀剥离/litellmStrip Path外部请求入口为http://134.185.90.98:31850/litellm/v1/models而后端 LiteLLM 监听的实际路径是/v1/models。早期尝试在 HTTPRoute 中使用 Gateway API 标准的URLRewrite过滤器filters:-type:URLRewriteurlRewrite:path:type:ReplacePrefixMatchreplacePrefixMatch:/但 Kong Gateway Controller 报警HTTPRoute cant be routed: httpFilter URLRewrite unsupported。解决通过在generic-web-service-v2Chart 中支持route.annotations并在HTTPRoute资源上附加 Kong 专用注解annotations:konghq.com/strip-path:trueKong 接收到请求后自动将/litellm前缀剥离干净透传给后端 LiteLLM。6.3 踩坑 3思考大模型Thinking Models的长超时处理在测试gemini-3.7-flash-freelayer启用 Thinking 推理模式时模型深入推演耗时达到了 49.6 秒触发了 Kong 网关默认的 60 秒上游读取超时客户端收到 HTTP 504The upstream server is timing out。解决在 KubernetesService层面增加 Kong 超时配置注解将读取超时放宽至 180 秒service:annotations:konghq.com/read-timeout:180000konghq.com/write-timeout:180000konghq.com/connect-timeout:600007. 全链路验收与实测验证全套部署完成后我们通过公网入口134.185.90.98:31850执行了多轮全量验证。7.1 健康检查与模型列表# 1. 存活探针curl-shttp://134.185.90.98:31850/litellm/health/liveliness# 返回: Im alive!# 2. 就绪探针curl-shttp://134.185.90.98:31850/litellm/health/readiness# 返回: {status:healthy,db:Not connected}# 3. 查看挂载的模型列表 (鉴权通过)curl-shttp://134.185.90.98:31850/litellm/v1/models\-HAuthorization: Bearer$LITELLM_MASTER_KEY返回数据{data:[{id:gemini-3.6-flash-freelayer,object:model,owned_by:openai},{id:gemini-3.7-flash-freelayer,object:model,owned_by:openai}],object:list}7.2 标准聊天与 Token 计量响应执行单次数学问答测试curl-s-XPOSThttp://134.185.90.98:31850/litellm/v1/chat/completions\-HAuthorization: Bearer$LITELLM_MASTER_KEY\-HContent-Type: application/json\-d{ model: gemini-3.6-flash-freelayer, messages: [{role: user, content: What is 10*10?}], max_tokens: 512 }返回包含精细化的 Token 与开销元数据{choices:[{finish_reason:stop,index:0,message:{content:10 * 10 100,role:assistant}}],usage:{completion_tokens:88,prompt_tokens:10,total_tokens:98,completion_tokens_details:{reasoning_tokens:77,text_tokens:11}}}7.3 SSE 流式输出测试 (stream: true)curl-s-N-XPOSThttp://134.185.90.98:31850/litellm/v1/chat/completions\-HAuthorization: Bearer$LITELLM_MASTER_KEY\-HContent-Type: application/json\-d{ model: gemini-3.6-flash-freelayer, messages: [{role: user, content: Count from 1 to 5.}], stream: true }输出流data: {choices:[{delta:{role:assistant,content:1, 2, 3,}}]} data: {choices:[{delta:{content: 4, 5}}]} data: [DONE]Kong 网关与 LiteLLM 对流式长连接支持稳定无缓冲阻断。7.4 Redis 缓存与速率限制检查查询集群内 Redis 实例的状态与 Key 列表kubectlexec-nredis deploy/redis -- redis-cli-a$REDIS_PASSWORDkeys*Redis 成功捕获并维护了网关层的全局路由与配额键值global_router:...:gemini/gemini-3.6-flash:rpm:16-21 global_router:...:gemini/gemini-3.6-flash:tpm:16-21 {model_per_key:litellm_proxy_master_key:gemini-3.6-flash-freelayer}:tokens {api_key:litellm_proxy_master_key}:tokens8. 总结与经验清单通过本次实战我们打通了一条真正企业级的多模型网关跨云 GitOps 部署流水线镜像不可变原则在 GitOps 场景中镜像必须通过Manifest Digest Pinning绑定防止 Tag 覆盖导致的运行时漂移机密解耦原则借助ESO 云厂商托管 Vault实现了 Git 仓库完全免机密化结合 Bootstrap 密钥实现优雅闭环12-Factor 配置非敏感配置config.yaml、服务发现域名归 ConfigMap敏感密码归 Secret严禁在 Deployment 模板中硬编码环境变量网关演进Kubernetes Gateway API 在跨 Namespace 场景下需显式配置from: All并结合 Ingress/Route 注解精细化管理前缀剥离与长超时180s。至此LiteLLM 基础设施与网关接入第一阶段Phase 1已 100% 验收收官。EOF
返回列表