2. 微服务在Google Cloud上的落地形态

把微服务架构落到 Google Cloud 上,最先要回答的不是“用哪个产品”,而是“每个服务应该以什么形态运行”。GKE、Cloud Run、App Engine 三者都能承载微服务,但它们对“服务边界”的约束方式完全不同:GKE 把调度、网络、扩缩容的控制权交给团队,Cloud Run 把运行单元收敛到容器加请求,App Engine 则把运行单元收敛到应用版本与实例类别。理解这三种落地形态,才能判断一个服务该拆到哪里、以什么粒度部署、按什么信号扩缩容。

三种落地形态的本质差异

GKE 是托管的 Kubernetes 控制平面。你提交的是 Pod、Deployment、Service、Ingress 等对象,GKE 负责控制平面可用性、节点池升级和集群自动扩缩容。一个微服务通常对应一个 Deployment,副本数、资源 requests/limits、就绪探针、HPA 指标都由你定义。它的优势是表达力最强:Sidecar、Service Mesh、NetworkPolicy、StatefulSet、Job 都能用;代价是你要理解集群、节点、网络与配额。

Cloud Run 是请求驱动的无服务器容器平台。你提交一个容器镜像,声明 CPU、内存、并发数、最小/最大实例数,平台按请求量自动伸缩,缩到零时不产生实例费用。它天然适合 HTTP/gRPC 微服务,尤其是流量波动大、单请求处理时间短的服务。它的约束也很明确:实例是无状态的,本地磁盘是内存文件系统,长连接和后台线程要谨慎使用,请求超时和并发模型决定了服务拆分方式。

App Engine 是更早的应用托管形态,分标准环境和柔性环境。标准环境按语言运行时部署,扩缩容由实例类别、自动扩缩容配置和流量拆分控制;柔性环境则运行 Docker 容器,更接近 Cloud Run 的模型。App Engine 的强项是版本管理与流量拆分:你可以把 10% 流量切到新版本做金丝雀,也可以按 IP 或 Cookie 做 A/B 测试。它更适合“应用版本”粒度,而不是“细粒度微服务”粒度。

微服务落地形态:控制权与运行单元的三种切法 GKE 托管 Kubernetes · 控制权最大 运行单元:Pod / Deployment 一个微服务 ≈ 一个 Deployment 扩缩容:HPA / Cluster Autoscaler CPU、内存、自定义指标 网络:Service / Ingress / Mesh 可做细粒度流量治理 状态:支持 StatefulSet / PVC 有状态服务可落地 成本:按节点/资源预留 空闲也计费,需容量规划 适合:复杂拓扑、有状态、需 Mesh Cloud Run 请求驱动 · 缩到零 · 运维最轻 运行单元:容器 + 请求 一个服务 ≈ 一个容器镜像 扩缩容:并发数 + 实例数 min/max instances,按请求伸缩 网络:HTTPS / gRPC 入口 内置域名与证书,流量拆分 状态:无状态,内存盘 状态外置到 Firestore / SQL 成本:按请求/实例秒计费 缩零时无实例费用 适合:HTTP/gRPC、突发流量、事件驱动 App Engine 应用版本 · 流量拆分 · 传统 PaaS 运行单元:版本 + 实例类别 标准环境按运行时,柔性环境按容器 扩缩容:自动 + 手动实例 按 QPS、延迟、CPU 触发 网络:内置路由 + 流量拆分 按版本/IP/Cookie 分流 状态:Memcache / Datastore 标准环境有专属 API 成本:按实例小时计费 常驻实例有基础成本 适合:单体应用、版本灰度、快速上线

服务拆分粒度:先看运行单元,再看边界

在 GKE 上,拆分粒度可以很细。一个订单服务可以拆成 order-api、order-worker、order-scheduler 三个 Deployment,分别用 HPA 按 QPS、队列长度、定时任务并发扩缩容。它们之间通过 Service 或 Istio VirtualService 通信,可以给 order-api 配 3 个副本、order-worker 配 2~20 个副本。这种细粒度拆分的前提是团队能维护 YAML、能看懂 Pod 事件、能处理节点资源不足。如果团队没有 Kubernetes 运维能力,细拆会变成负担。

在 Cloud Run 上,拆分粒度通常按“请求边界”来定。一个服务如果同时处理同步查询和异步批处理,就应该拆成两个 Cloud Run 服务:查询服务设 concurrency=80、min instances=1,保证冷启动不影响用户体验;批处理服务设 concurrency=1、max instances=10,避免同时拉起太多实例打爆下游数据库。Cloud Run 的并发模型决定了:单实例并发越高,单请求越要轻量;如果单请求要跑 30 秒以上,就要考虑拆成任务队列加 worker,而不是硬扛在请求线程里。

在 App Engine 上,拆分粒度往往停留在“应用版本”层面。一个 App Engine 应用可以包含多个服务(service),每个服务有多个版本(version),版本之间可以切流量。例如把 default 服务作为主 API,把 worker 服务作为后台任务,把 admin 服务作为管理后台。但 App Engine 标准环境的运行时约束较强,不适合把每个微服务都拆成一个独立应用;更常见的做法是把一组相关功能放在一个服务里,用版本做灰度,而不是用服务做细粒度拆分。

