AI Agent 教程

第16章:安全部署 —— 让 Agent 在生产环境安全运行

一句话:掌握 Agent 的安全最佳实践,从玩具变成正式产品。

本章目标

前置知识


16.1 Agent 安全的三层防护

为什么 Agent 的安全这么重要?

在前面的章节中,我们已经学会了怎么创建 Agent、怎么给它工具、怎么让它干活。在自己的电脑上跑跑测试,一切看起来都很美好。

但是,一旦你要把 Agent 部署到生产环境 —— 比如做成一个 SaaS 服务让用户使用,或者在公司服务器上长期运行 —— 安全问题就变得极其关键。

为什么?因为 Agent 和普通程序不一样。普通程序的行为是你写死的,它只会按你写好的代码执行。但 Agent 的行为是 由 LLM 决定的,LLM 的输出有不确定性。更可怕的是,如果 Agent 能使用 Bash、文件读写等工具,一旦它"做错事",后果可能比普通 Bug 严重得多。

想象几个恐怖场景:

所以,安全部署不是"锦上添花",而是 必须做的事

三层防护模型

我们把 Agent 的安全防护分为三层,就像一栋大楼的安保系统:

┌─────────────────────────────────────┐
│         第三层:网络限制             │
│    只允许访问特定地址,屏蔽其余      │
│  ┌─────────────────────────────┐    │
│  │       第二层:容器隔离        │    │
│  │   Agent 运行在沙箱中,       │    │
│  │   即使出错也伤不到宿主机     │    │
│  │  ┌─────────────────────┐    │    │
│  │  │   第一层:权限控制    │    │    │
│  │  │  Agent 只能用你       │    │    │
│  │  │  明确授权的工具       │    │    │
│  │  └─────────────────────┘    │    │
│  └─────────────────────────────┘    │
└─────────────────────────────────────┘

第一层:权限控制(应用层)

这一层我们在第6章已经学过了。核心思路是:Agent 只能使用你明确允许的工具

import { query, ClaudeCodeOptions } from "@anthropic-ai/claude-code";

// 只允许读文件和搜索,不允许写文件和执行命令
const options: ClaudeCodeOptions = {
  allowedTools: ["Read", "Glob", "Grep"],
  // 注意:没有 Write、Edit、Bash
};

这就像公司里的门禁卡 —— 你的工卡只能刷开你有权限进入的房间,其他房间的门你刷不开。

第二层:容器隔离(系统层)

即使你限制了工具,万一 Agent 通过某种方式绕过了限制呢?(虽然概率很低,但安全领域讲究 "defense in depth" —— 纵深防御)

容器隔离的思路是:把 Agent 关在一个"沙箱"里运行。即使 Agent 在沙箱里为所欲为,沙箱外面的宿主机和其他服务也不受影响。

这就像公司里的实验室 —— 做危险实验的时候,你在一个密封的安全柜里操作。即使里面爆炸了,实验室外面的人也不会受伤。

常见的容器方案:

方案 隔离级别 适用场景
Docker 容器 进程级隔离 最常用,够用了
gVisor / Kata Containers 内核级隔离 对安全要求更高
microVM (Firecracker) 虚拟机级隔离 最安全,但开销最大
Apple Container (macOS) 原生沙箱 macOS 开发环境

NanoClaw 的做法:使用操作系统级别的容器隔离(最安全的方案),每个 Agent 任务运行在独立的沙箱中。

OpenClaw 的做法:使用应用层的白名单机制(相对没那么安全,但更灵活),通过配置文件定义 Agent 能访问哪些资源。

对于大多数团队来说,Docker 容器已经足够了。本章后面会详细讲怎么用 Docker 部署。

第三层:网络限制(网络层)

Agent 运行在容器里了,但它还可能通过网络"搞事情" —— 比如把敏感数据发到外部服务器,或者访问你内网中不该访问的服务。

网络限制的思路是:只允许 Agent 访问特定的网络地址,其余一律屏蔽

# docker-compose.yml 中的网络限制示例
services:
  agent:
    networks:
      - agent_network

networks:
  agent_network:
    driver: bridge
    internal: true  # 禁止访问外部网络

这就像公司的防火墙 —— 只允许访问业务需要的网站,其他一律拦截。

三层防护的协作

三层防护不是"选一个用",而是叠加使用的。就像你的手机:

三层都做到,你的 Agent 服务才算真正安全。


16.2 容器化部署

为什么要用容器?

一句话:Agent 搞砸了也伤不到你的宿主机。

不用容器的情况下,Agent 直接跑在你的服务器上。如果 Agent 执行了危险命令(比如删文件、改系统配置),你的服务器直接遭殃。

用了容器之后,Agent 在一个隔离的"小房间"里运行。"小房间"里随便折腾,外面的世界毫发无损。最坏的情况也就是重建这个容器,几秒钟的事。

Dockerfile 示例

先来写一个用于运行 Agent 的 Dockerfile:

# ---- 阶段一:安装依赖 ----
FROM node:20-slim AS base

# 安全实践:不用 root 用户运行
RUN groupadd -r agent && useradd -r -g agent -m agent

# 安装 Claude Code SDK
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --production

# 复制应用代码
COPY src/ ./src/

# ---- 阶段二:精简运行镜像 ----
FROM node:20-slim AS runtime

# 创建非 root 用户
RUN groupadd -r agent && useradd -r -g agent -m agent

# 从构建阶段复制产物
WORKDIR /app
COPY --from=base /app/node_modules ./node_modules
COPY --from=base /app/src ./src

# 安全实践:设置只读文件系统的例外
RUN mkdir -p /app/workspace /app/tmp && \
    chown -R agent:agent /app/workspace /app/tmp

# 切换到非 root 用户
USER agent

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

# 启动应用
CMD ["node", "src/server.js"]

几个关键安全点,一个个说:

1. 多阶段构建:构建依赖(devDependencies、构建工具等)不会出现在最终镜像中,减少攻击面。

2. 非 root 用户USER agent 这行很关键。如果容器以 root 运行,一旦容器被突破,攻击者就有 root 权限。用普通用户运行,即使被突破,权限也是受限的。

3. 最小镜像:用 node:20-slim 而不是 node:20。slim 版本去掉了很多不必要的系统工具,攻击者能利用的东西更少。

4. 健康检查:容器挂了能被自动发现和重启。

docker-compose.yml 示例

单个 Dockerfile 只是构建镜像,实际部署还需要编排。用 docker-compose 把所有东西串起来:

version: "3.8"

