# auto_put_ad_mini 生产部署指南

## 概述

本文档说明如何将 `auto_put_ad_mini` 部署到海外服务器的 Docker/Kubernetes 环境，实现生产级自动化运行。

## 核心特性

### 已实现功能

✅ **Docker 容器化**
- Dockerfile 基于 Python 3.10 slim
- 多阶段构建，优化镜像大小
- 健康检查支持

✅ **账户白名单机制**
- 环境变量配置白名单账户
- 双重安全检查（决策阶段 + 执行阶段）
- 支持 whitelist.json 配置文件

✅ **海外环境适配**
- 代理配置从环境变量读取
- 时区支持（UTC/Asia/Shanghai）
- 移除硬编码本地代理

✅ **定时调度**
- FastAPI + APScheduler 常驻服务（推荐）
- Kubernetes CronJob 支持（备用）
- HTTP API 手动触发

✅ **监控和日志**
- 健康检查端点 `/health`
- 请求日志中间件
- Prometheus metrics 导出（可选）
- 日志输出到 stdout（Kubernetes 友好）

✅ **安全配置**
- Kubernetes Secret 管理敏感信息
- NetworkPolicy 网络隔离
- 非 root 用户运行
- 资源限制和配额

## 项目结构

```
auto_put_ad_mini/
├── Dockerfile              # Docker 镜像定义
├── .dockerignore          # Docker 构建排除文件
├── docker-compose.yml     # 本地开发环境
├── requirements.txt       # Python 依赖
├── server.py              # FastAPI + APScheduler 服务器（推荐）
├── execute_once.py        # 单次执行入口
├── schedule.sh            # Cron 脚本（备用）
├── whitelist.json         # 白名单账户配置
├── metrics.py             # Prometheus 指标导出
├── utils/
│   └── log_capture.py     # 并发日志捕获
├── k8s/                   # Kubernetes 部署清单
│   ├── README.md          # K8s 部署详细说明
│   ├── namespace.yaml     # 命名空间
│   ├── deployment.yaml    # Deployment + Service
│   ├── configmap.yaml     # 公开配置
│   ├── secret.yaml        # 敏感信息
│   ├── pvc.yaml           # 持久化存储
│   └── network-policy.yaml # 网络策略
└── DEPLOYMENT.md          # 本文档
```

## 快速开始

### 阶段 1：本地测试

#### 方式 A：Docker Compose（开发环境）

```bash
# 1. 进入项目目录
cd /Users/liulidong/project/agent/Agent/examples/auto_put_ad_mini

# 2. 创建 .env 文件（生产统一从仓库根目录模板复制并填入真实值）
cp ../../runtime.env.example .env
vim .env  # 填入实际的 API 密钥和配置

# 3. 构建镜像
docker build -t auto-put-ad-mini:test .

# 4. 启动服务（APScheduler 模式）
docker-compose up -d

# 5. 查看日志
docker-compose logs -f

# 6. 测试健康检查
curl http://localhost:8080/health | jq .

# 7. 手动触发任务
curl -X POST http://localhost:8080/trigger | jq .

# 8. 停止服务
docker-compose down
```

#### 方式 B：单次执行测试

```bash
# 测试单次执行（不启动调度服务）
docker run --rm \
  --env-file .env \
  -e EXECUTION_ENABLED=false \
  -e WHITELIST_ENABLED=true \
  -e WHITELIST_ACCOUNTS=80769799 \
  -v $(pwd)/outputs:/app/outputs \
  auto-put-ad-mini:test \
  python execute_once.py

# 验证输出
ls -lh outputs/reports/
cat outputs/reports/llm_decisions_*.csv | head
```

### 阶段 2：推送镜像到仓库

```bash
# 1. 登录镜像仓库
docker login your-registry.com

# 2. 打标签
docker tag auto-put-ad-mini:test your-registry.com/auto-put-ad-mini:v1.0.0
docker tag auto-put-ad-mini:test your-registry.com/auto-put-ad-mini:latest

# 3. 推送
docker push your-registry.com/auto-put-ad-mini:v1.0.0
docker push your-registry.com/auto-put-ad-mini:latest
```

