Cloud Run 部署核心要点:端口暴露、健康检查与VPC连接
2026/7/6 10:24:32 网站建设 项目流程

1. 项目概述:为什么 Cloud Run 是 GCP 上最值得认真对待的无服务器容器平台

Cloud Run 这个名字听起来像 Google Cloud Platform(GCP)里又一个“跑在云上的运行时”,但如果你真把它当成另一个 Kubernetes 管理控制台的简化版,或者只是“函数即服务(FaaS)的换皮产品”,那接下来的部署过程大概率会卡在第3步——不是报错,而是你发现它“太安静了”,安静到连日志都懒得吐一行,安静到健康检查反复失败却找不到源头。我第一次用 Cloud Run 部署一个 Python FastAPI 应用时,就栽在这“安静”上:本地 curl 通、Cloud Build 构建成功、服务状态显示“Ready”,但所有 HTTP 请求全部返回 503。查了47分钟,最后发现只因为 Dockerfile 里少写了一行EXPOSE 8080,而 Cloud Run 的默认端口探测逻辑根本不会告诉你“端口没暴露”,它只会默默把流量拒之门外。这就是 Cloud Run 的真实底色:它极度克制,极度依赖约定,极度不宽容任何偏离标准容器行为的微小偏差。

它不是 Kubernetes 的替代品,也不是 Cloud Functions 的升级版;它是 GCP 在“容器即交付单元”这个理念上,打磨出的一把精准手术刀。核心关键词非常明确:Cloud Run、GCP、容器化应用、无服务器、自动扩缩、HTTP 触发、托管式 Knative。它解决的不是“怎么把代码跑起来”这种初级问题,而是“如何让一个标准 Docker 镜像,在无需管理任何基础设施的前提下,获得毫秒级冷启动、按请求付费、自动弹性伸缩、内置 HTTPS 和全球 CDN 加速”的完整闭环。适合谁?三类人最该立刻上手:一是正在从 Heroku 或 Vercel 迁移中后台 API 的全栈开发者,二是需要快速验证 SaaS 微服务架构的初创技术负责人,三是运维团队里负责推动“容器标准化落地”的工程师——你们不用再为每个新服务搭一套 K8s Ingress 和 HPA,Cloud Run 就是那个开箱即用的、符合 OCI 标准的答案。

它背后的技术支点其实很硬核:底层完全基于开源的 Knative Serving 项目,但 Google 做了深度托管和工程优化。这意味着你写的 YAML 文件、配置的环境变量、设置的并发数,最终都会被翻译成 Knative 的 Revision、Configuration 和 Route 对象,再由 Google 的控制平面统一调度到全球边缘节点的容器运行时上。但它对用户完全隐藏了这些复杂性。你不需要懂 Istio 的 VirtualService 怎么写,也不用研究 Knative 的 PodAutoscaler 指标采集逻辑。你只需要确保你的容器监听在PORT环境变量指定的端口(默认8080),能响应 HTTP GET/的健康检查,并在 4 秒内完成启动。剩下的,包括 TLS 终止、WAF 防护、地域就近路由、甚至 IPv6 支持,全由 Google 托管。这种“契约式编程”带来的效率提升是颠覆性的:我团队上个月用 Cloud Run 替换了三个老旧的 Compute Engine 实例承载的内部工具,运维工单下降了 92%,部署频率从每周一次变成每天平均 17 次,而成本直接砍掉 63%——因为那些实例常年空转的 CPU 资源,现在只在真实请求到来时才被计费。

2. 核心设计思路与方案选型逻辑:为什么不是 Cloud Functions、不是 App Engine、更不是裸 K8s

2.1 为什么首选 Cloud Run 而非 Cloud Functions?

这个问题几乎每次内部技术评审都会被问到。答案不在功能列表里,而在调用模型和生命周期约束上。Cloud Functions 是典型的事件驱动模型:一个函数绑定到 Pub/Sub 主题、Cloud Storage 事件或 HTTP 路由,执行完就销毁。它的最大限制是10 分钟超时固定内存上限(8GB)。而 Cloud Run 的本质是“有状态的 HTTP 服务”,它的容器进程可以长期存活,只要不触发自动缩容策略。这意味着你可以轻松跑一个 WebSocket 服务、一个长连接的 gRPC 流式接口,甚至一个需要预热缓存的机器学习推理服务。我去年帮一家电商客户做实时库存同步,后端需要维持与 Redis 的长连接并监听多个 Pub/Sub 主题。用 Cloud Functions 实现,就得写复杂的重连逻辑和状态恢复机制,每次函数重启都要重新订阅;换成 Cloud Run 后,容器启动时一次性建立连接,后续所有请求共享这个连接池,代码量减少 60%,延迟稳定性提升 4 倍。

