Added a nginx reverse proxy to handle the two services (FastAPI+Streamlit) in one single container. Now, it is possible using the two services with only one exposed port (mandatory on Google Cloud)
This commit is contained in:
parent
1720a7c454
commit
b388c0b3f9
8 changed files with 79 additions and 45 deletions
|
|
@ -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)
|
||||
|
|
|
|||
5
deployment/api/.streamlit⁄config.toml
Normal file
5
deployment/api/.streamlit⁄config.toml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
[server]
|
||||
enableXsrfProtection = false
|
||||
enableCORS = false
|
||||
enableWebsocketCompression = false
|
||||
baseUrlPath = "/"
|
||||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ def send_error_report_email(error_reports):
|
|||
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")
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
34
deployment/api/nginx.conf
Normal file
34
deployment/api/nginx.conf
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;'
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue