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` 仍由健康檢查藍圖提供)。
## 系統架構 ## 系統架構
``` - 前端VueVite 打包後為靜態檔,容器內由後端服務)
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ - 後端Flask + Flask-SocketIOeventlet+ SQLAlchemy + JWT
│ Frontend │ │ Backend │ │ Celery │ - 佇列CeleryRedis broker/result
│ (Vue.js) │ │ (Flask) │ │ (Worker) │ - 資料庫MySQL透過 SQLAlchemy 連線池)
│ Port: 12010 │────│ Port: 12010 │────│ 獨立容器 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
└───────────────────────┼───────────────────────┘
┌─────────────────┐
│ Redis │
│ (Queue/Cache) │
│ Port: 6379 │
└─────────────────┘
```
**架構優化說明:** ## 需求與準備
- Flask 應用專注於 Web 服務,不再內建 Celery Worker
- Celery Worker 運行在獨立容器中,提供更好的資源管理 - Docker 20.10+、Docker Compose 1.28+
- 消除了重複的 worker 實例,避免任務重複執行風險 - 4GB 以上可用記憶體、20GB 以上可用磁碟空間
- 心跳同步更加穩定,減少集群管理開銷 - 內部網路可存取 MySQL、LDAP、SMTP、Dify API
## 快速部署 ## 快速部署
### 前置需求
- Docker 20.10+
- Docker Compose 1.28+
- 至少 4GB 可用記憶體
- 至少 20GB 可用磁碟空間
### 一鍵部署
```bash ```bash
# 1. 進入專案目錄 # 1) 進入專案目錄
cd Document_translator_V2 cd Document_translator_V2
# 2. 建置並啟動所有服務 # 2) 建置並啟動(首次執行會自動 build
docker-compose up -d docker-compose up -d
# 3. 檢查服務狀態 # 3) 檢查服務狀態
docker-compose ps docker-compose ps
# 4. 查看日誌 # 4) 追蹤應用日誌
docker-compose logs -f app docker-compose logs -f app
``` ```
### 驗證部署 驗證健康與前端:
```bash ```bash
# 檢查主應用健康狀態
curl http://localhost:12010/api/v1/health curl http://localhost:12010/api/v1/health
# 檢查前端訪問
curl http://localhost:12010/ curl http://localhost:12010/
```
# 檢查 Celery Worker 狀態 檢查 Celery worker
```bash
docker-compose exec celery-worker celery -A celery_app inspect active docker-compose exec celery-worker celery -A celery_app inspect active
``` ```
## 詳細部署步驟 ## 詳細部署步驟
### 1. 環境準備 ### 1) 主機檢查
**檢查系統資源**
```bash ```bash
# 檢查記憶體 # 記憶體 / 磁碟 / 埠使用
free -h free -h
# 檢查磁碟空間
df -h df -h
netstat -tulpn | grep 12010 || ss -lntp | grep 12010
# 檢查端口占用 # Docker 狀態
netstat -tulpn | grep 12010
```
**檢查 Docker 環境**
```bash
docker --version docker --version
docker-compose --version docker-compose --version
docker system info docker system info
``` ```
### 2. 配置文件檢查 ### 2) 建置映像
系統已包含完整的生產環境配置:
**資料庫配置**
- MySQL 主機mysql.theaken.com:33306
- 資料庫db_A060
- 連接已內建在 Docker 映像中
**郵件配置**
- SMTP 服務器mail.panjit.com.tw
- 端口25 (無認證)
**LDAP 配置**
- 服務器panjit.com.tw
- 認證已配置完成
### 3. 建置映像
```bash ```bash
# 建置主應用映像
docker build -t panjit-translator:latest . docker build -t panjit-translator:latest .
# 檢查映像大小
docker images panjit-translator docker images panjit-translator
``` ```
### 4. 啟動服務 ### 3) 使用 Docker Compose 啟動(推薦)
**使用 Docker Compose (推薦)**
```bash ```bash
# 啟動所有服務
docker-compose up -d docker-compose up -d
# 分別檢查各服務
docker-compose ps docker-compose ps
docker-compose logs app docker-compose logs app
docker-compose logs celery-worker docker-compose logs celery-worker
docker-compose logs redis docker-compose logs redis
``` ```
**手動 Docker 部署** ### 4) 純 Docker 佈署(可選)
```bash ```bash
# 啟動 Redis # 啟動 Redis(內部使用,無需對外開放)
docker run -d --name panjit-redis \ docker run -d --name panjit-redis \
-p 6379:6379 \
-v redis_data:/data \ -v redis_data:/data \
redis:7-alpine redis:7-alpine
# 啟動主應用 # 啟動主應用Gunicorn + eventlet, 12010
docker run -d --name panjit-translator \ docker run -d --name panjit-translator \
-p 12010:12010 \ -p 12010:12010 \
-v $(pwd)/uploads:/app/uploads \ -v $(pwd)/uploads:/app/uploads \
@@ -145,212 +102,61 @@ docker run -d --name panjit-translator \
-e REDIS_URL=redis://redis:6379/0 \ -e REDIS_URL=redis://redis:6379/0 \
panjit-translator:latest panjit-translator:latest
# 啟動 Celery Worker # 啟動 Celery Worker(可調整並行度)
docker run -d --name panjit-worker \ docker run -d --name panjit-worker \
-v $(pwd)/uploads:/app/uploads \ -v $(pwd)/uploads:/app/uploads \
-v $(pwd)/cache:/app/cache \ -v $(pwd)/cache:/app/cache \
--link panjit-redis:redis \ --link panjit-redis:redis \
-e REDIS_URL=redis://redis:6379/0 \ -e REDIS_URL=redis://redis:6379/0 \
panjit-translator:latest \ panjit-translator:latest \
celery -A celery_app worker --loglevel=info celery -A celery_app worker --loglevel=info --concurrency=8
``` ```
### 5. 服務驗證 ## 驗證與健康檢查
**健康檢查**
```bash ```bash
# API 健康檢查 # 健康檢查API 藍圖)
curl -f http://localhost:12010/api/v1/health curl http://localhost:12010/api/v1/health
# 預期回應: # 前端/靜態頁
{ curl http://localhost:12010/
"status": "healthy",
"timestamp": "2025-09-04T12:00:00Z", # WebSocket瀏覽器端透過前端頁面測試
"service": "PANJIT Document Translator API",
"version": "2.0.0"
}
``` ```
**功能測試** ## 擴展與監控
```bash ```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 stats
# 檢查服務狀態 # 觀察容器狀態
docker-compose ps docker-compose ps
# 檢查健康狀況 # 擴展 Celery Worker 副本(例如 3 副本)
docker inspect panjit-translator-app --format='{{json .State.Health}}' docker-compose up -d --scale celery-worker=3
``` ```
### 備份和恢復 ## 安全與網路
```bash ```bash
# 備份上傳檔案 # 僅開放必要端口(應用 12010
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
# 開放必要端口
sudo ufw allow 12010/tcp sudo ufw allow 12010/tcp
# 限制 Redis 端口(僅本機) # Redis 預設不對外開放;如需遠端維運才開放 6379 並限管理網段
sudo ufw deny 6379/tcp # sudo ufw allow from <管理網段> to any port 6379 proto tcp
``` ```
### SSL/TLS 配置 如需 HTTPS建議於前端加 Nginx/Traefik 反向代理:
如需 HTTPS建議在前端配置 Nginx 反向代理:
```nginx ```nginx
server { server {
listen 443 ssl; listen 443 ssl;
server_name translator.panjit.com.tw; server_name translator.panjit.com.tw;
ssl_certificate /path/to/certificate.crt; ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key; ssl_certificate_key /path/to/private.key;
location / { location / {
proxy_pass http://localhost:12010; proxy_pass http://localhost:12010;
proxy_set_header Host $host; 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 - Email: it-support@panjit.com.tw
- 內線電話: 2481 - 分機: 2481
- 緊急支援: 24/7 待命 - 緊急支援: 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 # Create required directories
RUN mkdir -p uploads logs scripts 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 && \ 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 chmod +x /app/start.sh
# Set permissions # 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 CMD curl -f http://localhost:12010/api/v1/health || exit 1
# Start application # Start application
CMD ["/app/start.sh"] CMD ["/app/start.sh"]

View File

@@ -138,6 +138,13 @@ def create_app(config_name=None):
# 初始化 WebSocket # 初始化 WebSocket
from app.websocket import init_websocket from app.websocket import init_websocket
app.socketio = init_websocket(app) 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") app.logger.info("Flask application created successfully")
return app return app
@@ -215,4 +222,4 @@ def create_default_admin():
print(f"Failed to create default admin: {str(e)}") 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 Modified: 2024-01-28
""" """
import os
from flask_socketio import SocketIO, emit, join_room, leave_room, disconnect from flask_socketio import SocketIO, emit, join_room, leave_room, disconnect
from flask_jwt_extended import decode_token, get_jwt from flask_jwt_extended import decode_token, get_jwt
from flask import request from flask import request
@@ -17,7 +18,9 @@ import logging
# 初始化 SocketIO # 初始化 SocketIO
socketio = SocketIO( socketio = SocketIO(
cors_allowed_origins="*", 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, logger=True,
engineio_logger=False engineio_logger=False
) )
@@ -227,4 +230,4 @@ def init_websocket(app):
""" """
socketio.init_app(app) socketio.init_app(app)
logger.info("WebSocket initialized") logger.info("WebSocket initialized")
return socketio return socketio

View File

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