services:
  agent-service:
    build: .
    container_name: claude-agent

    # ---- 资源限制 ----
    deploy:
      resources:
        limits:
          cpus: "2.0"       # 最多用 2 个 CPU 核心
          memory: 2G         # 最多用 2GB 内存
        reservations:
          cpus: "0.5"       # 至少保证 0.5 个核心
          memory: 512M       # 至少保证 512MB 内存

    # ---- 环境变量 ----
    environment:
      - NODE_ENV=production
      - LOG_LEVEL=info
      # API Key 通过 secrets 注入,不要写在这里!

    # ---- Secrets ----
    secrets:
      - anthropic_api_key

    # ---- 文件系统挂载 ----
    volumes:
      # 只读挂载:Agent 需要读取的项目代码
      - ./projects:/app/projects:ro
      # 读写挂载:Agent 的工作目录(输出文件用)
      - agent_workspace:/app/workspace

    # ---- 安全选项 ----
    read_only: true          # 根文件系统只读
    tmpfs:
      - /tmp:size=100M       # tmp 目录限制 100MB
    security_opt:
      - no-new-privileges    # 禁止提权
    cap_drop:
      - ALL                  # 移除所有 Linux capabilities
    cap_add:
      - NET_BIND_SERVICE     # 只保留绑定端口的能力

    # ---- 网络 ----
    networks:
      - agent_net
    ports:
      - "3000:3000"

    # ---- 重启策略 ----
    restart: unless-stopped

    # ---- 日志 ----
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

# ---- Secrets 定义 ----
secrets:
  anthropic_api_key:
    file: ./secrets/anthropic_api_key.txt

# ---- 存储卷 ----
volumes:
  agent_workspace:
    driver: local

# ---- 网络 ----
networks:
  agent_net:
    driver: bridge

这里面的每个配置都有讲究,逐个解释:

资源限制详解

deploy:
  resources:
    limits:
      cpus: "2.0"
      memory: 2G

为什么要限制资源?因为 Agent 有可能进入死循环,或者某个工具执行了消耗大量资源的操作。没有资源限制的话,一个失控的 Agent 能把整台服务器的资源吃光,影响其他服务。

经验参考值:

Agent 用途 CPU 内存 磁盘
简单的代码分析 1 核 1GB 500MB
代码生成 + 编译测试 2 核 2GB 2GB
大项目全面审查 4 核 4GB 5GB
多 Agent 协作 8 核 8GB 10GB

文件系统挂载详解

volumes:
  # 只读挂载
  - ./projects:/app/projects:ro
  # 读写挂载
  - agent_workspace:/app/workspace

核心原则:最小权限。 Agent 只需要读取项目代码?那就只给只读挂载(:ro)。Agent 需要输出文件?给一个专门的工作目录,而且这个目录是隔离的 Docker Volume,不会直接暴露宿主机的文件系统。

千万不要这样做:

# 危险!千万别这么干!
volumes:
  - /:/host  # 把整个宿主机文件系统挂进去了!

这相当于把你家的钥匙直接给了 Agent,它想进哪个房间就进哪个房间。

网络隔离详解

如果你的 Agent 不需要访问互联网(比如只是分析本地代码),可以完全禁止外部网络:

networks:
  agent_net:
    driver: bridge
    internal: true  # 关键!禁止访问外部网络

如果 Agent 需要访问特定的外部服务(比如 Anthropic API),可以用更精细的网络策略:

# 使用 iptables 规则限制容器的出站流量
# 在宿主机上执行:

# 只允许访问 Anthropic API
iptables -I DOCKER-USER -s 172.18.0.0/16 -d api.anthropic.com -j ACCEPT
# 只允许 DNS 查询
iptables -I DOCKER-USER -s 172.18.0.0/16 -p udp --dport 53 -j ACCEPT
# 拒绝其他所有出站流量
iptables -I DOCKER-USER -s 172.18.0.0/16 -j DROP

或者更简单的做法,在应用层通过代理来控制:

services:
  agent-service:
    environment:
      - HTTP_PROXY=http://proxy:8080
      - HTTPS_PROXY=http://proxy:8080
      - NO_PROXY=localhost,127.0.0.1
    networks:
      - agent_net

  # 代理服务,只放行白名单中的域名
  proxy:
    image: squid:latest
    volumes:
      - ./squid.conf:/etc/squid/squid.conf:ro
    networks:
      - agent_net

16.3 API Key 管理

铁律:永远不要硬编码 API Key

这话说起来像废话,但你绝对会惊讶有多少人把 API Key 直接写在代码里:

// 绝对不要这样做!!!
const client = new Anthropic({
  apiKey: "sk-ant-api03-xxxxxxxxxxxxxxxxxxxx"  // 完蛋,推到 GitHub 全世界都能看到
});

一旦你的代码推到了 GitHub(哪怕是 private repo,也可能因为各种原因被泄露),你的 API Key 就等于是公开的了。有专门的爬虫在 GitHub 上扫描泄露的 API Key,几分钟之内就会被发现。

方案一:环境变量(最简单)

// 正确做法:从环境变量读取
const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) {
  throw new Error("缺少 ANTHROPIC_API_KEY 环境变量");
}

启动时传入:

# 命令行方式
ANTHROPIC_API_KEY=sk-ant-xxx node src/server.js

# 或者用 .env 文件(但 .env 文件千万别提交到 Git)
# .env
ANTHROPIC_API_KEY=sk-ant-xxx

.gitignore 里一定要有:

.env
.env.local
.env.production
*.key
secrets/

方案二:Docker Secrets(推荐用于 Docker 部署)

Docker Secrets 比环境变量更安全,因为:

使用方式:

# 创建 secret
echo "sk-ant-xxx" > ./secrets/anthropic_api_key.txt
chmod 600 ./secrets/anthropic_api_key.txt
# docker-compose.yml
services:
  agent-service:
    secrets:
      - anthropic_api_key

secrets:
  anthropic_api_key:
    file: ./secrets/anthropic_api_key.txt

在代码中读取:

import { readFileSync } from "fs";

function getSecret(name: string): string {
  try {
    // Docker Secrets 会被挂载到 /run/secrets/ 目录
    return readFileSync(`/run/secrets/${name}`, "utf-8").trim();
  } catch {
    // 降级到环境变量
    const envValue = process.env[name.toUpperCase()];
    if (!envValue) {
      throw new Error(`Secret "${name}" not found`);
    }
    return envValue;
  }
}

const apiKey = getSecret("anthropic_api_key");