### 阶段 3：Kubernetes 部署

详细步骤见 [k8s/README.md](k8s/README.md)

**推荐部署方式**：Deployment + APScheduler

```bash
# 1. 创建命名空间
kubectl apply -f k8s/namespace.yaml

# 2. 修改 k8s/secret.yaml，填入真实密钥
vim k8s/secret.yaml

# 3. 部署所有资源
kubectl apply -f k8s/pvc.yaml
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/secret.yaml
kubectl apply -f k8s/deployment.yaml

# 4. 验证部署
kubectl get all -n ad-automation
kubectl logs -f -n ad-automation deployment/auto-put-ad-mini

# 5. 端口转发测试
kubectl port-forward -n ad-automation svc/auto-put-ad-mini 8080:8080

# 6. 测试健康检查
curl http://localhost:8080/health | jq .

# 7. 手动触发任务
curl -X POST http://localhost:8080/trigger | jq .
```

## 配置说明

### 日级 ROI 三日口径

- 正式任务每天北京时间11:00执行；在创建数据库批次和发送消息前，必须确认 `loghubods.opengid_base_data` 已存在精确T-1分区，且小程序投流和公众号投流相关数据均非空。未就绪时禁止退回旧分区。
- 日级 ROI 读取 T-1 至 T-3 三个连续完整数据日，数据深度过滤使用最新业务 SQL 的 `usersharedepth<=1`。
- 正式阈值样本为连续三天每天首层 UV>200、成本>0 且 ROI 有效的小程序创意级和公众号实体；两类渠道独立计算，禁止合并分位点。公众号即转使用自身合格实体等权 P20；小程序按 T-1 实际成本软预算动态反推关停线和实体分位点。
- 小程序广告级从 ODPS 原始数据按广告直接 `COUNT(DISTINCT mid)`，复用小程序三日动态关停线，但不重复进入创意成本预算样本池；低于关停线且广告 age>3 天时生成广告级关停建议。
- 金额、UV 和人数在三日汇总表展示为三日总量/3；ROI 与裂变率使用三日总分子/总分母的加权口径。
- 小程序创意关停以小程序创意级 T-1 实际成本为唯一成本分母，目标占比 5%、软上限 5.5%、绝对上限 6%。三日持续低 ROI 与单日补充规则按 6:4 获得 3%/2%基础成本额度；两组按对应预测总效率 ROI 从低到高取连续前缀，未用额度只在仍满足候选条件的组间软流转，不为凑额度跳过更差创意或关停非候选。
- 三日正式判断拥有严格优先级：只要小程序创意满足连续三日正式样本条件，无论是否被成本预算选中或最终建议是什么，都不再进入单日规则。其余创意只有在最新日首层 UV>200、成本>0、ROI有效且广告 age>3天时进入单日候选池；最新日预测总效率 ROI 必须同时≤0.20且≤候选池实体等权P10才成为单日关停候选。命中候选但未被预算选中时只观察；不再使用历史保护。
- 报表包含小程序创意级、小程序广告级、公众号的三日汇总与每日明细共 6 个主 Sheet；广告级两个 Sheet 默认隐藏，企微暂不进入本版计算和报表。
- 每日明细 Sheet 的 `dt` 位于第一列，整表按日期倒序排列。
- 三日汇总可见 ROI 列为“三日均值ROI”“当日效率ROI”和“预测总效率ROI”：分别表示三日加权实际ROI、最新日实际ROI和三日加权预测ROI，不单列最新日预测ROI；其后依次展示动态“关停线”和“扩量线（P80）”。公众号不参与扩量故扩量线留空；整体三日排名百分位保留为隐藏审计字段，关停线分位点只进入运行摘要和数据库快照，不写入业务 Sheet。
- “动作”可见列改为“建议动作”，其后展示“建议说明”；小程序创意级和广告级关停分别显示“关停创意”“关停广告”，但底层动作不变。创意关停按三日持续低 ROI、单日绝对低 ROI、单日高 UV 低分位三类原因分组，同类按对应 ROI 升序。底层无动作的正式中间区间在报表中显示为“观察”，但不生成可执行动作。昨日成本、关停规则、预算选择状态、实际关停成本占比、阈值样本状态和执行结果保留为隐藏审计字段。
- 当前三日策略以小程序 T-1 成本预算动态线生成低 ROI 关停建议，公众号使用独立实体等权 P20；高 ROI 仍使用合格小程序创意实体等权 P80。所有小程序创意级和广告级关停均要求广告 age>3 天；创意级关停批准后只暂停对应动态创意，广告级关停批准后暂停整个广告。
- 汇总表将三日总 T0 裂变人数 / 三日总首层 UV 的加权比例展示为“日均T0裂变率”。汇总顺序为关停、扩量、中间观察、条件不足观察；第一条扩量行和扩量后第一条观察行顶部均使用粗线分隔。“三日均值ROI”“当日效率ROI”和“预测总效率ROI”均使用深红—黄—绿色阶，绿色代表表现好。普通数值显示两位小数，UV、人数、数量和广告年龄显示整数；两个“裂变系数-总裂变UV/…”比率列固定显示两位小数。“传播裂变系数匹配”保留审计数据但默认隐藏。
- “审批选择”默认隐藏，需取消隐藏后审批；创意级“当前创意状态”只对关停建议只读腾讯状态，显示正常、已停止或读取失败，其他行留空。
- 完整主报表保存后，流程会额外按“小程序投流”渠道的“代理名称”生成一代理一份调控建议工作簿；文件名为 `YYYYMMDD_代理名称_调控建议.xlsx`。代理版只包含小程序创意级和广告级三日汇总，广告级 Sheet 继续隐藏，不包含每日明细。包名、广告age、日均首层UV、建议说明、收入、预测总效率ROI、P20/P80/P30、排名、两个裂变系数、日均T0裂变人数/率、审批、执行状态和幂等键均不会写入代理文件；“评分”使用最新日实际ROI并保留两位小数，建议动作仍按内部策略生成并保留“关停创意/关停广告/扩量/观察”。代理表默认只落本地；开启 `ROI_AGENCY_WEBHOOK_ENABLED` 后，飞书应用先上传在线表，再由 `ROI_AGENCY_WEBHOOKS_JSON` 中精确匹配的代理机器人发送卡片。未配置代理直接跳过，不回退总群；完整 webhook 只能放在真实 `.env` 或密钥系统，数据库和日志只保存哈希指纹。
- 可选内部测试任务每天北京时间15:30执行独立复算，只将“自动化投放”分表发送到“内部”Webhook，不发送完整主表、正式总群或代理群，也不生成可审批腾讯动作。缺少该分表时任务失败并告警。正式任务始终去重；内部测试使用 `ROI_INTERNAL_TEST_DEDUP_ENABLED` 控制，设为`0`时每次显式触发都发送新的测试批次。内部任务无startup入口，重启服务不会自动补发。
- 11:00正式任务或15:30内部测试执行失败时，调度服务通过飞书应用向 `ROI_FAILURE_FEISHU_CHAT_ID` 发送红色告警卡片；未配置时回退 `FEISHU_OPERATOR_CHAT_ID`。告警失败只记日志，不会掩盖原任务失败。