更重要的是资源模型差异。Cloud Functions 的内存和 CPU 是强绑定的(比如 2GB 内存必然配 1vCPU),而 Cloud Run 允许你独立配置CPU 分配比例(100%、200%、400%)和内存大小(128MB 到 8GB)。这让你能精准匹配工作负载:一个纯 JSON 解析的 API 可以用 128MB + 100% CPU,而一个需要大量计算的图像处理服务则可设为 4GB + 400% CPU。这种细粒度控制在 Cloud Functions 里根本不存在。另外,Cloud Run 支持并发请求数(concurrency)设置,这是应对突发流量的关键。你可以让单个容器实例同时处理 80 个请求,而不是像 Cloud Functions 那样每个实例只能串行处理一个。实测下来,当 QPS 从 50 突增到 300 时,Cloud Run 的扩容延迟稳定在 1.2 秒内,而同等配置的 Cloud Functions 平均冷启动时间飙升到 3.8 秒。

2.2 为什么不是 App Engine Standard Environment?

App Engine 是 GCP 的老将,但它的“标准环境”本质上是一个高度封装的运行时沙箱。你必须用它指定的语言版本(Python 3.9、Node.js 18 等),不能自由选择基础镜像,不能安装系统级依赖(比如ffmpegwkhtmltopdf),甚至不能修改gunicorn的 worker 数量。而 Cloud Run 的核心哲学是“你提供容器,我负责运行”。只要你能构建出一个符合 OCI 标准的镜像,里面装什么、怎么装、用什么启动命令,完全由你决定。我们有个内部报表服务,需要调用 Java 编写的 PDF 渲染引擎,还依赖特定版本的 Ghostscript。在 App Engine Standard 下,这条路根本走不通;但在 Cloud Run 里,我们直接用openjdk:17-jre-slim作为基础镜像,apt-get install ghostscript,再把 Java JAR 包 COPY 进去,整个流程不到 10 分钟。这种自由度带来的不仅是技术可行性,更是团队协作效率——后端用 Go 写 API,前端用 Node.js 写 SSR 渲染层,运维用 Python 写监控脚本,所有服务都用同一套 Cloud Run 部署流程,CI/CD 流水线复用率高达 85%。

2.3 为什么坚决不选自建 Kubernetes(GKE)?

GKE 当然强大,但它的复杂度是指数级的。要让一个简单 Web 服务上线,你需要:创建集群(考虑节点池类型、规模、自动扩缩配置)、配置 Ingress 控制器(Nginx 或 Istio)、编写 Deployment/YAML、设置 HorizontalPodAutoscaler、配置 Service 和 Endpoints、申请 Let's Encrypt 证书并绑定到 Ingress……这一套操作,资深 SRE 也要花至少 2 小时。而 Cloud Run 的等效操作是:gcloud run deploy --image gcr.io/my-project/my-app --platform managed --region us-central1,回车,等待 90 秒。它把所有这些抽象成了一个命令。但这不是偷懒,而是工程权衡。我们做过测算:一个 5 人研发团队,如果所有后端服务都跑在 GKE 上,每月平均要消耗 37 人时在集群维护、证书轮换、网络策略调试、HPA 参数调优上;换成 Cloud Run 后,这部分时间归零,工程师可以专注在业务逻辑迭代上。当然,GKE 仍有不可替代的场景:需要 GPU 加速的训练任务、必须使用 StatefulSet 管理有状态数据库、或者要求精细的网络策略隔离。但对于占团队服务总数 78% 的无状态 HTTP API,Cloud Run 是更优解——它把“基础设施即代码”的理念,推进到了“基础设施即参数”的阶段。

2.4 方案选型决策树:一张表帮你快速判断

当你面对一个新项目时,不必纠结,直接对照这张实战总结的决策表:

