实验 3:从原型到正式版 - 将 ADK 代理部署到支持 GPU 的 Cloud Run

1. 简介

概览

在本实验中,您将部署一个生产就绪的智能体开发套件 (ADK) 智能体,该智能体具有 GPU 加速的 Gemma 后端。重点在于关键部署模式:设置启用 GPU 的 Cloud Run 服务、将模型后端与 ADK 代理集成,以及在负载下观察自动扩缩行为。

您将执行的操作

在本实验中,您将重点学习以下关键的生产环境部署方面:

  1. 将 Gemma 部署到支持 GPU 的 Cloud Run - 设置高性能 Gemma 模型后端
  2. 将 Gemma 部署与 ADK 代理集成 - 将代理连接到 GPU 加速模型
  3. 使用 ADK Web 界面进行测试 - 验证对话式智能体是否正常运行
  4. 执行负载测试 - 观察两个 Cloud Run 实例在负载下如何自动扩缩

重点在于生产部署模式,而不是广泛的代理开发。

学习内容

  • 将 GPU 加速的 Gemma 模型部署到 Cloud Run 以供生产环境使用
  • 将外部模型部署与 ADK 代理集成
  • 配置和测试可用于生产环境的 AI 代理部署
  • 了解 Cloud Run 在负载下的自动扩缩行为
  • 观察多个 Cloud Run 实例在流量高峰期间如何协调工作
  • 应用负载测试来验证性能和自动扩缩

2. 项目设置

  1. 如果您还没有 Google 账号,则必须先创建一个 Google 账号
    • 请改用个人账号,而不是工作账号或学校账号。工作账号和学校账号可能存在限制,导致您无法启用本实验所需的 API。
  2. 登录 Google Cloud 控制台
  3. 在 Cloud 控制台中启用结算功能
    • 完成本实验的 Cloud 资源费用应不到 1 美元。
    • 您可以按照本实验结束时的步骤删除资源,以避免产生更多费用。
    • 新用户符合参与 $300 USD 免费试用计划的条件。
  4. 创建新项目或选择重复使用现有项目。

3. 打开 Cloud Shell Editor

  1. 点击此链接可直接前往 Cloud Shell 编辑器
  2. 如果系统在今天任何时间提示您进行授权,请点击授权继续。点击以授权 Cloud Shell
  3. 如果终端未显示在屏幕底部,请打开它:
    • 点击查看
    • 点击终端在 Cloud Shell 编辑器中打开新终端
  4. 在终端中,使用以下命令设置项目:
    • 格式:
      gcloud config set project [PROJECT_ID]
      
    • 示例:
      gcloud config set project lab-project-id-example
      
    • 如果您不记得项目 ID,请执行以下操作:
      • 您可以使用以下命令列出所有项目 ID:
        gcloud projects list | awk '/PROJECT_ID/{print $2}'
        
      在 Cloud Shell 编辑器终端中设置项目 ID
  5. 您应会看到以下消息:
    Updated property [core/project].
    
    如果您看到 WARNING 并被问到 Do you want to continue (Y/n)?,则很可能是您输入的项目 ID 有误。按 n,按 Enter,然后尝试再次运行 gcloud config set project 命令。

4. 启用 API 并设置默认区域

在部署支持 GPU 的 Cloud Run 服务之前,我们需要启用所需的 Google Cloud API 并配置项目设置。

  1. 在终端中,启用以下 API:
gcloud services enable \
  run.googleapis.com \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com \
  aiplatform.googleapis.com

如果系统提示您进行授权,请点击授权继续。点击以授权 Cloud Shell

此命令可能需要几分钟时间才能完成,但最终应会生成类似如下所示的成功消息:

Operation "operations/acf.p2-73d90d00-47ee-447a-b600" finished successfully.
  1. 设置默认 Cloud Run 区域。
gcloud config set run/region europe-west1

5. 准备 Python 项目

