From 31c2a493040bd64fea12051905462ec86e40f321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Mureau?= Date: Mon, 7 Jul 2025 15:13:01 +0200 Subject: [PATCH] Docker correctly configured --- .github/workflows/workflow.yml | 32 ++++++++++++++++++ deployment/api/.dockerignore | 14 ++++++++ deployment/api/Dockerfile | 21 ++++++++++++ .../api/__pycache__/main.cpython-312.pyc | Bin 7836 -> 7867 bytes deployment/api/docker-compose.yml | 31 +++++++++++++++++ deployment/api/email_service.py | 16 +++++---- deployment/api/main.py | 3 +- deployment/api/requirements.txt | 1 + deployment/api/start.sh | 7 ++++ deployment/api/tests/test_email_service.py | 24 ------------- 10 files changed, 118 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/workflow.yml create mode 100644 deployment/api/.dockerignore create mode 100644 deployment/api/Dockerfile create mode 100644 deployment/api/docker-compose.yml create mode 100644 deployment/api/start.sh diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 0000000..af458b6 --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,32 @@ +name: Build and Deploy API + +on: + push: + branches: [main] + +jobs: + build-and-run: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: ./deployment/api + push: false + tags: sentiment_api:latest + + - name: Run docker-compose + run: docker-compose up -d diff --git a/deployment/api/.dockerignore b/deployment/api/.dockerignore new file mode 100644 index 0000000..da47ce8 --- /dev/null +++ b/deployment/api/.dockerignore @@ -0,0 +1,14 @@ +# Ignorer les caches Python +__pycache__/ +*.pyc + +# Ignorer les artefacts temporaires +downloaded_artifacts/ +mlruns/ +logs/ + +# Ignorer les tests unitaires +tests/ + +# Ignorer les variables d'environnement. Elles seront plutôt chargées dans la commande docker run `docker run --env-file ./deployment/api/.env -p 8000:8000 sentiment-api` en développement local, et via des variables définies dans le yaml +*.env diff --git a/deployment/api/Dockerfile b/deployment/api/Dockerfile new file mode 100644 index 0000000..596420f --- /dev/null +++ b/deployment/api/Dockerfile @@ -0,0 +1,21 @@ +# 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 . + +# Installer les dépendances +RUN pip install --no-cache-dir -r requirements.txt + +# Copier le reste du code source +COPY . . + +# Exposer les ports 8000 (fastAPI) et 8501 (streamlit) +EXPOSE 8000 8501 + +RUN chmod +x /app/start.sh + +CMD ["/app/start.sh"] diff --git a/deployment/api/__pycache__/main.cpython-312.pyc b/deployment/api/__pycache__/main.cpython-312.pyc index 08c455a434e33daa606632d7a9dcbde0a45220b5..18201e03f59436b32671afa4e041e64bb1910ff2 100644 GIT binary patch delta 1218 zcmZ8hO>7%Q6yDkO+OgN`_$Q9Dwb!v@S&5-(fU2aeXp@R8Q7H`|{)8Kd?98StY`pIC zCa6&?Dilyb94PMwgTOkx2uyza>a|xcT~IX=yFNNg_H?+72@$A3!}id!R@Hnxg5Q%OQSeuC zXn(XrcB9m4fySyuQJ58I|As*0X1F8W^;SoGYl2ymI45S*)CQ@RMZpZxfh%bQ@sKjq zW1jKs$nOG{=@IL8DutSaN=IXtgC z5aCtvM5tGixT#dg82+J5XXPeX4c80VR?DRpc#-F8_KIl$IDki^r$`Y$j26fQ-ijWG z?_~rq&S4*iu@BTN(J-ful2x2hZw~w%2yaKmufP{;pRavg`C($_$AE!4MW}U`ZX!X zi(84parS`|9QcueKQ>6_9>z~{TIrrr_C2!f&=uP}3U9L*{x500k6z9aH&lE< icjSF!@0~i{*1yff6r(I`mF33*($;-(V@leZ;`%>3j}e&w delta 1261 zcmZ8hU2hvj6rI`i`fGRnZO7}6cw-kCCrycj($*yR8P)PUe@qG_1YqMd>Muym26=+9vRUg~rP_zTJ zXE>lcQEY#NoZ^aNV&ILErP`gw2{!F>#y`c2$B!DH}+_ znQ_`=ip)9{*y&wxR^e47e(Xb1+lgE)VFzFy$!%9XihNIIeme7+%-kL*&=Q8X_dYb4 zWnT+24TU}m(hSGH2hjC({oOz}xamOQj=SyVK+D~7J<-WkcjSfSz@p_Dl(j`O{gm~h z$l$G+25rgA43vv1`O&=sqeS&|dk;Mg2*=5cm<<$YLQ|liUZVpiNL`$S=gF=(w(}77^hL16LjzNK> z_;ZvhjDMAK6ER@ccU!Ff#vV_;p&o7niZ=(MWUt~+4Wodk+ z(HJ_FEE8CNc%8us@>5F5{qM}(GusBbT+?5~AJQE5*VDX1#B?=sgS+jz>AEf5ls?zK riv1iv`3oqLowOlL!lQq^O&+K3kBp0p5F9{ZQRNSufyE?$kYw>ch$j(% diff --git a/deployment/api/docker-compose.yml b/deployment/api/docker-compose.yml new file mode 100644 index 0000000..76fec07 --- /dev/null +++ b/deployment/api/docker-compose.yml @@ -0,0 +1,31 @@ +version: "3.9" +services: + api: + build: + context: ./deployment/api + dockerfile: Dockerfile + container_name: sentiment_api + ports: + - "8000:8000" # API FastAPI + - "8501:8501" # Streamlit + environment: + MLFLOW_TRACKING_URI: "${MLFLOW_TRACKING_URI}" # ex: http://mlflow:5000 + MLFLOW_S3_ENDPOINT_URL: "${MLFLOW_S3_ENDPOINT_URL}" # ex: url MinIO ou S3 + AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID}" # clé ID S3 + AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY}" # clé secrète S3 + + SMTP_SERVER: "${SMTP_SERVER}" # serveur SMTP + SMTP_PORT: "${SMTP_PORT}" # port SMTP (ex: 587) + SMTP_EMAIL: "${SMTP_EMAIL}" # email expéditeur + SMTP_PASSWORD: "${SMTP_PASSWORD}" # mot de passe SMTP + SMTP_FROM_ALIAS: "${SMTP_FROM_ALIAS}" # alias (optionnel) + ADMIN_EMAIL: "${ADMIN_EMAIL}" # email admin destinataire + +# only if you have a reverse proxy server +# networks: +# - swag + restart: always +# only if you have a reverse proxy server +# networks: +# swag: +# external: true diff --git a/deployment/api/email_service.py b/deployment/api/email_service.py index 58dfe5c..a1942db 100644 --- a/deployment/api/email_service.py +++ b/deployment/api/email_service.py @@ -11,10 +11,10 @@ def send_error_report_email(error_reports): """ smtp_server = os.getenv("SMTP_SERVER") smtp_port = int(os.getenv("SMTP_PORT", 587)) - smtp_email = os.getenv("SMTP_EMAIL") + smtp_email = os.getenv("SMTP_EMAIL") # main email address. If smt_from_alias is not, it will also be the mail sender smtp_password = os.getenv("SMTP_PASSWORD") - smtp_from_alias = os.getenv("SMTP_FROM_ALIAS", smtp_email) - admin_email = os.getenv("ADMIN_EMAIL") + 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") @@ -23,7 +23,11 @@ def send_error_report_email(error_reports): try: # Créer le message msg = MIMEMultipart() - msg['From'] = f"Air Paradis Monitor <{smtp_from_alias}>" + if smtp_from_alias is not None: + smtp_from = smtp_from_alias + else: + smtp_from = smtp_email + msg['From'] = f"Air Paradis Monitor <{smtp_from}>" msg['To'] = admin_email msg['Subject'] = f"🚨 Rapport d'erreurs - Prédictions sentiments ({datetime.now().strftime('%d/%m/%Y %H:%M')})" @@ -86,7 +90,7 @@ Un nouveau rapport d'erreurs a été généré pour l'API de prédiction de sent --- Cordialement, 🤖 API Air Paradis - Système de monitoring automatique -📧 Envoyé depuis : """ + smtp_from_alias +📧 Envoyé depuis : """ + smtp_from # Ajouter le corps du message msg.attach(MIMEText(body, 'plain', 'utf-8')) @@ -97,7 +101,7 @@ Cordialement, server.login(smtp_email, smtp_password) server.send_message(msg) - print(f"✅ Email de rapport envoyé avec succès depuis {smtp_from_alias}") + print(f"✅ Email de rapport envoyé avec succès depuis {smtp_from}") print(f"📊 JSON intégré dans le corps du message") return True diff --git a/deployment/api/main.py b/deployment/api/main.py index a123e43..9220457 100644 --- a/deployment/api/main.py +++ b/deployment/api/main.py @@ -14,7 +14,7 @@ from tensorflow.keras.preprocessing.sequence import pad_sequences PARENT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) sys.path.append(PARENT_DIR) # 🔐 Charger les variables d'environnement (.env à deux niveaux au-dessus) -load_dotenv(dotenv_path=os.path.join(PARENT_DIR, ".env")) +load_dotenv() # 🔁 Import de la fonction de prétraitement (LOCAL) @@ -69,6 +69,7 @@ def load_model_and_artifacts(model_name=MODEL_NAME, stage=STAGE): # Déterminer les chemins des artefacts local_dir = "./downloaded_artifacts" os.makedirs(local_dir, exist_ok=True) + print("Contenu de downloaded_artifacts :", os.listdir(local_dir)) base_path = "local_artifacts" tokenizer_file = "tokenizer.pickle" diff --git a/deployment/api/requirements.txt b/deployment/api/requirements.txt index f7d0971..fba6645 100644 --- a/deployment/api/requirements.txt +++ b/deployment/api/requirements.txt @@ -9,6 +9,7 @@ requests==2.32.4 # === Machine Learning === tensorflow==2.19.0 mlflow==3.1.1 +boto3==1.39.2 numpy==2.1.3 pandas==2.3.0 # === Traitement de texte === diff --git a/deployment/api/start.sh b/deployment/api/start.sh new file mode 100644 index 0000000..ee38fb0 --- /dev/null +++ b/deployment/api/start.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Lancer Streamlit en arrière-plan +streamlit run app.py --server.port 8501 --server.address 0.0.0.0 & + +# Lancer Uvicorn en premier plan (Docker "suit" ce processus) +uvicorn main:app --host 0.0.0.0 --port 8000 diff --git a/deployment/api/tests/test_email_service.py b/deployment/api/tests/test_email_service.py index 4a607a7..fe8a156 100644 --- a/deployment/api/tests/test_email_service.py +++ b/deployment/api/tests/test_email_service.py @@ -6,30 +6,6 @@ Created on Sat Jul 5 15:03:24 2025 @author: madfuckinman """ -# # test_alias_email.py -# import os -# from dotenv import load_dotenv -# from email_service import send_error_report_email - -# load_dotenv() - -# print("🔧 Configuration Email:") -# print(f"Serveur SMTP: {os.getenv('SMTP_SERVER')}") -# print(f"Authentification: {os.getenv('SMTP_EMAIL')}") -# print(f"Expéditeur (alias): {os.getenv('SMTP_FROM_ALIAS')}") -# print(f"Destinataire: {os.getenv('ADMIN_EMAIL')}") - -# # Test -# test_reports = { -# "Service client décevant": "positif", -# "J'adore cette compagnie": "negatif", -# "Vol annulé sans prévenir": "positif" -# } - -# print("\n📧 Test d'envoi depuis l'alias...") -# result = send_error_report_email(test_reports) -# print(f"Résultat : {'✅ Succès' if result else '❌ Échec'}") - import os import sys from dotenv import load_dotenv