评估维度Cloud Run 最佳匹配场景Cloud Functions 更合适场景GKE 必须选择场景
启动时间敏感度要求 < 1 秒冷启动(如用户交互 API)可接受 1-3 秒冷启动(如后台批处理)启动时间非首要考量(如数据管道)
执行时长需要 > 10 分钟持续运行(如流式处理)单次执行 < 10 分钟(如文件转码)无限制(如长期运行的守护进程)
依赖自由度需要自定义系统包、二进制、GPU 驱动仅需语言运行时标准库需要内核模块、特殊硬件访问
网络模型标准 HTTP/HTTPS 流量事件驱动(Pub/Sub、Storage)复杂网络拓扑(多子网、VPC 对等)
团队运维能力希望零基础设施管理接受轻量级事件抽象拥有专职 SRE 团队
成本结构偏好按请求毫秒计费,空闲零成本按执行时间+内存计费按节点小时计费,存在固定成本

这张表不是教条,而是我们踩过坑后提炼的血泪经验。比如第三行“依赖自由度”,曾让我们在一个金融风控服务上栽过大跟头:客户要求集成某国产加密 SDK,必须编译进 C++ 二进制。我们最初想用 Cloud Functions 的 Custom Runtime,结果发现其构建环境不支持交叉编译,折腾三天无果;切换到 Cloud Run,Dockerfile 里加两行RUN apt-get update && apt-get install -y build-essential,15 分钟搞定。所以选型的本质,是看你的瓶颈在哪里——是开发速度?运维负担?还是技术限制?Cloud Run 的价值,永远体现在它帮你消除了那个最痛的瓶颈。

3. 核心细节解析与实操要点:从 Dockerfile 到服务配置的 12 个生死关卡

3.1 Dockerfile 的黄金三原则:暴露端口、监听正确、健康检查就绪

Cloud Run 对容器镜像的要求,表面宽松,实则苛刻。它不校验你的镜像是否符合 Docker 最佳实践,但会在运行时用最严苛的方式测试它。我见过太多团队因为忽略以下三点,在部署后陷入“服务 Ready 但无法访问”的诡异状态。

第一原则:必须显式 EXPOSE 端口。很多人以为只要容器进程监听在 8080,Cloud Run 就能自动发现。错。Cloud Run 的健康检查探针(liveness probe)会向容器发送 HTTP GET 请求,目标端口就是PORT环境变量的值(默认 8080)。但如果 Dockerfile 里没有EXPOSE 8080,Kubernetes 层面的端口映射规则就不会生效,探针请求根本无法到达容器内部。这不是 Bug,而是 OCI 标准的强制约定。解决方案极其简单:在 Dockerfile 的最后,添加EXPOSE ${PORT:-8080}。注意,这里用了 Shell 变量展开语法,确保即使你后续通过--set-env-vars=PORT=3000覆盖环境变量,EXPOSE 也能同步更新。

第二原则:进程必须监听0.0.0.0:${PORT},而非127.0.0.1:${PORT}。这是新手最容易犯的错误。本地开发时,为了安全,我们习惯让 Web 服务器只绑定到 localhost;但容器里没有“本地”概念,127.0.0.1指向的是容器自己的 loopback 接口,外部流量(包括 Cloud Run 的探针)根本无法穿透。必须改成0.0.0.0。以 Python Flask 为例,启动命令不能是flask run --host=127.0.0.1 --port=8080,而必须是flask run --host=0.0.0.0 --port=$PORT。Node.js 的 Express 同理:app.listen(process.env.PORT || 8080, '0.0.0.0')。这个'0.0.0.0'字符串,是 Cloud Run 容器的生命线。

第三原则:健康检查路径/必须在 4 秒内返回 2xx。Cloud Run 默认用 HTTP GET/作为存活探针。很多框架(如 Django)默认根路径是 404,或者需要加载大量配置才能响应。你必须显式提供一个轻量级的健康检查端点。最佳实践是:在应用启动时,立即启动一个最小化的 HTTP 服务器,只响应/healthz,并在主应用初始化完成后,将/也指向健康检查。例如,Go 语言中:

http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) }) // 主应用启动后 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // 代理到主应用,或直接返回健康状态 http.Redirect(w, r, "/healthz", http.StatusTemporaryRedirect) })

这样,探针在应用启动的任何阶段都能得到响应,避免因初始化耗时过长导致实例被反复杀死。

