第16章:安全部署 —— 让 Agent 在生产环境安全运行
一句话:掌握 Agent 的安全最佳实践,从玩具变成正式产品。
本章目标
- 理解 Agent 安全的三层防护体系
- 学会用 Docker 容器化部署 Agent 服务
- 掌握 API Key 的安全管理方法
- 学会监控和控制 Token 消耗成本
- 了解四种部署架构方案的适用场景
- 了解主流云平台的部署选择
- 掌握生产环境的监控和告警策略
- 拿到一份可以直接用的安全检查清单
前置知识
- 需要先看完第6章(权限控制)
- 需要先看完第4章(query() 函数基础用法)
- 了解 Docker 基本概念(不了解也没关系,本章会从头讲)
- 了解基本的 HTTP / 网络概念
16.1 Agent 安全的三层防护
为什么 Agent 的安全这么重要?
在前面的章节中,我们已经学会了怎么创建 Agent、怎么给它工具、怎么让它干活。在自己的电脑上跑跑测试,一切看起来都很美好。
但是,一旦你要把 Agent 部署到生产环境 —— 比如做成一个 SaaS 服务让用户使用,或者在公司服务器上长期运行 —— 安全问题就变得极其关键。
为什么?因为 Agent 和普通程序不一样。普通程序的行为是你写死的,它只会按你写好的代码执行。但 Agent 的行为是 由 LLM 决定的,LLM 的输出有不确定性。更可怕的是,如果 Agent 能使用 Bash、文件读写等工具,一旦它"做错事",后果可能比普通 Bug 严重得多。
想象几个恐怖场景:
- Agent 被恶意 prompt 注入,执行了
rm -rf / - Agent 把你的 API Key 输出到了日志里,被人爬走了
- Agent 在循环中疯狂调用 API,一晚上烧掉了你半年的预算
- Agent 读到了敏感的用户数据,并把它发送到了外部 URL
所以,安全部署不是"锦上添花",而是 必须做的事。
三层防护模型
我们把 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 # 禁止访问外部网络
这就像公司的防火墙 —— 只允许访问业务需要的网站,其他一律拦截。
三层防护的协作
三层防护不是"选一个用",而是叠加使用的。就像你的手机:
- 第一层:App 的权限管理(相机、麦克风权限要单独授权)
- 第二层:App 运行在沙箱里(一个 App 崩溃了不影响其他 App)
- 第三层:网络防火墙(恶意流量被运营商拦截)
三层都做到,你的 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 比环境变量更安全,因为:
- 环境变量可能出现在
docker inspect的输出中 - 环境变量可能被子进程继承
- 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 真的泄露了怎么办?你需要能 尽快发现。
- 启用 Anthropic Console 的用量告警:如果突然出现异常用量,可能是 Key 被盗用了
- 监控 GitHub:用 GitGuardian 或 GitHub 自带的 Secret Scanning 来检测代码库中的泄露
- 立即轮换:发现泄露后,第一时间在 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 可以大幅降低成本。
缓存的核心概念:
cache_creation_input_tokens:第一次创建缓存时消耗的 Token,价格比正常输入 Token 贵 25%cache_read_input_tokens:后续从缓存读取的 Token,价格只有正常输入 Token 的 10%
也就是说,如果同样的内容被用了 10 次:
- 不缓存:10 次 x 全价 = 10 份钱
- 缓存:1 次 x 1.25 倍价格 + 9 次 x 0.1 倍价格 = 2.15 份钱
省了将近 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 和密钥
- API Key 没有硬编码在代码中
- API Key 通过环境变量或 Secrets Manager 注入
-
.env文件已添加到.gitignore - Git 历史中没有泄露过 API Key(如果有,已经轮换过)
- 已启用 API Key 的用量监控
- 已设置 Key 轮换计划(建议每 90 天)
容器安全
- 容器以非 root 用户运行
- 使用最小化的基础镜像(slim / alpine)
- 设置了 CPU 和内存限制
- 根文件系统设置为只读(
read_only: true) - 移除了不必要的 Linux capabilities(
cap_drop: ALL) - 禁止了特权提升(
no-new-privileges) - 只挂载了必要的目录,且尽量用只读挂载
网络安全
- Agent 容器不暴露不必要的端口
- 出站流量已限制(只允许访问 API 等必要地址)
- 使用了 HTTPS(不使用明文 HTTP)
- API 接口有认证(不是裸奔的)
Agent 权限
-
allowedTools只包含必要的工具 - 如果 Agent 不需要网络工具,就不给
WebFetch和WebSearch - 如果 Agent 不需要修改文件,就不给
Write和Edit - 如果 Agent 必须使用
Bash,限制了可执行的命令范围
成本控制
- 设置了每个请求的预算上限
- 设置了每日 / 每月的总预算上限
- 有 Token 消耗的实时监控
- 有成本超标的告警机制
- 选择了合适的模型(不是所有任务都用最贵的模型)
监控和日志
- 有健康检查端点
- 有错误率监控
- 有响应时间监控
- 有结构化的日志输出
- 日志中没有敏感信息
- 有告警通知渠道(邮件、Slack、钉钉等)
常见安全错误
以下是新手最容易犯的安全错误:
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。
步骤:
- 创建项目结构:
secure-agent/
├── Dockerfile
├── docker-compose.yml
├── package.json
├── secrets/
│ └── .gitkeep
├── src/
│ └── server.ts
└── .gitignore
- 编写
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"
}
}
- 编写
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}`);
});
- 编写
.gitignore:
node_modules/
dist/
.env
secrets/*.txt
- 测试运行:
# 构建并启动
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 安全部署的方方面面。回顾一下关键知识点:
三层防护:权限控制(应用层)、容器隔离(系统层)、网络限制(网络层)。三层叠加使用,缺一不可。
容器化部署:用 Docker 把 Agent 关在沙箱里,非 root 用户运行,限制资源,只读文件系统,最小权限挂载。
API Key 管理:永远不要硬编码,用环境变量或 Docker Secrets 注入,定期轮换,监控泄露。
成本控制:用 SDK 的
usage和total_cost_usd追踪消耗,设置预算上限,选择合适的模型,善用 Prompt Caching。四种部署架构:
- 短命容器:一次性任务,最安全
- 长命容器:交互式服务,最方便
- 混合模式:兼顾安全和上下文
- 多 Agent:协作任务
云平台选择:Modal 适合短命任务,Fly.io 适合长运行服务,E2B 适合代码执行沙箱。
监控告警:健康检查、错误率、Token 消耗、成本 —— 这些指标都要监控,超标要告警。
安全检查清单:部署前逐条对照检查,别抱侥幸心理。
一句话总结:安全部署的核心思想就是"最小权限" —— Agent 只给它需要的权限、需要的资源、需要的网络访问,多余的一律不给。
下一章预告
下一章我们将学习 Agent 的测试和调试 —— 你已经知道怎么安全地部署 Agent 了,但 Agent 的行为有不确定性,怎么给它写测试?出了 Bug 怎么调试?下一章会手把手教你搞定这些问题。