-
Notifications
You must be signed in to change notification settings - Fork 0
[Chore] FastAPI 운영 환경 설정 및 Secret 분리 #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| ENV=local | ||
|
|
||
| GEMINI_API_KEY= | ||
| GEMINI_MODEL=gemini-flash-latest | ||
| VIRUSTOTAL_API_KEY= | ||
| GOOGLE_SAFE_BROWSING_API_KEY= | ||
| MOCK_SECURITY_API=false | ||
|
|
||
| RABBITMQ_URL=amqp://safefam:safefam-local@localhost:5672/ | ||
| RABBITMQ_CONSUMER_ENABLED=false | ||
|
|
||
| NAIVE_BAYES_MODEL_PATH=data_science/SMSModel/phishing_model_artifact.pkl | ||
| NAIVE_BAYES_VECTORIZER_PATH=data_science/SMSModel/phishing_vectorizer.pkl |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| ENV=prod | ||
| FASTAPI_IMAGE_TAG= | ||
|
|
||
| GEMINI_API_KEY= | ||
| GEMINI_MODEL=gemini-flash-latest | ||
| VIRUSTOTAL_API_KEY= | ||
| GOOGLE_SAFE_BROWSING_API_KEY= | ||
|
|
||
| MOCK_SECURITY_API=false | ||
|
|
||
| RABBITMQ_URL= | ||
| RABBITMQ_ANALYSIS_EXCHANGE=safefam.analysis | ||
| RABBITMQ_ANALYSIS_REQUEST_QUEUE=safefam.analysis.requested.q | ||
| RABBITMQ_ANALYSIS_REQUEST_ROUTING_KEY=analysis.requested.v1 | ||
| RABBITMQ_ANALYSIS_COMPLETED_ROUTING_KEY=analysis.completed.v1 | ||
| RABBITMQ_ANALYSIS_PARTIAL_ROUTING_KEY=analysis.partial.v1 | ||
| RABBITMQ_ANALYSIS_FAILED_ROUTING_KEY=analysis.failed.v1 | ||
| RABBITMQ_ANALYSIS_DLQ=safefam.analysis.requested.dlq | ||
| RABBITMQ_ANALYSIS_DLQ_ROUTING_KEY=analysis.requested.dead.v1 | ||
| RABBITMQ_PREFETCH_COUNT=1 | ||
| RABBITMQ_CONSUMER_ENABLED=true | ||
| RABBITMQ_PUBLISH_TIMEOUT_SECONDS=5 | ||
| RABBITMQ_SHUTDOWN_TIMEOUT_SECONDS=30 | ||
| RABBITMQ_REQUEUE_BACKOFF_SECONDS=1 | ||
|
|
||
| NAIVE_BAYES_MODEL_PATH=/app/models/phishing_model_artifact.pkl | ||
| NAIVE_BAYES_VECTORIZER_PATH=/app/models/phishing_vectorizer.pkl |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,24 @@ | ||
| FROM python:3.11-slim | ||
|
|
||
| WORKDIR /workspace | ||
| WORKDIR /app | ||
|
|
||
| ENV PYTHONDONTWRITEBYTECODE=1 | ||
| ENV PYTHONUNBUFFERED=1 | ||
| ENV NAIVE_BAYES_MODEL_PATH=/app/models/phishing_model_artifact.pkl | ||
| ENV NAIVE_BAYES_VECTORIZER_PATH=/app/models/phishing_vectorizer.pkl | ||
|
|
||
| COPY requirements.txt . | ||
|
|
||
| RUN pip install --no-cache-dir -r requirements.txt | ||
|
|
||
| COPY app app | ||
| COPY data_science/SMSModel/phishing_model_artifact.pkl models/phishing_model_artifact.pkl | ||
| COPY data_science/SMSModel/phishing_vectorizer.pkl models/phishing_vectorizer.pkl | ||
|
|
||
| RUN useradd --create-home --shell /usr/sbin/nologin safefam | ||
| RUN useradd --create-home --shell /usr/sbin/nologin safefam \ | ||
| && chown -R safefam:safefam /app | ||
|
|
||
| USER safefam | ||
|
|
||
| EXPOSE 8000 | ||
|
|
||
| HEALTHCHECK \ | ||
| --interval=30s \ | ||
| --timeout=5s \ | ||
| --retries=3 \ | ||
| CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/', timeout=3)" | ||
|
|
||
| CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,18 +1,13 @@ | ||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||
| import logging | ||||||||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| from app.analysis.risk_policy import determine_text_risk_grade | ||||||||||||||||||||||||||||
| from app.core.config import settings | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| logger = logging.getLogger(__name__) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # 프로젝트 루트 기준 사전 학습된 아티팩트 위치 (data_science/SMSModel/train_sms.py 산출물) | ||||||||||||||||||||||||||||
| _BASE_DIR = Path(__file__).resolve().parents[3] | ||||||||||||||||||||||||||||
| _DEFAULT_MODEL_DIR = _BASE_DIR / "data_science" / "SMSModel" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| MODEL_PATH = Path(os.getenv("NAIVE_BAYES_MODEL_PATH", str(_DEFAULT_MODEL_DIR / "phishing_model_artifact.pkl"))) | ||||||||||||||||||||||||||||
| VECTORIZER_PATH = Path(os.getenv("NAIVE_BAYES_VECTORIZER_PATH", str(_DEFAULT_MODEL_DIR / "phishing_vectorizer.pkl"))) | ||||||||||||||||||||||||||||
| MODEL_PATH = settings.NAIVE_BAYES_MODEL_PATH | ||||||||||||||||||||||||||||
| VECTORIZER_PATH = settings.NAIVE_BAYES_VECTORIZER_PATH | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # --- 전처리 정규식 : data_science/SMSModel/train_sms.py의 정규화/피처 추출 로직과 반드시 동일하게 유지 --- | ||||||||||||||||||||||||||||
| # (학습 시 벡터라이저가 본 입력 분포와 서빙 시 입력 분포가 어긋나면 모델이 무의미해짐) | ||||||||||||||||||||||||||||
|
|
@@ -92,9 +87,12 @@ def _load_artifacts() -> None: | |||||||||||||||||||||||||||
| _classes = artifact["classes"] | ||||||||||||||||||||||||||||
| _vectorizer = joblib.load(VECTORIZER_PATH) | ||||||||||||||||||||||||||||
| logger.info(f"[NaiveBayes] 모델 로드 완료 (threshold={_threshold})") | ||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||
| _load_error = str(e) | ||||||||||||||||||||||||||||
| logger.error(f"[NaiveBayes] 모델 로드 실패: {_load_error}") | ||||||||||||||||||||||||||||
| except Exception as exception: | ||||||||||||||||||||||||||||
| _load_error = type(exception).__name__ | ||||||||||||||||||||||||||||
| logger.error( | ||||||||||||||||||||||||||||
| "[NaiveBayes] 모델 로드 실패. error_type=%s", | ||||||||||||||||||||||||||||
| _load_error, | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
Comment on lines
+90
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win Do not expose the exception type through
Proposed fix except Exception as exception:
- _load_error = type(exception).__name__
+ error_type = type(exception).__name__
+ _load_error = "MODEL_LOAD_FAILED"
logger.error(
"[NaiveBayes] 모델 로드 실패. error_type=%s",
- _load_error,
+ error_type,
)📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.16.0)[warning] 90-90: Do not catch blind exception: (BLE001) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def is_model_loaded() -> bool: | ||||||||||||||||||||||||||||
|
|
@@ -139,8 +137,11 @@ async def analyze_text_with_naive_bayes(text: str) -> dict: | |||||||||||||||||||||||||||
| "error_message": None | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||
| logger.error(f"[NaiveBayes] 추론 중 비정상 에러 발생: {str(e)}") | ||||||||||||||||||||||||||||
| except Exception as exception: | ||||||||||||||||||||||||||||
| logger.error( | ||||||||||||||||||||||||||||
| "[NaiveBayes] 추론 중 비정상 오류 발생. error_type=%s", | ||||||||||||||||||||||||||||
| type(exception).__name__, | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||
| "engine": "naive_bayes", | ||||||||||||||||||||||||||||
| "is_available": False, | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Document the production source for
RABBITMQ_URL.RABBITMQ_URLis a required production secret, but the Parameter Store examples omit it. A deployment that follows these examples cannot create a valid.env.runtimefile.Add a
RABBITMQ_URLParameter Store path, or document the alternate secret-injection mechanism.Proposed fix
/safefam/prod/ai/GEMINI_API_KEY /safefam/prod/ai/VIRUSTOTAL_API_KEY /safefam/prod/ai/GOOGLE_SAFE_BROWSING_API_KEY +/safefam/prod/ai/RABBITMQ_URL📝 Committable suggestion
🤖 Prompt for AI Agents