我们来设置包含 Gemma 后端和 ADK 代理服务的基本结构的起始代码。

  1. 克隆初始代码库:
    cd ~
    git clone https://github.com/amitkmaraj/accelerate-ai-lab3-starter.git
    cd accelerate-ai-lab3-starter
    
  2. 查看项目结构:
    ls -R
    
    您应该会看到以下初始结构:
    accelerate-ai-lab3-starter/
    ├── README.md                    # Project overview
    ├── ollama-backend/              # Ollama backend (separate deployment)
    │   └── Dockerfile               # Backend container (🚧 to implement)
    └── adk-agent/                   # ADK agent (separate deployment)
        ├── pyproject.toml           # Python dependencies (✅ completed)
        ├── server.py                # FastAPI server (🚧 to implement)
        ├── Dockerfile               # Container config (🚧 to implement)
        ├── load_test.py             # Load testing (🚧 to implement)
        └── production_agent/        # Agent implementation
            ├── __init__.py         # Package init (✅ completed)
            └── agent.py            # Agent logic (🚧 to implement)
    

6. 架构概览

在实现之前,我们先来了解一下双服务架构:

实验 3:双服务架构

关键洞见:在负载测试期间,您会发现这两个服务会独立扩缩 - GPU 后端(瓶颈服务)会扩缩到 1-3 个实例以处理推理负载,而 ADK 代理会保持在 1 个实例以处理请求。

7. 将 Gemma 后端部署到支持 GPU 的 Cloud Run

实验 3:Gemma 服务

第一个关键步骤是部署 GPU 加速的 Gemma 模型,该模型将作为 ADK 代理的大脑。在需要单独的微调模型或需要隔离扩缩的架构中,分离式已部署的 LLM 可能更具优势。

  1. 导航到 Ollama 后端目录:
    cd ollama-backend
    
  2. 打开并实现 Ollama Dockerfile:
    cloudshell edit Dockerfile
    
    将 TODO 注释替换为:
    FROM ollama/ollama:latest
    
    # Listen on all interfaces, port 8080
    ENV OLLAMA_HOST 0.0.0.0:8080
    
    # Store model weight files in /models
    ENV OLLAMA_MODELS /models
    
    # Reduce logging verbosity
    ENV OLLAMA_DEBUG false
    
    # Never unload model weights from the GPU
    ENV OLLAMA_KEEP_ALIVE -1
    
    # Store the model weights in the container image
    ENV MODEL gemma3:270m
    RUN ollama serve & sleep 5 && ollama pull $MODEL
    
    # Start Ollama
    ENTRYPOINT ["ollama", "serve"]
    
    🔧 此工具的作用
    • 使用官方 Ollama 映像作为基础
    • OLLAMA_HOST 设置为接受来自任何 IP 地址的连接
    • 公开端口 8080
  3. 部署支持 GPU 的 Gemma 后端:
gcloud run deploy ollama-gemma3-270m-gpu \
  --source . \
  --region europe-west1 \
  --concurrency 4 \
  --cpu 8 \
  --set-env-vars OLLAMA_NUM_PARALLEL=4 \
  --gpu 1 \
  --gpu-type nvidia-l4 \
  --max-instances 3 \
  --memory 16Gi \
  --allow-unauthenticated \
  --no-cpu-throttling \
  --no-gpu-zonal-redundancy \
  --timeout 600 \
  --labels dev-tutorial=codelab-agent-gpu

如果您收到“从源代码进行部署需要 Artifact Registry Docker 代码库来存储构建的容器。系统会显示一条消息,指出“将在 [europe-west1] 区域中创建一个名为 [cloud-run-source-deploy] 的代码库”,请继续。

⚙️ 主要配置说明

  • GPU:NVIDIA L4,因其在推理工作负载方面具有出色的性价比而入选。L4 提供 24 GB GPU 内存和优化的张量运算,非常适合用于 2.7 亿参数模型(例如 Gemma)
  • 内存:16 GB 系统内存,用于处理模型加载、CUDA 操作和 Ollama 的内存管理
  • CPU:8 个核心,可实现最佳 I/O 处理和预处理任务
  • 并发性:每个实例 4 个请求,可在吞吐量和 GPU 内存用量之间实现平衡
  • 超时:600 秒,可满足初始模型加载和容器启动的需求