方案三:密钥管理服务(大规模部署)

如果你在 AWS、GCP、Azure 等云平台上部署,用它们的密钥管理服务最合适:

云平台 服务名称 优点
AWS Secrets Manager 自动轮换、审计日志
GCP Secret Manager 版本管理、IAM 集成
Azure Key Vault HSM 支持、访问策略

Key 轮换策略

API Key 不是创建一次就永远不变的。定期更换 Key 可以降低泄露的风险。

// 一个简单的 Key 轮换检查器
class ApiKeyManager {
  private currentKey: string;
  private keyCreatedAt: Date;
  private maxAgeDays: number = 90;  // 90 天轮换一次

  constructor() {
    this.currentKey = getSecret("anthropic_api_key");
    this.keyCreatedAt = new Date();
  }

  getKey(): string {
    const ageDays = (Date.now() - this.keyCreatedAt.getTime()) / (1000 * 60 * 60 * 24);
    if (ageDays > this.maxAgeDays) {
      console.warn(`[安全警告] API Key 已使用 ${Math.floor(ageDays)} 天,请尽快轮换!`);
    }
    return this.currentKey;
  }
}

泄露监控

万一 Key 真的泄露了怎么办?你需要能 尽快发现

  1. 启用 Anthropic Console 的用量告警:如果突然出现异常用量,可能是 Key 被盗用了
  2. 监控 GitHub:用 GitGuardian 或 GitHub 自带的 Secret Scanning 来检测代码库中的泄露
  3. 立即轮换:发现泄露后,第一时间在 Anthropic Console 中吊销旧 Key,生成新 Key

16.4 成本控制

Token 消耗是你最大的运营成本

Agent 和传统软件不同。传统软件的服务器成本相对固定(CPU、内存、带宽)。但 Agent 的成本主要来自 LLM API 调用,而且这个成本可能非常惊人 —— 一个写得不好的 Agent,一天就能烧掉你几百美元。

用 SDK 追踪成本

Claude Code SDK 提供了详细的用量信息,你可以直接用来追踪成本:

import { query } from "@anthropic-ai/claude-code";

let totalInputTokens = 0;
let totalOutputTokens = 0;
let totalCost = 0;
let cacheCreationTokens = 0;
let cacheReadTokens = 0;

for await (const message of query({
  prompt: "分析这个项目的代码质量",
  options: {
    allowedTools: ["Read", "Glob", "Grep"]
  }
})) {
  // result 消息包含用量信息
  if (message.type === "result") {
    // 直接获取总花费(美元)
    if (message.total_cost_usd !== undefined) {
      totalCost = message.total_cost_usd;
      console.log(`总花费: $${totalCost.toFixed(4)}`);
    }

    // 获取 token 使用明细
    if (message.usage) {
      totalInputTokens += message.usage.input_tokens;
      totalOutputTokens += message.usage.output_tokens;
      console.log(`输入 Token: ${message.usage.input_tokens}`);
      console.log(`输出 Token: ${message.usage.output_tokens}`);
    }

    // 缓存相关的 Token(如果开启了 Prompt Caching)
    if (message.usage?.cache_creation_input_tokens) {
      cacheCreationTokens = message.usage.cache_creation_input_tokens;
      console.log(`缓存创建 Token: ${cacheCreationTokens}`);
    }
    if (message.usage?.cache_read_input_tokens) {
      cacheReadTokens = message.usage.cache_read_input_tokens;
      console.log(`缓存读取 Token: ${cacheReadTokens}`);
    }
  }
}

按模型统计用量

如果你在一个 Agent 会话中使用了多个模型(比如简单任务用 Haiku,复杂任务用 Opus),modelUsage 字段可以帮你分别统计:

for await (const message of query({
  prompt: "分析并重构这段代码",
  options: {
    allowedTools: ["Read", "Edit", "Bash"]
  }
})) {
  if (message.type === "result" && message.modelUsage) {
    // modelUsage 是一个 Map,key 是模型名称
    for (const [model, usage] of Object.entries(message.modelUsage)) {
      console.log(`模型: ${model}`);
      console.log(`  输入 Token: ${usage.inputTokens}`);
      console.log(`  输出 Token: ${usage.outputTokens}`);
      console.log(`  缓存创建: ${usage.cacheCreationInputTokens}`);
      console.log(`  缓存读取: ${usage.cacheReadInputTokens}`);
    }
  }
}

预算限制

设定预算上限,超出就停止,防止失控:

class BudgetGuard {
  private spent: number = 0;
  private budgetUsd: number;

  constructor(budgetUsd: number) {
    this.budgetUsd = budgetUsd;
  }

  check(cost: number): void {
    this.spent += cost;
    const percentage = (this.spent / this.budgetUsd) * 100;

    if (percentage >= 100) {
      throw new Error(
        `预算超限!已花费 $${this.spent.toFixed(4)},` +
        `预算 $${this.budgetUsd.toFixed(2)}`
      );
    }

    if (percentage >= 80) {
      console.warn(
        `[预算警告] 已使用 ${percentage.toFixed(1)}% 的预算 ` +
        `($${this.spent.toFixed(4)} / $${this.budgetUsd.toFixed(2)})`
      );
    }
  }

  getRemaining(): number {
    return this.budgetUsd - this.spent;
  }
}

// 使用
const budget = new BudgetGuard(1.00);  // 每次任务预算 1 美元

for await (const message of query({ prompt: "..." })) {
  if (message.type === "result" && message.total_cost_usd !== undefined) {
    budget.check(message.total_cost_usd);
  }
}

选择合适的模型

不是所有任务都需要用最强的模型。用对模型能省很多钱:

任务类型 推荐模型 价格(相对) 说明
简单文本分类 Haiku $ 便宜又快
代码生成 Sonnet $$ 性价比最高
复杂推理、架构设计 Opus $$$$ 最强但最贵

一个常见的策略是 分级处理

// 简单任务用便宜模型
async function classifyTask(input: string): Promise<string> {
  // Haiku 就够了
  for await (const msg of query({
    prompt: `请将以下任务分类为 simple/medium/complex:\n${input}`,
    options: { model: "claude-haiku-4" }
  })) {
    if (msg.type === "result") return msg.result;
  }
  return "medium";
}

// 根据分类结果选模型
async function handleTask(input: string) {
  const complexity = await classifyTask(input);

  const modelMap: Record<string, string> = {
    simple: "claude-haiku-4",
    medium: "claude-sonnet-4",
    complex: "claude-opus-4",
  };

  for await (const msg of query({
    prompt: input,
    options: { model: modelMap[complexity] }
  })) {
    // 处理结果
  }
}

