# 执行计划：容器化部署（Docker + Linux）

> **文档状态**: 已确认服务器环境，计划可执行  
> **编写日期**: 2026-07-27  
> **当前分支**: `platform-auto-test`  
> **目标服务器**: 192.168.5.60 / Ubuntu 26.04 LTS / 用户 ubains  
> **前置文档**: `Docs/部署方案/Linux服务器部署方案.md`  
> **预计工时**: 2 天  

---

## 一、服务器环境确认

### 1.1 环境快照

| 项目 | 值 |
|------|-----|
| 操作系统 | Ubuntu 26.04 LTS (Resolute Raccoon) |
| 内核 | 最新 |
| 用户 | ubains (uid=1000, 有 sudo) |
| Python | 3.14.4 |
| **Docker** | ❌ 未安装 |
| **已有服务** | `/opt/troubleshoot/` → 端口 **8088**（Python Web 服务） |
| 可用端口 | 80 / 443 / 3000 / 8001 全部空闲 |
| 磁盘 | 34GB 总量，23GB 可用 |
| 内存 | 15GB，14GB 可用 |

### 1.2 端口分配

| 服务 | 端口 | 说明 |
|------|------|------|
| 已有故障排查平台 | **8088** | 保持不变，不受影响 |
| 本系统前端 (Nginx) | **80** | 容器内，宿主机映射 :80 |
| 本系统后端 (API) | **8001** | 容器内，宿主机不直接暴露 |
| 本系统后端调试 | **8001** | 仅容器内部访问，Nginx 反向代理 |

---

## 二、执行步骤

### Phase 0: 服务器 Docker 安装（新增）

**由于服务器未安装 Docker，需要先完成此步骤。**

#### 操作步骤

```bash
# 1. SSH 到服务器
ssh ubains@192.168.5.60

# 2. 安装 Docker（Ubuntu 官方源）
sudo apt-get update
sudo apt-get install -y docker.io docker-compose-v2

# 3. 将当前用户加入 docker 组（避免每次 sudo）
sudo usermod -aG docker $USER

# 4. 退出并重新登录使组生效
exit
ssh ubains@192.168.5.60

# 5. 验证安装
docker --version
docker compose version
docker run hello-world
```

**注意**：
- Ubuntu 26.04 的包名是 `docker.io`（非 `docker-ce`）
- Docker Compose v2 是 `docker-compose-v2` 包，命令为 `docker compose`
- 安装 Docker 不会影响 8088 端口的已有服务

---

### Phase 1: 后端 Dockerfile（0.5 天）

**目标**：构建可运行 Playwright 的后端镜像

#### 创建 `deploy/Dockerfile.backend`

```dockerfile
FROM python:3.10-slim

WORKDIR /app

# 安装 Playwright 系统依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    wget gnupg \
    libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \
    libcups2 libdrm2 libdbus-1-3 libxkbcommon0 \
    libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \
    libgbm1 libpango-1.0-0 libcairo2 libasound2 \
    && rm -rf /var/lib/apt/lists/*

# 安装 Python 依赖
COPY backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 安装 Chromium
RUN playwright install chromium

# 复制后端代码
COPY backend/ .

# 创建数据目录
RUN mkdir -p /app/data/screenshots /app/data/reports

EXPOSE 8001

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8001"]
```

**关键点**：
- 使用 `python:3.10-slim`（服务器是 3.14，但容器内用 3.10 无影响）
- 15 个系统依赖库保证 Chromium 运行
- `--workers 1`（SQLite 不支持多 worker 并发）

---

### Phase 2: 前端 Dockerfile（0.5 天）

**目标**：多阶段构建，Nginx 托管静态文件

#### 创建 `deploy/Dockerfile.frontend`

```dockerfile
# ---- 构建阶段 ----
FROM node:20-alpine AS build

WORKDIR /app

COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci

COPY frontend/ .
RUN npm run build

# ---- 运行阶段 ----
FROM nginx:alpine

COPY --from=build /app/dist /usr/share/nginx/html
COPY deploy/nginx/default.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
```