离线复算不发送飞书：

```bash
.venv/bin/python examples/auto_put_ad_mini/run_daily_roi.py \
  --end-date YYYYMMDD \
  --source-revision <稳定修订标识>
```

只有显式增加 `--send-feishu` 才会发布飞书表格；该命令本身不会直接执行腾讯写操作。

### 环境变量

日志由本工程自己的 `logging_setup.py` 初始化，不依赖 `agent/utils/logging.py`。
所有 `INFO` 及以上 Python 日志、`print` 输出和未捕获异常直接输出到控制台，
不再写入本地文件。配置 SLS 凭证后，`INFO/WARNING` 发送到
`auto-put-tecent/info-log`，`ERROR/CRITICAL` 发送到
`auto-put-tecent/error-log`。可使用 `event`、`task`、`stage`、`result`、
`account_id` 和 `duration_ms` 检索执行路径。

```bash
LOG_LEVEL=INFO
SLS_ENDPOINT=cn-hangzhou.log.aliyuncs.com
SLS_ACCESS_KEY_ID=<RAM AccessKey ID>
SLS_ACCESS_KEY_SECRET=<RAM AccessKey Secret>
SLS_PROJECT=auto-put-tecent
SLS_INFO_LOGSTORE=info-log
SLS_ERROR_LOGSTORE=error-log
```