提示:不要试图禁用健康检查!Cloud Run 不提供--disable-health-check选项。这是它的安全基线,绕过等于放弃服务稳定性。

3.2 环境变量与密钥管理:为什么 secrets manager 比 .env 文件更安全

在本地开发,.env文件方便快捷;但在生产环境,硬编码密钥到镜像或环境变量里,是重大安全风险。Cloud Run 原生集成了 Google Secret Manager,这是必须掌握的核心能力。

关键在于理解它的注入机制:Secret Manager 中的 secret 版本(version)会被挂载为容器内的文件(/secrets/my-db-password),而不是环境变量。为什么?因为环境变量可能被进程意外泄露(如通过/proc/<pid>/environ),而文件权限可以精确控制为0400(仅 owner 可读)。实操步骤分三步:

  1. 创建 secret 并添加版本

    echo -n "my-super-secret-password" | gcloud secrets create my-db-password --replication-policy="automatic" gcloud secrets versions add my-db-password --data-file=-
  2. 在部署时绑定 secret 到服务

    gcloud run deploy my-api \ --image=gcr.io/my-project/my-api \ --set-secrets="/secrets/db-password=my-db-password:latest" \ --platform managed \ --region us-central1

    这里的--set-secrets参数格式是MOUNT_PATH=SECRET_NAME:VERSIONlatest表示最新版本,你也可以指定1,2等具体版本号,便于灰度发布。

  3. 在应用代码中读取

    # Python 示例 with open("/secrets/db-password", "r") as f: password = f.read().strip() # 构建数据库连接字符串 db_url = f"postgresql://user:{password}@host:5432/db"

这种方法的优势是双重的:一是密钥永远不会出现在 CI/CD 日志或容器镜像层中;二是密钥轮换时,只需在 Secret Manager 中创建新版本,然后更新服务绑定(gcloud run services update ... --set-secrets=...),应用无需重启即可获取新密钥。我们有个客户曾因数据库密码泄露导致数据被爬取,迁移至 Secret Manager 后,密钥轮换时间从 4 小时缩短到 47 秒,且全程零停机。

注意:Secret Manager 的访问权限必须显式授予 Cloud Run 服务账号。默认情况下,服务账号只有run.invoker权限,没有secretmanager.versions.access。你需要额外执行:

gcloud projects add-iam-policy-binding my-project \ --member="serviceAccount:my-api@my-project.iam.gserviceaccount.com" \ --role="roles/secretmanager.secretAccessor"

3.3 自动扩缩与并发控制:如何用 3 个参数驯服流量洪峰

Cloud Run 的自动扩缩能力是其灵魂,但若配置不当,可能从“救星”变成“噩梦”。核心参数只有三个,但组合起来威力巨大:--min-instances,--max-instances,--concurrency

  • --concurrency(单实例并发请求数):这是最关键的参数,决定了单个容器实例能同时处理多少请求。默认值是 80,但并非越大越好。对于 I/O 密集型服务(如调用外部 API),提高并发能充分利用等待时间,80 是合理值;但对于 CPU 密集型服务(如图像压缩),高并发会导致 CPU 争抢,响应延迟飙升。我们实测过一个 FFmpeg 转码服务:--concurrency=1时,单请求耗时 1200ms;--concurrency=4时,单请求耗时反而升到 1800ms,因为 4 个 FFmpeg 进程在 1vCPU 上疯狂抢占。最终我们设为--concurrency=2,平衡了吞吐和延迟。

  • --min-instances(最小实例数):这是对抗冷启动的终极武器。设为1,意味着服务永远有一个实例处于 Warm 状态,首次请求延迟从 1-2 秒降到 50ms 以内。但代价是固定成本——即使零请求,你也为这 1 个实例付费。我们的经验法则是:对核心业务 API(如登录、支付),必须设--min-instances=1;对低频内部工具(如日志查询),保持0即可。

  • --max-instances(最大实例数):这是防止费用失控的安全阀。不设上限,当遭遇 DDoS 或流量误配置时,实例数可能瞬间飙到数千,账单一夜暴增。我们给所有服务都设置了硬性上限,计算公式是:最大实例数 = (峰值 QPS × 平均请求耗时秒数) / 并发数。例如,一个 API 峰值 QPS 为 1000,平均耗时 0.5 秒,并发设为 10,则1000 × 0.5 / 10 = 50,所以--max-instances=50。这个数字要留 20% 余量,最终设为 60。

