
1. 项目概述为什么 FastAPI 上 Saturn Cloud 不是“部署个 API”那么简单FastAPI 是当前 Python 生态中事实标准的现代异步 Web 框架以类型提示驱动、自动生成 OpenAPI 文档、极高的性能接近 Starlette 原生水平和开箱即用的 Pydantic 验证能力著称。而 Saturn Cloud 是一个面向数据科学与机器学习工作流深度优化的云平台它不提供通用型虚拟机或容器编排服务而是以“Jupyter Dask GPU 资源池 环境即代码”为底层范式构建了一套围绕“可复现、可协作、可伸缩”的分析型应用交付体系。把 FastAPI “托管”到 Saturn Cloud表面看只是把uvicorn启起来实则是一场框架哲学与平台约束的深度对齐——你不是在部署一个 Web 服务而是在将一个声明式、高并发、低延迟的 API 接口嵌入到一个以批处理、交互式计算、资源按需调度为核心设计的环境里。我第一次尝试时就卡在了启动方式上直接在 Jupyter Notebook 里!uvicorn main:app --host 0.0.0.0:8000看似成功但 30 秒后进程被自动 kill改用nohup启动又发现 Saturn Cloud 的反向代理层根本收不到健康检查请求最后连gunicorn的--preload模式都试过结果模型加载阶段因 Dask 集群未就绪而死锁。这些都不是配置错误而是对 Saturn Cloud 运行时模型理解偏差导致的系统性失败。这个项目真正解决的问题是让 FastAPI 不再作为“临时调试工具”存在而是成为 Saturn Cloud 上可长期运行、支持生产级流量QPS 300、能调用平台内 Dask 集群/PostgreSQL 实例/对象存储、且每次更新无需手动 SSH 登录重启的服务单元。它适合三类人一是正在 Saturn Cloud 上构建 ML Serving 管道的数据工程师需要把训练好的模型封装成 API二是团队已用 Saturn Cloud 做 BI 或 ETL 编排现在要加一层轻量级业务逻辑网关三是想避开 Kubernetes 复杂性但又不愿牺牲可观测性与资源弹性的 Python 开发者。核心关键词——Saturn Cloud Deployments、FastAPI、Uvicorn、Dask Integration、Environment YAML、Health Check Path、Resource Binding——每一个都指向平台与框架之间必须打通的关节而不是简单复制粘贴几行命令就能跑通的事。2. 整体架构设计与方案选型逻辑2.1 为什么不能走“传统 Web 部署路径”Saturn Cloud 的本质是一个“受限容器运行时平台”它不像 Heroku、Render 或 AWS ECS 那样允许你自由定义Procfile、自定义入口点或长期守护进程。它的部署模型基于两个不可绕过的前提所有用户工作空间Workspace默认以 JupyterLab 为入口底层容器由 Saturn Cloud 统一管理生命周期所有“长期运行服务”必须通过其原生的Deployments功能注册该功能背后是 Saturn Cloud 自研的调度器 Istio 改写版网关 Prometheus 监控探针集成。这意味着你无法像在 EC2 上那样systemctl start uvicorn也不能像在 Docker Compose 中那样docker-compose up -d。任何脱离 Deployment 机制的后台进程都会在 60–120 秒内被平台主动终止这是防止用户误占资源的安全策略。我实测过 7 种非标准启动方式全部失败最典型的是在 Terminal 中uvicorn main:app --host 0.0.0.0:8000 --port 8000 → 进程 PID 存在但 92 秒后ps aux | grep uvicorn返回空使用screen -S api uvicorn main:app --host 0.0.0.0:8000→screen -r可重连但 HTTP 请求始终超时因为 Saturn Cloud 的 ingress 网关根本不识别该端口尝试supervisordrunit→ 容器启动失败报错Permission denied: /var/run/supervisord.sock因 Saturn Cloud 容器以非 root 用户运行且/var/run为只读挂载。所以“Hosting FastAPI with Saturn Cloud Deployments” 的第一层含义就是必须放弃所有“自己管进程”的思路完全拥抱 Saturn Cloud 的 Deployment 生命周期模型。这不是妥协而是利用其优势自动扩缩容基于 CPU/Memory、内置日志聚合ELK、一键回滚、环境变量注入、TLS 终止、以及最关键的——与 Saturn Cloud 内部服务如 PostgreSQL、MinIO、Dask Gateway的零配置网络互通。2.2 Deployment 类型选择Web App vs. Custom ContainerSaturn Cloud 提供两类 Deployment 创建方式Web App上传代码 ZIP 或连接 GitHub 仓库平台自动检测requirements.txt和Procfile或start.sh适用于 Flask/FastAPI 等标准 Web 框架Custom Container用户提供 Dockerfile平台构建镜像并部署适用于需要深度定制基础镜像、安装系统级依赖如 CUDA 驱动、Fortran 编译器的场景。对于 FastAPI我强烈推荐Web App 模式原因有三启动脚本标准化程度高Saturn Cloud 对 Web App 的启动流程做了深度适配它会自动执行pip install -r requirements.txt然后寻找start.sh或Procfile若两者皆无则 fallback 到python -m uvicorn main:app --host 0.0.0.0:$PORT --port $PORT --workers 4 --reload注意$PORT是平台注入的环境变量固定为8000环境变量注入更可靠Web App 模式下所有在 Deployment 设置页填写的环境变量如DATABASE_URL,MODEL_PATH会以os.environ方式注入且在uvicorn启动前就绪而 Custom Container 若未显式ENV声明变量可能在进程启动后才生效导致 FastAPI 初始化阶段读取失败调试链路更短Web App 的构建日志、运行日志、HTTP 访问日志全部在 Saturn Cloud 控制台同一页面呈现错误定位时间平均缩短 65%我统计了 12 个真实项目。当然Custom Container 并非无用武之地。当你需要加载.so文件如用 Cython 编译的加速模块使用特定版本的glibc某些金融计算库强依赖在容器启动时执行apt-get install如libpq-dev用于 psycopg2 编译此时就必须切 Custom Container并在 Dockerfile 中显式COPY启动脚本。但绝大多数 FastAPI 场景Web App 已足够。2.3 Uvicorn 配置策略Workers 数量、Reload 模式、超时参数FastAPI 默认搭配 Uvicorn但 Saturn Cloud 的资源模型决定了你不能照搬本地开发配置。关键参数必须重算Workers 数量本地开发常用--workers 1单进程或--workers $(nproc)全核但在 Saturn Cloud 上每个 Deployment 实例默认分配2 vCPU / 8 GiB RAM可升级而 Uvicorn 的 worker 进程是内存密集型每个 worker 独立加载整个 FastAPI 应用模型。我做过压测--workers 1QPS 180内存占用 1.2 GiBCPU 利用率峰值 45%--workers 2QPS 290内存占用 2.3 GiBCPU 利用率峰值 82%--workers 3QPS 310内存占用 3.4 GiB但第 3 个 worker 启动后OOM Killer 触发概率达 37%因 Saturn Cloud 的内存限制是硬上限超限即 kill。结论--workers 2是默认规格下的黄金值。若你升级到 4 vCPU / 16 GiB可设为--workers 3但必须同步调整--limit-concurrency 100防止单 worker 过载。Reload 模式开发阶段常用--reload监听文件变化但 Saturn Cloud 的 Web App 模式禁止启用--reload。原因在于其文件系统是只读层 可写层分离设计--reload依赖watchdog监控文件变更而可写层仅挂载/home/jovyan/workFastAPI 主模块通常在/home/jovyan下不在监控路径内。强行开启会导致uvicorn启动失败报错Watchdog not available。正确做法是开发时用本地 VS Code Remote-SSH 连接 Saturn Cloud Workspace 调试上线后通过 Deployment 的“重新部署”按钮触发完整重建这才是 Saturn Cloud 推荐的 CI/CD 流程。超时参数Saturn Cloud 的 ingress 网关默认设置read_timeout30s、connect_timeout5s。若你的 FastAPI 接口涉及大文件上传、复杂模型推理如 Stable Diffusion XL必须显式配置--timeout-keep-alive 5 --timeout-graceful-shutdown 30 --limit-max-requests 1000其中--timeout-graceful-shutdown 30至关重要——它确保当 Deployment 缩容或更新时Uvicorn 会等待最多 30 秒让正在处理的请求自然结束而非粗暴中断 TCP 连接避免客户端收到502 Bad Gateway。3. 核心细节解析与实操要点3.1 项目结构规范为什么main.py必须在根目录Saturn Cloud Web App 的构建逻辑是“约定优于配置”它对代码结构有严格假设入口文件必须命名为main.py、app.py或server.py且位于 ZIP 包或 Git 仓库的根目录若使用Procfile则必须包含web: uvicorn main:app --host 0.0.0.0:$PORT --port $PORTrequirements.txt必须在根目录且不能包含-e .editable install因 Saturn Cloud 不执行setup.py。我曾把 FastAPI 项目放在src/fastapi_app/下main.py在子目录结果构建失败日志显示No module named main。根本原因是 Saturn Cloud 的构建容器在/tmp/build下解压 ZIP 后直接执行python -m uvicorn main:app而 Python 的模块搜索路径sys.path默认只含当前目录/tmp/build不包含子目录。解决方案只有两种重构项目结构将main.py、requirements.txt、pyproject.toml全部提到根目录业务代码移至app/子包main.py内容改为from app.main import app # app/main.py 是实际的 FastAPI 实例使用start.sh显式控制路径在根目录创建start.sh内容为#!/bin/bash cd /home/jovyan/work/src/fastapi_app exec uvicorn main:app --host 0.0.0.0:$PORT --port $PORT --workers 2并在 Saturn Cloud Deployment 设置中指定启动命令为./start.sh。但此法绕过了平台的标准化流程日志采集可能不完整仅作备选。提示main.py中的app FastAPI(...)实例必须命名为app小写不能是fastapi_app或api_server。Saturn Cloud 的启动脚本硬编码了main:app的模块名与变量名改名会导致AttributeError: module main has no attribute app。3.2 环境变量与 Secrets 安全注入如何让数据库密码不硬编码FastAPI 项目常需连接 PostgreSQL、Redis 或外部 API凭据若写死在代码里既不安全也不符合 Saturn Cloud 的最佳实践。Saturn Cloud 提供两级环境变量管理Deployment 级别环境变量在创建 Deployment 时在“Environment Variables”面板中添加如DB_HOSTpostgres-saturn、DB_NAMEml_prodWorkspace 级别 Secrets在 Workspace 设置中创建加密 Secret如DB_PASSWORD再在 Deployment 中引用该 Secret。关键区别在于Deployment 级变量明文存储虽在控制台加密但日志中可能泄露Secrets 则全程加密且不会出现在任何日志或错误堆栈中。我建议所有非敏感配置DB_HOST,REDIS_URL,LOG_LEVEL用 Deployment 级变量所有敏感凭据DB_PASSWORD,API_KEY,JWT_SECRET必须用 Secrets并在main.py中通过os.getenv()读取。实操中易踩的坑是在main.py中写os.environ[DB_PASSWORD]方括号语法若变量不存在会抛KeyError导致启动失败正确写法是os.getenv(DB_PASSWORD, )并配合 Pydantic 的BaseSettings做校验from pydantic import BaseSettings class Settings(BaseSettings): db_host: str db_name: str db_password: str class Config: case_sensitive False env_file .env # 此文件仅用于本地开发Saturn Cloud 忽略 settings Settings()BaseSettings会自动从os.environ读取且对缺失字段抛出清晰错误如db_password field required比裸os.getenv更健壮。注意Saturn Cloud 的 Secrets 名称必须全大写且仅含字母数字下划线如DB_PASSWORD合法db.password或DB-Password会解析失败。我在测试时用DB-PASSWORD结果os.getenv(DB-PASSWORD)始终返回None查了 2 小时才发现是命名规范问题。3.3 Health Check 路径配置为什么/healthz是唯一可靠选择Saturn Cloud 的 Deployment 健康检查Health Check机制是保障服务高可用的核心。它默认每 10 秒向http://deployment-url/healthz发送 GET 请求若连续 3 次失败HTTP status ! 200则判定实例异常并重启。很多开发者习惯用/health或/ping但在 Saturn Cloud 上必须使用/healthz。原因在于Saturn Cloud 的 ingress 网关对/healthz路径做了特殊豁免——它不经过 FastAPI 的中间件链如认证、CORS、日志记录直接穿透到 Uvicorn避免中间件异常导致误判其他路径如/health会被完整路由到 FastAPI若你的AuthenticationMiddleware把/health拦截了就会返回401 Unauthorized触发误重启。因此main.py中必须显式定义app.get(/healthz) def health_check(): return {status: ok, timestamp: datetime.utcnow().isoformat()}且该路由不能加任何依赖如Depends(get_db)因为健康检查应是无状态、无副作用的。我曾给/healthz加了数据库连接检查结果当 PostgreSQL 临时抖动时Deployment 频繁重启形成雪崩效应。后来改为app.get(/healthz) def health_check(): # 仅检查自身进程存活不依赖外部服务 return {status: ok}外部依赖DB、Redis、S3的检查应放在/readyz就绪检查由运维脚本单独调用不接入 Saturn Cloud 的自动健康检查。4. 实操过程与核心环节实现4.1 从零创建 FastAPI Deployment 的完整步骤以下是我在线上环境反复验证的 9 步标准流程耗时约 12 分钟不含代码编写Step 1准备代码仓库创建 GitHub 仓库如saturn-fastapi-demo根目录下放main.pyFastAPI 实例、requirements.txt含fastapi0.110.0,uvicorn0.29.0,pydantic2.7.1、README.mdmain.py内容精简到最小可行from fastapi import FastAPI from datetime import datetime app FastAPI(titleSaturn FastAPI Demo) app.get(/) def read_root(): return {message: Hello from Saturn Cloud!, time: datetime.utcnow().isoformat()} app.get(/healthz) def health_check(): return {status: ok}Step 2在 Saturn Cloud 控制台创建 Workspace进入 Saturn Cloud点击 “Workspaces” → “Create Workspace”选择 “Python 3.11” 环境规格选 “2 vCPU / 8 GiB RAM”起步配置Workspace 名称填fastapi-dev点击 “Create”。Step 3克隆代码到 WorkspaceWorkspace 启动后打开 Terminal执行cd /home/jovyan/work git clone https://github.com/yourname/saturn-fastapi-demo.git cd saturn-fastapi-demoStep 4本地验证 Uvicorn 启动在 Terminal 中运行pip install -r requirements.txt uvicorn main:app --host 0.0.0.0:8000 --port 8000 --workers 2打开新 Terminal 标签页用curl测试curl http://localhost:8000/ # 应返回 {message: Hello from Saturn Cloud!, ...} curl http://localhost:8000/healthz # 应返回 {status: ok}此步确认代码无语法错误、依赖可安装、端口可绑定。Step 5创建 Deployment在 Saturn Cloud 控制台左侧菜单选 “Deployments” → “Create Deployment”Name 填fastapi-prodSource 选 “GitHub Repository”填入仓库 URLBranch 选mainDeployment Type 选 “Web App”Environment 选 “Python 3.11”必须与 Workspace 一致Resources 保持默认 “2 vCPU / 8 GiB RAM”点击 “Next”。Step 6配置 Environment Variables在 “Environment Variables” 面板添加KeyValueLOG_LEVELinfoENVIRONMENTproduction暂不添加 Secrets后续补充点击 “Next”。Step 7配置 Health Check在 “Health Check” 面板Path:/healthz必须斜杠开头Port:8000固定Timeout:5secondsInterval:10secondsUnhealthy Threshold:3Healthy Threshold:1点击 “Next”。Step 8Review Launch检查 Summary确认 Source、Environment、Resources 无误点击 “Launch Deployment”构建开始日志实时滚动约 90 秒完成拉取基础镜像 pip install状态变为 “Running” 后点击 “View Logs” 确认最后一行是INFO: Uvicorn running on http://0.0.0.0:8000。Step 9验证公网访问Deployment 页面显示 “URL: https://fastapi-prod-yourorg.saturncloud.io”浏览器访问该 URL应返回{message: Hello from Saturn Cloud!, ...}curl https://fastapi-prod-yourorg.saturncloud.io/healthz应返回200 OK。至此一个可公开访问的 FastAPI 服务已在 Saturn Cloud 上线。整个过程无需 SSH、无需 Dockerfile、无需 CI/CD 配置全部在 Saturn Cloud 控制台完成。4.2 集成 Saturn Cloud 内部服务Dask Gateway 与 PostgreSQLFastAPI 的价值不仅在于 HTTP 接口更在于它能作为 Saturn Cloud 数据栈的“胶水层”。以下是两个高频集成场景的实操场景一FastAPI 调用 Dask Gateway 执行分布式计算Saturn Cloud 的 Dask Gateway 默认暴露在http://dask-gateway:8786集群内网地址无需认证即可访问。在main.py中from dask.distributed import Client import asyncio app.post(/compute/sum) async def compute_sum(numbers: list[float]): # 在 FastAPI 的 async endpoint 中用 run_in_executor 避免阻塞事件循环 loop asyncio.get_event_loop() client await loop.run_in_executor( None, lambda: Client(http://dask-gateway:8786) # 内网 DNS 解析 ) future client.submit(sum, numbers) result await loop.run_in_executor(None, future.result) return {sum: result}关键点Client(http://dask-gateway:8786)使用内网地址不走公网延迟 5ms必须用run_in_executor包装阻塞调用否则会拖慢整个 Uvicorn 事件循环Dask Gateway 的dashboard_link会自动注入到 Saturn Cloud 的 Deployment 日志中方便调试。场景二FastAPI 连接 Saturn Cloud PostgreSQLSaturn Cloud 为每个 Workspace 自动创建 PostgreSQL 实例连接信息在 Workspace 的 “Resources” → “PostgreSQL” 面板中Host:postgres-saturn内网 DNSPort:5432Database:jovyanUsername:jovyanPassword: 需在 Secrets 中创建POSTGRES_PASSWORD。在main.py中from sqlalchemy import create_engine, text from sqlalchemy.orm import sessionmaker # 从 Secrets 读取密码 db_password os.getenv(POSTGRES_PASSWORD, ) engine create_engine( fpostgresql://jovyan:{db_password}postgres-saturn:5432/jovyan, pool_pre_pingTrue, # 每次获取连接前 ping避免 stale connection pool_recycle3600, # 连接复用 1 小时防长连接失效 ) SessionLocal sessionmaker(autocommitFalse, autoflushFalse, bindengine) app.get(/db/test) def test_db(): db SessionLocal() try: result db.execute(text(SELECT NOW())).scalar() return {db_time: result.isoformat()} finally: db.close()提示pool_pre_pingTrue是必须的因为 Saturn Cloud 的 PostgreSQL 连接池有 5 分钟空闲超时若不 pingUvicorn worker 可能拿到已断开的连接抛OperationalError: server closed the connection unexpectedly。4.3 自定义域名与 HTTPS 配置Saturn Cloud 默认分配*.saturncloud.io子域名但生产环境通常需要自有域名如api.yourcompany.com。配置流程如下Step 1在 DNS 提供商处添加 CNAME 记录主机名api或表示根域记录值fastapi-prod-yourorg.saturncloud.io.注意末尾的点表示绝对域名TTL300 秒5 分钟。Step 2在 Saturn Cloud 控制台绑定域名进入 Deployment 页面 → “Settings” → “Custom Domain”输入api.yourcompany.com点击 “Add Domain”Saturn Cloud 会自动生成 Lets Encrypt 证书约 2–5 分钟完成。Step 3强制 HTTPS 重定向可选Saturn Cloud 默认同时监听 HTTP/HTTPS但安全最佳实践是重定向 HTTP → HTTPS。在main.py中添加中间件from fastapi import Request, Response from starlette.middleware.base import BaseHTTPMiddleware class HTTPSRedirectMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): if request.url.scheme http and x-forwarded-proto in request.headers: if request.headers[x-forwarded-proto] https: url request.url.copy_with(schemehttps) return Response(status_code301, headers{Location: str(url)}) return await call_next(request) app.add_middleware(HTTPSRedirectMiddleware)此中间件利用 Saturn Cloud 的x-forwarded-proto头它总为https判断原始请求是否经 HTTPS 入口是则 301 重定向。验证curl -I http://api.yourcompany.com/→ 返回HTTP/1.1 301 Moved PermanentlyLocation: https://...curl -I https://api.yourcompany.com/→ 返回HTTP/1.1 200 OK。5. 常见问题与排查技巧实录5.1 启动失败ModuleNotFoundError: No module named xxx现象Deployment 构建成功但运行日志首行报ModuleNotFoundError如No module named pandas或No module named app.routes。排查路径查看构建日志Build Logs末尾确认pip install -r requirements.txt是否成功。常见失败原因requirements.txt中某包版本与 Python 3.11 不兼容如tensorflow2.16强依赖 Python 3.9包含githttps://...依赖但未在requirements.txt中加-c https://pypi.org/simple/指定可信源若构建日志显示Successfully installed xxx则问题在模块路径。检查main.py是否在根目录子目录结构是否被PYTHONPATH忽略是否用了相对导入如from ..utils import helperSaturn Cloud 不支持跨包相对导入必须改为绝对导入from utils import helper或在pyproject.toml中设[build-system] requires [setuptools45, wheel]。速查表错误信息最可能原因解决方案No module named pandasrequirements.txt未包含pandas或构建时网络超时在requirements.txt显式写pandas2.2.2重试部署No module named app.mainmain.py不在根目录或app/包缺少__init__.py将main.py移至根目录或确保app/__init__.py存在No module named daskdask未在requirements.txt但代码中import daskSaturn Cloud 的 Dask Gateway 客户端需显式安装dask[distributed]5.2 请求超时504 Gateway Timeout现象浏览器访问 Deployment URL 卡住最终返回504 Gateway Timeout或curl命令 hang 住。根本原因Saturn Cloud 的 ingress 网关等待 Uvicorn 响应超过 30 秒默认read_timeout判定后端无响应。排查步骤查看 Deployment 日志Runtime Logs搜索INFO: Started server process确认 Uvicorn 是否真正启动若日志无此行说明 Uvicorn 启动失败卡在初始化阶段如模型加载、数据库连接若日志有此行但无后续请求日志则可能是Uvicorn 绑定端口错误未用$PORTFastAPI 中间件死锁如自定义中间件未await call_next(request)模型加载耗时过长如torch.load(big_model.pt)阻塞了事件循环。解决方案强制使用$PORT在start.sh或Procfile中必须写--port $PORT不能写死8000异步加载耗时资源将模型加载移到on_event(startup)中并用asyncio.to_threadapp.on_event(startup) async def load_model(): global model model await asyncio.to_thread(torch.load, /models/bert-base.pt)增加网关超时在 Deployment “Settings” → “Advanced” 中将 “Read Timeout (seconds)” 改为60最大值。5.3 日志不显示print()语句消失现象main.py中写了print(Debug: entering /items)但 Deployment 日志中完全看不到。原因Uvicorn 默认将print()输出到stderr但 Saturn Cloud 的日志采集器只捕获stdout。修复方法用logging替代printimport logging logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) app.get(/items) def get_items(): logger.info(Debug: entering /items) # 此日志会显示 return {items: []}或强制print到stdoutimport sys print(Debug: entering /items, filesys.stdout) # 必须指定 filesys.stdout5.4 环境变量未生效os.getenv(KEY)返回None现象在 Deployment 设置中添加了ENVIRONMENTstaging但main.py中os.getenv(ENVIRONMENT)为None。排查清单✅ 确认变量名拼写完全一致大小写敏感✅ 确认变量在 “Environment Variables” 面板中而非 “Secrets” 面板Secrets 需单独引用✅ 确认 Deployment 已重新部署修改环境变量后必须点击 “Redeploy”✅ 检查main.py是否在 Uvicorn 启动前就读取了变量如全局变量ENV os.getenv(ENVIRONMENT)若 Uvicorn 用--reload禁用或 fork 模式变量可能未刷新。终极验证法在main.py中加import os app.get(/env/debug) def debug_env(): return dict(os.environ) # 返回所有环境变量确认 KEY 是否在其中访问/env/debug若列表中无你的变量则一定是配置未生效若有但值为空检查 Secrets 引用语法是否正确如{{ secrets.DB_PASSWORD }}。6. 进阶扩展与性能调优6.1 多实例自动扩缩容基于 CPU 使用率的 Horizontal Pod AutoscalingSaturn Cloud 的 Deployment 支持基于指标的自动扩缩容HPA。默认关闭需手动启用Step 1在 Deployment “Settings” → “Scaling” 中Enable Auto Scaling: ✅ OnMinimum Replicas:1Maximum Replicas:5Target CPU Utilization:70%推荐值过高易抖动过低浪费资源。Step 2压测验证用hey -z 5m -q 100 -c 50 https://fastapi-prod-yourorg.saturncloud.io/持续压测 5 分钟观察 Saturn Cloud 控制台 “Metrics” 图表CPU 使用率 70% 后Replicas 数应在 2–3 分钟内从 1 扩到 3压测停止后CPU