| 变量名 | 说明 | 默认值 | 必需 |
|--------|------|--------|------|
| `WHITELIST_ENABLED` | 启用账户白名单 | true | 否 |
| `WHITELIST_ACCOUNTS` | 白名单账户ID（逗号分隔） | - | 是（启用白名单时） |
| `EXECUTION_ENABLED` | 启用实际执行 | false | 否 |
| `HTTP_PROXY` | HTTP 代理地址 | - | 否 |
| `HTTPS_PROXY` | HTTPS 代理地址 | - | 否 |
| `TZ` | 时区 | UTC | 否 |
| `CRON_SCHEDULE` | 定时表达式 | 0 2 * * * | 否 |
| `RUN_ON_STARTUP` | 启动时立即执行 | false | 否 |
| `PORT` | FastAPI 端口 | 8080 | 否 |
| `FEISHU_APP_ID` | 飞书应用ID | - | 是 |
| `FEISHU_APP_SECRET` | 飞书应用密钥 | - | 是 |
| `TENCENT_AD_ACCOUNT_ID` | 腾讯广告账户ID | - | 是 |
| `ODPS_ACCESS_ID` | ODPS 访问ID | - | 是 |
| `ODPS_ACCESS_SECRET` | ODPS 访问密钥 | - | 是 |
| `OPEN_ROUTER_API_KEY` | OpenRouter API Key | - | 是 |
| `EXTERNAL_RECALL_SOURCE_LABEL` | 外部素材召回源标签 | `外部合作` | 否 |
| `EXTERNAL_RECALL_CANDIDATE_LIMIT` | 外部素材进入 UV 排序的候选上限 | `300` | 否 |
| `EXTERNAL_RECALL_EDIT_LIMIT_PER_LANDING` | 单个承接视频最多尝试清理的外部素材数 | `3` | 否 |
| `EXTERNAL_RECALL_UV_WINDOW_DAYS` | 外部素材访问 UV 汇总窗口天数 | `90` | 否 |
| `EXTERNAL_RECALL_MIN_UV` | 外部合作素材访问UV最低门槛(窗口求和,<此值排除) | `500` | 否 |
| `EXTERNAL_IMAGE_MODEL` | 外部素材参考图编辑模型 | 复用 `OPENROUTER_IMAGE_MODEL` | 否 |
| `RECALL_MIN_COST_YUAN` | 内部素材召回历史成本最低门槛(元)；外部合作素材不走此门槛(见 `EXTERNAL_RECALL_MIN_UV`)；无成本数据(cost is None)的素材严格排除，即使设为 0 | `100` | 否 |

### 白名单配置

**方式 1：环境变量（推荐）**

```bash
# .env 或 k8s/secret.yaml
WHITELIST_ENABLED=true
WHITELIST_ACCOUNTS=80769799,71305011,12345678
```

**方式 2：配置文件**

编辑 `whitelist.json`：

```json
{
  "accounts": [80769799, 71305011],
  "description": "生产环境白名单账户列表",
  "last_updated": "2026-04-22"
}
```

### 定时调度配置

**Cron 表达式格式**：`分 时 日 月 周`

示例：
- `0 2 * * *` - 每天凌晨 2 点（UTC）
- `30 1 * * *` - 每天凌晨 1:30（UTC）
- `0 */6 * * *` - 每 6 小时执行一次
- `0 9 * * 1-5` - 工作日上午 9 点

修改定时：

```bash
# Kubernetes ConfigMap
kubectl patch configmap ad-config -n ad-automation \
  --patch '{"data":{"CRON_SCHEDULE":"0 3 * * *"}}'

kubectl rollout restart deployment/auto-put-ad-mini -n ad-automation
```

## 监控和运维

### 健康检查

```bash
# 本地
curl http://localhost:8080/health

# Kubernetes
kubectl exec -n ad-automation <pod-name> -- curl -f http://localhost:8080/health
```