💰 费用注意事项:GPU 实例比仅使用 CPU 的实例贵得多(每小时约 2-4 美元,而后者每小时约 0.10 美元)。--max-instances 1 设置有助于防止不必要的 GPU 实例扩缩,从而控制费用。

  1. 等待部署完成,并记下服务网址:
    export OLLAMA_URL=$(gcloud run services describe ollama-gemma3-270m-gpu \
        --region=europe-west1 \
        --format='value(status.url)')
    
    echo "🎉 Gemma backend deployed at: $OLLAMA_URL"
    

8. 实现 ADK 代理集成

现在,我们来创建一个连接到已部署的 Gemma 后端的最小 ADK 代理。

  1. 前往 ADK 代理目录:
    cd ../adk-agent
    
  2. 打开并实现代理配置:
    cloudshell edit production_agent/agent.py
    
    将所有 TODO 注释替换为此极简实现:
    import os
    from pathlib import Path
    
    from dotenv import load_dotenv
    from google.adk.agents import Agent
    from google.adk.models.lite_llm import LiteLlm
    import google.auth
    
    # Load environment variables
    root_dir = Path(__file__).parent.parent
    dotenv_path = root_dir / ".env"
    load_dotenv(dotenv_path=dotenv_path)
    
    # Configure Google Cloud
    try:
        _, project_id = google.auth.default()
        os.environ.setdefault("GOOGLE_CLOUD_PROJECT", project_id)
    except Exception:
        pass
    
    os.environ.setdefault("GOOGLE_CLOUD_LOCATION", "europe-west1")
    
    # Configure model connection
    gemma_model_name = os.getenv("GEMMA_MODEL_NAME", "gemma3:270m")
    
    # Production Gemma Agent - GPU-accelerated conversational assistant
    gemma_agent = Agent(
       model=LiteLlm(model=f"ollama_chat/{gemma_model_name}"),
       name="gemma_agent",
       description="A production-ready conversational assistant powered by GPU-accelerated Gemma.",
       instruction="""You are 'Gem', a friendly, knowledgeable, and enthusiastic zoo tour guide.
       Your main goal is to make a zoo visit more fun and educational for guests by answering their questions.
    
       You can provide general information and interesting facts about different animal species, such as:
       - Their natural habitats and diet. 🌲🍓
       - Typical lifespan and behaviors.
       - Conservation status and unique characteristics.
    
       IMPORTANT: You do NOT have access to any tools. This means you cannot look up real-time, specific information about THIS zoo. You cannot provide:
       - The names or ages of specific animals currently at the zoo.
       - The exact location or enclosure for an animal.
       - The daily schedule for feedings or shows.
    
       Always answer based on your general knowledge about the animal kingdom. Keep your tone cheerful, engaging, and welcoming for visitors of all ages. 🦁✨""",
       tools=[],  # Gemma focuses on conversational capabilities
    )
    
    # Set as root agent
    root_agent = gemma_agent
    
    🔧 此工具的作用
    • 通过 LiteLlm 连接到已部署的 Gemma 后端
    • 创建简单的对话式智能体
    • 配置 Google Cloud 集成
  3. 打开并实现 FastAPI 服务器:
    cloudshell edit server.py
    
    将所有 TODO 注释替换为:
    import os
    from dotenv import load_dotenv
    from fastapi import FastAPI
    from google.adk.cli.fast_api import get_fast_api_app
    
    # Load environment variables
    load_dotenv()
    
    AGENT_DIR = os.path.dirname(os.path.abspath(__file__))
    app_args = {"agents_dir": AGENT_DIR, "web": True}
    
    # Create FastAPI app with ADK integration
    app: FastAPI = get_fast_api_app(**app_args)
    
    # Update app metadata
    app.title = "Production ADK Agent - Lab 3"
    app.description = "Gemma agent with GPU-accelerated backend"
    app.version = "1.0.0"
    
    @app.get("/health")
    def health_check():
        return {"status": "healthy", "service": "production-adk-agent"}
    
    @app.get("/")
    def root():
        return {
            "service": "Production ADK Agent - Lab 3",
            "description": "GPU-accelerated Gemma agent",
            "docs": "/docs",
            "health": "/health"
        }
    
    if __name__ == "__main__":
        import uvicorn
        uvicorn.run(app, host="0.0.0.0", port=8080, log_level="info")
    
    🔧 此工具的作用
    • 创建与 ADK 集成的 FastAPI 服务器
    • 启用用于测试的 Web 界面
    • 提供健康检查端点
  4. 打开并实现 Dockerfile:
    cloudshell edit Dockerfile
    
    将所有 TODO 注释替换为:
    FROM python:3.13-slim
    
    # Copy uv from the official image
    COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
    
    # Install system dependencies
    RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
    
    # Set working directory
    WORKDIR /app
    
    # Copy all files
    COPY . .
    
    # Install Python dependencies
    RUN uv sync
    
    # Expose port
    EXPOSE 8080
    
    # Run the application
    CMD ["uv", "run", "uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8080"]
    
    技术选择说明
    • uv:比 pip 快 10-100 倍的现代 Python 软件包管理器。它使用全局缓存和并行下载,可显著缩短容器构建时间
    • Python 3.13-slim:最新的 Python 版本,具有最少的系统依赖项,可减小容器大小和攻击面
    • 多阶段构建:从官方映像复制 uv 可确保我们获得最新的优化二进制文件