Prompt Caching 省钱

如果你的 Agent 会反复用到相同的上下文(比如系统提示词、项目描述),开启 Prompt Caching 可以大幅降低成本。

缓存的核心概念:

也就是说,如果同样的内容被用了 10 次:

省了将近 80%!

// 监控缓存效率
function logCacheEfficiency(usage: any) {
  const cacheCreation = usage.cache_creation_input_tokens || 0;
  const cacheRead = usage.cache_read_input_tokens || 0;
  const normalInput = usage.input_tokens || 0;

  const totalInput = normalInput + cacheCreation + cacheRead;
  if (totalInput === 0) return;

  const cacheHitRate = (cacheRead / totalInput) * 100;
  console.log(`缓存命中率: ${cacheHitRate.toFixed(1)}%`);
  console.log(`正常输入: ${normalInput}, 缓存创建: ${cacheCreation}, 缓存读取: ${cacheRead}`);

  if (cacheHitRate < 30) {
    console.warn("缓存命中率偏低,考虑优化 Prompt 结构以提高缓存利用率");
  }
}

16.5 部署架构方案

根据你的业务场景,有四种常见的 Agent 部署架构。每种有不同的特点和适用场景。

方案一:短命容器(Ephemeral)

核心思路:每个任务启动一个新容器,任务完成后容器销毁。

用户请求 → 启动新容器 → Agent 执行任务 → 返回结果 → 销毁容器

优点

缺点

适合场景:Bug 修复、一次性代码分析、文件处理

import { execSync } from "child_process";
import { query } from "@anthropic-ai/claude-code";
import express from "express";

const app = express();
app.use(express.json());

app.post("/api/analyze", async (req, res) => {
  const { projectPath, task } = req.body;
  const taskId = `task-${Date.now()}`;

  try {
    // 启动新容器执行任务
    const result = await runInContainer(taskId, projectPath, task);
    res.json({ success: true, result });
  } catch (error) {
    res.status(500).json({ success: false, error: String(error) });
  }
});

async function runInContainer(
  taskId: string,
  projectPath: string,
  task: string
): Promise<string> {
  // 每次创建新容器
  const containerName = `agent-${taskId}`;

  try {
    // 用 Docker 启动容器
    execSync(`docker run -d \
      --name ${containerName} \
      --cpus="1.0" \
      --memory="1g" \
      --read-only \
      --tmpfs /tmp:size=100M \
      -v ${projectPath}:/workspace:ro \
      -e ANTHROPIC_API_KEY \
      agent-image`);

    // 在容器中执行 Agent 任务
    let output = "";
    for await (const message of query({
      prompt: task,
      options: {
        allowedTools: ["Read", "Glob", "Grep"],
        cwd: "/workspace"
      }
    })) {
      if (message.type === "result") {
        output = message.result;
      }
    }

    return output;
  } finally {
    // 任务完成后,无论成功失败都销毁容器
    execSync(`docker rm -f ${containerName}`);
  }
}

app.listen(3000, () => {
  console.log("Agent 服务启动在端口 3000");
});

方案二:长命容器(Long-running)

核心思路:容器一直运行,接收并处理多个请求。

容器启动 → 等待请求 → 处理请求1 → 处理请求2 → ... → 一直运行

优点

缺点

适合场景:聊天机器人、持续监控、交互式 Agent

import { query, type ConversationMessage } from "@anthropic-ai/claude-code";
import express from "express";

const app = express();
app.use(express.json());

// 存储每个用户的对话历史
const sessions = new Map<string, ConversationMessage[]>();

app.post("/api/chat", async (req, res) => {
  const { sessionId, message } = req.body;

  // 获取或创建对话历史
  if (!sessions.has(sessionId)) {
    sessions.set(sessionId, []);
  }
  const history = sessions.get(sessionId)!;

  try {
    let result = "";
    const newMessages: ConversationMessage[] = [];

    for await (const msg of query({
      prompt: message,
      options: {
        allowedTools: ["Read", "Glob", "Grep"],
        // 传入历史对话,保持上下文
        conversationHistory: history
      }
    })) {
      if (msg.type === "assistant") {
        newMessages.push(msg.message);
      }
      if (msg.type === "result") {
        result = msg.result;
      }
    }

    // 更新对话历史
    history.push(
      { role: "user", content: message },
      ...newMessages
    );

    // 防止对话历史无限增长(保留最近 50 条)
    if (history.length > 50) {
      sessions.set(sessionId, history.slice(-50));
    }

    res.json({ success: true, result });
  } catch (error) {
    res.status(500).json({ success: false, error: String(error) });
  }
});

// 定期清理过期的 session
setInterval(() => {
  const maxAge = 30 * 60 * 1000;  // 30 分钟
  // 实际项目中应该记录每个 session 的最后活动时间
  console.log(`当前活跃 session 数: ${sessions.size}`);
}, 60 * 1000);

app.listen(3000, () => {
  console.log("长运行 Agent 服务启动在端口 3000");
});

方案三:混合模式(Hybrid)

核心思路:用短命容器,但每次启动时注入历史上下文。兼顾安全和上下文连续性。

存储历史 ← → 启动新容器(注入历史) → Agent 执行 → 保存新历史 → 销毁容器

适合场景:长期项目的多次迭代、研究类任务

import { query, type ConversationMessage } from "@anthropic-ai/claude-code";

// 历史上下文存储(实际项目中用 Redis 或数据库)
const historyStore = new Map<string, ConversationMessage[]>();

async function runHybridTask(
  projectId: string,
  task: string
): Promise<string> {
  // 1. 加载历史上下文
  const history = historyStore.get(projectId) || [];

  // 2. 在新容器中执行(带历史上下文)
  let result = "";
  const newMessages: ConversationMessage[] = [];

  for await (const msg of query({
    prompt: task,
    options: {
      allowedTools: ["Read", "Glob", "Grep", "Edit"],
      conversationHistory: history,
      systemPrompt: `你正在处理项目 ${projectId}。` +
        `这是你之前处理过这个项目的第 ${history.length + 1} 次交互。`
    }
  })) {
    if (msg.type === "assistant") {
      newMessages.push(msg.message);
    }
    if (msg.type === "result") {
      result = msg.result;
    }
  }

  // 3. 保存新的历史
  history.push(
    { role: "user", content: task },
    ...newMessages
  );
  historyStore.set(projectId, history);

  return result;
}

