fix(cd): 讓 HUP 熱重載載入新版 app
All checks were successful
CD Pipeline / deploy (push) Successful in 9m40s
All checks were successful
CD Pipeline / deploy (push) Successful in 9m40s
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
> 本文件定義專案開發的核心準則與不可違反的規範
|
||||
> **建立日期**: 2026-01-12
|
||||
> **當前版本**: V10.28 (CD sync hot reload 降 502 版)
|
||||
> **當前版本**: V10.29 (Gunicorn HUP hot reload 修正版)
|
||||
> **最後更新**: 2026-04-30
|
||||
|
||||
---
|
||||
|
||||
4
app.py
4
app.py
@@ -95,8 +95,8 @@ except Exception as e:
|
||||
sys_log.error(f"無法檢測磁碟空間: {e}")
|
||||
|
||||
# 🚩 系統版本定義 (備份與顯示用)
|
||||
# 🚩 2026-04-30 V10.28: CD sync hot reload without app container restart
|
||||
SYSTEM_VERSION = "V10.28"
|
||||
# 🚩 2026-04-30 V10.29: Gunicorn HUP reload imports updated app code
|
||||
SYSTEM_VERSION = "V10.29"
|
||||
|
||||
# ==========================================
|
||||
# 🔒 SQL Injection 防護函數
|
||||
|
||||
@@ -254,7 +254,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.28"
|
||||
SYSTEM_VERSION = "V10.29"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
- Monitoring compose 的 cAdvisor 只需在 `monitoring` network 內提供 `cadvisor:8080` 給 Prometheus,不應綁定 host `8080`,避免與其他服務衝突;blackbox target 需要 `blackbox-exporter` 容器存在於同一 network。
|
||||
- 2026-04-30 線上驗證:目前 active MOMO UAT blackbox target 只保留 `https://mo.wooo.work`;`momo.wooo.work` 與 `wooo.work` 需等 DNS/Nginx 恢復後再加入 active monitoring。
|
||||
- 110 Gitea runner 必須只宣告 `ewoooc-host` label;若混入 `ubuntu-latest` / `awoooi-host`,EWOOOC 與 AWOOOI workflows 會互相搶同一個 runner,導致推版卡住或跨專案污染。
|
||||
- CD sync 模式應對 `momo-pro-system` 發 Gunicorn `HUP` 熱重載,不重啟 app 容器;scheduler / telegram-bot 才用 compose restart,避免每次一般程式碼推版都產生短暫 502。
|
||||
- CD sync 模式應對 `momo-pro-system` 發 Gunicorn `HUP` 熱重載,不重啟 app 容器;scheduler / telegram-bot 才用 compose restart。Gunicorn 必須維持 `preload_app = False`,否則 HUP 只重啟 worker、但 app object 仍來自舊 master 預載程式碼。
|
||||
- App container 的 runtime `gunicorn.conf.py` 由 `docker-compose.yml` bind mount;若未來改 gunicorn 設定,不應再手動 `docker cp` 作為常態流程。
|
||||
|
||||
## 驗證紀錄
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
- **Active blackbox target 降噪**: 線上 Nginx 與 curl 驗證目前有效 MOMO 入口為 `https://mo.wooo.work`;`momo.wooo.work` 逾時、`wooo.work` DNS 不解析,先從 active UAT blackbox targets 移除,避免舊域名噪音誤導告警。
|
||||
- **action_plans schema drift 修復**: CodeReview pipeline 寫入 action plan 時發現線上表只有 NemoTron Group B 欄位;啟動期 PostgreSQL metadata repair 會補 `action_type` / `description` / `priority` / `metadata_json` 與 index,恢復 AI code review action plan 閉環。
|
||||
- **Gitea runner label 隔離**: EWOOOC CD workflow 改用 `ewoooc-host`;110 的 `/home/wooo/act-runner` runner config 必須只宣告 `ewoooc-host`,避免 user-level runner 混接 AWOOOI workflow。
|
||||
- **CD sync hot reload**: 一般 Python/模板同步不再 `restart momo-app`,改為 `docker kill -s HUP momo-pro-system` 讓 Gunicorn 熱重載 workers,只重啟 scheduler / telegram-bot,降低推版 502 窗口。
|
||||
- **CD sync hot reload**: 一般 Python/模板同步不再 `restart momo-app`,改為 `docker kill -s HUP momo-pro-system` 讓 Gunicorn 熱重載 workers,只重啟 scheduler / telegram-bot;Gunicorn 關閉 `preload_app`,確保 HUP 後 workers 會 import 新版 app code。
|
||||
|
||||
### 2026-04-28~29:Phase 3e 重構大戰 + daily_sales cache 隱形 bug 根除
|
||||
- **app.py 縮減 -10.8%**: 7,386 → 6,590 行,11 commits 全綠零 502。
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"""Gunicorn runtime config.
|
||||
|
||||
`preload_app` imports Flask before worker fork. Any SQLAlchemy engines created
|
||||
during import must drop inherited connection pools in each child worker.
|
||||
Workers import Flask themselves so `HUP` can reload bind-mounted Python files
|
||||
without restarting the app container. If preload is re-enabled, hot reload will
|
||||
restart workers but keep the preloaded app object from the old master process.
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -15,7 +16,7 @@ workers = int(os.getenv("WEB_CONCURRENCY", "4"))
|
||||
timeout = int(os.getenv("GUNICORN_TIMEOUT", "300"))
|
||||
accesslog = "-"
|
||||
errorlog = "-"
|
||||
preload_app = True
|
||||
preload_app = False
|
||||
|
||||
|
||||
def _dispose_engine(engine, label, server):
|
||||
|
||||
@@ -24,11 +24,13 @@ def test_cd_health_check_validates_internal_and_external_health():
|
||||
|
||||
def test_cd_sync_mode_hot_reloads_app_without_container_restart():
|
||||
workflow = CD_WORKFLOW.read_text(encoding="utf-8")
|
||||
gunicorn_config = (ROOT / "gunicorn.conf.py").read_text(encoding="utf-8")
|
||||
|
||||
assert "docker compose up -d --no-deps momo-app scheduler telegram-bot" in workflow
|
||||
assert "docker kill -s HUP momo-pro-system" in workflow
|
||||
assert "docker compose restart scheduler telegram-bot" in workflow
|
||||
assert "docker compose restart momo-app" not in workflow
|
||||
assert "preload_app = False" in gunicorn_config
|
||||
|
||||
|
||||
def test_cd_rebuild_builds_image_before_stopping_running_containers():
|
||||
|
||||
@@ -72,3 +72,9 @@ def test_post_fork_disposes_database_manager_instance_cache(monkeypatch):
|
||||
config.post_fork(_Server(), _Worker())
|
||||
|
||||
assert id(engine.pool) != before_pool
|
||||
|
||||
|
||||
def test_gunicorn_disables_preload_for_hup_hot_reload():
|
||||
config = _load_gunicorn_config()
|
||||
|
||||
assert config.preload_app is False
|
||||
|
||||
Reference in New Issue
Block a user