
本文详解在 Vertex AI 上成功部署 FastAPI + YOLO 自定义容器时的关键缺失项——必须显式声明 /health 和 /predict 端点,并通过 gcloud 参数正确注册,否则服务将因健康探针失败而持续 Crash,且无有效日志输出。
本文详解在 vertex ai 上成功部署 fastapi + yolo 自定义容器时的关键缺失项——必须显式声明 `/health` 和 `/predict` 端点,并通过 `gcloud` 参数正确注册,否则服务将因健康探针失败而持续 crash,且无有效日志输出。
在 Vertex AI 上部署自定义容器模型(如基于 FastAPI 封装的 YOLO 推理服务)与 Cloud Run 的核心差异,并非在于运行时能力,而在于平台对服务生命周期的契约化约束。Cloud Run 仅需容器监听指定端口(默认 8080)并响应 HTTP 请求即可;而 Vertex AI 要求容器严格遵循一套可观察、可编排的服务协议——其中最关键的是两个标准化端点:/health(就绪探针)和 /predict(推理入口)。若未显式声明或实现,Vertex AI 会持续发送健康检查请求并因超时/404 失败判定实例不可用,最终触发反复重建,导致“容器能跑、平台不认”的典型问题。
✅ 正确实现 FastAPI 健康与预测端点
在您的 main.py(或 FastAPI 启动文件)中,务必添加以下标准路由:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import base64
from io import BytesIO
from PIL import Image
import torch
app = FastAPI(title="YOLO26 Vertex AI Endpoint")
# --- 必须:健康检查端点 ---
@app.get("/health")
def health_check():
"""
Vertex AI 健康探针调用此端点。
要求:返回 200 OK,且响应体轻量(建议空 JSON 或 {"status": "ok"})
注意:此端点应在模型加载完成后才返回成功,避免“假就绪”。
"""
# 可选:校验模型是否已加载(例如检查全局 model 变量)
if 'model' not in globals() or not hasattr(globals()['model'], 'names'):
raise HTTPException(status_code=503, detail="Model not loaded")
return {"status": "ok"}
# --- 必须:预测端点(Vertex AI 强制要求路径为 /predict)---
class PredictRequest(BaseModel):
image: str # base64 encoded JPEG/PNG
@app.post("/predict")
def predict(request: PredictRequest):
try:
# 解码 base64 图像
image_bytes = base64.b64decode(request.image)
image = Image.open(BytesIO(image_bytes)).convert("RGB")
# 执行 YOLO 推理(此处简化,实际应复用已加载模型)
results = model(image) # 假设 model 已全局加载
# 返回结构化结果(符合 Vertex AI 预期格式)
return {
"predictions": [
{
"label": result.names[int(box.cls)],
"confidence": float(box.conf),
"bbox": [int(x) for x in box.xyxy[0].tolist()]
}
for result in results
for box in result.boxes
]
}
except Exception as e:
raise HTTPException(status_code=400, detail=f"Inference error: {str(e)}")⚠️ 关键注意事项:
/health必须返回200 OK,且不能有重定向、延迟或异步等待;建议在模型加载完成后再启动 Uvicorn(例如在startup事件中加载模型,再开放/health);/predict是 Vertex AI 唯一识别的推理入口,不可改名或加前缀(如/api/predict无效);- 请求体必须为
application/json,且结构需与您代码中PredictRequest定义一致;Vertex AI 不支持multipart/form-data或原始二进制流。
✅ 正确上传模型时声明端点与端口
使用 gcloud ai models upload 时,必须显式指定健康与预测路由及监听端口,否则 Vertex AI 默认使用 / 和 8080,但您的 FastAPI 并未暴露这些路径:
gcloud ai models upload \ --region=us-central1 \ --display-name=freshpet-mobile \ --container-image-uri=us-central1-docker.pkg.dev/my-project/my-repo/yolo26-fastapi:v1 \ --container-health-route="/health" \ --container-predict-route="/predict" \ --container-ports=8080 \ --container-command="sh" \ --container-args="-c","exec gunicorn --bind :8080 --workers 1 --worker-class uvicorn.workers.UvicornWorker --timeout 300 'main:app'"
? 为什么
--container-ports=8080不可省略?
Vertex AI 依赖该参数配置内部负载均衡器与容器的通信通道。即使 Dockerfile 中已声明EXPOSE 8080,也必须在此显式传入,否则探针无法到达容器。
✅ 查看真实错误日志的正确方式
Vertex AI 默认不会将容器 stdout/stderr 直接透出到 UI 控制台。请通过以下路径定位根本原因:
- 进入 Google Cloud Console → Vertex AI → Endpoints → [your-endpoint] → Logs
- 在日志过滤器中设置:
resource.type="aiplatform.googleapis.com/Endpoint" resource.labels.endpoint_id="[your-endpoint-id]" logName:"logs/cloudaudit.googleapis.com%2Fdata_access"
- 或更高效地,在 Cloud Logging 中搜索:
resource.type="k8s_container" resource.labels.cluster_name="vertex-ai-cluster-*" jsonPayload.message:"ERROR\|CRITICAL\|Traceback"
常见报错示例及修复:
-
Health check failed: GET http://<ip>/health returned 404</ip>→ 未实现/health或路径拼写错误; -
Failed to bind to port 8080→gunicorn启动命令未指定--bind :8080,或端口被其他进程占用; -
CUDA initialization: no CUDA-capable device is detected→ GPU 机型未正确启用(检查--accelerator参数是否与所选机器匹配,P4 需type=nvidia-tesla-p4)。
✅ 最佳实践总结
| 项目 | Cloud Run | Vertex AI(必需) |
|---|---|---|
| 健康端点路径 | 任意(如 /healthz),可自定义探针 |
固定 /health,且必须返回 200
|
| 推理端点路径 | 任意(如 /v1/predict) |
强制 /predict,不可更改 |
| 端口声明 | 自动探测 PORT 环境变量或 8080
|
必须 --container-ports=8080
|
| GPU 驱动加载 | 由平台自动注入 | 需确保基础镜像含 nvidia-container-toolkit 兼容层(推荐 pytorch/pytorch:2.2.0-cuda12.1-cudnn8-runtime) |
| 日志可见性 | 实时 stdout 显示 | 仅通过 Cloud Logging 查看,需主动过滤 |
完成上述配置后,重新构建镜像、推送、上传模型并部署,Vertex AI 将能稳定拉起容器,健康探针通过,推理请求正常流转。这不仅是技术适配,更是将模型服务从“能跑”升级为“可运维、可审计、可演进”的生产级实践——正如智能仓储客户所要求的那样:每个检测框嵌入设备水印、每帧图像哈希落库、每次 GPU 显存占用写入 Splunk。真正的 MLOps,始于一个正确的 /health。

