9. 配置环境并部署代理

现在,我们将配置 ADK 代理以连接到已部署的 Gemma 后端,并将其部署为 Cloud Run 服务。这包括设置环境变量并部署具有正确配置的代理。

  1. 设置环境配置:
    cat << EOF > .env
    GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project)
    GOOGLE_CLOUD_LOCATION=europe-west1
    GEMMA_MODEL_NAME=gemma3:270m
    OLLAMA_API_BASE=$OLLAMA_URL
    EOF
    

了解 Cloud Run 中的环境变量

环境变量是键值对,可在运行时配置应用。它们特别适用于以下情况:

  • API 端点和服务网址(例如我们的 Ollama 后端)
  • 在不同环境(开发、预演、生产)之间发生变化的配置
  • 不应进行硬编码的敏感数据

部署 ADK 智能体:

export PROJECT_ID=$(gcloud config get-value project)

gcloud run deploy production-adk-agent \
   --source . \
   --region europe-west1 \
   --allow-unauthenticated \
   --memory 4Gi \
   --cpu 2 \
   --max-instances 1 \
   --concurrency 10 \
   --timeout 300 \
   --set-env-vars GOOGLE_CLOUD_PROJECT=$PROJECT_ID \
   --set-env-vars GOOGLE_CLOUD_LOCATION=europe-west1 \
   --set-env-vars GEMMA_MODEL_NAME=gemma3:270m \
   --set-env-vars OLLAMA_API_BASE=$OLLAMA_URL \
   --labels dev-tutorial=codelab-agent-gpu

⚙️ 主要配置

  • 自动扩缩:固定为 1 个实例(轻量级请求处理)
  • 并发:每个实例 10 个请求
  • 内存:4GB(适用于 ADK 代理)
  • 环境:连接到 Gemma 后端

🔒 安全注意事项:为简单起见,本实验使用 --allow-unauthenticated。在生产环境中,请使用以下方法实现适当的身份验证:

  • 使用服务账号进行 Cloud Run 服务到服务身份验证
  • Identity and Access Management (IAM) 政策
  • 用于外部访问的 API 密钥或 OAuth
  • 考虑使用 gcloud run services add-iam-policy-binding 来控制访问权限

获取代理服务网址:

export AGENT_URL=$(gcloud run services describe production-adk-agent \
    --region=europe-west1 \
    --format='value(status.url)')

echo "🎉 ADK Agent deployed at: $AGENT_URL"