方案四:单容器多 Agent

核心思路:一个容器内运行多个 Agent 实例,Agent 之间可以协作。

┌──────────────── 容器 ────────────────┐
│                                       │
│  Agent A (架构师) ──→ Agent B (程序员)│
│       ↑                    │          │
│       └── Agent C (测试员) ←┘          │
│                                       │
└───────────────────────────────────────┘

适合场景:多角色协作、代码审查流水线、模拟讨论

import { query } from "@anthropic-ai/claude-code";

interface AgentConfig {
  name: string;
  role: string;
  systemPrompt: string;
  allowedTools: string[];
}

async function runAgent(config: AgentConfig, task: string): Promise<string> {
  let result = "";
  for await (const msg of query({
    prompt: task,
    options: {
      systemPrompt: `你是 ${config.name},角色是 ${config.role}。\n${config.systemPrompt}`,
      allowedTools: config.allowedTools
    }
  })) {
    if (msg.type === "result") {
      result = msg.result;
    }
  }
  return result;
}

// 定义多个 Agent
const agents: AgentConfig[] = [
  {
    name: "架构师",
    role: "系统架构设计",
    systemPrompt: "你负责审查代码架构,关注可维护性、扩展性和设计模式。",
    allowedTools: ["Read", "Glob", "Grep"]
  },
  {
    name: "安全专家",
    role: "安全审计",
    systemPrompt: "你负责发现安全漏洞,关注注入攻击、权限问题、数据泄露等。",
    allowedTools: ["Read", "Glob", "Grep"]
  },
  {
    name: "性能工程师",
    role: "性能优化",
    systemPrompt: "你负责发现性能问题,关注时间复杂度、内存使用、数据库查询等。",
    allowedTools: ["Read", "Glob", "Grep"]
  }
];

// 多 Agent 协作审查
async function multiAgentReview(projectPath: string) {
  const task = `请审查 ${projectPath} 目录下的代码,给出你的专业意见。`;

  // 并行执行所有 Agent
  const results = await Promise.all(
    agents.map(agent => runAgent(agent, task))
  );

  // 汇总所有 Agent 的结果
  const summary = results.map((result, i) =>
    `## ${agents[i].name}(${agents[i].role})的意见:\n${result}`
  ).join("\n\n---\n\n");

  console.log(summary);
  return summary;
}

四种方案对比

特性 短命容器 长命容器 混合模式 多 Agent
安全性 最高 中等 中等
冷启动 有延迟 有延迟
上下文保持 不保持 自然保持 通过注入保持 各自独立
资源消耗 按需 持续 按需 较高
实现复杂度
适合场景 一次性任务 交互服务 长期项目 协作任务

16.6 云平台部署指南

如果你不想自己管服务器,可以用现成的云平台。下面介绍几个适合部署 Agent 的平台。

Modal Sandbox

特点:专为 AI 工作负载设计,冷启动极快(亚秒级)。

适合:短命容器模式的 Agent。

import modal

app = modal.App("claude-agent")

# 定义容器镜像
image = modal.Image.debian_slim().pip_install(
    "claude-code-sdk",
    "anthropic"
)

@app.function(
    image=image,
    secrets=[modal.Secret.from_name("anthropic-key")],
    timeout=300,       # 5 分钟超时
    memory=1024,       # 1GB 内存
    cpu=1.0,           # 1 个 CPU
)
async def run_agent(task: str) -> str:
    from claude_code_sdk import query, ClaudeCodeOptions

    result = ""
    async for msg in query(
        prompt=task,
        options=ClaudeCodeOptions(
            allowed_tools=["Read", "Glob", "Grep"]
        )
    ):
        if msg.type == "result":
            result = msg.result

    return result

# 部署后通过 HTTP 调用
@app.function(image=image)
@modal.web_endpoint(method="POST")
async def api(body: dict):
    result = await run_agent.remote(body["task"])
    return {"result": result}

Fly Machines

特点:全球边缘部署,容器可以休眠(不用时不收费),启动快。

适合:长命容器模式,但需要节省成本的场景。

# fly.toml
app = "my-claude-agent"
primary_region = "nrt"  # 东京,离中国最近的节点

[build]
  dockerfile = "Dockerfile"

[env]
  NODE_ENV = "production"
  LOG_LEVEL = "info"

[http_service]
  internal_port = 3000
  force_https = true
  auto_stop_machines = true    # 空闲时自动休眠
  auto_start_machines = true   # 有请求时自动启动
  min_machines_running = 0     # 允许缩到 0

[[vm]]
  cpu_kind = "shared"
  cpus = 2
  memory_mb = 2048

E2B

特点:专门为 AI Agent 设计的云沙箱,有完整的文件系统和代码执行环境。

适合:需要 Agent 安全执行代码的场景。

其他平台

平台 特点 适合场景
Cloudflare Workers 边缘计算,冷启动极快 轻量级 Agent
Vercel 前端友好,集成好 Agent 驱动的 Web 应用
Daytona 专注开发环境 代码开发 Agent
Railway 部署简单 快速原型

云平台选择对比

考量因素 Modal Fly.io E2B Cloudflare
冷启动速度 极快 中等 极快
价格 按用量 按用量 + 基础费 按用量 按用量
容器隔离 很好 N/A (V8隔离)
文件系统 临时 持久 临时
GPU 支持
最大运行时间 24小时 无限 24小时 30秒(免费)/15分钟
适合的 Agent 类型 短命 长命 短命 轻量

16.7 监控和告警

Agent 部署上线后,你不能就撒手不管了。你需要持续监控它的运行状态,一旦出问题能第一时间发现。

健康检查

最基本的监控:服务还活着吗?

import express from "express";

const app = express();

// 简单的健康检查端点
app.get("/health", (req, res) => {
  res.json({
    status: "healthy",
    uptime: process.uptime(),
    memoryUsage: process.memoryUsage(),
    timestamp: new Date().toISOString()
  });
});

// 更详细的就绪检查
app.get("/ready", async (req, res) => {
  const checks = {
    memory: process.memoryUsage().heapUsed < 1.5 * 1024 * 1024 * 1024,  // < 1.5GB
    uptime: process.uptime() > 5,  // 启动超过 5 秒
  };

  const allHealthy = Object.values(checks).every(Boolean);

  res.status(allHealthy ? 200 : 503).json({
    status: allHealthy ? "ready" : "not_ready",
    checks
  });
});

核心指标监控

一个生产级别的 Agent 服务应该监控这些指标:

// 简易版监控指标收集器
class AgentMetrics {
  private metrics = {
    totalRequests: 0,
    successfulRequests: 0,
    failedRequests: 0,
    totalTokensUsed: 0,
    totalCostUsd: 0,
    avgResponseTimeMs: 0,
    activeRequests: 0,
    responseTimes: [] as number[],
  };

  recordRequest(startTime: number, success: boolean, tokens: number, cost: number) {
    const duration = Date.now() - startTime;
    this.metrics.totalRequests++;
    if (success) {
      this.metrics.successfulRequests++;
    } else {
      this.metrics.failedRequests++;
    }
    this.metrics.totalTokensUsed += tokens;
    this.metrics.totalCostUsd += cost;

    // 滑动窗口计算平均响应时间
    this.metrics.responseTimes.push(duration);
    if (this.metrics.responseTimes.length > 100) {
      this.metrics.responseTimes.shift();
    }
    this.metrics.avgResponseTimeMs =
      this.metrics.responseTimes.reduce((a, b) => a + b, 0) /
      this.metrics.responseTimes.length;
  }

  getMetrics() {
    const errorRate = this.metrics.totalRequests > 0
      ? (this.metrics.failedRequests / this.metrics.totalRequests) * 100
      : 0;

    return {
      ...this.metrics,
      errorRate: `${errorRate.toFixed(2)}%`,
    };
  }
}

const metrics = new AgentMetrics();

// 暴露 Prometheus 格式的指标(配合 Grafana 使用)
app.get("/metrics", (req, res) => {
  const m = metrics.getMetrics();
  res.type("text/plain").send(`
# HELP agent_requests_total Total number of agent requests
# TYPE agent_requests_total counter
agent_requests_total{status="success"} ${m.successfulRequests}
agent_requests_total{status="failure"} ${m.failedRequests}

# HELP agent_tokens_total Total tokens consumed
# TYPE agent_tokens_total counter
agent_tokens_total ${m.totalTokensUsed}

# HELP agent_cost_usd_total Total cost in USD
# TYPE agent_cost_usd_total counter
agent_cost_usd_total ${m.totalCostUsd}

# HELP agent_response_time_ms Average response time in ms
# TYPE agent_response_time_ms gauge
agent_response_time_ms ${m.avgResponseTimeMs}

# HELP agent_error_rate Error rate percentage
# TYPE agent_error_rate gauge
agent_error_rate ${parseFloat(m.errorRate)}
  `.trim());
});

告警规则

当指标超过阈值时,应该自动告警。以下是常见的告警场景:

interface AlertRule {
  name: string;
  condition: (metrics: any) => boolean;
  message: string;
  severity: "warning" | "critical";
}

const alertRules: AlertRule[] = [
  {
    name: "高错误率",
    condition: (m) => parseFloat(m.errorRate) > 10,
    message: "Agent 错误率超过 10%,请检查服务状态",
    severity: "critical"
  },
  {
    name: "响应时间过长",
    condition: (m) => m.avgResponseTimeMs > 60000,
    message: "Agent 平均响应时间超过 60 秒",
    severity: "warning"
  },
  {
    name: "每日成本超标",
    condition: (m) => m.totalCostUsd > 50,
    message: "今日 API 花费已超过 $50",
    severity: "critical"
  },
  {
    name: "Token 消耗异常",
    condition: (m) => m.totalTokensUsed > 1000000,
    message: "今日 Token 消耗超过 100 万",
    severity: "warning"
  }
];

// 定期检查告警规则
function checkAlerts(metrics: any) {
  for (const rule of alertRules) {
    if (rule.condition(metrics)) {
      sendAlert(rule);
    }
  }
}

function sendAlert(rule: AlertRule) {
  // 实际项目中替换为你的告警渠道
  // 比如:企业微信、钉钉、Slack、PagerDuty 等
  console.error(`[${rule.severity.toUpperCase()}] ${rule.name}: ${rule.message}`);

  // 示例:发送到 Slack webhook
  // fetch(SLACK_WEBHOOK_URL, {
  //   method: "POST",
  //   body: JSON.stringify({ text: `🚨 ${rule.name}: ${rule.message}` })
  // });
}

// 每分钟检查一次
setInterval(() => {
  checkAlerts(metrics.getMetrics());
}, 60 * 1000);

日志最佳实践

Agent 的日志非常重要,出问题时全靠它来排查:

// 结构化日志
function log(level: string, event: string, data: Record<string, any>) {
  const entry = {
    timestamp: new Date().toISOString(),
    level,
    event,
    ...data
  };
  console.log(JSON.stringify(entry));
}

// 使用示例
log("info", "agent_request_start", {
  requestId: "req-123",
  task: "代码分析",
  model: "claude-sonnet-4"
});

log("info", "agent_request_complete", {
  requestId: "req-123",
  durationMs: 15000,
  inputTokens: 5000,
  outputTokens: 2000,
  costUsd: 0.035
});

log("error", "agent_request_failed", {
  requestId: "req-124",
  error: "API rate limit exceeded",
  retryAfter: 30
});

关键提醒:日志中 绝对不要记录 API Key、用户密码、敏感数据等信息。


16.8 安全检查清单

在把 Agent 部署到生产环境之前,逐条检查以下清单:

API Key 和密钥

容器安全

网络安全

Agent 权限

成本控制

监控和日志

常见安全错误

以下是新手最容易犯的安全错误:

1. 给 Agent 过多权限

// 错误:给了所有权限
options: {
  allowedTools: ["Read", "Write", "Edit", "Bash", "WebFetch", "WebSearch"]
}

// 正确:只给需要的权限
// 如果任务只是分析代码,根本不需要 Write、Edit、Bash
options: {
  allowedTools: ["Read", "Glob", "Grep"]
}

2. 挂载了整个文件系统

# 错误
volumes:
  - /:/host

# 正确
volumes:
  - ./specific-project:/workspace:ro

3. 没有设置超时

// 错误:没有超时,Agent 可能永远运行下去
for await (const msg of query({ prompt: task })) { ... }

// 正确:设置超时
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5 * 60 * 1000);  // 5 分钟

try {
  for await (const msg of query({
    prompt: task,
    options: { abortController: controller }
  })) { ... }
} finally {
  clearTimeout(timeout);
}

4. 没有限制输入长度

// 错误:用户可以发送超长的 prompt,消耗大量 Token
app.post("/api/agent", (req, res) => {
  const task = req.body.task;  // 没有限制!
  // ...
});