这三个参数的协同效果,可以用一个真实案例说明:我们部署了一个实时聊天消息推送服务,使用 WebSocket。初始配置是默认值(min=0, max=unlimited, concurrency=80),结果在凌晨 3 点用户量低谷时,实例数归零;早上 8 点通勤高峰,瞬间涌入 5000 连接,Cloud Run 在 3 秒内拉起 200 个实例,但每个实例的 WebSocket 连接数超过 100,导致内存溢出崩溃,形成雪崩。修复方案是:--min-instances=5(保证基础连接池),--concurrency=100(WebSocket 是 I/O 密集,高并发友好),--max-instances=150(根据历史峰值计算)。调整后,服务在 7x24 小时内保持 99.99% 可用性。

3.4 网络与安全配置:VPC 连接器、防火墙与 IAM 的铁三角

Cloud Run 默认运行在 Google 的共享 VPC 中,这意味着你的容器可以直接访问公网,但无法访问私有 VPC 内的资源(如 Cloud SQL、Redis 实例)。要打通这条链路,必须配置Serverless VPC Access Connector

配置过程看似简单,但有三个致命陷阱:

  1. Connector 必须与 Cloud Run 服务在同一区域。比如你的服务部署在us-central1,那么 VPC Connector 也必须创建在us-central1。跨区连接会失败,且错误信息极其模糊(Failed to connect to VPC),排查起来非常痛苦。

  2. Connector 的 IP 地址范围不能与目标 VPC 的 CIDR 冲突。VPC Connector 本身需要一个独立的/28子网(16 个 IP)。如果你的目标 VPC 是10.0.0.0/16,那么 Connector 子网就不能设为10.0.0.0/28,否则路由冲突。我们推荐使用10.128.0.0/28这样的“隔离网段”。

  3. Cloud SQL 的授权必须包含 Connector 的 IP 段。很多人配置完 Connector,却发现连不上 Cloud SQL。原因在于 Cloud SQL 的授权列表里,只加了0.0.0.0/0(不安全)或具体的 Compute Engine IP,却忘了添加 Connector 子网。正确做法是:在 Cloud SQL 实例的“连接”设置中,添加一条授权网络,CIDR 为 Connector 子网(如10.128.0.0/28)。

一旦 VPC 连接器配置完成,你就可以在部署时启用它:

gcloud run deploy my-api \ --image=gcr.io/my-project/my-api \ --vpc-connector=my-vpc-connector \ --vpc-egress=all-traffic \ --platform managed \ --region us-central1

其中--vpc-egress=all-traffic表示所有出站流量(包括访问公网)都经过 VPC Connector。如果你只想让流量访问私有 VPC,而公网流量直连,应设为private-ranges-only

安全的最后一环是 IAM。Cloud Run 服务默认有一个服务账号(<service-name>@<project-id>.iam.gserviceaccount.com),它拥有run.invoker角色,允许被 HTTP 请求调用。但如果你的服务需要访问其他 GCP 服务(如 Pub/Sub、BigQuery),就必须给这个服务账号授予对应角色。切记:不要给服务账号授予editorowner这种宽泛角色。最小权限原则在这里是铁律。例如,一个只读取 Pub/Sub 消息的服务,只需roles/pubsub.subscriber;一个只写入 BigQuery 的服务,只需roles/bigquery.dataEditor。我们曾因误授editor角色,导致一个被黑的前端组件获得了删除整个 Cloud Storage 存储桶的权限,损失惨重。

4. 实操过程与核心环节实现:从零开始部署一个生产级 FastAPI 服务

4.1 准备工作:项目结构、依赖与 GCP 权限梳理

我们以一个真实的内部工具——“员工假期余额查询 API”为例,完整走一遍部署流程。这个 API 需要连接 Cloud SQL(PostgreSQL)查询员工数据,并通过 Secret Manager 获取数据库密码。项目结构如下:

vacation-api/ ├── main.py # FastAPI 主应用 ├── requirements.txt # Python 依赖 ├── Dockerfile # 构建镜像 ├── cloudbuild.yaml # Cloud Build 配置(可选) └── README.md

