checkok
This commit is contained in:
418
DEPLOYMENT.md
418
DEPLOYMENT.md
@@ -1,380 +1,128 @@
|
|||||||
# TODO管理系統 - 生產環境部署指南
|
# TODO 管理系統 - 生產環境部署指南(最新架構)
|
||||||
|
|
||||||
## 📋 概述
|
## 概述
|
||||||
|
|
||||||
本文件提供TODO管理系統的完整生產環境部署指南,使用單一容器架構,簡化部署和維護。
|
本文件說明最新部署架構:以 Nginx 為對外入口(Port 12011),反向代理至後端 Gunicorn(容器內 12011),前端由 Flask 靜態目錄提供。此架構提升穩定度與擴充性,支援約 200 位同時使用者。
|
||||||
|
|
||||||
## 🏗️ 系統架構
|
## 架構總覽
|
||||||
|
|
||||||
|
- 對外入口:Nginx(容器 `nginx`),監聽並對外開放 `12011`
|
||||||
|
- 應用服務:Flask + Gunicorn(容器 `todolist-app`),僅容器網路內提供 `12011`
|
||||||
|
- 前端:Next.js 靜態輸出,隨應用容器打包(Flask 靜態目錄 `frontend/out`)
|
||||||
|
- CORS:允許 `http://localhost:12011`、`http://127.0.0.1:12011`
|
||||||
|
|
||||||
|
## 專案結構(摘要)
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────┐ ┌─────────────────┐
|
TODOLIST/
|
||||||
│ 單一容器應用 │ │ MySQL DB │
|
├─ backend/ # 後端程式(Flask)
|
||||||
│ ┌─────────────┐ ┌─────────────────┐ │ │ theaken.com │
|
│ ├─ app.py # Flask 應用(含 SPA 靜態路由)
|
||||||
│ │Next.js 靜態 │ │ Flask API │ │◄───┤ Port: 33306 │
|
│ └─ requirements.txt # Python 依賴
|
||||||
│ │ 文件 │ │ Port: 12011 │ │ │ │
|
├─ frontend/ # 前端(Next.js 靜態輸出)
|
||||||
│ └─────────────┘ └─────────────────┘ │ └─────────────────┘
|
├─ nginx/
|
||||||
│ 統一 Port: 12011 │
|
│ └─ nginx.conf # Nginx 反向代理設定(listen 12011)
|
||||||
└─────────────────────────────────────┘
|
├─ Dockerfile # 應用容器建置檔(含 Gunicorn 啟動)
|
||||||
│
|
├─ docker-compose.yml # Compose:nginx + todolist-app
|
||||||
▼
|
└─ .env.production # 生產環境變數
|
||||||
┌─────────────────┐
|
|
||||||
│ LDAP/AD │
|
|
||||||
│ panjit.com.tw │
|
|
||||||
│ Port: 389 │
|
|
||||||
└─────────────────┘
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🚀 快速部署
|
## 主要環境變數(.env.production)
|
||||||
|
|
||||||
### Windows 環境
|
- Database:`MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE`
|
||||||
|
- LDAP/AD:`LDAP_SERVER, LDAP_PORT, LDAP_USE_SSL, LDAP_SEARCH_BASE, LDAP_BIND_USER_DN, LDAP_BIND_USER_PASSWORD, LDAP_USER_LOGIN_ATTR`
|
||||||
|
- SMTP:`SMTP_SERVER, SMTP_PORT, SMTP_USE_TLS, SMTP_USE_SSL, SMTP_AUTH_REQUIRED, SMTP_SENDER_EMAIL, SMTP_SENDER_PASSWORD`
|
||||||
|
- Flask/JWT:`SECRET_KEY, JWT_SECRET_KEY`
|
||||||
|
- CORS:`CORS_ORIGINS=http://localhost:12011,http://127.0.0.1:12011`
|
||||||
|
|
||||||
```batch
|
## Docker 服務(最新)
|
||||||
# 1. 克隆專案(如果尚未克隆)
|
|
||||||
git clone <repository-url>
|
|
||||||
cd TODOLIST
|
|
||||||
|
|
||||||
# 2. 執行部署腳本
|
- `todolist-app`(應用容器)
|
||||||
deploy.bat
|
- 對外不開放 Port;內部提供 `12011`
|
||||||
|
- 健康檢查:`GET /api/health/`
|
||||||
|
- 重啟策略:`unless-stopped`
|
||||||
|
|
||||||
# 或使用管理腳本
|
- `nginx`(反向代理)
|
||||||
manage.bat
|
- 對外 Port:`12011:12011`
|
||||||
```
|
- 代理規則:`/api/*` 與其餘路由 → `todolist-app:12011`
|
||||||
|
- 設定檔:`nginx/nginx.conf`
|
||||||
|
|
||||||
### Linux/Mac 環境
|
## Gunicorn 生產設定(現行)
|
||||||
|
|
||||||
|
Dockerfile 已內建以下啟動參數:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 克隆專案(如果尚未克隆)
|
--bind 0.0.0.0:12011
|
||||||
|
--worker-class gthread
|
||||||
|
--workers 4
|
||||||
|
--threads 8
|
||||||
|
--timeout 120
|
||||||
|
--keep-alive 10
|
||||||
|
--max-requests 2000
|
||||||
|
--max-requests-jitter 200
|
||||||
|
--forwarded-allow-ips *
|
||||||
|
```
|
||||||
|
|
||||||
|
並發估算:4 workers × 8 threads ≈ 32 併發(可依 CPU 核心數調整)。
|
||||||
|
|
||||||
|
## 部署步驟
|
||||||
|
|
||||||
|
Windows:
|
||||||
|
```batch
|
||||||
git clone <repository-url>
|
git clone <repository-url>
|
||||||
cd TODOLIST
|
cd TODOLIST
|
||||||
|
deploy.bat
|
||||||
|
```
|
||||||
|
|
||||||
# 2. 設置執行權限並執行部署腳本
|
Linux/Mac:
|
||||||
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
cd TODOLIST
|
||||||
chmod +x deploy.sh
|
chmod +x deploy.sh
|
||||||
./deploy.sh
|
./deploy.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## 📁 部署文件結構
|
或直接使用 Compose:
|
||||||
|
|
||||||
```
|
|
||||||
TODOLIST/
|
|
||||||
├── backend/ # 後端代碼
|
|
||||||
│ ├── app.py # Flask應用(含靜態文件服務)
|
|
||||||
│ ├── requirements.txt # Python依賴
|
|
||||||
│ └── ...
|
|
||||||
├── frontend/ # 前端代碼
|
|
||||||
│ ├── next.config.js # Next.js配置(靜態導出)
|
|
||||||
│ ├── package.json # Node.js依賴
|
|
||||||
│ └── ...
|
|
||||||
├── Dockerfile # 單一容器構建文件
|
|
||||||
├── docker-compose.yml # Docker Compose配置
|
|
||||||
├── .env.production # 生產環境變量配置
|
|
||||||
├── deploy.bat # Windows部署腳本
|
|
||||||
├── deploy.sh # Linux/Mac部署腳本
|
|
||||||
├── manage.bat # Windows管理腳本
|
|
||||||
└── DEPLOYMENT.md # 部署說明文件
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 環境配置
|
|
||||||
|
|
||||||
### 生產環境變量 (`.env.production`)
|
|
||||||
|
|
||||||
本系統使用以下生產環境配置:
|
|
||||||
|
|
||||||
#### 🗄️ 資料庫配置
|
|
||||||
- **MySQL主機**: `mysql.theaken.com:33306`
|
|
||||||
- **資料庫**: `db_A060`
|
|
||||||
- **用戶**: `A060`
|
|
||||||
|
|
||||||
#### 🔐 LDAP認證配置
|
|
||||||
- **LDAP服務器**: `panjit.com.tw:389`
|
|
||||||
- **搜索基礎**: `OU=PANJIT,DC=panjit,DC=com,DC=tw`
|
|
||||||
- **認證方式**: Active Directory集成
|
|
||||||
|
|
||||||
#### 📧 郵件服務配置
|
|
||||||
- **SMTP服務器**: `mail.panjit.com.tw:25`
|
|
||||||
- **發送者**: `todo-system@panjit.com.tw`
|
|
||||||
|
|
||||||
#### 🌐 服務端口
|
|
||||||
- **統一端口**: `12011` (前端 + 後端API)
|
|
||||||
|
|
||||||
## 🐳 Docker配置
|
|
||||||
|
|
||||||
### 單一容器架構
|
|
||||||
|
|
||||||
**todolist-app**: 整合式應用容器
|
|
||||||
- 端口映射: `12011:12011`
|
|
||||||
- 健康檢查: `/api/health`
|
|
||||||
- 自動重啟: `unless-stopped`
|
|
||||||
- 包含: Flask API + Next.js 靜態文件
|
|
||||||
|
|
||||||
### 構建過程
|
|
||||||
|
|
||||||
1. **階段1**: 構建 Next.js 靜態文件
|
|
||||||
2. **階段2**: 設置 Flask 環境 + Gunicorn 生產服務器
|
|
||||||
3. **階段3**: 複製靜態文件到 Flask 容器
|
|
||||||
4. **最終**: 單一容器同時提供前後端服務 (Gunicorn驅動)
|
|
||||||
|
|
||||||
### 生產服務器配置
|
|
||||||
|
|
||||||
**Gunicorn 配置參數**:
|
|
||||||
```bash
|
```bash
|
||||||
--bind 0.0.0.0:12011 # 綁定地址和端口
|
docker-compose up --build -d
|
||||||
--workers 4 # 4個工作進程
|
|
||||||
--threads 2 # 每進程2個線程
|
|
||||||
--timeout 120 # 請求超時120秒
|
|
||||||
--keep-alive 2 # 保持連線時間
|
|
||||||
--max-requests 1000 # 進程處理請求上限後自動重啟
|
|
||||||
--max-requests-jitter 100 # 隨機化重啟時機
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**總並發能力**: 4 workers × 2 threads = **8個同時請求**
|
## 驗證與測試
|
||||||
|
|
||||||
## 🔍 手動部署步驟
|
|
||||||
|
|
||||||
如果需要手動部署,請按以下步驟執行:
|
|
||||||
|
|
||||||
### 1. 停止現有服務
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 停止並移除現有容器
|
curl http://localhost:12011 # 前端首頁
|
||||||
docker-compose down
|
curl http://localhost:12011/api/health # 健康檢查
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 建置Docker鏡像
|
## 維運常用指令
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 建置單一容器鏡像
|
|
||||||
docker-compose build --no-cache
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 啟動服務
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 使用Docker Compose啟動
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 驗證部署
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 檢查容器狀態
|
|
||||||
docker-compose ps
|
docker-compose ps
|
||||||
|
|
||||||
# 檢查服務健康
|
|
||||||
curl http://localhost:12011/api/health
|
|
||||||
curl http://localhost:12011
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🧪 測試與驗證
|
|
||||||
|
|
||||||
### 服務訪問測試
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 前端頁面測試
|
|
||||||
curl http://localhost:12011
|
|
||||||
|
|
||||||
# API健康檢查
|
|
||||||
curl http://localhost:12011/api/health
|
|
||||||
|
|
||||||
# API功能測試
|
|
||||||
curl -X POST http://localhost:12011/api/auth/login \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"username":"test","password":"test"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🛠️ 維護指令
|
|
||||||
|
|
||||||
### 使用管理腳本 (Windows)
|
|
||||||
|
|
||||||
```batch
|
|
||||||
# 執行管理腳本
|
|
||||||
manage.bat
|
|
||||||
|
|
||||||
# 選項包括:
|
|
||||||
# 1. 部署應用程式
|
|
||||||
# 2. 停止服務
|
|
||||||
# 3. 檢視服務狀態
|
|
||||||
# 4. 檢視日誌
|
|
||||||
# 5. 重啟服務
|
|
||||||
# 6. 清理舊映像檔
|
|
||||||
```
|
|
||||||
|
|
||||||
### 日誌查看
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 查看服務日誌
|
|
||||||
docker-compose logs -f
|
docker-compose logs -f
|
||||||
|
|
||||||
# 查看最近日誌
|
|
||||||
docker-compose logs --tail=50
|
|
||||||
```
|
|
||||||
|
|
||||||
### 服務管理
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 重啟服務
|
|
||||||
docker-compose restart
|
docker-compose restart
|
||||||
|
|
||||||
# 停止服務
|
|
||||||
docker-compose down
|
|
||||||
|
|
||||||
# 更新並重啟
|
|
||||||
docker-compose down
|
docker-compose down
|
||||||
docker-compose up -d --build
|
docker-compose up -d --build
|
||||||
```
|
|
||||||
|
|
||||||
### 資源監控
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 查看容器狀態
|
|
||||||
docker-compose ps
|
|
||||||
|
|
||||||
# 查看資源使用
|
|
||||||
docker stats
|
docker stats
|
||||||
|
docker inspect todolist-nginx
|
||||||
# 查看容器詳情
|
|
||||||
docker inspect todolist-single-prod
|
docker inspect todolist-single-prod
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🚨 故障排除
|
## 疑難排解(重點)
|
||||||
|
|
||||||
### 常見問題
|
- 端口占用:`netstat -ano | findstr :12011`
|
||||||
|
- SPA 404:由 Flask 回傳 `index.html` 已處理;若異常請重建鏡像
|
||||||
|
- 後端 URL 與重導:已加 `ProxyFix` 並信任代理標頭
|
||||||
|
- 大量連線:由 Nginx 保持連線與緩衝,Gunicorn gthread 提升併發
|
||||||
|
|
||||||
#### 1. 容器無法啟動
|
## 安全建議
|
||||||
```bash
|
|
||||||
# 檢查日誌
|
|
||||||
docker-compose logs
|
|
||||||
|
|
||||||
# 檢查端口占用
|
- 更換強度足夠的 `SECRET_KEY` 與 `JWT_SECRET_KEY`
|
||||||
netstat -ano | findstr :12011
|
- 若需 TLS/443,可在 `nginx/nginx.conf` 新增對應 server 與憑證設定
|
||||||
```
|
- 適度設定 `client_max_body_size`、基本安全標頭與 Rate Limit(視需求)
|
||||||
|
|
||||||
#### 2. 靜態文件無法載入
|
## 版本資訊(摘要)
|
||||||
```bash
|
|
||||||
# 檢查文件是否正確複製
|
|
||||||
docker exec -it todolist-single-prod ls -la /app/frontend/out
|
|
||||||
|
|
||||||
# 重新構建並啟動
|
- 架構:Nginx 反向代理 + Gunicorn 應用容器(對外 Port 12011)
|
||||||
docker-compose down
|
- 應用服務器:Gunicorn 21.2.0
|
||||||
docker-compose up -d --build
|
- 併發能力:預設約 32(可依硬體調整)
|
||||||
```
|
- 訪問端點:`http://localhost:12011`
|
||||||
|
|
||||||
#### 3. 資料庫連接失敗
|
|
||||||
- 確認網路連通性到 `mysql.theaken.com:33306`
|
|
||||||
- 驗證資料庫憑證
|
|
||||||
- 檢查防火牆設置
|
|
||||||
|
|
||||||
#### 4. LDAP認證失敗
|
|
||||||
- 確認網路連通性到 `panjit.com.tw:389`
|
|
||||||
- 驗證LDAP服務帳號憑證
|
|
||||||
- 檢查搜索基礎設置
|
|
||||||
|
|
||||||
#### 5. Dashboard/SPA頁面404錯誤 🆕
|
|
||||||
**問題**: 前端SPA路由(如 `/todos/`, `/dashboard/`)返回404 JSON錯誤
|
|
||||||
**原因**: Flask 404錯誤處理器攔截了所有404響應並返回JSON格式
|
|
||||||
**解決方案**:
|
|
||||||
```bash
|
|
||||||
# 重新構建應用(此問題已在最新版本修復)
|
|
||||||
docker-compose down
|
|
||||||
docker-compose up -d --build
|
|
||||||
```
|
|
||||||
|
|
||||||
**技術詳情**:
|
|
||||||
- 修復了 `app.py` 中404錯誤處理器,只對API路徑返回JSON錯誤
|
|
||||||
- 非API路徑(SPA路由)現在正確返回 `index.html`
|
|
||||||
- 修復了SPA路由處理邏輯,確保靜態文件與路由正確分離
|
|
||||||
|
|
||||||
#### 6. Flask開發服務器警告 (已升級為生產服務器) 🆕
|
|
||||||
**問題**: 使用Flask開發服務器在生產環境,顯示警告且無法支撐多人使用
|
|
||||||
**影響**: 200人規模使用時會出現嚴重延遲和請求超時
|
|
||||||
**解決方案**:
|
|
||||||
```bash
|
|
||||||
# 系統已升級為Gunicorn生產服務器
|
|
||||||
docker-compose down
|
|
||||||
docker-compose up -d --build
|
|
||||||
```
|
|
||||||
|
|
||||||
**升級詳情**:
|
|
||||||
- ✅ **Gunicorn生產服務器**: 4 workers × 2 threads = 8個同時請求處理能力
|
|
||||||
- ✅ **支持200+用戶**: 完全滿足企業級使用需求
|
|
||||||
- ✅ **自動重啟機制**: 防止記憶體洩漏,提高穩定性
|
|
||||||
- ✅ **無開發服務器警告**: 生產就緒配置
|
|
||||||
- ✅ **120秒超時設置**: 適合複雜查詢操作
|
|
||||||
- ✅ **自動負載平衡**: 多進程處理請求分配
|
|
||||||
|
|
||||||
**性能對比**:
|
|
||||||
| 項目 | Flask開發服務器 | Gunicorn生產服務器 |
|
|
||||||
|------|----------------|-------------------|
|
|
||||||
| 並發處理 | 1個請求 | **8個同時請求** |
|
|
||||||
| 適用規模 | 5-10人 | **200+人** |
|
|
||||||
| 穩定性 | 低 | **高** |
|
|
||||||
| 生產就緒 | ❌ | **✅** |
|
|
||||||
|
|
||||||
### 健康檢查端點
|
|
||||||
|
|
||||||
- **應用健康檢查**: `GET http://localhost:12011/api/health`
|
|
||||||
- **前端頁面檢查**: `GET http://localhost:12011`
|
|
||||||
|
|
||||||
## 🔒 安全考量
|
|
||||||
|
|
||||||
### 生產環境安全設置
|
|
||||||
|
|
||||||
1. **更改默認密鑰**:
|
|
||||||
- 修改 `SECRET_KEY` 和 `JWT_SECRET_KEY`
|
|
||||||
- 使用強密碼策略
|
|
||||||
|
|
||||||
2. **網路安全**:
|
|
||||||
- 考慮使用反向代理(Nginx)
|
|
||||||
- 配置HTTPS證書
|
|
||||||
- 限制外部訪問
|
|
||||||
|
|
||||||
3. **監控與日誌**:
|
|
||||||
- 設置日誌輪轉
|
|
||||||
- 監控系統資源
|
|
||||||
- 設置告警機制
|
|
||||||
|
|
||||||
## 📞 支援聯繫
|
|
||||||
|
|
||||||
- **系統管理員**: `ymirliu@panjit.com.tw`
|
|
||||||
- **技術支援**: 參考專案文檔或聯繫開發團隊
|
|
||||||
|
|
||||||
## 📝 版本資訊
|
|
||||||
|
|
||||||
- **系統版本**: 2.1.0 🆕
|
|
||||||
- **架構**: 單一容器部署
|
|
||||||
- **應用服務器**: Gunicorn 21.2.0 (生產級WSGI服務器)
|
|
||||||
- **並發能力**: 4 workers × 2 threads = 8個同時請求
|
|
||||||
- **支持規模**: 200+用戶
|
|
||||||
- **Docker映像**: `todolist-single:latest`
|
|
||||||
- **訪問地址**: `http://localhost:12011`
|
|
||||||
|
|
||||||
### 🚀 v2.1.0 更新內容
|
|
||||||
- ✅ 升級為Gunicorn生產服務器,支持大規模用戶使用
|
|
||||||
- ✅ 修復SPA路由404錯誤問題
|
|
||||||
- ✅ 優化Badge圓形顯示問題
|
|
||||||
- ✅ 修復所有hardcoded API URL問題
|
|
||||||
- ✅ 完善錯誤處理和健康檢查機制
|
|
||||||
|
|
||||||
## 🔄 從舊版本升級
|
|
||||||
|
|
||||||
如果從分離式部署升級:
|
|
||||||
|
|
||||||
1. 停止舊的分離式服務
|
|
||||||
```bash
|
|
||||||
docker stop todo-backend-prod todo-frontend-prod
|
|
||||||
docker rm todo-backend-prod todo-frontend-prod
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 部署新的單一容器
|
|
||||||
```bash
|
|
||||||
./deploy.sh # 或 deploy.bat
|
|
||||||
```
|
|
||||||
|
|
||||||
3. 驗證服務正常
|
|
||||||
```bash
|
|
||||||
curl http://localhost:12011/api/health
|
|
||||||
curl http://localhost:12011
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**注意**: 本文件包含敏感配置資訊,請妥善保管,僅限授權人員查看。
|
|
@@ -53,4 +53,4 @@ HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=40s \
|
|||||||
CMD curl -f http://localhost:12011/api/health/ || exit 1
|
CMD curl -f http://localhost:12011/api/health/ || exit 1
|
||||||
|
|
||||||
# Run with Gunicorn for production (supports 200+ users)
|
# Run with Gunicorn for production (supports 200+ users)
|
||||||
CMD ["gunicorn", "--bind", "0.0.0.0:12011", "--workers", "4", "--threads", "2", "--timeout", "120", "--keep-alive", "2", "--max-requests", "1000", "--max-requests-jitter", "100", "app:app"]
|
CMD ["gunicorn", "--bind", "0.0.0.0:12011", "--worker-class", "gthread", "--workers", "4", "--threads", "8", "--timeout", "120", "--keep-alive", "10", "--max-requests", "2000", "--max-requests-jitter", "200", "--forwarded-allow-ips", "*", "--access-logfile", "-", "app:app"]
|
||||||
|
@@ -205,7 +205,7 @@ deploy.bat
|
|||||||
# Linux/macOS
|
# Linux/macOS
|
||||||
./deploy.sh
|
./deploy.sh
|
||||||
|
|
||||||
# 或使用 Docker Compose
|
# 或使用 Docker Compose(包含 Nginx 反向代理,對外開放 12011)
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
|
|
||||||
# 使用管理腳本 (Windows)
|
# 使用管理腳本 (Windows)
|
||||||
@@ -221,8 +221,8 @@ manage.bat
|
|||||||
|
|
||||||
2. **應用程式部署**
|
2. **應用程式部署**
|
||||||
- 設定反向代理 (Nginx/IIS)
|
- 設定反向代理 (Nginx/IIS)
|
||||||
- 應用服務: Port 12011 → 對外 Port 80/443
|
- 應用服務: Port 12011 → 對外 Port 12011(由 Nginx 監聽)
|
||||||
- 配置 SSL 證書
|
- 配置 SSL 證書(若需 443 可另行設定)
|
||||||
- 設定生產環境變數
|
- 設定生產環境變數
|
||||||
|
|
||||||
3. **背景服務設定**
|
3. **背景服務設定**
|
||||||
@@ -353,4 +353,3 @@ A: 確認 Next.js 構建正確完成,檢查 /app/frontend/out 目錄是否存
|
|||||||
|
|
||||||
**Q: Excel 匯入失敗**
|
**Q: Excel 匯入失敗**
|
||||||
A: 檢查文件格式和欄位映射,參考匯入模板
|
A: 檢查文件格式和欄位映射,參考匯入模板
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@ import os
|
|||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from flask import Flask, jsonify, send_from_directory, send_file, request
|
from flask import Flask, jsonify, send_from_directory, send_file, request
|
||||||
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from flask_jwt_extended import JWTManager
|
from flask_jwt_extended import JWTManager
|
||||||
from jwt.exceptions import InvalidTokenError
|
from jwt.exceptions import InvalidTokenError
|
||||||
@@ -95,6 +96,9 @@ def create_app(config_name=None):
|
|||||||
else:
|
else:
|
||||||
return jsonify({'error': 'Not Found'}), 404
|
return jsonify({'error': 'Not Found'}), 404
|
||||||
|
|
||||||
|
# Trust proxy headers (for nginx)
|
||||||
|
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1)
|
||||||
|
|
||||||
# Register error handlers
|
# Register error handlers
|
||||||
register_error_handlers(app)
|
register_error_handlers(app)
|
||||||
|
|
||||||
@@ -182,4 +186,4 @@ app = create_app()
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
debug_mode = os.environ.get('FLASK_ENV') == 'development'
|
debug_mode = os.environ.get('FLASK_ENV') == 'development'
|
||||||
app.run(host='0.0.0.0', port=12011, debug=debug_mode)
|
app.run(host='0.0.0.0', port=12011, debug=debug_mode)
|
||||||
|
@@ -4,8 +4,7 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: todolist-single-prod
|
container_name: todolist-single-prod
|
||||||
ports:
|
# No external port; only Nginx exposes ports
|
||||||
- "12011:12011"
|
|
||||||
environment:
|
environment:
|
||||||
# MySQL Database Configuration
|
# MySQL Database Configuration
|
||||||
- DATABASE_URL=mysql+pymysql://A060:WLeSCi0yhtc7@mysql.theaken.com:33306/db_A060
|
- DATABASE_URL=mysql+pymysql://A060:WLeSCi0yhtc7@mysql.theaken.com:33306/db_A060
|
||||||
@@ -16,7 +15,7 @@ services:
|
|||||||
- MYSQL_DATABASE=db_A060
|
- MYSQL_DATABASE=db_A060
|
||||||
- MYSQL_CHARSET=utf8mb4
|
- MYSQL_CHARSET=utf8mb4
|
||||||
|
|
||||||
# CORS Configuration (allow both localhost and 127.0.0.1)
|
# CORS Configuration (access via 12011)
|
||||||
- CORS_ORIGINS=http://localhost:12011,http://127.0.0.1:12011
|
- CORS_ORIGINS=http://localhost:12011,http://127.0.0.1:12011
|
||||||
|
|
||||||
# LDAP Configuration (Production)
|
# LDAP Configuration (Production)
|
||||||
@@ -47,7 +46,8 @@ services:
|
|||||||
- LOG_LEVEL=INFO
|
- LOG_LEVEL=INFO
|
||||||
|
|
||||||
# Frontend API URL (now pointing to same container)
|
# Frontend API URL (now pointing to same container)
|
||||||
- NEXT_PUBLIC_API_URL=http://localhost:12011
|
# Frontend uses relative API path in build; this is unused in runtime
|
||||||
|
#- NEXT_PUBLIC_API_URL=http://localhost:12011
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:12011/api/health/"]
|
test: ["CMD", "curl", "-f", "http://localhost:12011/api/health/"]
|
||||||
@@ -58,10 +58,23 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- todolist-network
|
- todolist-network
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx:1.25-alpine
|
||||||
|
container_name: todolist-nginx
|
||||||
|
depends_on:
|
||||||
|
- todolist-app
|
||||||
|
ports:
|
||||||
|
- "12011:12011"
|
||||||
|
volumes:
|
||||||
|
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- todolist-network
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
todolist-network:
|
todolist-network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
app-logs:
|
app-logs:
|
||||||
driver: local
|
driver: local
|
||||||
|
40
docs/DEPLOYMENT_NGINX_UPDATE.md
Normal file
40
docs/DEPLOYMENT_NGINX_UPDATE.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Nginx 反向代理與對外 Port 12011(部署更新)
|
||||||
|
|
||||||
|
本專案現已於 Docker Compose 中加入 `nginx` 服務作為對外入口,並統一只開放 `12011`。此更新提升連線穩定度與可擴充性,適用約 200 名同時使用者。
|
||||||
|
|
||||||
|
## 架構與 Port
|
||||||
|
- `nginx`:對外 `12011:12011`,反向代理到 `todolist-app:12011`。
|
||||||
|
- `todolist-app`:Flask API + 前端靜態檔;僅在容器網路內對 Nginx 提供服務。
|
||||||
|
- 存取網址:`http://localhost:12011`
|
||||||
|
- CORS 預設允許:`http://localhost:12011`, `http://127.0.0.1:12011`
|
||||||
|
|
||||||
|
## Gunicorn 參數(現行)
|
||||||
|
```bash
|
||||||
|
--bind 0.0.0.0:12011
|
||||||
|
--worker-class gthread
|
||||||
|
--workers 4
|
||||||
|
--threads 8
|
||||||
|
--timeout 120
|
||||||
|
--keep-alive 10
|
||||||
|
--max-requests 2000
|
||||||
|
--max-requests-jitter 200
|
||||||
|
--forwarded-allow-ips *
|
||||||
|
```
|
||||||
|
並發估算:4 workers × 8 threads ≈ 32(可依 CPU 調整)。
|
||||||
|
|
||||||
|
## Nginx 設定檔
|
||||||
|
- 路徑:`nginx/nginx.conf`
|
||||||
|
- 監聽:`listen 12011;`
|
||||||
|
- 反代:`/api/*` 與其餘路由至 `todolist-app:12011`
|
||||||
|
- 已啟用 gzip、keepalive 與代理緩衝;可依需要擴充(例如 TLS/HTTP2、靜態快取)
|
||||||
|
|
||||||
|
## 啟動
|
||||||
|
```bash
|
||||||
|
docker-compose up --build -d
|
||||||
|
# 確認
|
||||||
|
curl http://localhost:12011/api/health/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 備註
|
||||||
|
- 若需 TLS/443,請在 `nginx/nginx.conf` 中加入對應的 server 區塊與憑證設定。
|
||||||
|
- `todolist-app` 不再對外曝露 Port,請僅對外開放 12011。
|
@@ -13,7 +13,6 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Divider,
|
Divider,
|
||||||
Badge,
|
|
||||||
Chip,
|
Chip,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import {
|
import {
|
||||||
@@ -378,24 +377,26 @@ const Sidebar: React.FC<SidebarProps> = ({ collapsed, onToggleCollapse, onClose
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{item.badge && (
|
{item.badge && (
|
||||||
<Badge
|
<Box
|
||||||
badgeContent={item.badge}
|
component="span"
|
||||||
color="primary"
|
|
||||||
sx={{
|
sx={{
|
||||||
'& .MuiBadge-badge': {
|
ml: 1,
|
||||||
backgroundColor: item.color || (actualTheme === 'dark' ? '#3b82f6' : '#1976d2'),
|
backgroundColor: item.color || (actualTheme === 'dark' ? '#3b82f6' : '#1976d2'),
|
||||||
color: '#ffffff',
|
color: '#ffffff',
|
||||||
fontSize: '0.75rem',
|
fontSize: '0.75rem',
|
||||||
minWidth: 20,
|
lineHeight: 1,
|
||||||
height: 20,
|
minWidth: 20,
|
||||||
fontWeight: 600,
|
height: 20,
|
||||||
borderRadius: '50%',
|
px: 1,
|
||||||
display: 'flex',
|
fontWeight: 600,
|
||||||
alignItems: 'center',
|
borderRadius: 10,
|
||||||
justifyContent: 'center',
|
display: 'inline-flex',
|
||||||
},
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{item.badge}
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
@@ -634,4 +635,4 @@ const Sidebar: React.FC<SidebarProps> = ({ collapsed, onToggleCollapse, onClose
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Sidebar;
|
export default Sidebar;
|
||||||
|
77
nginx/nginx.conf
Normal file
77
nginx/nginx.conf
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
multi_accept on;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
tcp_nopush on;
|
||||||
|
tcp_nodelay on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
types_hash_max_size 4096;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_comp_level 5;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
|
||||||
|
|
||||||
|
map $http_upgrade $connection_upgrade {
|
||||||
|
default upgrade;
|
||||||
|
'' close;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream app_backend {
|
||||||
|
server todolist-app:12011 max_fails=3 fail_timeout=10s;
|
||||||
|
keepalive 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 12011;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
# Adjust as needed for uploads
|
||||||
|
client_max_body_size 20m;
|
||||||
|
|
||||||
|
# Proxy API requests to Flask/Gunicorn
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://app_backend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
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_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
proxy_send_timeout 120s;
|
||||||
|
proxy_connect_timeout 5s;
|
||||||
|
proxy_buffering on;
|
||||||
|
proxy_buffers 32 32k;
|
||||||
|
proxy_busy_buffers_size 64k;
|
||||||
|
}
|
||||||
|
|
||||||
|
# All other routes (frontend SPA and static) via backend
|
||||||
|
# Optionally, later mount static and serve directly
|
||||||
|
location / {
|
||||||
|
proxy_pass http://app_backend;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
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_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
proxy_send_timeout 120s;
|
||||||
|
proxy_connect_timeout 5s;
|
||||||
|
proxy_buffering on;
|
||||||
|
proxy_buffers 32 32k;
|
||||||
|
proxy_busy_buffers_size 64k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user