// 正确:限制输入长度
app.post("/api/agent", (req, res) => {
  const task = req.body.task;
  if (!task || task.length > 10000) {
    return res.status(400).json({ error: "任务描述不能超过 10000 字符" });
  }
  // ...
});

Prompt 注入防护

Prompt 注入是 Agent 面临的一个特殊安全威胁。恶意用户可能通过精心构造的输入,让 Agent 做出非预期的行为。

// 简单的输入清洗
function sanitizeInput(input: string): string {
  // 移除可能的指令注入
  const suspicious = [
    "ignore previous instructions",
    "ignore all instructions",
    "system prompt",
    "你的系统提示词是",
    "忽略之前的指令"
  ];

  let cleaned = input;
  for (const pattern of suspicious) {
    if (cleaned.toLowerCase().includes(pattern.toLowerCase())) {
      console.warn(`[安全警告] 检测到可疑输入: "${pattern}"`);
      // 你可以选择拒绝或清洗
      // cleaned = cleaned.replace(new RegExp(pattern, "gi"), "[已过滤]");
    }
  }

  return cleaned;
}

不过要注意,Prompt 注入没有完美的解决方案。最根本的防护还是 限制 Agent 的权限(第一层防护)和 容器隔离(第二层防护)—— 即使 Agent 被注入了恶意指令,它也做不了权限之外的事情。


动手练习

练习1:用 Docker 部署一个安全的 Agent 服务

目标:把前面学到的安全知识用起来,部署一个可以对外提供服务的 Agent API。

步骤

  1. 创建项目结构:
secure-agent/
├── Dockerfile
├── docker-compose.yml
├── package.json
├── secrets/
│   └── .gitkeep
├── src/
│   └── server.ts
└── .gitignore
  1. 编写 package.json
{
  "name": "secure-agent",
  "version": "1.0.0",
  "scripts": {
    "start": "node dist/server.js",
    "build": "tsc"
  },
  "dependencies": {
    "@anthropic-ai/claude-code": "latest",
    "express": "^4.18.0"
  },
  "devDependencies": {
    "@types/express": "^4.17.0",
    "typescript": "^5.0.0"
  }
}
  1. 编写 src/server.ts
import express from "express";
import { query } from "@anthropic-ai/claude-code";
import { readFileSync } from "fs";

const app = express();
app.use(express.json({ limit: "10kb" }));  // 限制请求体大小

// 从 Docker Secret 或环境变量读取 API Key
function getApiKey(): string {
  try {
    return readFileSync("/run/secrets/anthropic_api_key", "utf-8").trim();
  } catch {
    const key = process.env.ANTHROPIC_API_KEY;
    if (!key) throw new Error("API Key 未配置");
    return key;
  }
}

// 简单的 API 认证(生产环境请用更完善的方案)
function authenticate(req: express.Request): boolean {
  const token = req.headers.authorization?.replace("Bearer ", "");
  return token === process.env.SERVICE_TOKEN;
}

// 健康检查
app.get("/health", (req, res) => {
  res.json({ status: "healthy", timestamp: new Date().toISOString() });
});

// Agent API
app.post("/api/analyze", async (req, res) => {
  // 认证
  if (!authenticate(req)) {
    return res.status(401).json({ error: "未授权" });
  }

  const { task } = req.body;

  // 输入验证
  if (!task || typeof task !== "string" || task.length > 5000) {
    return res.status(400).json({ error: "无效的任务描述" });
  }

  // 超时控制
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 3 * 60 * 1000);

  try {
    let result = "";
    let cost = 0;

    for await (const msg of query({
      prompt: task,
      options: {
        allowedTools: ["Read", "Glob", "Grep"],  // 只读权限
        abortController: controller
      }
    })) {
      if (msg.type === "result") {
        result = msg.result;
        cost = msg.total_cost_usd || 0;
      }
    }

    res.json({ success: true, result, cost });
  } catch (error: any) {
    if (error.name === "AbortError") {
      res.status(408).json({ error: "请求超时" });
    } else {
      console.error("Agent 执行错误:", error.message);
      res.status(500).json({ error: "内部错误" });
    }
  } finally {
    clearTimeout(timeout);
  }
});

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`安全 Agent 服务启动在端口 ${port}`);
});
  1. 编写 .gitignore