第一步:确认 GCP 项目权限。这不是可选步骤,而是前置条件。你需要确保当前 gcloud CLI 登录的账号拥有以下权限:

  • roles/run.admin(部署 Cloud Run 服务)
  • roles/storage.objectAdmin(上传构建镜像到 Container Registry)
  • roles/secretmanager.secretAccessor(访问 Secret Manager)
  • roles/cloudsql.client(连接 Cloud SQL)

最简单的方式是:在 GCP Console 的 IAM 页面,为你的账号添加Project Editor角色(仅用于开发环境)。生产环境则必须拆分为上述最小权限角色。

第二步:创建必要的 GCP 资源。打开 Cloud Console,依次创建:

  • Cloud SQL 实例:选择 PostgreSQL,版本 14,区域us-central1,实例 IDvacation-db。记住生成的密码。
  • Secret Manager Secret:创建名为vacation-db-password的 secret,添加版本,值为上一步的数据库密码。
  • VPC Connector:在VPC network > Serverless VPC Access中,创建 connectorvacation-connector,区域us-central1,子网10.128.0.0/28
  • Cloud SQL 授权:进入vacation-db实例的“连接”设置,添加授权网络10.128.0.0/28

这四步做完,基础设施层就准备好了。整个过程约 8 分钟,比手动配置一台虚拟机快得多。

4.2 编写生产就绪的 FastAPI 应用与 Dockerfile

main.py的核心逻辑必须包含健康检查、数据库连接和错误处理:

from fastapi import FastAPI, HTTPException, Depends from sqlalchemy import create_engine, text from sqlalchemy.exc import SQLAlchemyError import os app = FastAPI(title="Vacation Balance API") # 从 Secret Manager 读取密码 def get_db_password(): try: with open("/secrets/vacation-db-password", "r") as f: return f.read().strip() except FileNotFoundError: raise RuntimeError("Database password not found in secrets") # 创建数据库引擎(使用连接池) DB_PASSWORD = get_db_password() DB_URL = f"postgresql://vacation_user:{DB_PASSWORD}@10.128.0.2:5432/vacation_db" engine = create_engine(DB_URL, pool_size=5, max_overflow=10) @app.get("/healthz") def health_check(): """轻量级健康检查,不依赖数据库""" return {"status": "ok", "service": "vacation-api"} @app.get("/vacation/{employee_id}") def get_vacation_balance(employee_id: str): """查询员工假期余额""" try: with engine.connect() as conn: result = conn.execute(text("SELECT balance FROM employees WHERE id = :id"), {"id": employee_id}) row = result.fetchone() if row is None: raise HTTPException(status_code=404, detail="Employee not found") return {"employee_id": employee_id, "balance_days": row[0]} except SQLAlchemyError as e: raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")

requirements.txt只保留最小依赖:

fastapi==0.104.1 sqlalchemy==2.0.23 psycopg2-binary==2.9.7 uvicorn==0.24.0

Dockerfile是成败关键,必须严格遵循前述三原则:

# 使用官方 Python slim 镜像 FROM python:3.11-slim # 设置工作目录 WORKDIR /app # 复制依赖文件并安装(利用 Docker layer cache) COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制应用代码 COPY main.py . # 关键:暴露端口(必须!) EXPOSE ${PORT:-8080} # 关键:设置启动命令,监听 0.0.0.0 CMD exec uvicorn main:app --host 0.0.0.0:$PORT --port $PORT --workers 4 --proxy-headers --forwarded-allow-ips="*"

注意--proxy-headers--forwarded-allow-ips="*"这两个参数。Cloud Run 会在请求头中添加X-Forwarded-For等字段,Uvicorn 默认不信任这些头,会导致request.client.host返回127.0.0.1。加上这两个参数,Uvicorn 才能正确解析真实客户端 IP。

4.3 构建、推送与部署:一条命令完成全流程

现在进入最激动人心的环节。我们有两种方式构建镜像:本地docker build+docker push,或使用 Google Cloud Build(推荐,更安全、可审计)。

方式一:本地构建(适合快速验证)

# 构建镜像(tag 为 GCR 地址) docker build -t gcr.io/my-project/vacation-api . # 推送镜像到 Google Container Registry docker push gcr.io/my-project/vacation-api # 部署到 Cloud Run gcloud run deploy vacation-api \ --image=gcr.io/my-project/vacation-api \ --platform=managed \ --region=us-central1 \ --allow-unauthenticated \ --min-instances=1 \ --max-instances=10 \ --concurrency=80 \ --cpu=1 \ --memory=512Mi \ --set-secrets="/secrets/vacation-db-password=vacation-db-password:latest" \ --vpc-connector=vacation-connector \ --vpc-egress=private-ranges-only

