diff --git a/deployment/README.md b/deployment/README.md index 9957f04..9d61ac4 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -76,6 +76,8 @@ Pour le lancement du docker en local (développement), il sera important de donn Pour un déploiement sur un cloud personnel ou grand public (avec docker-compose, kubernetes), il sera préférable de renseigner les variables d'environnement nécessaires dans `docker-compose.yml` ou dans les secrets. +Ajouter --rm comme argument pour que le conteneur soit supprimé après son arrêt. + Dans notre cas, il a été décidé d'utiliser Google Cloud. Une problématique classique se pose ici pour nous : on ne peut exposer plusieurs ports (contrairement à l'instruction ci-dessus) sur un service cloud. 2 options sont donc possibles: - Séparer le service en deux docker (un pour le backend fastAPI et un pour le frontend streamlit) diff --git a/deployment/api/.streamlit⁄config.toml b/deployment/api/.streamlit⁄config.toml new file mode 100644 index 0000000..9390ad3 --- /dev/null +++ b/deployment/api/.streamlit⁄config.toml @@ -0,0 +1,5 @@ +[server] +enableXsrfProtection = false +enableCORS = false +enableWebsocketCompression = false +baseUrlPath = "/" diff --git a/deployment/api/Dockerfile b/deployment/api/Dockerfile index 0027e06..302a15f 100644 --- a/deployment/api/Dockerfile +++ b/deployment/api/Dockerfile @@ -1,22 +1,33 @@ -# Utiliser une image légère Python 3.12 FROM python:3.12-slim -# Définir le dossier de travail WORKDIR /app -# Copier uniquement requirements.txt d'abord (pour optimiser le cache Docker) -COPY requirements.txt . +# Mettre à jour les packages et installer nginx +RUN apt-get update && \ + apt-get install -y nginx && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* -# Installer les dépendances +# Copier les fichiers de requirements et installer les dépendances Python +COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -# Copier le reste du code source +# Copier le code de l'application COPY . . -# Exposer les ports et 8501 (streamlit) et 8000 (fastAPI) +# Copier la configuration nginx +COPY nginx.conf /etc/nginx/nginx.conf -EXPOSE 8501 8000 +# Créer les répertoires nécessaires pour nginx +RUN mkdir -p /var/log/nginx && \ + mkdir -p /var/lib/nginx && \ + mkdir -p /run +# Donner les droits d'exécution au script de démarrage RUN chmod +x /app/start.sh +# Exposer le port 8080 +EXPOSE 8080 + +# Lancer le script de démarrage CMD ["/app/start.sh"] diff --git a/deployment/api/app.py b/deployment/api/app.py index 1284019..3cd4db4 100644 --- a/deployment/api/app.py +++ b/deployment/api/app.py @@ -3,6 +3,8 @@ import requests st.title("Analyse de Sentiment - Air Paradis") +API_BASE_URL = "http://127.0.0.1:8000" + tweet = st.text_area("Entrez votre tweet ici :", height=150) # Initialisation des états @@ -32,7 +34,7 @@ if st.button("Analyser le sentiment"): else: try: response = requests.post( - "http://localhost:8000/predict", + f"{API_BASE_URL}/predict", json={"tweet": tweet} ) if response.status_code == 200: @@ -60,7 +62,7 @@ if st.session_state.show_report_button and not st.session_state.report_sent: if st.button("Signaler une mauvaise prédiction"): try: response = requests.post( - "http://localhost:8000/report_error", + f"{API_BASE_URL}/report_error", json={ "tweet": tweet, "prediction": sentiment, diff --git a/deployment/api/email_service.py b/deployment/api/email_service.py index a1942db..5c66f28 100644 --- a/deployment/api/email_service.py +++ b/deployment/api/email_service.py @@ -15,6 +15,7 @@ def send_error_report_email(error_reports): smtp_password = os.getenv("SMTP_PASSWORD") smtp_from_alias = os.getenv("SMTP_FROM_ALIAS", smtp_email) # mail sender if not none (alias) admin_email = os.getenv("ADMIN_EMAIL") # this one will receive the mail + if not all([smtp_server, smtp_email, smtp_password, admin_email]): print("❌ Configuration email manquante dans .env") diff --git a/deployment/api/main.py b/deployment/api/main.py index 9220457..9a4678b 100644 --- a/deployment/api/main.py +++ b/deployment/api/main.py @@ -98,6 +98,8 @@ def load_model_and_artifacts(model_name=MODEL_NAME, stage=STAGE): # 🚀 Lancer l'API app = FastAPI(title="API Prédiction Sentiment - Air Paradis") +# app = FastAPI(title="API Prédiction Sentiment - Air Paradis", +# root_path="/api") try: model, tokenizer, embedding_type = load_model_and_artifacts() @@ -139,37 +141,6 @@ def predict_sentiment(request: TweetRequest): "probability": float(prediction_prob) } -# @app.post("/report_error") -# def report_error(request: ReportRequest): -# tweet = request.tweet.strip() -# prediction = request.prediction.strip().lower() -# probability = request.probability - -# if not tweet or not prediction: -# raise HTTPException(status_code=400, detail="Tweet ou prédiction manquants.") -# if probability < 0 or probability > 1: -# raise HTTPException(status_code=400, detail="Probabilité invalide. Elle doit être entre 0 et 1.") - -# # Ajouter ou mettre à jour le signalement du tweet avec la probabilité -# error_reports[tweet] = prediction + ' (p = ' + str(probability) + ') ' - -# # ✅ Envoi de l'email sans attendre un multiple de 3 -# try: -# email_success = send_error_report_email(error_reports.copy()) -# if email_success: -# print(f"✅ Email envoyé avec succès pour {len(error_reports)} signalements") -# report_sent = True -# else: -# print(f"❌ Échec de l'envoi de l'email") -# report_sent = False -# except Exception as e: -# print(f"❌ Erreur lors de l'envoi de l'email : {e}") -# report_sent = False - -# return {"report_sent": report_sent} - - - @app.post("/report_error") def report_error(request: ReportRequest): tweet = request.tweet.strip() diff --git a/deployment/api/nginx.conf b/deployment/api/nginx.conf new file mode 100644 index 0000000..de209c9 --- /dev/null +++ b/deployment/api/nginx.conf @@ -0,0 +1,34 @@ +worker_processes 1; + +events { worker_connections 1024; } + +http { + server { + listen 8080; + + # Proxy pour le backend FastAPI + location /api/ { + # Le / à la fin de proxy_pass est important, + # il retire /api/ avant de le transmettre à FastAPI. + proxy_pass http://127.0.0.1:8000/; + 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 pour le frontend Streamlit + location / { + # Le / à la fin est crucial pour que Streamlit fonctionne bien + proxy_pass http://127.0.0.1:8501/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + 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 86400; # Empêche les timeouts sur les connexions longues + } + } +} diff --git a/deployment/api/start.sh b/deployment/api/start.sh index e543031..ecd0379 100644 --- a/deployment/api/start.sh +++ b/deployment/api/start.sh @@ -1,7 +1,15 @@ #!/bin/bash -# Lancer Streamlit en arrière-plan -streamlit run app.py --server.port 8501 --server.address 0.0.0.0 & +# Lancer Streamlit en arrière-plan avec un baseUrlPath dédié +streamlit run app.py \ + --server.port 8501 \ + --server.address 127.0.0.1 \ + --server.baseUrlPath "/app" \ + --server.enableXsrfProtection false \ + & -# Lancer Uvicorn en premier plan (Docker "suit" ce processus) -uvicorn main:app --host 0.0.0.0 --port 8000 --loop asyncio +# Lancer Uvicorn en arrière-plan +uvicorn main:app --host 127.0.0.1 --port 8000 --loop asyncio & + +# Lancer nginx au premier plan (Docker suit ce processus) +nginx -g 'daemon off;'