ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LangGraph 部署实战:FastAPI、Docker 与 K8s 三条路径

LangGraph 部署实战:FastAPI、Docker 与 K8s 三条路径 1. 从脚本到服务为什么部署这一步最容易翻车LangGraph 这个框架写过 demo 的人都知道有多顺手。几十行代码就能把状态机、节点、条件边串起来本地跑一个python main.py看着终端里节点一个个执行心里美滋滋。但真到了要把它变成别人能访问的服务问题就来了脚本能跑不代表服务能跑本地能跑不代表容器里能跑单机能跑不代表集群里能跑。我见过太多团队卡在这一步。模型调通了逻辑写完了结果部署折腾了两周还没上线。核心原因就一个LangGraph 本质上是一个有状态的运行时而部署环境对状态的处理方式和本地脚本完全不同。你在本地用内存存 checkpointer重启就丢你在线程里跑图并发一上来就乱你把 API key 写在代码里容器一构建就泄露。这些问题不解决部署就是给自己挖坑。这篇文章要聊的就是 LangGraph 从脚本走向服务的三条部署路径FastAPI 单机服务、Docker 容器化、K8s 集群编排。三条路径不是递进关系而是对应不同阶段的需求。你一个人做原型FastAPI 就够了你要给团队内部用Docker 是底线你要给全公司甚至外部客户用K8s 才扛得住。我会把每条路径的核心细节、踩坑经验、参数选择都摊开讲让你看完能直接抄作业。适合谁看写过 LangGraph demo 但没部署过的开发者、正在做 AI 应用后端的技术负责人、以及被“本地能跑线上报错”折磨过的运维同学。不管你现在处于哪个阶段都能找到对应的落地方法。2. 三条部署路径的整体设计与选型逻辑2.1 为什么是这三条路径而不是别的LangGraph 的部署方式其实有很多种官方文档里提到了不少选项。但我把实际项目中最常用的归纳为三条路径原因很简单它们对应了服务成熟度的三个台阶。第一条路径是FastAPI 直接托管。你把 LangGraph 的图编译好挂到 FastAPI 的路由里用 Uvicorn 启动。这是从脚本到服务最小的改动量适合原型验证和内部小范围使用。它的核心价值是快半小时就能把脚本变成 HTTP 接口。第二条路径是Docker 容器化。当你的服务需要给别人用环境依赖开始变复杂Python 版本、系统库、模型文件、环境变量这些东西不能再靠“我这台机器能跑”来保证时Docker 就是必选项。它解决的是环境一致性问题让你的服务在任何装了 Docker 的机器上都能以相同方式运行。第三条路径是K8s 集群编排。当你的服务需要高可用、需要弹性伸缩、需要滚动更新不中断服务时单容器就不够了。K8s 解决的是编排和调度问题它管的是多个容器实例怎么协同工作。这三条路径的选型逻辑可以用一个简单的判断表来说明场景推荐路径核心原因个人原型、内部演示FastAPI 单机改动最小启动最快团队内部工具、小流量Docker Docker Compose环境隔离部署可复现对外服务、高并发、多实例K8s弹性伸缩故障自愈需要 GPU 推理Docker GPU 运行时资源隔离驱动版本可控多租户、需要隔离K8s Namespace资源配额网络隔离2.2 三条路径共享的核心问题不管走哪条路径有几个问题是绕不开的我称之为 LangGraph 部署的“三座大山”。第一座山是状态持久化。LangGraph 的 checkpointer 在本地开发时通常用MemorySaver进程一重启所有对话状态全丢。生产环境必须换成持久化方案比如 PostgreSQL 或 Redis。这里有个坑checkpointer 的选型会影响你的并发模型。用MemorySaver时你可以随便开多线程但换成数据库后连接池大小、事务隔离级别都会成为瓶颈。第二座山是流式输出。LangGraph 支持stream和astream但流式输出和 FastAPI 的响应模型配合起来有讲究。用普通的def路由流式会被缓冲必须用async def加StreamingResponse还要注意 Uvicorn 的 worker 配置。我见过有人用gunicorn配uvicorn worker结果流式输出变成一次性返回排查了半天才发现是 worker 类型的问题。第三座山是配置与密钥管理。LangGraph 节点里经常要调模型 APIAPI key 绝对不能硬编码。本地开发可以用.env文件但容器和 K8s 环境需要更安全的方案。Docker 用--env-file或 secretsK8s 用 Secret 对象。这里的原则是代码里只读环境变量不关心值从哪来。2.3 选型时容易忽略的隐性成本很多人在选部署方案时只看“能不能跑”忽略了运维成本。FastAPI 单机看起来简单但你要自己管进程守护、日志轮转、证书续期。Docker 看起来标准化但镜像构建时间、层缓存、多架构支持都是坑。K8s 看起来强大但学习曲线陡峭一个小配置错误可能排查一整天。我的建议是不要为了用 K8s 而用 K8s。如果你的服务每天只有几百次调用Docker Compose 完全够用上 K8s 是给自己找麻烦。反过来如果你的服务需要横向扩展那 K8s 的投入是值得的。选型的核心不是技术先进性而是匹配当前阶段的真实需求。3. FastAPI 单机部署从脚本到 HTTP 服务的最小改动3.1 项目目录结构怎么设计才不乱从脚本变成服务第一件事是整理目录。我见过太多人把所有代码堆在一个main.py里几百行下来自己都找不到北。一个可维护的 FastAPI LangGraph 项目目录结构应该长这样langgraph-service/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI 入口 │ ├── config.py # 配置管理 │ ├── graph/ │ │ ├── __init__.py │ │ ├── builder.py # 图构建逻辑 │ │ ├── nodes.py # 节点函数 │ │ └── state.py # 状态定义 │ ├── api/ │ │ ├── __init__.py │ │ ├── routes.py # 路由定义 │ │ └── schemas.py # 请求响应模型 │ └── services/ │ ├── __init__.py │ └── checkpointer.py # 持久化配置 ├── tests/ ├── .env.example ├── requirements.txt └── README.md这个结构的好处是职责分离。图构建逻辑和 API 层分开节点函数独立成文件状态定义单独管理。当你需要改一个节点逻辑时不用在几百行的main.py里翻找。当你要换 checkpointer 时只改services/checkpointer.py一个文件。注意不要小看目录结构。我接手过一个项目所有逻辑在一个 800 行的文件里改一个 prompt 要花半小时定位。重构目录结构花了两天但后续每次改动节省的时间远超这个投入。3.2 图构建与 FastAPI 集成的关键代码图构建的核心是把 LangGraph 的StateGraph编译成一个可调用的对象。这里的关键是图实例应该在应用启动时创建一次而不是每次请求都创建。每次请求都编译图性能开销很大而且 checkpointer 的连接也会重复建立。# app/graph/builder.py from langgraph.graph import StateGraph, END from langgraph.checkpoint.postgres import PostgresSaver from app.graph.state import AgentState from app.graph.nodes import call_model, should_continue, execute_tool def build_graph(checkpointer): workflow StateGraph(AgentState) workflow.add_node(agent, call_model) workflow.add_node(tools, execute_tool) workflow.set_entry_point(agent) workflow.add_conditional_edges( agent, should_continue, {continue: tools, end: END} ) workflow.add_edge(tools, agent) return workflow.compile(checkpointercheckpointer)然后在 FastAPI 的 lifespan 里初始化# app/main.py from contextlib import asynccontextmanager from fastapi import FastAPI from app.graph.builder import build_graph from app.services.checkpointer import get_checkpointer asynccontextmanager async def lifespan(app: FastAPI): checkpointer get_checkpointer() app.state.graph build_graph(checkpointer) yield # 清理资源 checkpointer.conn.close() app FastAPI(lifespanlifespan)用lifespan而不是app.on_event(startup)是因为后者在新版 FastAPI 里已经废弃。lifespan的好处是能保证资源在应用关闭时正确释放避免连接泄漏。3.3 流式输出的正确打开方式LangGraph 的流式输出是它的一大卖点但和 FastAPI 配合时有几个细节要注意。首先路由必须是async def否则流式会被阻塞。其次要用StreamingResponse包装异步生成器。# app/api/routes.py from fastapi import APIRouter, Request from fastapi.responses import StreamingResponse from app.api.schemas import ChatRequest router APIRouter() router.post(/chat/stream) async def chat_stream(request: Request, body: ChatRequest): graph request.app.state.graph config {configurable: {thread_id: body.thread_id}} async def event_generator(): async for event in graph.astream( {messages: [(user, body.message)]}, configconfig, stream_modemessages ): if event: yield fdata: {event[0].content}\n\n return StreamingResponse( event_generator(), media_typetext/event-stream )这里用stream_modemessages而不是默认的values是因为messages模式会逐个 token 返回适合聊天场景。values模式返回的是完整状态适合需要看中间步骤的场景。实操心得Uvicorn 启动时不要用--workers大于 1 的配置配合内存 checkpointer。多 worker 意味着多个进程每个进程有独立的内存状态不共享。如果必须多 workercheckpointer 必须换成数据库。3.4 单机部署的进程管理与日志FastAPI 单机部署很多人直接用uvicorn app.main:app启动然后挂个nohup就完事。这在开发环境没问题但生产环境有几个隐患进程挂了不会自动重启、日志没有轮转、没有优雅关闭。我的做法是用systemd管理进程或者用supervisor。以 systemd 为例[Unit] DescriptionLangGraph Service Afternetwork.target [Service] Typesimple Userappuser WorkingDirectory/opt/langgraph-service EnvironmentPATH/opt/langgraph-service/venv/bin ExecStart/opt/langgraph-service/venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000 Restartalways RestartSec5 [Install] WantedBymulti-user.target日志方面Uvicorn 默认输出到 stdoutsystemd 会接管并写入 journal。如果你想把日志写到文件可以在 Uvicorn 启动参数里加--log-config指定配置文件。但更推荐的做法是让应用输出结构化日志到 stdout由外部系统收集这样容器化时不用改配置。关于uvicorn fastapi 日志丢失问题这是热词里出现的一个典型问题。原因通常是用了gunicorn的uvicorn worker而 gunicorn 默认会捕获子进程的 stdout。解决办法是给 gunicorn 加--capture-output参数或者直接用 Uvicorn 的多 worker 模式--workers N后者在新版本里已经支持。4. Docker 容器化让服务在任何机器上都能跑4.1 Dockerfile 怎么写才能又快又小LangGraph 服务的 Dockerfile核心目标是镜像小、构建快、运行稳。我见过有人用python:3.11完整镜像构建出来 1.5G推送到镜像仓库要十分钟。其实用python:3.11-slim就能省下一大半空间。FROM python:3.11-slim AS builder WORKDIR /build COPY requirements.txt . RUN pip install --no-cache-dir --user -r requirements.txt FROM python:3.11-slim WORKDIR /app # 创建非 root 用户 RUN useradd -m -u 1000 appuser # 从 builder 阶段复制依赖 COPY --frombuilder /root/.local /home/appuser/.local COPY app/ ./app/ # 设置环境变量 ENV PATH/home/appuser/.local/bin:$PATH ENV PYTHONUNBUFFERED1 ENV PYTHONDONTWRITEBYTECODE1 USER appuser EXPOSE 8000 CMD [uvicorn, app.main:app, --host, 0.0.0.0, --port, 8000]这个 Dockerfile 用了多阶段构建builder 阶段装依赖最终镜像只复制装好的包不包含编译工具。PYTHONUNBUFFERED1保证日志实时输出PYTHONDONTWRITEBYTECODE1避免生成.pyc文件。非 root 用户运行是安全底线很多团队忽略这一点容器被攻破就是 root 权限。注意如果你的 LangGraph 服务需要调用本地模型比如通过 OllamaDocker 网络配置要注意。容器内的localhost指向容器本身不是宿主机。需要用host.docker.internalDocker Desktop或宿主机的实际 IP。4.2 Docker Compose 编排 LangGraph 与依赖服务单容器跑 LangGraph 不够通常还需要数据库做 checkpointer、Redis 做缓存、可能还有 Ollama 做本地模型推理。这时候 Docker Compose 就派上用场了。version: 3.9 services: langgraph-api: build: . ports: - 8000:8000 environment: - DATABASE_URLpostgresql://user:passpostgres:5432/langgraph - REDIS_URLredis://redis:6379/0 - OLLAMA_HOSThttp://ollama:11434 depends_on: postgres: condition: service_healthy redis: condition: service_started restart: unless-stopped postgres: image: postgres:16-alpine environment: - POSTGRES_USERuser - POSTGRES_PASSWORDpass - POSTGRES_DBlanggraph volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: [CMD-SHELL, pg_isready -U user] interval: 5s timeout: 5s retries: 5 redis: image: redis:7-alpine volumes: - redisdata:/data ollama: image: ollama/ollama:latest volumes: - ollamadata:/root/.ollama ports: - 11434:11434 volumes: pgdata: redisdata: ollamadata:这个配置里有两个关键点。第一depends_on配合condition: service_healthy保证 Postgres 真正就绪后才启动 API避免连接失败。第二所有数据用 volume 持久化容器重建数据不丢。关于docker安装mysql失败这类问题在 LangGraph 场景下更常见的是 Postgres 连接问题。如果 API 容器启动时报connection refused先检查depends_on的健康检查是否生效再检查数据库的pg_hba.conf是否允许容器网段连接。4.3 镜像构建的缓存优化与多架构支持Docker 构建最耗时的步骤是pip install。如果每次改代码都重新装依赖构建时间会让人崩溃。优化的关键是把依赖安装和代码复制分开让依赖层能被缓存。上面 Dockerfile 里COPY requirements.txt .在COPY app/ ./app/之前就是这个道理。只要requirements.txt不变依赖层就一直用缓存。改代码只重新构建最后一层几秒钟搞定。多架构支持是另一个容易被忽略的点。如果你在 Mac M 系列芯片上构建镜像推到 Linux 服务器上跑会遇到架构不匹配的问题。解决办法是用docker buildxdocker buildx build --platform linux/amd64,linux/arm64 -t your-registry/langgraph-api:latest --push .这条命令会构建两种架构的镜像并推送到仓库部署时 Docker 会自动选择匹配的架构。实操心得docker desktop failed to start because virtualisation support wasnt detected这个报错在 Windows 上很常见。原因是 BIOS 里的虚拟化支持没开或者 Hyper-V 和 WSL2 冲突。解决办法是进 BIOS 开启 VT-x/AMD-V然后在 Windows 功能里确保 WSL2 和虚拟机平台都启用。如果还是不行检查是不是装了其他虚拟化软件如 VMware抢占了 Hyper-V。4.4 容器内的配置管理与密钥安全Docker 环境下的配置管理原则是镜像里不含任何密钥所有敏感信息通过运行时注入。常见做法有三种第一种是--env-file启动容器时指定环境变量文件。简单但不适合生产因为环境变量在docker inspect里能看到。第二种是 Docker secrets适合 Swarm 模式。密钥以文件形式挂载到容器内应用读文件而不是读环境变量。第三种是外部密钥管理服务比如 Vault。应用启动时从 Vault 拉取密钥。这是最安全的方案但复杂度也最高。对于大多数团队我的建议是开发环境用.env文件生产环境用 Docker secrets 或 K8s Secret。关键是代码里统一用os.environ.get()读取不关心值从哪来。这样从 Docker 迁移到 K8s 时代码不用改。# app/config.py import os from pydantic_settings import BaseSettings class Settings(BaseSettings): database_url: str os.environ.get(DATABASE_URL, ) redis_url: str os.environ.get(REDIS_URL, ) openai_api_key: str os.environ.get(OPENAI_API_KEY, ) ollama_host: str os.environ.get(OLLAMA_HOST, http://localhost:11434) class Config: env_file .env settings Settings()用 Pydantic 的BaseSettings好处是类型校验和默认值管理。如果某个必填项没配启动时就会报错而不是运行到一半才崩。5. K8s 集群部署从单容器到高可用服务5.1 什么时候该上 K8s什么时候不该K8s 不是银弹。我见过一个日请求量不到一千的服务上 K8s结果运维复杂度翻了三倍收益几乎为零。判断是否该上 K8s看三个指标第一是否需要多实例横向扩展。如果你的服务单实例 CPU 就跑满了需要加机器分担流量K8s 的 Deployment 和 HPA 能自动搞定。如果单实例绰绰有余上 K8s 是浪费。第二是否需要零停机更新。K8s 的滚动更新能保证更新过程中服务不中断。如果你能接受半夜停机更新Docker Compose 就够了。第三是否有多个服务需要编排。如果你只有 LangGraph 一个服务Docker Compose 更简单。如果你有 API、Worker、定时任务、多个模型服务需要协同K8s 的编排能力才有价值。k8s和docker区别这个热词问的人很多。简单说Docker 管的是单个容器的生命周期K8s 管的是多个容器在集群里的调度和编排。Docker 是地基K8s 是楼上的管理系统。没有 Docker 基础直接上 K8s会非常痛苦。5.2 LangGraph 服务的 K8s 资源清单一个完整的 LangGraph K8s 部署至少需要四个资源对象Deployment、Service、ConfigMap、Secret。# deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: langgraph-api namespace: ai-services spec: replicas: 3 selector: matchLabels: app: langgraph-api template: metadata: labels: app: langgraph-api spec: containers: - name: api image: your-registry/langgraph-api:v1.0.0 ports: - containerPort: 8000 envFrom: - configMapRef: name: langgraph-config - secretRef: name: langgraph-secrets resources: requests: memory: 512Mi cpu: 250m limits: memory: 2Gi cpu: 1000m livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 8000 initialDelaySeconds: 10 periodSeconds: 5这里有几个关键配置。replicas: 3保证有三个实例一个挂了还有两个。resources的 requests 和 limits 必须设否则调度器不知道如何分配。livenessProbe检测进程是否存活失败会重启容器readinessProbe检测是否准备好接收流量失败会从 Service 的 Endpoints 里移除。注意LangGraph 服务如果有启动时的图编译和 checkpointer 连接initialDelaySeconds要设够。我见过设 5 秒的结果容器还没初始化完就被 liveness 探针判定失败反复重启。建议至少 30 秒具体看你的图编译耗时。5.3 状态持久化在 K8s 里的正确姿势K8s 环境下Pod 是随时可能被调度到别的节点的本地存储不可靠。LangGraph 的 checkpointer 必须用外部数据库。Postgres 在 K8s 里通常用 StatefulSet 部署保证稳定的网络标识和存储。# postgres-statefulset.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres namespace: ai-services spec: serviceName: postgres replicas: 1 selector: matchLabels: app: postgres template: metadata: labels: app: postgres spec: containers: - name: postgres image: postgres:16-alpine env: - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: postgres-secret key: password volumeMounts: - name: pgdata mountPath: /var/lib/postgresql/data volumeClaimTemplates: - metadata: name: pgdata spec: accessModes: [ReadWriteOnce] resources: requests: storage: 20GiStatefulSet 和 Deployment 的区别在于StatefulSet 的 Pod 有稳定的名称postgres-0、postgres-1和稳定的存储。数据库这类有状态服务必须用 StatefulSet。k8s redis 集群是另一个常见需求。如果 LangGraph 用 Redis 做缓存或消息队列Redis 集群模式能提供高可用。但 Redis 集群配置复杂如果只是做简单缓存单实例加 Sentinel 就够了。5.4 配置与密钥的 K8s 管理K8s 里配置和密钥分开管理。非敏感配置放 ConfigMap敏感信息放 Secret。# configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: langgraph-config namespace: ai-services data: OLLAMA_HOST: http://ollama-service:11434 LOG_LEVEL: INFO MAX_CONCURRENCY: 10# secret.yaml apiVersion: v1 kind: Secret metadata: name: langgraph-secrets namespace: ai-services type: Opaque data: DATABASE_URL: cG9zdGdyZXNxbDovL3VzZXI6cGFzc0Bwb3N0Z3Jlczo1NDMyL2xhbmdyYXBo OPENAI_API_KEY: c2st...Secret 的值是 base64 编码不是加密。所以 K8s Secret 的安全性依赖于 RBAC 配置。确保只有需要的 ServiceAccount 能读取 Secret并且开启 etcd 的加密存储。实操心得k8s种namespace的使用建议按环境或团队划分。比如ai-services-dev、ai-services-prod分开避免误操作。Namespace 还能配合 ResourceQuota 限制资源使用防止某个团队把集群资源吃光。5.5 滚动更新与回滚策略K8s 的滚动更新是它相比 Docker Compose 的一大优势。更新时逐个替换 Pod保证服务不中断。配置在 Deployment 的strategy字段spec: strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0maxSurge: 1表示更新时最多多出一个 PodmaxUnavailable: 0表示不允许有 Pod 不可用。这样更新过程中始终有足够的实例在服务。代价是需要额外的资源如果集群资源紧张可以设maxUnavailable: 1。回滚用kubectl rollout undokubectl rollout undo deployment/langgraph-api -n ai-services kubectl rollout status deployment/langgraph-api -n ai-servicesrollout status能实时看到更新进度卡住时能快速定位是哪个 Pod 出了问题。6. 三条路径的常见问题与排查实录6.1 FastAPI 单机部署的典型故障问题一流式输出变成一次性返回。排查思路先确认路由是async def再确认没有中间件缓冲响应。常见原因是用了gunicorn的uvicorn workergunicorn 默认会缓冲。解决办法是加--capture-output或直接用 Uvicorn 多 worker。问题二并发请求时状态串了。排查思路检查 checkpointer 是不是MemorySaver以及thread_id是否每个会话唯一。MemorySaver在多线程下会有竞态问题生产环境必须换数据库。问题三Uvicorn 日志丢失。排查思路确认PYTHONUNBUFFERED1确认没有重定向 stdout。如果用 systemdStandardOutputjournal能保证日志被 journald 收集。6.2 Docker 容器化的典型故障问题一容器内连不上宿主机的 Ollama。排查思路容器内localhost是容器自己不是宿主机。Linux 上用--network host或宿主机的实际 IPMac/Windows 上用host.docker.internal。问题二镜像构建时 pip install 超时。排查思路换国内镜像源或者在 Dockerfile 里加--timeout参数。如果公司有私有 PyPI 源配置pip.conf指向内部源。问题三容器启动后立即退出。排查思路docker logs container看报错。常见原因是环境变量缺失导致配置校验失败或者端口被占用。用docker run -it --entrypoint /bin/bash进容器手动排查。6.3 K8s 集群部署的典型故障问题一Pod 一直 Pending。排查思路kubectl describe pod name看 Events。常见原因是资源不足、节点选择器不匹配、PVC 无法绑定。问题二Pod 反复重启。排查思路kubectl logs name --previous看上次崩溃的日志。常见原因是 liveness 探针太激进、内存超限被 OOM Kill、依赖服务连不上。问题三Service 访问不通。排查思路先kubectl get endpoints看有没有后端 Pod再检查 Service 的 selector 是否匹配 Pod 的 labels最后检查网络策略是否放行。6.4 常见问题速查表现象可能原因排查命令解决方案流式输出被缓冲gunicorn worker 缓冲检查启动命令用 Uvicorn 多 worker 或加 --capture-output状态丢失MemorySaver 重启清空检查 checkpointer 类型换 Postgres/Redis checkpointer容器连不上宿主机服务localhost 指向容器docker exec内 ping用 host.docker.internal 或宿主机 IPPod Pending资源不足或 PVC 未绑定kubectl describe pod调整 resources 或检查 StorageClassPod 反复重启liveness 探针失败kubectl logs --previous增大 initialDelaySecondsService 无后端selector 不匹配kubectl get endpoints修正 labels 和 selector镜像架构不匹配Mac 构建推 Linuxdocker inspect看架构用 buildx 构建多架构镜像密钥泄露硬编码在代码或镜像扫描镜像层用 Secret 或外部密钥管理7. 从部署到运维几条踩坑换来的经验部署只是开始运维才是长期战场。我在实际项目里踩过的坑分享几条最有价值的。第一条日志要结构化不要 print。LangGraph 节点里用print调试很方便但生产环境必须换成logging并且输出 JSON 格式。这样日志收集系统能直接解析排查问题时能按thread_id、node_name过滤。我见过用 print 的服务出问题时只能grep效率极低。第二条健康检查要区分 liveness 和 readiness。liveness 只检查进程是否活着readiness 检查依赖是否就绪。如果把数据库连接检查放在 liveness 里数据库抖动会导致所有 Pod 重启雪崩。正确做法是 liveness 只检查/health返回 200readiness 检查/ready里验证数据库和 Redis 连接。第三条资源限制要留余量。K8s 的limits设得太紧Pod 会被 OOM Kill设得太松调度器会过度分配。我的经验是requests设实际用量的 70%limits设实际用量的 150%。LangGraph 服务的内存波动大因为模型调用和状态序列化都吃内存留足余量很重要。第四条更新要灰度不要一把梭。K8s 的滚动更新虽然安全但如果新版本有 bug所有 Pod 更新完就全挂了。更好的做法是用两个 Deployment 加 Service 的 selector 切换先更新一个实例观察没问题再全量。或者用 Istio 这类服务网格做流量切分。第五条备份要自动化不要靠手动。Postgres 里的 checkpointer 数据是 LangGraph 服务的核心资产丢了就全没了。用 CronJob 定期pg_dump备份到对象存储。恢复演练也要定期做确保备份真的能用。关于certum证书自动部署ssldun这类证书管理需求在 K8s 里通常用 cert-manager 自动续期。配置好 Issuer 和 Certificate 资源证书到期前会自动更新不用手动干预。这是 K8s 生态相比单机部署的一大优势。最后说一个我个人的体会部署方案的选择本质是在开发效率和运维复杂度之间找平衡。FastAPI 单机开发效率最高但运维要自己扛K8s 运维复杂度最高但扩展性和可靠性最好。没有绝对正确的答案只有匹配当前团队能力和业务阶段的方案。我见过用 Docker Compose 撑起日活十万的服务也见过用 K8s 跑日请求几百的应用。工具是死的人是活的选适合自己的就好。
返回列表