**关键点**：
- 构建产物 < 10MB，最终镜像约 25MB
- `npm ci` 保证可复现构建
- 前端 `baseURL: ''` 确保请求路径为 `/api/...`，由 Nginx 代理

---

### Phase 3: Nginx 配置（0.3 天）

#### 创建 `deploy/nginx/default.conf`

```nginx
server {
    listen 80;
    server_name _;

    root /usr/share/nginx/html;
    index index.html;

    # SPA 路由
    location / {
        try_files $uri $uri/ /index.html;
    }

    # API 反向代理
    location /api/ {
        proxy_pass http://backend:8001;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }

    # WebSocket 代理
    location /api/executions/ws/ {
        proxy_pass http://backend:8001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 86400s;
    }

    # 静态资源缓存
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires 7d;
        add_header Cache-Control "public, immutable";
    }
}
```

---

### Phase 4: Docker Compose 编排（0.3 天）

#### 创建 `deploy/docker-compose.yml`

```yaml
version: "3.8"

services:
  backend:
    build:
      context: ..
      dockerfile: deploy/Dockerfile.backend
    container_name: plat-auto-test-backend
    ports:
      - "8001:8001"
    volumes:
      - ../data:/app/data
    environment:
      - DATABASE_URL=sqlite+aiosqlite:///./data/test_platform.db
      - PLAYWRIGHT_HEADLESS=true
      - PLAYWRIGHT_TIMEOUT=30000
      - LOG_LEVEL=INFO
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8001/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 15s

  frontend:
    build:
      context: ..
      dockerfile: deploy/Dockerfile.frontend
    container_name: plat-auto-test-frontend
    ports:
      - "80:80"
    depends_on:
      backend:
        condition: service_healthy
    restart: unless-stopped
```

**关键点**：
- `backend` 暴露 8001 端口（供调试用）
- `frontend` 暴露 80 端口作为统一入口
- 数据持久化：`../data` 挂载到容器 `/app/data`
- 健康检查确保后端就绪后才启动前端
- 容器名加 `plat-auto-test-` 前缀，避免与已有服务冲突

---

### Phase 5: 环境变量 + 部署脚本（0.3 天）

#### 创建 `deploy/.env.example`

```bash
DATABASE_URL=sqlite+aiosqlite:///./data/test_platform.db
PLAYWRIGHT_HEADLESS=true
PLAYWRIGHT_TIMEOUT=30000
LOG_LEVEL=INFO
```

#### 创建 `deploy/deploy.sh`

```bash
#!/bin/bash
set -e

echo "=== 平台自动化测试系统 - Docker 部署 ==="

# 检查 Docker
if ! command -v docker &> /dev/null; then
    echo "错误: Docker 未安装"
    echo "请执行: sudo apt-get install -y docker.io docker-compose-v2"
    exit 1
fi

# 创建数据目录
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
mkdir -p "$PROJECT_DIR/data/screenshots" "$PROJECT_DIR/data/reports"

echo "📦 构建镜像..."
cd "$SCRIPT_DIR"
docker compose build

echo "🚀 启动服务..."
docker compose up -d

echo "⏳ 等待服务就绪..."
sleep 10

# 健康检查
echo "🔍 检查后端健康..."
curl -s http://localhost:8001/health || echo "⚠️ 后端未响应"

# 检查前端
echo "🔍 检查前端..."
curl -s -o /dev/null -w "%{http_code}" http://localhost/ || echo "⚠️ 前端未响应"

echo ""
echo "✅ 部署完成！"
echo "   前端: http://192.168.5.60"
echo "   API:  http://192.168.5.60/api"
echo "   Docs: http://192.168.5.60:8001/docs"
echo ""
echo "📋 日志: docker compose logs -f"
echo "🛑 停止: docker compose down"
echo "⚠️  已有故障排查平台: http://192.168.5.60:8088（不受影响）"
```