node_modules/
dist/
.env
secrets/*.txt
  1. 测试运行:
# 构建并启动
docker-compose up --build -d

# 测试健康检查
curl http://localhost:3000/health

# 测试 Agent API
curl -X POST http://localhost:3000/api/analyze \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-service-token" \
  -d '{"task": "分析当前目录的代码结构"}'

# 查看日志
docker-compose logs -f

# 停止服务
docker-compose down

练习2:实现成本监控和预算告警

目标:在 Agent 服务中加入成本追踪功能,超出预算自动停止。

import { query } from "@anthropic-ai/claude-code";

class CostMonitor {
  private dailyCost = 0;
  private monthlyCost = 0;
  private dailyLimit: number;
  private monthlyLimit: number;
  private lastResetDay: number;
  private lastResetMonth: number;

  constructor(dailyLimit: number, monthlyLimit: number) {
    this.dailyLimit = dailyLimit;
    this.monthlyLimit = monthlyLimit;
    this.lastResetDay = new Date().getDate();
    this.lastResetMonth = new Date().getMonth();
  }

  private maybeReset() {
    const now = new Date();
    if (now.getDate() !== this.lastResetDay) {
      this.dailyCost = 0;
      this.lastResetDay = now.getDate();
    }
    if (now.getMonth() !== this.lastResetMonth) {
      this.monthlyCost = 0;
      this.lastResetMonth = now.getMonth();
    }
  }

  canProceed(): { allowed: boolean; reason?: string } {
    this.maybeReset();

    if (this.dailyCost >= this.dailyLimit) {
      return { allowed: false, reason: `日预算已用完($${this.dailyCost.toFixed(2)} / $${this.dailyLimit})` };
    }
    if (this.monthlyCost >= this.monthlyLimit) {
      return { allowed: false, reason: `月预算已用完($${this.monthlyCost.toFixed(2)} / $${this.monthlyLimit})` };
    }
    return { allowed: true };
  }

  recordCost(cost: number) {
    this.dailyCost += cost;
    this.monthlyCost += cost;

    // 80% 警告
    if (this.dailyCost >= this.dailyLimit * 0.8) {
      console.warn(`[成本警告] 日消费已达 $${this.dailyCost.toFixed(2)},接近日限额 $${this.dailyLimit}`);
    }
  }

  getReport(): string {
    return [
      `日消费: $${this.dailyCost.toFixed(4)} / $${this.dailyLimit}`,
      `月消费: $${this.monthlyCost.toFixed(4)} / $${this.monthlyLimit}`,
    ].join("\n");
  }
}

// 使用示例
const monitor = new CostMonitor(10, 200);  // 日限 $10,月限 $200

async function safeAgentCall(task: string): Promise<string> {
  const check = monitor.canProceed();
  if (!check.allowed) {
    throw new Error(`预算限制: ${check.reason}`);
  }

  let result = "";
  for await (const msg of query({ prompt: task })) {
    if (msg.type === "result") {
      result = msg.result;
      if (msg.total_cost_usd) {
        monitor.recordCost(msg.total_cost_usd);
      }
    }
  }

  console.log(monitor.getReport());
  return result;
}

验证:多次调用 safeAgentCall,观察成本累计,验证预算限制是否生效。

练习3:构建完整的 Agent 后端 API

目标:用 Express.js 构建一个完整的 Agent 后端,包含认证、限流、监控。

import express from "express";
import rateLimit from "express-rate-limit";
import { query } from "@anthropic-ai/claude-code";

const app = express();
app.use(express.json({ limit: "10kb" }));

// ---- 限流:防止滥用 ----
const limiter = rateLimit({
  windowMs: 60 * 1000,  // 1 分钟
  max: 10,              // 最多 10 个请求
  message: { error: "请求过于频繁,请稍后再试" }
});
app.use("/api/", limiter);

// ---- 简易指标收集 ----
let totalRequests = 0;
let totalCost = 0;
let totalErrors = 0;

// ---- 路由 ----

// 健康检查
app.get("/health", (req, res) => {
  res.json({ status: "ok" });
});

// 指标
app.get("/metrics", (req, res) => {
  res.json({
    totalRequests,
    totalCost: `$${totalCost.toFixed(4)}`,
    totalErrors,
    errorRate: totalRequests > 0
      ? `${((totalErrors / totalRequests) * 100).toFixed(2)}%`
      : "0%"
  });
});

// Agent 接口:代码分析(只读)
app.post("/api/analyze", async (req, res) => {
  totalRequests++;
  const { task, project } = req.body;

  if (!task) {
    totalErrors++;
    return res.status(400).json({ error: "缺少 task 参数" });
  }

  try {
    let result = "";
    for await (const msg of query({
      prompt: task,
      options: {
        allowedTools: ["Read", "Glob", "Grep"],
        cwd: project || "/workspace"
      }
    })) {
      if (msg.type === "result") {
        result = msg.result;
        totalCost += msg.total_cost_usd || 0;
      }
    }
    res.json({ success: true, result });
  } catch (error: any) {
    totalErrors++;
    res.status(500).json({ error: error.message });
  }
});

// Agent 接口:代码修改(需要更高权限)
app.post("/api/edit", async (req, res) => {
  totalRequests++;
  const { task, project } = req.body;

  // 这个接口需要额外的认证
  const adminToken = req.headers["x-admin-token"];
  if (adminToken !== process.env.ADMIN_TOKEN) {
    totalErrors++;
    return res.status(403).json({ error: "需要管理员权限" });
  }

  if (!task) {
    totalErrors++;
    return res.status(400).json({ error: "缺少 task 参数" });
  }

  try {
    let result = "";
    for await (const msg of query({
      prompt: task,
      options: {
        allowedTools: ["Read", "Glob", "Grep", "Edit", "Write"],
        cwd: project || "/workspace"
      }
    })) {
      if (msg.type === "result") {
        result = msg.result;
        totalCost += msg.total_cost_usd || 0;
      }
    }
    res.json({ success: true, result });
  } catch (error: any) {
    totalErrors++;
    res.status(500).json({ error: error.message });
  }
});

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`Agent API 服务启动: http://localhost:${port}`);
  console.log(`健康检查: http://localhost:${port}/health`);
  console.log(`监控面板: http://localhost:${port}/metrics`);
});

验证

# 代码分析(只读,普通权限)
curl -X POST http://localhost:3000/api/analyze \
  -H "Content-Type: application/json" \
  -d '{"task": "分析项目结构"}'

# 代码修改(读写,需要管理员权限)
curl -X POST http://localhost:3000/api/edit \
  -H "Content-Type: application/json" \
  -H "X-Admin-Token: your-admin-token" \
  -d '{"task": "给所有函数添加 JSDoc 注释"}'

# 查看监控指标
curl http://localhost:3000/metrics

# 测试限流(快速发送超过 10 个请求)
for i in {1..15}; do
  curl -s -o /dev/null -w "%{http_code}\n" \
    -X POST http://localhost:3000/api/analyze \
    -H "Content-Type: application/json" \
    -d '{"task": "hello"}'
done
# 前 10 个返回 200,后 5 个返回 429(Too Many Requests)

本章小结

这一章我们学了 Agent 安全部署的方方面面。回顾一下关键知识点:

  1. 三层防护:权限控制(应用层)、容器隔离(系统层)、网络限制(网络层)。三层叠加使用,缺一不可。

  2. 容器化部署:用 Docker 把 Agent 关在沙箱里,非 root 用户运行,限制资源,只读文件系统,最小权限挂载。

  3. API Key 管理:永远不要硬编码,用环境变量或 Docker Secrets 注入,定期轮换,监控泄露。

  4. 成本控制:用 SDK 的 usagetotal_cost_usd 追踪消耗,设置预算上限,选择合适的模型,善用 Prompt Caching。

  5. 四种部署架构

    • 短命容器:一次性任务,最安全
    • 长命容器:交互式服务,最方便
    • 混合模式:兼顾安全和上下文
    • 多 Agent:协作任务
  6. 云平台选择:Modal 适合短命任务,Fly.io 适合长运行服务,E2B 适合代码执行沙箱。

  7. 监控告警:健康检查、错误率、Token 消耗、成本 —— 这些指标都要监控,超标要告警。

  8. 安全检查清单:部署前逐条对照检查,别抱侥幸心理。

一句话总结:安全部署的核心思想就是"最小权限" —— Agent 只给它需要的权限、需要的资源、需要的网络访问,多余的一律不给。


下一章预告

下一章我们将学习 Agent 的测试和调试 —— 你已经知道怎么安全地部署 Agent 了,但 Agent 的行为有不确定性,怎么给它写测试?出了 Bug 怎么调试?下一章会手把手教你搞定这些问题。

← 上一章15. Prompt 工程