From 526bd36c5ae662e897280ece22d18e33dcb156ac Mon Sep 17 00:00:00 2001 From: projectdx Date: Tue, 27 Jan 2026 16:01:56 +0900 Subject: [PATCH] Gunicorn Transition: add wsgi entrypoint and gunicorn config, bump version to 4.1.41 --- gunicorn_config.py | 32 +++++++++++ lib/framework/init_main.py | 2 - lib/framework/version.py | 2 +- restart_gunicorn.sh | 110 +++++++++++++++++++++++++++++++++++++ wsgi.py | 16 ++++++ 로그 보는법.md | 34 ++++++++++++ 6 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 gunicorn_config.py create mode 100755 restart_gunicorn.sh create mode 100644 wsgi.py create mode 100644 로그 보는법.md diff --git a/gunicorn_config.py b/gunicorn_config.py new file mode 100644 index 0000000..54c3806 --- /dev/null +++ b/gunicorn_config.py @@ -0,0 +1,32 @@ +# Gunicorn 최적화 설정 (Bottleproof v2) +import multiprocessing +import os + +# 1. 네트워크 설정 +bind = "0.0.0.0:9099" +backlog = 2048 + +# 2. 프로세스 관리 +# GeventWebSocketWorker: SocketIO(WebSocket) 지원을 위해 필수 +worker_class = "geventwebsocket.gunicorn.workers.GeventWebSocketWorker" +workers = 1 # SocketIO 세션 일관성을 위해 1개 권장 (Redis message queue 미사용 시) +worker_connections = 1000 +timeout = 600 +keepalive = 5 + +# 3. PID 관리 (정밀한 프로세스 제어를 위해 추가) +pidfile = "data/gunicorn.pid" + +# 4. 로깅 설정 +# data/log 폴더 자동 생성 보장 +log_dir = "data/log" +if not os.path.exists(log_dir): + os.makedirs(log_dir, exist_ok=True) + +accesslog = os.path.join(log_dir, "gunicorn_access.log") +errorlog = os.path.join(log_dir, "gunicorn_error.log") +loglevel = "info" +capture_output = True + +# 5. 개발 편의성 +reload = False # 운영 환경에서는 False diff --git a/lib/framework/init_main.py b/lib/framework/init_main.py index 48b3dfd..a289399 100644 --- a/lib/framework/init_main.py +++ b/lib/framework/init_main.py @@ -319,8 +319,6 @@ class Framework: # 이건 필요 없음 self.config['DEFINE']['GIT_VERSION_URL'] = 'https://raw.githubusercontent.com/flaskfarm/flaskfarm/main/lib/framework/version.py' self.config['DEFINE']['CHANGELOG'] = 'https://github.com/flaskfarm/flaskfarm' - #self.config['DEFINE']['WEB_DIRECT_URL'] = "http://52.78.103.230:49734" - def __process_args(self): # celery 에서 args 처리시 문제 발생. diff --git a/lib/framework/version.py b/lib/framework/version.py index ea0c0c5..df5e82e 100644 --- a/lib/framework/version.py +++ b/lib/framework/version.py @@ -1 +1 @@ -VERSION="4.1.40" +VERSION="4.1.41" diff --git a/restart_gunicorn.sh b/restart_gunicorn.sh new file mode 100755 index 0000000..415498e --- /dev/null +++ b/restart_gunicorn.sh @@ -0,0 +1,110 @@ +#!/bin/bash +# FlaskFarm + Celery Gunicorn 정밀 재시작 스크립트 (v2) +# "한 번에 정확하게" - PID 기반 제어 및 포트 자동 대기 포함 + +cd /Volumes/WD/Users/Work/python/flaskfarm + +# 색상 및 스타일 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' +BOLD='\033[1m' + +echo -e "${YELLOW}${BOLD}========================================${NC}" +echo -e "${YELLOW}${BOLD} FlaskFarm Gunicorn Precise Launcher${NC}" +echo -e "${YELLOW}${BOLD}========================================${NC}" + +# 1. 환경 및 의존성 검증 +echo -e "\n${BLUE}[1/5] 환경 및 의존성 검증...${NC}" + +# pyenv 초기화 +export PYENV_ROOT="$HOME/.pyenv" +export PATH="$PYENV_ROOT/bin:$PATH" +if command -v pyenv &> /dev/null; then + eval "$(pyenv init -)" + eval "$(pyenv virtualenv-init -)" 2>/dev/null + pyenv rehash +fi + +# 필수 패키지 체크 및 버전 강제 (Gunicorn gevent worker 요구사항 대응) +PYTHON_EXE=$(which python) +if ! $PYTHON_EXE -c "import gevent; from packaging import version; exit(0) if version.parse(gevent.__version__) >= version.parse('24.10.1') else exit(1)" &>/dev/null; then + echo -e "${YELLOW} ! gevent 버전 낮음 또는 미설치 감지. 업그레이드를 진행합니다 (>=24.10.1)...${NC}" + $PYTHON_EXE -m pip install "gevent>=24.10.1" gevent-websocket gunicorn --upgrade +fi +echo -e " ✓ Python: $($PYTHON_EXE --version)" +echo -e " ✓ Gunicorn: $($PYTHON_EXE -m gunicorn --version | head -n 1)" + +# 2. 기존 프로세스 정밀 종료 +echo -e "\n${RED}[2/5] 기존 프로세스 종료 중...${NC}" + +# PID 파일 기반 종료 (Gunicorn) +PID_FILE="data/gunicorn.pid" +if [ -f "$PID_FILE" ]; then + OLD_PID=$(cat "$PID_FILE") + if ps -p $OLD_PID > /dev/null; then + echo " - PID $OLD_PID 종료 시도 (SIGTERM)..." + kill -15 $OLD_PID 2>/dev/null + sleep 2 + if ps -p $OLD_PID > /dev/null; then + echo " - PID $OLD_PID 강제 종료 (SIGKILL)..." + kill -9 $OLD_PID 2>/dev/null + fi + fi + rm -f "$PID_FILE" +fi + +# 남은 고아 프로세스 정리 +pkill -9 -f "gunicorn.*wsgi:app" 2>/dev/null +echo " ✓ FlaskFarm Gunicorn 프로세스 정리 완료" + +# Celery 워커 종료 +pkill -9 -f "celery.*worker" 2>/dev/null +pkill -9 -f "celery -A flaskfarm" 2>/dev/null +echo " ✓ Celery 워커 프로세스 정리 완료" + +# 3. 포트 가용성 확인 (9099) +echo -e "\n${BLUE}[3/5] 포트(9099) 상태 확인...${NC}" +MAX_WAIT=10 +COUNT=0 +while lsof -i :9099 > /dev/null; do + if [ $COUNT -ge $MAX_WAIT ]; then + echo -e "${RED} ! 포트 9099가 여전히 점유 중입니다. 강제 해제 시도...${NC}" + lsof -ti :9099 | xargs kill -9 2>/dev/null + break + fi + echo " - 포트 해제 대기 중... ($COUNT/$MAX_WAIT)" + sleep 1 + COUNT=$((COUNT+1)) +done +echo " ✓ 포트 9099 사용 가능" + +# 4. Celery 워커 시작 (백그라운드) +echo -e "\n${GREEN}[4/5] Celery 워커 시작 중...${NC}" +CELERY_LOG_FILE="data/log/celery_worker.log" +mkdir -p "data/log" + +export GEVENT_NOWAITPID=1 +nohup celery -A main.celery worker \ + --loglevel=info \ + --pool=gevent \ + --concurrency=4 \ + --config_filepath=data/config_mac.yaml \ + --running_type=local \ + > "$CELERY_LOG_FILE" 2>&1 & + +echo " ✓ Celery 워커 시작됨 (Log: $CELERY_LOG_FILE)" + +# 5. Gunicorn 정밀 기동 +echo -e "\n${GREEN}[5/5] Gunicorn 기동...${NC}" +echo -e "${YELLOW}========================================${NC}" + +# 프레임워크 우회 환경변수 +export FF_CONFIG="data/config_mac.yaml" +export FF_GUNICORN="true" +export FF_REPEAT="0" + +# 실행 (exec을 통해 셸을 gunicorn으로 대체하여 신호 전달 보장) +exec $PYTHON_EXE -m gunicorn -c gunicorn_config.py wsgi:app diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 0000000..5942ade --- /dev/null +++ b/wsgi.py @@ -0,0 +1,16 @@ +import os +import sys + +# Gunicorn 환경에서 프레임워크의 argparse 충돌을 방지하기 위한 sys.argv 조작 +# main.py나 프레임워크 코드를 직접 수정하지 않기 위해 별도의 wsgi entrypoint를 사용합니다. +if 'gunicorn' in sys.argv[0] or os.environ.get('FF_GUNICORN') == 'true': + config_path = os.environ.get('FF_CONFIG', '.') + repeat = os.environ.get('FF_REPEAT', '0') + # 프레임워크가 기대하는 인자 형식으로 sys.argv를 재구성 + sys.argv = [sys.argv[0], '--config', config_path, '--repeat', repeat] + +# 프레임워크의 메인 앱 임포트 +from main import app, celery + +if __name__ == "__main__": + app.run() diff --git a/로그 보는법.md b/로그 보는법.md new file mode 100644 index 0000000..60a8f68 --- /dev/null +++ b/로그 보는법.md @@ -0,0 +1,34 @@ +# FlaskFarm Gunicorn 환경 로그 확인 가이드 + +Gunicorn 및 Celery 환경에서 발생하는 로그를 확인하는 방법입니다. + +## 1. 실시간 로그 모니터링 (추천) +터미널에서 아래 명령어를 실행하여 실시간으로 로그가 쌓이는 것을 볼 수 있습니다. + +### 웹 서버 (Gunicorn) 로그 +웹 접속 오류나 서버 상태를 확인할 때 사용합니다. +```bash +tail -f data/log/gunicorn_error.log +``` + +### 백그라운드 작업 (Celery) 로그 +다운로드 및 데이터 처리 등 비동기 작업 상태를 확인할 때 사용합니다. +```bash +tail -f data/log/celery_worker.log +``` + +### 애플리케이션 통합 로그 (FlaskFarm) +플러그인 로직 및 시스템 전반의 상세 로그를 확인할 때 사용합니다. +```bash +tail -f data/log/framework.log +``` + +--- + +## 2. 주요 로그 파일 경로 +모든 로그는 `data/log/` 폴더에 집중되어 있습니다. + +- `data/log/gunicorn_access.log`: HTTP 요청 기록 (방문자 기록) +- `data/log/gunicorn_error.log`: Gunicorn 서버 구동 및 에러 기록 +- `data/log/celery_worker.log`: Celery 워커의 기동 및 작업 로그 +- `data/log/framework.log`: 애플리케이션 비즈니스 로직 로그