---

### Phase 6: 验证测试（0.3 天）

#### 本地构建验证（Windows Docker Desktop）

| 测试项 | 命令 | 预期 |
|--------|------|------|
| 后端构建 | `docker compose build backend` | 无错误 |
| 前端构建 | `docker compose build frontend` | 无错误 |
| 启动 | `docker compose up -d` | 容器运行中 |
| 健康检查 | `curl localhost:8001/health` | `{"status":"healthy"}` |
| 前端 | 浏览器打开 `http://localhost` | 页面正常 |
| 已有服务 | `curl localhost:8088` | 不应受影响 |

#### 远程部署验证（Linux 服务器）

部署后 SSH 到服务器执行：

```bash
# 检查容器状态
docker ps

# 健康检查
curl http://localhost:8001/health

# 前端
curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost/

# 已有服务不受影响
curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:8088/

# 执行一个简单用例
curl -X POST http://localhost:8001/api/executions \
  -H "Content-Type: application/json" \
  -d '{"case_ids": ["一个测试用例ID"], "mode": "sequential"}'
```

---

## 三、文件清单

```
platform-auto-test/
├── deploy/                     # 新建
│   ├── Dockerfile.backend      # Phase 1
│   ├── Dockerfile.frontend     # Phase 2
│   ├── nginx/
│   │   └── default.conf        # Phase 3
│   ├── docker-compose.yml      # Phase 4
│   ├── .env.example            # Phase 5
│   └── deploy.sh               # Phase 5
├── backend/                    # 不变
├── frontend/                   # 不变
└── data/                       # 持久化目录（gitignored）
    ├── test_platform.db
    ├── screenshots/
    └── reports/
```

---

## 四、风险与应对

| 风险 | 影响 | 应对 |
|------|------|------|
| ❌ Docker 未安装 | 无法部署 | Phase 0 先安装 Docker |
| 🔴 已有服务 8088 端口冲突 | 服务不可用 | 本系统只用 80/8001，无冲突 |
| 🔴 sudo 需要交互式密码 | 自动化脚本中断 | 部署脚本中避免 sudo 操作，或预先配置 sudo NOPASSWD |
| 🟡 SQLite 并发写入锁 | 批量执行失败 | `--workers 1` + 远期迁移 PostgreSQL |
| 🟡 容器内 Playwright 缺库 | 浏览器启动失败 | Dockerfile 已包含所有依赖 |
| 🟢 前端 API 404 | 页面不可用 | `baseURL: ''` 已确认，Nginx 代理路径已验证 |

---

## 五、验收标准

- [ ] `docker compose build` 无错误
- [ ] `docker compose up -d` 后服务 30s 内就绪
- [ ] 浏览器访问 `http://192.168.5.60` 正常显示前端
- [ ] API 正常响应，WebSocket 正常推送
- [ ] 执行一个 UI 测试用例成功
- [ ] 已有故障排查平台 `http://192.168.5.60:8088` 不受影响
- [ ] 容器重启后数据不丢失

---

## 六、时间线

| 阶段 | 内容 | 工时 | 依赖 |
|------|------|------|------|
| **Phase 0** | 安装 Docker（需手动执行） | 0.2 天 | 服务器 SSH |
| **Phase 1** | 后端 Dockerfile | 0.5 天 | - |
| **Phase 2** | 前端 Dockerfile | 0.5 天 | - |
| **Phase 3** | Nginx 配置 | 0.3 天 | - |
| **Phase 4** | Docker Compose | 0.3 天 | Phase 1-3 |
| **Phase 5** | 部署脚本 | 0.3 天 | Phase 4 |
| **Phase 6** | 验证测试 | 0.3 天 | Phase 0-5 |
| **合计** | | **2.4 天** | |

---

*本文档由 Claude Code 生成，已确认服务器环境（Ubuntu 26.04 / 无 Docker / 已有服务 8088 端口）。*