bea288e414
== 已部署上线 (9项) == - 代办事项真实数据源集成 (企微审批API 8bug修复链) - H5/坐席端 Logo样式统一+绿色背景 - 视频引导页修复 (localStorage key v2) - 坐席端 v9 Vue版本修复 (ElMessage._context) - 截图按钮 v10 修复 (getDisplayMedia user gesture) - 扫码样式恢复+H5扫码登录跳转修复 - H5截图快捷键提示 == 代码完成待部署 (3项) == - 知识迭代3Bug修复 (#8 POST端点/#7 MERGE幂等/#6 过期检查) - 会议室预定-小鱼易联终端 (40文件, 40/40测试通过) - IT资产升级审批推送 (asset_service.py) == 需求文档 (2项) == - 坐席端AI辅助消息框-PRD (4项新功能确认) - 坐席端布局优化建议 v2.0 (7天计划) == 新增文档 == - 日报-2026-07-11.md - 知识迭代Bug修复报告-20260711.md - 会议室预定-部署指南.md - CHANGELOG.md 更新 == 测试 == - test_todo_integration.py: 40/40 - test_meetingroom.py: 40/40 - test_bugfix_ki_suggestions.py: 21/21
647 lines
21 KiB
Markdown
647 lines
21 KiB
Markdown
# 任务说明书 — 后端部署卷挂载改造
|
||
|
||
> **版本**: v1.0 | **日期**: 2026-07-10
|
||
|
||
---
|
||
|
||
## 📋 基本信息
|
||
|
||
| 项目 | 内容 |
|
||
|------|------|
|
||
| **任务名称** | 后端部署架构改造:镜像烘焙 → 代码卷挂载 |
|
||
| **任务ID** | #107 |
|
||
| **优先级** | 🟠 P1 |
|
||
| **类型** | 部署优化 |
|
||
| **状态** | 待开始 |
|
||
| **负责人** | 宋献 |
|
||
| **创建日期** | 2026-07-10 |
|
||
| **计划完成日期** | 2026-07-11 |
|
||
|
||
---
|
||
|
||
## 📥 输入项来源
|
||
|
||
### 产品需求
|
||
|
||
| 来源文档 | 相关章节 | 说明 |
|
||
|----------|----------|------|
|
||
| 无 | — | 运维需求,非产品功能 |
|
||
|
||
### 技术架构
|
||
|
||
| 来源文档 | 相关章节 | 说明 |
|
||
|----------|----------|------|
|
||
| `03-技术架构/IT智能服务台-系统架构设计文档v2.md` | §5.0 部署模式演进 | 方案 C 设计说明 |
|
||
| `09-部署运维/卷挂载重构方案.md` | 全文 | 完整 8 章节方案(架构师高见远产出) |
|
||
| `09-部署运维/00-标准故障排查手册.md` | §1.4 + CASE-20260710-02 | 部署前同步检查清单 + 镜像缺文件案例 |
|
||
|
||
### 事故背景
|
||
|
||
| 日期 | 事故 | 根因 |
|
||
|------|------|------|
|
||
| 2026-07-07 | 认证路由缺失 | Docker 镜像未重新构建 |
|
||
| 2026-07-10 | auth.py 缺失导致认证全断 | 镜像从 backend/app/(旧代码)构建,两份代码不同步 |
|
||
|
||
**共同根因**:代码通过 `COPY . .` 烘焙进镜像,服务器两份代码不同步导致构建出缺文件的镜像。
|
||
|
||
---
|
||
|
||
## 📤 输出成果要求
|
||
|
||
### 交付物清单
|
||
|
||
| # | 交付物 | 类型 | 说明 |
|
||
|---|--------|------|------|
|
||
| 1 | `backend/Dockerfile` | 配置 | 删除 `COPY . .`,新增 `ENV PYTHONDONTWRITEBYTECODE=1` |
|
||
| 2 | `docker-compose.yml` | 配置 | backend 服务 volumes 新增 `./app:/app/app` |
|
||
| 3 | 服务器代码目录调整 | 运维 | `app/` 成为唯一代码源,`backend/app/` 保留 48h 后删除 |
|
||
| 4 | 部署验证通过 | 验证 | 健康检查 + auth 模块 + 卷挂载 + 代码一致性 |
|
||
|
||
### 代码要求
|
||
|
||
- Dockerfile 变更:仅删除 `COPY . .`(第 53 行),新增 `ENV PYTHONDONTWRITEBYTECODE=1`,其余不变
|
||
- docker-compose.yml 变更:backend 服务 volumes 段新增一行 `./app:/app/app`,插入到 `backend-uploads` 行之前
|
||
- 不涉及任何 Python 业务代码变更
|
||
|
||
### 文档要求
|
||
|
||
- 架构设计文档已更新(§5.0 部署模式演进,v2.1)
|
||
- 项目管理主文档已更新(看板新增 #107)
|
||
- 完整方案文档已归档:`docs/09-部署运维/卷挂载重构方案.md`
|
||
|
||
---
|
||
|
||
## 🔧 验证方式
|
||
|
||
### 功能验证
|
||
|
||
| 验证项 | 验证方法 | 预期结果 |
|
||
|--------|----------|-----------|
|
||
| 容器运行状态 | `docker compose ps backend` | Status 为 `Up (healthy)` |
|
||
| 健康检查 | `curl -sf http://localhost:8000/health` | 返回 `{"status":"healthy"}` 或类似 |
|
||
| **Auth 模块** | `docker exec wecom_it_backend python -c "from app.auth import router; print('OK')"` | 输出 `OK`(曾因缺失导致故障) |
|
||
| 卷挂载 | `docker exec wecom_it_backend ls -la /app/app/main.py` | 文件存在且可读 |
|
||
| 代码一致性 | `md5sum` 对比宿主机与容器内 `main.py` | md5 值一致 |
|
||
| 日志检查 | `docker compose logs --tail=50 backend` | 无 `ModuleNotFoundError` / `ImportError` |
|
||
|
||
### 安全验证
|
||
|
||
| 验证项 | 验证方法 | 预期结果 |
|
||
|--------|----------|-----------|
|
||
| `COPY . .` 已移除 | `grep -c "COPY . ." backend/Dockerfile` | 返回 0 |
|
||
| 卷挂载已添加 | `grep "app:/app/app" docker-compose.yml` | 返回匹配 |
|
||
| `__pycache__` 禁止 | `docker exec wecom_it_backend python -c "import sys; print(sys.dont_write_bytecode)"` | 输出 `True` |
|
||
|
||
### 性能验证
|
||
|
||
| 验证项 | 验证方法 | 预期结果 |
|
||
|--------|----------|-----------|
|
||
| 容器重启速度 | 部署后 `docker compose restart backend` + `time` 计时 | < 15 秒(此前需 4-6 分钟) |
|
||
| 健康检查就绪 | 重启后 `curl /health` 轮询 | 15 秒内就绪 |
|
||
|
||
---
|
||
|
||
## 🔄 回滚策略(CRITICAL)
|
||
|
||
### 回滚触发条件
|
||
|
||
满足以下 **任一** 条件即触发回滚:
|
||
|
||
| # | 触发条件 | 检测方法 |
|
||
|---|----------|----------|
|
||
| 1 | 容器启动失败(反复重启) | `docker compose ps backend` 状态为 `restarting` 或 `unhealthy` |
|
||
| 2 | 健康检查连续失败 | `curl -sf http://localhost:8000/health` 返回非 200 |
|
||
| 3 | 关键模块导入失败(如 auth) | `docker exec wecom_it_backend python -c "from app.auth import router"` 报错 |
|
||
| 4 | 卷挂载路径不存在或权限拒绝 | 容器日志出现 `ModuleNotFoundError` 或 `PermissionError` |
|
||
| 5 | 业务接口大面积 500 错误 | Nginx 日志或后端日志大量 500 状态码 |
|
||
|
||
### 回滚步骤(可通过 jumpserver-ops 执行)
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# =============================================================================
|
||
# 回滚脚本:卷挂载方案 → 镜像烘焙方案
|
||
# 执行方式:通过 jumpserver-ops 在 10.90.5.110 上执行
|
||
# 前提:备份文件存在(部署时已创建 .bak.{TIMESTAMP} 后缀文件)
|
||
# =============================================================================
|
||
|
||
set -e
|
||
cd /opt/wecom-it-desk
|
||
|
||
echo "===== 回滚开始: $(date) ====="
|
||
|
||
# --- 步骤 1: 恢复备份的配置文件 ---
|
||
echo ">>> [1/6] 恢复配置文件..."
|
||
|
||
COMPOSE_BAK=$(ls -t /opt/wecom-it-desk/docker-compose.yml.bak.* 2>/dev/null | head -1)
|
||
if [ -z "$COMPOSE_BAK" ]; then
|
||
echo "ERROR: 未找到 docker-compose.yml 备份文件!"
|
||
echo "可手动从 git 恢复: git checkout -- docker-compose.yml"
|
||
exit 1
|
||
fi
|
||
cp "$COMPOSE_BAK" /opt/wecom-it-desk/docker-compose.yml
|
||
echo " 已恢复 docker-compose.yml <- $COMPOSE_BAK"
|
||
|
||
DOCKERFILE_BAK=$(ls -t /opt/wecom-it-desk/backend/Dockerfile.bak.* 2>/dev/null | head -1)
|
||
if [ -z "$DOCKERFILE_BAK" ]; then
|
||
echo "ERROR: 未找到 Dockerfile 备份文件!"
|
||
echo "可手动从 git 恢复: git checkout -- backend/Dockerfile"
|
||
exit 1
|
||
fi
|
||
cp "$DOCKERFILE_BAK" /opt/wecom-it-desk/backend/Dockerfile
|
||
echo " 已恢复 Dockerfile <- $DOCKERFILE_BAK"
|
||
|
||
# --- 步骤 2: 确保 backend/app/ 存在(回滚安全网) ---
|
||
echo ">>> [2/6] 检查 backend/app/ 目录..."
|
||
|
||
if [ ! -d /opt/wecom-it-desk/backend/app/ ] || [ -z "$(ls -A /opt/wecom-it-desk/backend/app/ 2>/dev/null)" ]; then
|
||
echo " backend/app/ 不存在或为空,从 app/ 同步代码..."
|
||
mkdir -p /opt/wecom-it-desk/backend/app/
|
||
cp -r /opt/wecom-it-desk/app/* /opt/wecom-it-desk/backend/app/
|
||
cp -r /opt/wecom-it-desk/app/.* /opt/wecom-it-desk/backend/app/ 2>/dev/null || true
|
||
echo " 已同步代码到 backend/app/"
|
||
else
|
||
echo " backend/app/ 已存在,跳过同步"
|
||
fi
|
||
|
||
# --- 步骤 3: 重新构建镜像(使用原始 Dockerfile,含 COPY . .) ---
|
||
echo ">>> [3/6] 重新构建后端镜像..."
|
||
docker compose build --no-cache backend
|
||
|
||
# --- 步骤 4: 重启容器 ---
|
||
echo ">>> [4/6] 重启后端容器..."
|
||
docker compose up -d backend
|
||
|
||
# --- 步骤 5: 等待服务就绪 ---
|
||
echo ">>> [5/6] 等待服务启动..."
|
||
echo " 等待 45 秒(healthcheck start_period)..."
|
||
sleep 45
|
||
|
||
# --- 步骤 6: 验证 ---
|
||
echo ">>> [6/6] 验证回滚结果..."
|
||
|
||
echo "--- 容器状态 ---"
|
||
docker compose ps backend
|
||
|
||
echo "--- 健康检查 ---"
|
||
if curl -sf http://localhost:8000/health > /dev/null 2>&1; then
|
||
echo " PASS: /health 返回正常"
|
||
else
|
||
echo " WARN: /health 未就绪,再等待 15 秒..."
|
||
sleep 15
|
||
if curl -sf http://localhost:8000/health > /dev/null 2>&1; then
|
||
echo " PASS: /health 返回正常(延迟就绪)"
|
||
else
|
||
echo " FAIL: /health 仍然失败"
|
||
echo " 查看日志: docker compose logs --tail=50 backend"
|
||
fi
|
||
fi
|
||
|
||
echo "--- Auth 模块验证 ---"
|
||
if docker exec wecom_it_backend python -c "from app.auth import router; print('auth OK')" 2>/dev/null; then
|
||
echo " PASS: auth 模块可导入"
|
||
else
|
||
echo " FAIL: auth 模块导入失败"
|
||
echo " 查看日志: docker compose logs --tail=50 backend"
|
||
fi
|
||
|
||
echo ""
|
||
echo "===== 回滚完成: $(date) ====="
|
||
echo ""
|
||
echo "如回滚后仍有问题,请检查:"
|
||
echo " 1. backend/app/ 代码是否完整: ls -la /opt/wecom-it-desk/backend/app/"
|
||
echo " 2. 镜像构建是否成功: docker images | grep wecom-it-desk-backend"
|
||
echo " 3. 容器日志: docker compose logs -f backend"
|
||
```
|
||
|
||
### 回滚后验证
|
||
|
||
| 验证项 | 命令 | 预期结果 |
|
||
|--------|------|----------|
|
||
| 容器运行状态 | `docker compose ps backend` | Status 为 `Up (healthy)` |
|
||
| 健康检查 | `curl -sf http://localhost:8000/health` | 返回正常 |
|
||
| Auth 模块 | `docker exec wecom_it_backend python -c "from app.auth import router; print('OK')"` | 输出 `OK` |
|
||
| Nginx 代理 | `curl -sf http://localhost:80/itdesk/health` | 返回正常 |
|
||
| 日志无异常 | `docker compose logs --tail=50 backend` | 无 `ModuleNotFoundError` / `ImportError` |
|
||
|
||
### 回滚时间预估
|
||
|
||
| 步骤 | 耗时 |
|
||
|------|------|
|
||
| 恢复配置文件 | 5 秒 |
|
||
| 检查/同步 backend/app/ | 5-30 秒 |
|
||
| 重建镜像 | 2-4 分钟 |
|
||
| 重启容器 | 10 秒 |
|
||
| 等待就绪 | 45 秒 |
|
||
| 验证 | 15 秒 |
|
||
| **总计** | **3.5-5.5 分钟** |
|
||
|
||
---
|
||
|
||
## ✅ 完成标准
|
||
|
||
### 验收条件
|
||
|
||
- [ ] Dockerfile 已删除 `COPY . .`,已新增 `PYTHONDONTWRITEBYTECODE=1`
|
||
- [ ] docker-compose.yml 已新增 `./app:/app/app` 卷挂载
|
||
- [ ] 镜像已重建并重启成功
|
||
- [ ] 健康检查通过(`/health` 返回正常)
|
||
- [ ] Auth 模块可导入(`from app.auth import router` 成功)
|
||
- [ ] 卷挂载验证通过(容器内 `/app/app/main.py` 可访问)
|
||
- [ ] 代码一致性验证通过(宿主机与容器 md5 一致)
|
||
- [ ] 日志无 `ModuleNotFoundError` / `ImportError`
|
||
- [ ] 部署 48 小时后 `backend/app/` 已清理
|
||
- [ ] 文档已更新(架构设计文档 v2.1、项目管理主文档 v2.1)
|
||
|
||
### 产出确认
|
||
|
||
- [ ] 服务器 Dockerfile 已更新
|
||
- [ ] 服务器 docker-compose.yml 已更新
|
||
- [ ] 镜像重建成功
|
||
- [ ] 容器运行正常
|
||
- [ ] 回滚脚本已验证可用
|
||
- [ ] 备份文件已创建(`.bak.{TIMESTAMP}`)
|
||
- [ ] `.rollback-info` 文件已记录
|
||
|
||
---
|
||
|
||
## 📊 工作分解
|
||
|
||
### 子任务
|
||
|
||
| 子任务 | 负责人 | 预估工时 | 状态 | 依赖 |
|
||
|--------|--------|----------|------|------|
|
||
| S1: 前置验证与备份 | 宋献 | 2 分钟 | 待开始 | 无 |
|
||
| S2: 代码同步与验证 | 宋献 | 1 分钟 | 待开始 | S1 |
|
||
| S3: 修改 Dockerfile | 宋献 | 1 分钟 | 待开始 | S2 |
|
||
| S4: 修改 docker-compose.yml | 宋献 | 1 分钟 | 待开始 | S3 |
|
||
| S5: 重建镜像并重启 | 宋献 | 3 分钟 | 待开始 | S4 |
|
||
| S6: 部署后验证 | 宋献 | 2 分钟 | 待开始 | S5 |
|
||
| S7: 清理 backend/app/(48h 后) | 宋献 | 1 分钟 | 待开始 | S6 + 48h |
|
||
|
||
### 预估总时间
|
||
|
||
| 阶段 | 步骤 | 耗时 |
|
||
|------|------|------|
|
||
| 准备 | S1 + S2 | 3 分钟 |
|
||
| 变更 | S3 + S4 | 2 分钟 |
|
||
| 部署 | S5 | 3 分钟 |
|
||
| 验证 | S6 | 2 分钟 |
|
||
| **总计** | S1-S6 | **约 10 分钟** |
|
||
| 清理 | S7(48h 后) | 1 分钟 |
|
||
| **回滚(如需)** | 回滚脚本 | **3.5-5.5 分钟** |
|
||
|
||
---
|
||
|
||
## 📞 依赖与阻塞
|
||
|
||
### 前置依赖
|
||
|
||
| 依赖任务 | 依赖说明 | 状态 |
|
||
|----------|----------|------|
|
||
| 无 | 独立运维任务 | — |
|
||
|
||
### 阻塞因素
|
||
|
||
| 阻塞项 | 影响范围 | 解决方案 |
|
||
|--------|----------|-----------|
|
||
| 无 | — | — |
|
||
|
||
---
|
||
|
||
## ⚠️ 风险评估
|
||
|
||
| # | 风险 | 概率 | 等级 | 对策 |
|
||
|---|------|------|------|------|
|
||
| R1 | `__pycache__` 污染宿主机代码目录 | 中 | 中 | Dockerfile 设置 `PYTHONDONTWRITEBYTECODE=1` |
|
||
| R2 | 宿主机代码被意外修改导致运行中服务异常 | 低 | 中 | 生产不启用 `--reload`;限制目录写权限 |
|
||
| R3 | `backend/app/` 被提前删除导致回滚失败 | 低 | 高 | 48 小时内不删除;回滚脚本含自动同步逻辑 |
|
||
| R4 | requirements.txt 与代码不同步 | 低 | 中 | 部署前 diff 对比;新增依赖时先重建镜像 |
|
||
| R5 | 卷挂载路径与现有挂载冲突 | 极低 | 低 | `/app/app` 与 `/app/uploads`、`/app/logs` 无交集 |
|
||
|
||
---
|
||
|
||
## 📈 变更记录
|
||
|
||
| 日期 | 变更内容 | 变更人 | 说明 |
|
||
|------|----------|--------|------|
|
||
| 2026-07-10 | 创建任务 | 宋献 | 初始版本,基于架构师高见远的卷挂载重构方案 |
|
||
|
||
---
|
||
|
||
## 📎 附件
|
||
|
||
- **完整方案文档**:`docs/09-部署运维/卷挂载重构方案.md`(含完整命令块、时序图、风险评估)
|
||
- **架构设计文档**:`docs/03-技术架构/IT智能服务台-系统架构设计文档v2.md` §5.0
|
||
- **故障排查手册**:`docs/09-部署运维/00-标准故障排查手册.md` §1.4 + CASE-20260710-02
|
||
- **deploy-troubleshoot skill**:`~/.workbuddy/skills/deploy-troubleshoot/SKILL.md` Step -1 部署前同步检查
|
||
- **task-intake skill**:`.workbuddy/skills/task-intake/SKILL.md` Step 3.1 部署运维前置检查
|
||
|
||
---
|
||
|
||
## 📝 完整部署命令块
|
||
|
||
以下命令可通过 jumpserver-ops 在服务器 10.90.5.110 上按步骤执行。
|
||
|
||
### S1: 前置验证与备份
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
set -e
|
||
cd /opt/wecom-it-desk
|
||
|
||
# 1. 验证当前服务正常
|
||
echo ">>> 验证当前服务状态..."
|
||
curl -sf http://localhost:8000/health > /dev/null && echo " PASS: 当前服务正常" || { echo " FAIL: 当前服务异常,请先修复再部署"; exit 1; }
|
||
|
||
# 2. 备份配置文件
|
||
BACKUP_TS=$(date +%Y%m%d%H%M%S)
|
||
cp /opt/wecom-it-desk/docker-compose.yml /opt/wecom-it-desk/docker-compose.yml.bak.${BACKUP_TS}
|
||
cp /opt/wecom-it-desk/backend/Dockerfile /opt/wecom-it-desk/backend/Dockerfile.bak.${BACKUP_TS}
|
||
echo " PASS: 备份完成 (timestamp: ${BACKUP_TS})"
|
||
|
||
# 3. 记录回滚信息
|
||
cat > /opt/wecom-it-desk/.rollback-info << EOF
|
||
ROLLBACK_TIMESTAMP=${BACKUP_TS}
|
||
COMPOSE_BAK=/opt/wecom-it-desk/docker-compose.yml.bak.${BACKUP_TS}
|
||
DOCKERFILE_BAK=/opt/wecom-it-desk/backend/Dockerfile.bak.${BACKUP_TS}
|
||
DEPLOY_DATE=$(date)
|
||
EOF
|
||
echo " PASS: 回滚信息已记录到 .rollback-info"
|
||
echo ""
|
||
echo "===== S1 完成 ====="
|
||
```
|
||
|
||
### S2: 代码同步与验证
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
set -e
|
||
cd /opt/wecom-it-desk
|
||
|
||
# 1. 验证 app/ 目录存在且包含关键文件
|
||
echo ">>> 验证代码目录..."
|
||
for f in app/__init__.py app/main.py app/config.py app/database.py; do
|
||
if [ -f "$f" ]; then
|
||
echo " PASS: $f 存在"
|
||
else
|
||
echo " FAIL: $f 不存在!请先解压部署包: tar -xf /tmp/deploy-backend.tar -C ./"
|
||
exit 1
|
||
fi
|
||
done
|
||
|
||
# 2. 验证 auth 模块(关键!曾因缺失导致故障)
|
||
if [ -f "app/api/auth.py" ]; then
|
||
echo " PASS: app/api/auth.py 存在"
|
||
else
|
||
echo " FAIL: app/api/auth.py 不存在!认证功能将不可用"
|
||
exit 1
|
||
fi
|
||
|
||
# 3. 统计代码文件数
|
||
FILE_COUNT=$(find app/ -name "*.py" | wc -l)
|
||
echo " INFO: app/ 目录共 ${FILE_COUNT} 个 Python 文件"
|
||
|
||
echo ""
|
||
echo "===== S2 完成 ====="
|
||
```
|
||
|
||
### S3: 修改 Dockerfile
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
set -e
|
||
|
||
DOCKERFILE=/opt/wecom-it-desk/backend/Dockerfile
|
||
|
||
echo ">>> 修改 Dockerfile..."
|
||
|
||
# 直接写入完整文件(最可靠)
|
||
cat > "$DOCKERFILE" << 'DOCKERFILE_EOF'
|
||
# =============================================================================
|
||
# 企微IT智能服务台 — 后端 Docker 镜像构建文件
|
||
# =============================================================================
|
||
# 说明:基于 Python 3.12 构建后端镜像
|
||
# 变更:2025-07-10 方案C — 代码改为 volume 挂载,镜像不再 COPY 业务代码
|
||
# 用法:docker build -t wecom-it-desk-backend .
|
||
# =============================================================================
|
||
|
||
# --------------------------------------------------------------------------
|
||
# 第一阶段:构建阶段
|
||
# --------------------------------------------------------------------------
|
||
FROM python:3.12-slim AS builder
|
||
|
||
# 设置工作目录
|
||
WORKDIR /app
|
||
|
||
# 安装系统依赖(psycopg2 编译需要 + qrcode 图片处理需要 + healthcheck 需要 curl)
|
||
RUN apt-get update && \
|
||
apt-get install -y --no-install-recommends gcc libpq-dev libjpeg-dev zlib1g-dev curl && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
|
||
# 复制依赖声明文件并安装(利用 Docker 层缓存,依赖不变则不重新安装)
|
||
COPY requirements.txt .
|
||
RUN pip install --no-cache-dir \
|
||
--timeout 180 \
|
||
--retries 5 \
|
||
-i https://mirrors.aliyun.com/pypi/simple/ \
|
||
--trusted-host mirrors.aliyun.com \
|
||
-r requirements.txt
|
||
|
||
# --------------------------------------------------------------------------
|
||
# 第二阶段:运行阶段(更小的镜像体积)
|
||
# --------------------------------------------------------------------------
|
||
FROM python:3.12-slim
|
||
|
||
# 设置标签信息
|
||
LABEL maintainer="IT服务台开发团队"
|
||
LABEL description="企微IT智能服务台后端服务"
|
||
LABEL changelog="2025-07-10: 移除 COPY . .,代码改为 volume 挂载"
|
||
|
||
# 安装运行时依赖(psycopg2 运行时需要 libpq + healthcheck 需要 curl)
|
||
RUN apt-get update && \
|
||
apt-get install -y --no-install-recommends libpq5 curl && \
|
||
rm -rf /var/lib/apt/lists/*
|
||
|
||
# 设置工作目录
|
||
WORKDIR /app
|
||
|
||
# 禁止 Python 写入 __pycache__(防止污染宿主机代码目录)
|
||
ENV PYTHONDONTWRITEBYTECODE=1
|
||
|
||
# 从构建阶段复制已安装的 Python 包
|
||
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
|
||
COPY --from=builder /usr/local/bin /usr/local/bin
|
||
|
||
# 业务代码通过 docker-compose volumes 挂载(./app:/app/app),不再 COPY 进镜像
|
||
|
||
# 暴露端口
|
||
EXPOSE 8000
|
||
|
||
# 启动命令(Docker Compose 中会覆盖)
|
||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||
DOCKERFILE_EOF
|
||
|
||
# 验证
|
||
if grep -q "COPY . ." "$DOCKERFILE"; then
|
||
echo " FAIL: Dockerfile 仍包含 COPY . ."
|
||
exit 1
|
||
fi
|
||
if grep -q "PYTHONDONTWRITEBYTECODE" "$DOCKERFILE"; then
|
||
echo " PASS: PYTHONDONTWRITEBYTECODE 已设置"
|
||
else
|
||
echo " FAIL: PYTHONDONTWRITEBYTECODE 未找到"
|
||
exit 1
|
||
fi
|
||
echo " PASS: Dockerfile 已更新"
|
||
|
||
echo ""
|
||
echo "===== S3 完成 ====="
|
||
```
|
||
|
||
### S4: 修改 docker-compose.yml
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
set -e
|
||
cd /opt/wecom-it-desk
|
||
|
||
COMPOSE_FILE=/opt/wecom-it-desk/docker-compose.yml
|
||
|
||
echo ">>> 修改 docker-compose.yml..."
|
||
|
||
# 在 backend-uploads 行之前插入代码卷挂载行
|
||
sed -i '/backend-uploads:\/app\/uploads/i\ - ./app:/app/app # 代码卷挂载(方案C)' "$COMPOSE_FILE"
|
||
|
||
# 验证
|
||
if grep -q "./app:/app/app" "$COMPOSE_FILE"; then
|
||
echo " PASS: 代码卷挂载已添加"
|
||
else
|
||
echo " FAIL: 代码卷挂载未找到,请手动编辑 docker-compose.yml"
|
||
echo " 在 backend 服务的 volumes: 下添加:"
|
||
echo " - ./app:/app/app"
|
||
exit 1
|
||
fi
|
||
|
||
echo ""
|
||
echo "===== S4 完成 ====="
|
||
```
|
||
|
||
### S5: 重建镜像并重启
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
set -e
|
||
cd /opt/wecom-it-desk
|
||
|
||
echo ">>> 重建后端镜像..."
|
||
docker compose build backend
|
||
echo " PASS: 镜像构建完成"
|
||
|
||
echo ""
|
||
echo ">>> 重启后端容器..."
|
||
docker compose up -d backend
|
||
echo " PASS: 容器已启动"
|
||
|
||
echo ""
|
||
echo ">>> 等待服务就绪 (45秒)..."
|
||
sleep 45
|
||
|
||
echo ""
|
||
echo "===== S5 完成 ====="
|
||
```
|
||
|
||
### S6: 部署后验证
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
cd /opt/wecom-it-desk
|
||
|
||
echo "===== 部署后验证 ====="
|
||
echo ""
|
||
|
||
# 1. 容器状态
|
||
echo "--- 1. 容器状态 ---"
|
||
docker compose ps backend
|
||
echo ""
|
||
|
||
# 2. 健康检查
|
||
echo "--- 2. 健康检查 ---"
|
||
if curl -sf http://localhost:8000/health; then
|
||
echo ""
|
||
echo " PASS: /health 正常"
|
||
else
|
||
echo " FAIL: /health 异常"
|
||
fi
|
||
echo ""
|
||
|
||
# 3. Auth 模块验证
|
||
echo "--- 3. Auth 模块验证 ---"
|
||
if docker exec wecom_it_backend python -c "from app.auth import router; print(' auth module: OK')" 2>/dev/null; then
|
||
echo " PASS: auth 模块可导入"
|
||
else
|
||
echo " FAIL: auth 模块导入失败"
|
||
echo " 查看日志: docker compose logs --tail=50 backend"
|
||
fi
|
||
echo ""
|
||
|
||
# 4. 卷挂载验证
|
||
echo "--- 4. 卷挂载验证 ---"
|
||
if docker exec wecom_it_backend ls -la /app/app/main.py > /dev/null 2>&1; then
|
||
echo " PASS: /app/app/main.py 可访问(卷挂载正常)"
|
||
else
|
||
echo " FAIL: /app/app/main.py 不可访问(卷挂载异常)"
|
||
fi
|
||
echo ""
|
||
|
||
# 5. 代码来源验证
|
||
echo "--- 5. 代码来源验证 ---"
|
||
HOST_HASH=$(md5sum /opt/wecom-it-desk/app/main.py | awk '{print $1}')
|
||
CONTAINER_HASH=$(docker exec wecom_it_backend md5sum /app/app/main.py 2>/dev/null | awk '{print $1}')
|
||
if [ "$HOST_HASH" = "$CONTAINER_HASH" ]; then
|
||
echo " PASS: 宿主机与容器代码一致 (md5: ${HOST_HASH})"
|
||
else
|
||
echo " WARN: 宿主机与容器代码不一致"
|
||
echo " 宿主机: $HOST_HASH"
|
||
echo " 容器: $CONTAINER_HASH"
|
||
fi
|
||
echo ""
|
||
|
||
# 6. 日志检查
|
||
echo "--- 6. 最近 20 行日志 ---"
|
||
docker compose logs --tail=20 backend
|
||
echo ""
|
||
|
||
echo "===== 验证完成 ====="
|
||
echo ""
|
||
echo "如全部 PASS,部署成功。"
|
||
echo "如出现 FAIL,请执行回滚脚本(见上方回滚策略章节)。"
|
||
```
|
||
|
||
### S7: 清理(48 小时后执行)
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
set -e
|
||
cd /opt/wecom-it-desk
|
||
|
||
echo ">>> 清理旧代码目录..."
|
||
echo " 注意:仅在部署成功 48 小时后执行此步骤!"
|
||
echo ""
|
||
|
||
# 确认服务稳定
|
||
curl -sf http://localhost:8000/health > /dev/null && echo " 服务正常" || { echo " 服务异常,取消清理"; exit 1; }
|
||
|
||
# 删除 backend/app/(不再需要)
|
||
if [ -d /opt/wecom-it-desk/backend/app/ ]; then
|
||
echo " 删除 backend/app/..."
|
||
rm -rf /opt/wecom-it-desk/backend/app/
|
||
echo " PASS: backend/app/ 已删除"
|
||
else
|
||
echo " INFO: backend/app/ 已不存在,跳过"
|
||
fi
|
||
|
||
echo ""
|
||
echo "===== 清理完成 ====="
|
||
```
|