返回示例：

```json
{
  "status": "healthy",
  "timestamp": "2026-04-22T10:00:00Z",
  "scheduler_running": true,
  "latest_report": "llm_decisions_20260422.csv",
  "jobs": [
    {
      "id": "decision_pipeline",
      "name": "广告决策流程",
      "next_run": "2026-04-23T02:00:00Z"
    }
  ]
}
```

### 日志查看

**Docker**：
```bash
docker logs -f auto-put-ad-mini
```

**Kubernetes**：
```bash
# 实时日志
kubectl logs -f -n ad-automation deployment/auto-put-ad-mini

# 最近 100 行
kubectl logs -n ad-automation deployment/auto-put-ad-mini --tail=100

# 查看多个 Pod 日志
kubectl logs -n ad-automation -l app=auto-put-ad-mini --all-containers=true
```

### 查看输出文件

**Docker**：
```bash
docker exec -it auto-put-ad-mini ls -lh /app/outputs/reports/
docker exec -it auto-put-ad-mini cat /app/outputs/reports/llm_decisions_*.csv
```

**Kubernetes**：
```bash
POD_NAME=$(kubectl get pods -n ad-automation -l app=auto-put-ad-mini -o jsonpath='{.items[0].metadata.name}')
kubectl exec -n ad-automation $POD_NAME -- ls -lh /app/outputs/reports/
kubectl exec -n ad-automation $POD_NAME -- cat /app/outputs/reports/llm_decisions_*.csv
```

### Prometheus 监控（可选）

如果启用了 Prometheus metrics：

```bash
# 查看 metrics 文件
kubectl exec -n ad-automation $POD_NAME -- cat /app/outputs/metrics.prom
```

配置 Prometheus 抓取：

```yaml
scrape_configs:
  - job_name: 'auto-put-ad-mini'
    file_sd_configs:
      - files:
        - /app/outputs/metrics.prom
```

## 故障排查

### 问题 1：容器启动失败

```bash
# 查看 Pod 状态
kubectl describe pod <pod-name> -n ad-automation

# 查看事件
kubectl get events -n ad-automation --sort-by='.lastTimestamp'

# 查看日志
kubectl logs <pod-name> -n ad-automation --previous
```

常见原因：
- Secret 未配置或配置错误
- PVC 未创建或无法挂载
- 镜像拉取失败

### 问题 2：定时任务未执行

```bash
# 检查调度器状态
curl http://localhost:8080/health | jq .scheduler_running

# 查看下次执行时间
curl http://localhost:8080/health | jq .jobs

# 手动触发测试
curl -X POST http://localhost:8080/trigger
```

### 问题 3：白名单过滤异常

```bash
# 查看日志，搜索白名单相关信息
kubectl logs -n ad-automation deployment/auto-put-ad-mini | grep "白名单"

# 检查配置
kubectl get secret ad-secrets -n ad-automation -o jsonpath='{.data.WHITELIST_ACCOUNTS}' | base64 -d
```

### 问题 4：代理连接失败

```bash
# 检查代理配置
kubectl get configmap ad-config -n ad-automation -o yaml | grep PROXY

# 测试代理连接
kubectl exec -n ad-automation $POD_NAME -- curl -x $HTTP_PROXY https://api.e.qq.com
```

## 回滚和恢复

### 回滚到上一个版本

```bash
# Deployment 模式
kubectl rollout undo deployment/auto-put-ad-mini -n ad-automation

# CronJob 模式
kubectl set image cronjob/auto-put-ad-mini \
  decision-engine=your-registry/auto-put-ad-mini:v0.9.0 \
  -n ad-automation
```

### 暂停服务

```bash
# Deployment 模式
kubectl scale deployment auto-put-ad-mini --replicas=0 -n ad-automation

# CronJob 模式
kubectl patch cronjob auto-put-ad-mini -n ad-automation -p '{"spec":{"suspend":true}}'
```

### 恢复服务

```bash
# Deployment 模式
kubectl scale deployment auto-put-ad-mini --replicas=1 -n ad-automation

# CronJob 模式
kubectl patch cronjob auto-put-ad-mini -n ad-automation -p '{"spec":{"suspend":false}}'
```