根据 Cloud Run 环境变量文档✅ 环境变量最佳实践

  1. 避免使用预留变量:请勿设置 PORT(Cloud Run 会自动设置此变量)或以 X_GOOGLE_ 开头的变量
  2. 使用描述性名称:为变量添加前缀以避免冲突(例如,GEMMA_MODEL_NAME,而非 MODEL
  3. 转义英文逗号:如果您的值包含英文逗号,请使用其他分隔符:--set-env-vars "^@^KEY1=value1,value2@KEY2=..."
  4. 更新与替换:使用 --update-env-vars 可添加/更改特定变量,而不会影响其他变量

如何在 Cloud Run 中设置变量

  • 来自文件gcloud run deploy SERVICE_NAME --env-vars-file .env --labels dev-tutorial codelab-adk(从文件加载多个变量)
  • 多个标志:对于无法以英文逗号分隔的复杂值,请重复使用 --set-env-vars

10. 使用 ADK Web 界面进行测试

部署完这两项服务后,接下来需要验证 ADK 代理是否可以成功与 GPU 加速的 Gemma 后端通信并响应用户查询。

  1. 测试健康端点:
    curl $AGENT_URL/health
    
    您应该会看到:
    { "status": "healthy", "service": "production-adk-agent" }
    
  2. 在新的浏览器标签页中输入 production-adk-agent 的网址,与您的代理互动。您应该会看到 ADK 网页界面。
  3. 不妨使用以下示例对话测试您的代理:
    • “小熊猫在野外通常吃什么?”
    • “你能告诉我一些关于雪豹的趣闻吗?”
    • “为什么箭毒蛙的颜色如此鲜艳?”
    • “我在动物园的哪里可以找到新出生的小袋鼠?”
    👀 观察内容
    • 代理会使用您部署的 Gemma 模型进行回答。您可以通过查看已部署的 Gemma 服务的日志来验证这一点。我们将在下一部分中执行此操作
    • 回答由 GPU 加速的后端生成
    • 网页界面提供简洁的聊天体验

实验 3 ADK 测试

11. 实现并运行负载测试

实验 3:负载测试

为了解生产部署如何处理实际流量,我们将实施全面的负载测试,以触发 ADK 代理和 GPU 后端服务之间的自动扩缩。

  1. 打开并实现负载测试脚本:
    cloudshell edit load_test.py
    
    将 TODO 注释替换为:
    import random
    import uuid
    from locust import HttpUser, task, between
    
    class ProductionAgentUser(HttpUser):
        """Load test user for the Production ADK Agent."""
    
        wait_time = between(1, 3)  # Faster requests to trigger scaling
    
        def on_start(self):
            """Set up user session when starting."""
            self.user_id = f"user_{uuid.uuid4()}"
            self.session_id = f"session_{uuid.uuid4()}"
    
            # Create session for the Gemma agent using proper ADK API format
            session_data = {"state": {"user_type": "load_test_user"}}
    
            self.client.post(
                f"/apps/production_agent/users/{self.user_id}/sessions/{self.session_id}",
                headers={"Content-Type": "application/json"},
                json=session_data,
            )
    
        @task(4)
        def test_conversations(self):
            """Test conversational capabilities - high frequency to trigger scaling."""
            topics = [
                "What do red pandas typically eat in the wild?",
                "Can you tell me an interesting fact about snow leopards?",
                "Why are poison dart frogs so brightly colored?",
                "Where can I find the new baby kangaroo in the zoo?",
                "What is the name of your oldest gorilla?",
                "What time is the penguin feeding today?"
            ]
    
            # Use proper ADK API format for sending messages
            message_data = {
                "app_name": "production_agent",
                "user_id": self.user_id,
                "session_id": self.session_id,
                "new_message": {
                    "role": "user",
                    "parts": [{
                        "text": random.choice(topics)
                    }]
                }
            }
    
            self.client.post(
                "/run",
                headers={"Content-Type": "application/json"},
                json=message_data,
            )
    
        @task(1)
        def health_check(self):
            """Test the health endpoint."""
            self.client.get("/health")
    
    🔧 此工具的作用
    • 会话创建:使用正确的 ADK API 格式,通过 POST 请求发送到 /apps/production_agent/users/{user_id}/sessions/{session_id}。创建 session_iduser_id 后,可以向代理发出请求。
    • 消息格式:遵循 ADK 规范,包含 app_nameuser_idsession_id 和结构化 new_message 对象
    • 对话端点:使用 /run 端点一次性收集所有事件(建议用于负载测试)
    • 实际负载:创建等待时间较短的对话负载,以触发自动扩缩
    📚 如需详细了解 ADK API 端点和测试模式,请参阅 ADK 测试指南
  2. 安装依赖项:
    uv sync
    pip install locust
    
  3. Locust 是一款基于 Python 的开源负载测试工具,旨在对 Web 应用和其他系统进行性能和负载测试。它的主要特点是使用标准 Python 代码定义测试场景和用户行为,与依赖图形界面或特定于网域的语言的工具相比,具有更高的灵活性和表现力。我们将使用 Locust 模拟对服务的用户流量。运行负载测试。
    # Run a load test to trigger autoscaling
    locust -f load_test.py \
       -H $AGENT_URL \
       --headless \
       -t 50s \
       -u 3 \
       -r 1
    
    尝试更改测试中的参数,并观察输出结果。您会注意到 ollama-gemma3-270m-gpu 峰值达到 2-3 个实例。📊 负载测试参数
    • 时长:50 秒
    • 用户:3 位并发用户
    • 生成速率:每秒 1 位用户
    • 目标:在两个服务上触发自动扩缩

12. 观察自动扩缩行为

在负载测试运行期间,您将能够观察到 Cloud Run 的自动扩缩功能在运行。在此处,您将看到将 ADK 代理与 GPU 后端分离的主要架构优势。

在负载测试期间,在控制台中监控这两个 Cloud Run 服务的扩缩情况。

  1. 在 Cloud 控制台中,前往:
    • Cloud Run → production-adk-agent → 指标
    • Cloud Run → ollama-gemma3-270m-gpu → 指标

👀 您应观察到的情况:

🤖 ADK Agent Service

  • 在流量增加时,应保持稳定在 1 个实例
  • 高流量期间 CPU 和内存用量激增
  • 高效处理会话管理和请求路由

🎮 Gemma 后端服务(瓶颈)

  • 根据推理需求将实例数量从 1 个扩缩到 3 个
  • 在负载下,GPU 利用率显著提高
  • 由于 GPU 密集型模型推理,此服务成为瓶颈
  • 由于 GPU 加速,模型推理时间保持不变

💡 重要数据洞见

  • GPU 后端是瓶颈,并且扩展速度更快(1-3 个实例)
  • ADK 代理保持一致
  • 两种服务会根据各自的负载特征独立扩缩
  • 自动扩缩有助于在不同的负载条件下保持性能

13. 总结

恭喜!您已成功部署了生产就绪型 ADK 代理,该代理具有 GPU 加速的 Gemma 后端,并观察了自动扩缩行为。

✅ 您完成的操作

  • ✅ 在 Cloud Run 上部署了 GPU 加速的 Gemma 模型后端
  • ✅ 创建并部署了与 Gemma 后端集成的 ADK 代理
  • ✅ 使用 ADK 网页界面测试了代理
  • ✅ 观察到两个协调的 Cloud Run 服务之间的自动扩缩行为

💡 本实验的关键分析洞见

  1. 🎮 GPU 加速:NVIDIA L4 GPU 可显著提升模型推理性能
  2. 🔗 服务协调:两个 Cloud Run 服务可以无缝协同工作
  3. 📈 独立扩缩:每个服务都会根据其各自的负载特征进行扩缩
  4. 🚀 生产环境就绪状态:架构可有效处理实际流量模式

🔄 后续步骤

  • 尝试不同的负载模式并观察扩缩行为
  • 尝试不同的 Gemma 模型大小(相应地调整内存和 GPU)
  • 为生产环境部署实现监控和提醒
  • 探索多区域部署,实现全球可用性

🧹 清理

为避免产生费用,请在完成后删除资源:

gcloud run services delete production-adk-agent --region=europe-west1
gcloud run services delete ollama-gemma3-270m-gpu --region=europe-west1

📖 资源