方式二:Cloud Build(生产环境首选)
创建cloudbuild.yaml

steps: - name: 'gcr.io/cloud-builders/docker' args: ['build', '-t', 'gcr.io/$PROJECT_ID/vacation-api', '.'] - name: 'gcr.io/cloud-builders/docker' args: ['push', 'gcr.io/$PROJECT_ID/vacation-api'] images: - 'gcr.io/$PROJECT_ID/vacation-api'

然后触发构建:

gcloud builds submit --config=cloudbuild.yaml

构建成功后,再执行gcloud run deploy命令(去掉--image参数,Cloud Build 会自动推送到 GCR)。

部署命令中的每个参数都有深意:

  • --allow-unauthenticated:允许公开访问。如果这是内部 API,应改为--no-allow-unauthenticated,然后通过 IAM 授予特定用户roles/run.invoker
  • --cpu=1--memory=512Mi:根据应用实际需求设置。FastAPI + SQLAlchemy 的轻量 API,1vCPU + 512MB 内存足够。
  • --vpc-egress=private-ranges-only:因为我们只需要访问 Cloud SQL(私有 IP10.128.0.2),不需要走公网,所以设为private-ranges-only,更安全、更省钱。

执行gcloud run deploy后,你会看到类似这样的输出:

Deploying container to Cloud Run service [vacation-api] in project [my-project] region [us-central1] ✓ Deploying new service... Done. ✓ Creating Revision... ✓ Routing traffic... ✓ Setting up SSL... Done. Service URL: https://vacation-api-abcdef-uc.a.run.app

这个https://vacation-api-abcdef-uc.a.run.app就是你的服务地址。Cloud Run 已自动为你申请了免费的 Google 管理的 TLS 证书,并配置了全球 CDN。

4.4 验证与监控:用 curl 和 Cloud Logging 快速定位问题

部署完成后,别急着庆祝,立即验证四个关键点:

  1. 健康检查端点

    curl -I https://vacation-api-abcdef-uc.a.run.app/healthz # 应返回 HTTP/2 200 OK
  2. 数据库连接

    curl "https://vacation-api-abcdef-uc.a.run.app/vacation/EMP-001" # 应返回 JSON:{"employee_id":"EMP-001","balance_days":15}
  3. 冷启动性能

    time curl -o /dev/null -s -w "%{http_code}\n" https://vacation-api-abcdef-uc.a.run.app/healthz # 第一次执行(冷启动)应在 1.5 秒内;第二次(热实例)应在 50ms 内
  4. 日志查看: 打开 Cloud Console > Logging > Logs Explorer,输入查询:

    resource.type="cloud_run_revision" resource.labels.service_name="vacation-api"

    你可以看到每条请求的详细日志,包括响应时间、状态码、错误堆栈。如果出现 500 错误,日志里会清晰显示Database error: ...,而不是像传统服务器那样需要 SSH 登录查journalctl

监控方面,Cloud Run 内置了丰富的指标。在 Cloud Console > Cloud Run > 你的服务 > “监控”标签页,重点关注:

  • 请求计数(Requests count):确认流量是否正常接入。
  • 实例数(Instance count):观察扩缩是否符合预期(如流量激增时是否及时扩容)。
  • CPU 使用率(CPU utilization):如果长期高于 80%,说明需要增加 CPU 或优化代码。
  • 内存用量(Memory usage):如果接近上限(512Mi),考虑增加内存或检查内存泄漏。

我们曾在一个服务中发现内存用量缓慢爬升,从 200Mi 一周内涨到 480Mi。通过 Cloud Logging 查看日志,发现是 SQLAlchemy 的连接池未正确关闭,导致连接对象堆积。修复后,内存稳定在 180Mi。

5. 常见问题与排查技巧实录:那些文档里不会写的“血泪教训”

5.1 问题速查表:高频故障与一键修复方案

问题现象根本原因诊断命令修复方案我的实操心得
服务状态 Ready,但所有请求返回 503Dockerfile

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询