## 安全最佳实践

### 1. Secret 管理

- ❌ **不要**将真实 Secret 提交到 Git
- ✅ 使用 Kubernetes External Secrets 或 Sealed Secrets
- ✅ 定期轮换敏感凭据
- ✅ 限制 Secret 访问权限

### 2. 网络安全

- ✅ 启用 NetworkPolicy 限制出入站流量
- ✅ 仅允许必要的外部连接（腾讯 API、飞书 API）
- ✅ 使用代理服务访问外部资源

### 3. 资源隔离

- ✅ 使用独立命名空间（`ad-automation`）
- ✅ 设置 CPU 和内存 limits
- ✅ 使用 LimitRange 和 ResourceQuota

### 4. 运行时安全

- ✅ 非 root 用户运行（UID 1000）
- ✅ 禁用特权容器
- ✅ 使用 readOnlyRootFilesystem（输出目录除外）

## 性能优化

### 资源配置建议

| 环境 | CPU Request | CPU Limit | Memory Request | Memory Limit |
|------|-------------|-----------|----------------|--------------|
| 测试 | 250m | 500m | 512Mi | 1Gi |
| 生产 | 500m | 1 | 1Gi | 2Gi |
| 高负载 | 1 | 2 | 2Gi | 4Gi |

### 并发控制

- APScheduler `max_instances=1` 防止任务并发
- Kubernetes CronJob `concurrencyPolicy: Forbid`

### 日志轮转

```bash
# 输出目录定期清理
find /app/outputs -name "cron_*.log" -mtime +30 -delete
```

## 升级策略

### 滚动更新

```bash
# 更新镜像
kubectl set image deployment/auto-put-ad-mini \
  decision-engine=your-registry/auto-put-ad-mini:v1.1.0 \
  -n ad-automation

# 查看更新状态
kubectl rollout status deployment/auto-put-ad-mini -n ad-automation
```

### 灰度发布

```bash
# 创建 Canary Deployment
kubectl apply -f k8s/deployment-canary.yaml

# 验证 Canary 版本
kubectl logs -n ad-automation deployment/auto-put-ad-mini-canary

# 全量发布
kubectl set image deployment/auto-put-ad-mini \
  decision-engine=your-registry/auto-put-ad-mini:v1.1.0 \
  -n ad-automation

# 删除 Canary
kubectl delete deployment auto-put-ad-mini-canary -n ad-automation
```

## 附录

### A. 两种部署模式对比

| 特性 | Deployment + APScheduler | CronJob |
|------|-------------------------|---------|
| 资源占用 | 常驻 Pod（低） | 每次启动新 Pod |
| 启动速度 | 快（任务触发即执行） | 慢（需启动容器） |
| 健康检查 | ✅ HTTP 端点 | ❌ 需额外脚本 |
| 手动触发 | ✅ HTTP API | ⚠️ 需创建 Job |
| 日志查看 | ✅ 持续输出 | ⚠️ 分散在多个 Job |
| 监控集成 | ✅ Prometheus metrics | ⚠️ 需额外配置 |
| 适用场景 | 生产环境、频繁调度 | 简单定时任务 |

**推荐**：生产环境使用 Deployment + APScheduler 模式。

### B. 常用命令速查

```bash
# 快速重启
kubectl rollout restart deployment/auto-put-ad-mini -n ad-automation

# 查看最近事件
kubectl get events -n ad-automation --sort-by='.lastTimestamp' | tail -20

# 进入 Pod Shell
kubectl exec -it -n ad-automation <pod-name> -- bash

# 复制文件到本地
kubectl cp ad-automation/<pod-name>:/app/outputs/reports/llm_decisions_*.csv ./local-reports/

# 查看资源使用
kubectl top pod -n ad-automation
```

### C. 联系支持

- 文档问题：查看 `k8s/README.md` 和本文档
- 代码问题：查看 `CLAUDE.md` 和源码注释
- 配置问题：查看仓库根目录 `runtime.env.example` 和 `k8s/configmap.yaml`

---

**文档版本**：v1.0.0
**最后更新**：2026-04-22
**维护者**：auto_put_ad_mini team