部署模型与流量治理的对应关系

三种形态的部署模型差异,直接决定了流量治理方式。GKE 的部署是“声明式对象 + 控制器循环”:你提交 Deployment,ReplicaSet 保证副本数,Service 提供稳定 VIP,Ingress 或 Gateway 暴露入口。灰度发布通常靠两个 Deployment 加 Service 权重,或者用 Istio 的 VirtualService 按 Header 分流。它的流量治理能力最强,但配置也最复杂。

Cloud Run 的部署是“镜像 + 修订版本”:每次部署生成一个 revision,流量可以 100% 切到新 revision,也可以按百分比切分。例如把 5% 流量切到新 revision,观察 10 分钟错误率和 P95 延迟,再逐步放大到 100%。如果新 revision 异常,把流量切回旧 revision 即可,不需要重新部署。这种模型适合快速迭代的微服务,但要注意 revision 之间的兼容性:数据库 schema 变更必须向前兼容,否则回滚会失败。

App Engine 的部署是“版本 + 流量拆分”:每个版本是一个独立可寻址单元,你可以用 gcloud app services set-traffic 把 90% 流量给 v1、10% 给 v2,也可以按 IP 或 Cookie 做定向分流。它的灰度能力很成熟,但版本数量过多会增加管理成本。对于微服务架构,App Engine 更适合作为“边缘应用”或“单体应用”的托管形态,而不是细粒度微服务的主要载体。

选择判断:从服务特征反推落地形态

实际选型时,可以按服务特征反推。第一,看是否有状态:需要本地磁盘、长连接、StatefulSet 的服务,优先 GKE;无状态 HTTP/gRPC 服务,优先 Cloud Run。第二,看流量形态:流量突发且不可预测、希望缩到零的服务,Cloud Run 更合适;流量平稳、需要预留容量的服务,GKE 的节点池更可控。第三,看团队能力:有 Kubernetes 经验的团队可以用 GKE 做细粒度治理;运维人力有限的团队用 Cloud Run 能显著降低负担。第四,看发布策略:需要按版本做复杂流量拆分的,App Engine 或 Cloud Run revision 都能满足;需要 Mesh 级治理的,GKE 加 Istio 更合适。

一个常见的混合落地方式是:入口层用 Cloud Run 承载 API 网关和轻量服务,核心交易服务用 GKE 承载并接入 Service Mesh,后台批处理和定时任务用 Cloud Run Jobs 或 GKE Job。三种形态不是互斥的,而是按服务特征分层使用。

部署配置示例:同一服务的两种落地写法

以“订单查询服务”为例,如果部署到 GKE,核心配置如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-query
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-query
  template:
    metadata:
      labels:
        app: order-query
    spec:
      containers:
      - name: app
        image: gcr.io/demo/order-query:1.4.0
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: "250m"
            memory: "256Mi"
          limits:
            cpu: "500m"
            memory: "512Mi"
        readinessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 5
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-query-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-query
  minReplicas: 3
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 65

如果部署到 Cloud Run,同一服务的核心配置如下:

gcloud run deploy order-query \
  --image=gcr.io/demo/order-query:1.4.0 \
  --region=asia-east1 \
  --cpu=1 \
  --memory=512Mi \
  --concurrency=80 \
  --min-instances=1 \
  --max-instances=20 \
  --timeout=30s \
  --set-env-vars=DB_HOST=10.0.0.5,DB_NAME=orders \
  --allow-unauthenticated

两段配置的差异很直观:GKE 需要显式声明副本数、资源 requests/limits、探针和 HPA 指标;Cloud Run 只需要声明并发、实例上下限和超时。GKE 的扩缩容基于 CPU 利用率,Cloud Run 的扩缩容基于并发请求数。GKE 的副本数下限是 3,意味着即使没有流量也有 3 个 Pod 在运行;Cloud Run 的 min-instances=1 只保留 1 个实例,缩零时甚至可以为 0。这些差异会直接影响成本模型和容量规划。

结果观察与判断依据

部署完成后,判断服务是否健康,三种形态的观察点不同。GKE 要看 kubectl get pods 的 Ready 状态、HPA 的当前副本数与目标副本数、节点池的分配率;如果 Pod 一直 Pending,通常是节点资源不足或亲和性配置过严。Cloud Run 要看 revision 的流量分配、实例数曲线、请求延迟 P50/P95/P99 和错误率;如果实例数频繁在 0 和 1 之间抖动,说明 min-instances 设得太低或冷启动太慢。App Engine 要看版本的实例数、QPS、延迟和错误率,以及流量拆分是否按预期生效。

一个可操作的判断标准是:如果服务在流量低谷时仍需要保留大量常驻实例,GKE 的固定成本会偏高;如果服务在流量高峰时频繁触发冷启动,Cloud Run 的 min-instances 需要上调;如果服务需要按版本做精细灰度,App Engine 或 Cloud Run revision 的流量拆分能力更直接。选型不是一次性的,而是随着服务特征变化不断调整的过程。