This commit is contained in:
beabigegg
2025-09-12 11:50:43 +08:00
parent 0bc8c4c81c
commit ed9250db1a
7 changed files with 230 additions and 279 deletions

View File

@@ -1,141 +1,98 @@
# PANJIT 文件翻譯系統 - 部署手冊
# PANJIT 文件翻譯系統 - 部署指南
## 部署概述
本指南說明如何在公司內部以 Docker 方式部署系統至生產環境,並提供日常維運要點。
本系統已完成生產環境準備,包含完整的 Docker 配置和環境設定。系統使用 12010 端口,符合公司端口規範 (12010-12019)。
## 生產最佳化更新(重要)
- 後端以 Gunicorn + eventlet 啟動WSGI 入口:`wsgi:app`),提升併發與穩定性。
- Socket.IO 啟用 Redis message queue`REDIS_URL`),支援多進程/多副本一致廣播。
- Celery worker 預設併發提高至 8可依 CPU 與佇列長度再水平擴展。
- Redis 僅供容器內部使用Compose 預設不再對外暴露 6379。
- 新增套件內根路由提供 SPA 與 `/api``/api/health``/api/v1/health` 仍由健康檢查藍圖提供)。
## 系統架構
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Frontend │ │ Backend │ │ Celery │
│ (Vue.js) │ │ (Flask) │ │ (Worker) │
│ Port: 12010 │────│ Port: 12010 │────│ 獨立容器 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
└───────────────────────┼───────────────────────┘
┌─────────────────┐
│ Redis │
│ (Queue/Cache) │
│ Port: 6379 │
└─────────────────┘
```
- 前端VueVite 打包後為靜態檔,容器內由後端服務)
- 後端Flask + Flask-SocketIOeventlet+ SQLAlchemy + JWT
- 佇列CeleryRedis broker/result
- 資料庫MySQL透過 SQLAlchemy 連線池)
**架構優化說明:**
- Flask 應用專注於 Web 服務,不再內建 Celery Worker
- Celery Worker 運行在獨立容器中,提供更好的資源管理
- 消除了重複的 worker 實例,避免任務重複執行風險
- 心跳同步更加穩定,減少集群管理開銷
## 需求與準備
- Docker 20.10+、Docker Compose 1.28+
- 4GB 以上可用記憶體、20GB 以上可用磁碟空間
- 內部網路可存取 MySQL、LDAP、SMTP、Dify API
## 快速部署
### 前置需求
- Docker 20.10+
- Docker Compose 1.28+
- 至少 4GB 可用記憶體
- 至少 20GB 可用磁碟空間
### 一鍵部署
```bash
# 1. 進入專案目錄
# 1) 進入專案目錄
cd Document_translator_V2
# 2. 建置並啟動所有服務
# 2) 建置並啟動(首次執行會自動 build
docker-compose up -d
# 3. 檢查服務狀態
# 3) 檢查服務狀態
docker-compose ps
# 4. 查看日誌
# 4) 追蹤應用日誌
docker-compose logs -f app
```
### 驗證部署
驗證健康與前端:
```bash
# 檢查主應用健康狀態
curl http://localhost:12010/api/v1/health
# 檢查前端訪問
curl http://localhost:12010/
```
# 檢查 Celery Worker 狀態
檢查 Celery worker
```bash
docker-compose exec celery-worker celery -A celery_app inspect active
```
## 詳細部署步驟
### 1. 環境準備
### 1) 主機檢查
**檢查系統資源**
```bash
# 檢查記憶體
# 記憶體 / 磁碟 / 埠使用
free -h
# 檢查磁碟空間
df -h
netstat -tulpn | grep 12010 || ss -lntp | grep 12010
# 檢查端口占用
netstat -tulpn | grep 12010
```
**檢查 Docker 環境**
```bash
# Docker 狀態
docker --version
docker-compose --version
docker system info
```
### 2. 配置文件檢查
系統已包含完整的生產環境配置:
**資料庫配置**
- MySQL 主機mysql.theaken.com:33306
- 資料庫db_A060
- 連接已內建在 Docker 映像中
**郵件配置**
- SMTP 服務器mail.panjit.com.tw
- 端口25 (無認證)
**LDAP 配置**
- 服務器panjit.com.tw
- 認證已配置完成
### 3. 建置映像
### 2) 建置映像
```bash
# 建置主應用映像
docker build -t panjit-translator:latest .
# 檢查映像大小
docker images panjit-translator
```
### 4. 啟動服務
### 3) 使用 Docker Compose 啟動(推薦)
**使用 Docker Compose (推薦)**
```bash
# 啟動所有服務
docker-compose up -d
# 分別檢查各服務
docker-compose ps
docker-compose logs app
docker-compose logs celery-worker
docker-compose logs redis
```
**手動 Docker 部署**
### 4) 純 Docker 佈署(可選)
```bash
# 啟動 Redis
# 啟動 Redis(內部使用,無需對外開放)
docker run -d --name panjit-redis \
-p 6379:6379 \
-v redis_data:/data \
redis:7-alpine
# 啟動主應用
# 啟動主應用Gunicorn + eventlet, 12010
docker run -d --name panjit-translator \
-p 12010:12010 \
-v $(pwd)/uploads:/app/uploads \
@@ -145,212 +102,61 @@ docker run -d --name panjit-translator \
-e REDIS_URL=redis://redis:6379/0 \
panjit-translator:latest
# 啟動 Celery Worker
# 啟動 Celery Worker(可調整並行度)
docker run -d --name panjit-worker \
-v $(pwd)/uploads:/app/uploads \
-v $(pwd)/cache:/app/cache \
--link panjit-redis:redis \
-e REDIS_URL=redis://redis:6379/0 \
panjit-translator:latest \
celery -A celery_app worker --loglevel=info
celery -A celery_app worker --loglevel=info --concurrency=8
```
### 5. 服務驗證
## 驗證與健康檢查
**健康檢查**
```bash
# API 健康檢查
curl -f http://localhost:12010/api/v1/health
# 健康檢查API 藍圖)
curl http://localhost:12010/api/v1/health
# 預期回應:
{
"status": "healthy",
"timestamp": "2025-09-04T12:00:00Z",
"service": "PANJIT Document Translator API",
"version": "2.0.0"
}
# 前端/靜態頁
curl http://localhost:12010/
# WebSocket瀏覽器端透過前端頁面測試
```
**功能測試**
## 擴展與監控
```bash
# 測試 LDAP 連接 (需要有效帳號)
curl -X POST http://localhost:12010/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "test@panjit.com.tw", "password": "password"}'
# 測試檔案上傳端點
curl -f http://localhost:12010/api/v1/files/supported-formats
```
**Celery 檢查**
```bash
# 檢查 Worker 狀態
docker-compose exec celery-worker celery -A celery_app inspect active
# 檢查佇列狀態
docker-compose exec celery-worker celery -A celery_app inspect reserved
```
## 監控和維護
### 日誌管理
```bash
# 查看實時日誌
docker-compose logs -f
# 查看特定服務日誌
docker-compose logs -f app
docker-compose logs -f celery-worker
# 查看錯誤日誌
docker-compose logs app | grep ERROR
```
### 性能監控
```bash
# 檢查容器資源使用
# 觀察資源
docker stats
# 檢查服務狀態
# 觀察容器狀態
docker-compose ps
# 檢查健康狀況
docker inspect panjit-translator-app --format='{{json .State.Health}}'
# 擴展 Celery Worker 副本(例如 3 副本)
docker-compose up -d --scale celery-worker=3
```
### 備份和恢復
## 安全與網路
```bash
# 備份上傳檔案
tar -czf uploads_backup_$(date +%Y%m%d).tar.gz uploads/
# 備份快取
tar -czf cache_backup_$(date +%Y%m%d).tar.gz cache/
# 備份日誌
tar -czf logs_backup_$(date +%Y%m%d).tar.gz logs/
```
## 更新和升級
### 更新應用
```bash
# 1. 備份重要數據
docker-compose exec app tar -czf /app/backup_$(date +%Y%m%d).tar.gz uploads/ cache/
# 2. 停止服務
docker-compose down
# 3. 更新代碼和重新建置
git pull origin main
docker-compose build
# 4. 啟動服務
docker-compose up -d
# 5. 驗證更新
curl http://localhost:12010/api/v1/health
```
### 滾動更新(零停機時間)
```bash
# 1. 建置新映像
docker build -t panjit-translator:v2.1.0 .
# 2. 更新 docker-compose.yml 中的映像版本
# 3. 逐一重啟服務
docker-compose up -d --no-deps app
docker-compose up -d --no-deps celery-worker
```
## 故障排除
### 常見問題
**1. 容器無法啟動**
```bash
# 檢查端口占用
sudo netstat -tulpn | grep 12010
# 檢查映像是否存在
docker images panjit-translator
# 檢查容器日誌
docker-compose logs app
```
**2. 資料庫連接失敗**
```bash
# 測試資料庫連接
docker-compose exec app python -c "
import pymysql
try:
conn = pymysql.connect(
host='mysql.theaken.com',
port=33306,
user='A060',
password='WLeSCi0yhtc7',
database='db_A060'
)
print('資料庫連接成功')
conn.close()
except Exception as e:
print(f'資料庫連接失敗: {e}')
"
```
**3. Celery Worker 無法啟動**
```bash
# 檢查 Redis 連接
docker-compose exec app python -c "
import redis
try:
r = redis.Redis.from_url('redis://redis:6379/0')
r.ping()
print('Redis 連接成功')
except Exception as e:
print(f'Redis 連接失敗: {e}')
"
# 重啟 Worker
docker-compose restart celery-worker
```
### 緊急恢復
```bash
# 完全重置並重啟
docker-compose down -v
docker-compose up -d
# 清理未使用的映像和容器
docker system prune -f
# 重新建置
docker-compose build --no-cache
docker-compose up -d
```
## 安全配置
### 防火牆設定
```bash
# 開放必要端口
# 僅開放必要端口(應用 12010
sudo ufw allow 12010/tcp
# 限制 Redis 端口(僅本機)
sudo ufw deny 6379/tcp
# Redis 預設不對外開放;如需遠端維運才開放 6379 並限管理網段
# sudo ufw allow from <管理網段> to any port 6379 proto tcp
```
### SSL/TLS 配置
如需 HTTPS建議在前端配置 Nginx 反向代理:
如需 HTTPS建議於前端加 Nginx/Traefik 反向代理:
```nginx
server {
listen 443 ssl;
server_name translator.panjit.com.tw;
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
location / {
proxy_pass http://localhost:12010;
proxy_set_header Host $host;
@@ -361,24 +167,81 @@ server {
}
```
## 疑難排解(內部)
資料庫連線測試(內部憑證):
```bash
docker-compose exec app python -c "
import pymysql
try:
conn = pymysql.connect(
host='mysql.theaken.com',
port=33306,
user='A060',
password='WLeSCi0yhtc7',
database='db_A060'
)
print('資料庫連線成功')
conn.close()
except Exception as e:
print(f'資料庫連線失敗: {e}')
"
```
Redis 連線測試:
```bash
docker-compose exec app python -c "
import redis
try:
r = redis.Redis.from_url('redis://redis:6379/0')
r.ping()
print('Redis 連線成功')
except Exception as e:
print(f'Redis 連線失敗: {e}')
"
```
重建與清理:
```bash
docker-compose down -v
docker system prune -f
docker-compose build --no-cache
docker-compose up -d
```
## 維運與更新
```bash
# 備份重要資料uploads/cache/logs
docker-compose exec app tar -czf /app/backup_$(date +%Y%m%d).tar.gz uploads/ cache/
# 更新程式碼與重建
docker-compose down
git pull origin main
docker-compose build
docker-compose up -d
# 驗證
curl http://localhost:12010/api/v1/health
```
零停機滾動更新(僅針對單一服務重新拉起):
```bash
docker-compose up -d --no-deps app
docker-compose up -d --no-deps celery-worker
```
## 聯繫支援
如遇到部署問題,請聯繫:
**PANJIT IT Team**
PANJIT IT Team內部
- Email: it-support@panjit.com.tw
- 內線電話: 2481
- 分機: 2481
- 緊急支援: 24/7 待命
**系統資訊**
- 版本v2.1.0 (優化版)
- 部署日期2025-09-11
- 維護人員System Administrator
- 更新內容:
- 優化 Celery 架構,移除重複 worker
- 修正 Excel 翻譯輸出格式
- 清理開發階段檔案
- 簡化 Docker 映像構建
---
*本部署手冊適用於 PANJIT 文件翻譯系統 v2.1.0*
本文件適用於 PANJIT 文件翻譯系統 v2.1.0

View File

@@ -62,9 +62,9 @@ COPY --from=frontend-builder /app/frontend/dist ./static
# Create required directories
RUN mkdir -p uploads logs scripts
# Create startup script (removed redundant celery worker)
# Create startup script using Gunicorn + eventlet for production
RUN echo '#!/bin/bash' > /app/start.sh && \
echo 'python app.py' >> /app/start.sh && \
echo 'exec gunicorn -k eventlet -w 1 -b 0.0.0.0:12010 wsgi:app' >> /app/start.sh && \
chmod +x /app/start.sh
# Set permissions
@@ -83,4 +83,4 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:12010/api/v1/health || exit 1
# Start application
CMD ["/app/start.sh"]
CMD ["/app/start.sh"]

View File

@@ -138,6 +138,13 @@ def create_app(config_name=None):
# 初始化 WebSocket
from app.websocket import init_websocket
app.socketio = init_websocket(app)
# 註冊 Root 路由(提供 SPA 與基本 API 資訊)
try:
from app.root import root_bp
app.register_blueprint(root_bp)
except Exception as e:
app.logger.warning(f"Root routes not registered: {e}")
app.logger.info("Flask application created successfully")
return app
@@ -215,4 +222,4 @@ def create_default_admin():
print(f"Failed to create default admin: {str(e)}")
# 導入模型在需要時才進行,避免循環導入
# 導入模型在需要時才進行,避免循環導入

64
app/root.py Normal file
View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Root routes and static file serving for SPA in production.
These were originally defined in the top-level app.py. Moving them into the
package allows a clean WSGI entry (wsgi:app) without importing app.py.
"""
from datetime import datetime
from flask import Blueprint, current_app, send_from_directory
root_bp = Blueprint('root', __name__)
@root_bp.route('/')
def index():
try:
return send_from_directory('/app/static', 'index.html')
except Exception:
# Fallback API info when frontend is not present
return {
'application': 'PANJIT Document Translator',
'version': '1.0.0',
'status': 'running',
'api_base_url': '/api/v1',
'note': 'Frontend files not found, serving API info'
}
@root_bp.route('/<path:path>')
def serve_static(path):
try:
return send_from_directory('/app/static', path)
except Exception:
# SPA fallback
return send_from_directory('/app/static', 'index.html')
@root_bp.route('/api')
def api_info():
return {
'api_version': 'v1',
'base_url': '/api/v1',
'endpoints': {
'auth': '/api/v1/auth',
'files': '/api/v1/files',
'jobs': '/api/v1/jobs',
'admin': '/api/v1/admin',
'health': '/api/v1/health'
},
'documentation': 'Available endpoints provide RESTful API for document translation'
}
@root_bp.route('/api/health')
def health_check():
# Keep a simple health endpoint here for compatibility
return {
'status': 'healthy',
'timestamp': datetime.utcnow().isoformat(),
'service': 'PANJIT Document Translator API',
'version': '1.0.0'
}, 200

View File

@@ -8,6 +8,7 @@ Created: 2024-01-28
Modified: 2024-01-28
"""
import os
from flask_socketio import SocketIO, emit, join_room, leave_room, disconnect
from flask_jwt_extended import decode_token, get_jwt
from flask import request
@@ -17,7 +18,9 @@ import logging
# 初始化 SocketIO
socketio = SocketIO(
cors_allowed_origins="*",
async_mode='threading',
# Use eventlet for production and enable Redis message queue for multi-process/replica support
async_mode='eventlet',
message_queue=os.getenv('REDIS_URL'),
logger=True,
engineio_logger=False
)
@@ -227,4 +230,4 @@ def init_websocket(app):
"""
socketio.init_app(app)
logger.info("WebSocket initialized")
return socketio
return socketio

View File

@@ -3,8 +3,7 @@ services:
redis:
image: redis:7-alpine
container_name: panjit-translator-redis
ports:
- "6379:6379"
# Redis only for internal network use; no public port exposure
volumes:
- redis_data:/data
restart: unless-stopped
@@ -50,7 +49,7 @@ services:
environment:
- REDIS_URL=redis://redis:6379/0
restart: unless-stopped
command: celery -A celery_app worker --loglevel=info --concurrency=4
command: celery -A celery_app worker --loglevel=info --concurrency=8
healthcheck:
test: ["CMD", "celery", "-A", "celery_app", "inspect", "ping"]
interval: 30s
@@ -82,4 +81,4 @@ volumes:
networks:
default:
name: panjit-translator-network
name: panjit-translator-network

15
wsgi.py Normal file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
WSGI entrypoint for production (Gunicorn)
This creates the Flask application via the application factory in app/__init__.py,
so we avoid importing the top-level app.py to prevent name clashes with the
package module name "app".
"""
from app import create_app
app = create_app()