Primera prueba de código de microservicios
This commit is contained in:
30
.gitea/workflows/build.yml
Normal file
30
.gitea/workflows/build.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
name: build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: [k8s, kaniko]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Login a Nexus
|
||||||
|
env:
|
||||||
|
NEXUS_USER: ${{ secrets.NEXUS_USER }}
|
||||||
|
NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
echo "$NEXUS_PASSWORD" | docker login nexus.rancherk3.duckdns.org -u "$NEXUS_USER" --password-stdin
|
||||||
|
|
||||||
|
- name: Build & Push image with Kaniko
|
||||||
|
image: gcr.io/kaniko-project/executor:latest
|
||||||
|
command:
|
||||||
|
- /kaniko/executor
|
||||||
|
args:
|
||||||
|
- --context=.
|
||||||
|
- --dockerfile=./Dockerfile
|
||||||
|
- --destination=nexus.rancherk3.duckdns.org/tfm/microserviciospython:latest
|
||||||
8
.gitea/workflows/test.yml
Normal file
8
.gitea/workflows/test.yml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
name: test
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
echo:
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- run: echo "Gitea Actions funcionando!"
|
||||||
72
Dockerfile
Normal file
72
Dockerfile
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# =========================
|
||||||
|
# Etapa de Build
|
||||||
|
# =========================
|
||||||
|
FROM python:3.12-slim AS build
|
||||||
|
|
||||||
|
# Instalar dependencias de build mínimas
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
python3-venv \
|
||||||
|
pip \
|
||||||
|
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
|
# Crear un entorno virtual Python para build
|
||||||
|
RUN python -m venv /opt/venv
|
||||||
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
|
|
||||||
|
# Actualizar pip y herramientas de build
|
||||||
|
RUN pip install --upgrade pip setuptools wheel build pipenv
|
||||||
|
|
||||||
|
# Copiar código y preparar build
|
||||||
|
ADD src /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Instalar dependencias y construir la rueda
|
||||||
|
RUN pipenv install --deploy --system || true
|
||||||
|
RUN python -m build -w
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Etapa de Runtime
|
||||||
|
# =========================
|
||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
ARG BUILD_DATE
|
||||||
|
LABEL org.label-schema.maintainer="Diego Fernández Carvajal (diego.fdezcarvajal@emtmadrid.es)" \
|
||||||
|
org.label-schema.build-dockerfile="11/12/2025" \
|
||||||
|
org.label-schema.build=$BUILD_DATE
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV TZ=Europe/Madrid
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Instalar dependencias mínimas de runtime
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
python3-venv \
|
||||||
|
supervisor \
|
||||||
|
tzdata \
|
||||||
|
locales \
|
||||||
|
&& ln -fs /usr/share/zoneinfo/$TZ /etc/localtime \
|
||||||
|
&& echo $TZ > /etc/timezone \
|
||||||
|
&& echo "es_ES.UTF-8 UTF-8" > /etc/locale.gen \
|
||||||
|
&& echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen \
|
||||||
|
&& locale-gen \
|
||||||
|
&& apt-get clean && apt-get autoremove -y \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
|
# Copiar supervisord
|
||||||
|
COPY /man/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
|
||||||
|
# Copiar ruedas desde build
|
||||||
|
COPY --from=build /app/dist/*.whl /app/
|
||||||
|
|
||||||
|
# Crear entorno virtual para runtime
|
||||||
|
RUN python -m venv /opt/venv
|
||||||
|
|
||||||
|
# Instalar ruedas en el entorno virtual
|
||||||
|
RUN pip install --upgrade pip setuptools wheel
|
||||||
|
RUN pip install /app/*.whl -t /app
|
||||||
|
|
||||||
|
CMD ["/usr/bin/supervisord","-c","/etc/supervisor/conf.d/supervisord.conf"]
|
||||||
23
man/supervisord.conf
Normal file
23
man/supervisord.conf
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
[supervisord]
|
||||||
|
logfile=/tmp/supervisord.log ; supervisord log file
|
||||||
|
logfile_maxbytes=10MB ; maximum size of logfile before rotation
|
||||||
|
logfile_backups=2 ; number of backed up logfiles
|
||||||
|
loglevel=error ; info, debug, warn, trace
|
||||||
|
pidfile=/var/run/supervisord.pid ; pidfile location
|
||||||
|
nodaemon=true ; run supervisord as a daemon
|
||||||
|
minfds=1024 ; number of startup file descriptors
|
||||||
|
minprocs=200 ; number of process descriptors
|
||||||
|
user=root ; default user
|
||||||
|
|
||||||
|
[unix_http_server]
|
||||||
|
file = /tmp/supervisor.sock ; supervisor unix http server sock file
|
||||||
|
|
||||||
|
[program:microservicios]
|
||||||
|
command=python3 flask.py
|
||||||
|
directory=/app
|
||||||
|
stdout_logfile=/dev/fd/1
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/fd/2
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
85
src/flask.py
Normal file
85
src/flask.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
from flask import Flask, jsonify, request
|
||||||
|
import os
|
||||||
|
import psycopg2
|
||||||
|
import pandas as pd
|
||||||
|
import xgboost as xgb
|
||||||
|
import joblib
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# Configuración de la base de datos
|
||||||
|
DB_CONFIG = {
|
||||||
|
'dbname': os.getenv('DB_NAME', 'postgres'),
|
||||||
|
'user': os.getenv('DB_USER', 'postgres'),
|
||||||
|
'password': os.getenv('DB_PASSWORD', 'tfmuocdfcarvajal'),
|
||||||
|
'host': os.getenv('DB_HOST', '10.10.5.32'),
|
||||||
|
'port': os.getenv('DB_PORT', '5432')
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ruta para entrenar o cargar el modelo
|
||||||
|
MODEL_PATH = 'modelo_xgb.joblib'
|
||||||
|
|
||||||
|
def get_db_connection():
|
||||||
|
conn = psycopg2.connect(**DB_CONFIG)
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def fetch_data():
|
||||||
|
conn = get_db_connection()
|
||||||
|
query = """
|
||||||
|
SELECT h3, hour, dow, total_events, total_nulos, nulo_rate
|
||||||
|
FROM demanda_h3_hour
|
||||||
|
"""
|
||||||
|
df = pd.read_sql(query, conn)
|
||||||
|
conn.close()
|
||||||
|
return df
|
||||||
|
|
||||||
|
def train_model(df):
|
||||||
|
# Variables predictoras
|
||||||
|
X = df[['hour', 'dow', 'total_events']]
|
||||||
|
# Variable objetivo: nulo_rate > 0 -> 1, else 0
|
||||||
|
y = (df['nulo_rate'] > 0).astype(int)
|
||||||
|
|
||||||
|
model = xgb.XGBClassifier(
|
||||||
|
max_depth=4, n_estimators=100, learning_rate=0.1, use_label_encoder=False, eval_metric='logloss'
|
||||||
|
)
|
||||||
|
model.fit(X, y)
|
||||||
|
joblib.dump(model, MODEL_PATH)
|
||||||
|
return model
|
||||||
|
|
||||||
|
def load_model():
|
||||||
|
try:
|
||||||
|
model = joblib.load(MODEL_PATH)
|
||||||
|
return model
|
||||||
|
except:
|
||||||
|
df = fetch_data()
|
||||||
|
return train_model(df)
|
||||||
|
|
||||||
|
model = load_model()
|
||||||
|
|
||||||
|
@app.route('/predict', methods=['GET'])
|
||||||
|
def predict():
|
||||||
|
# Parámetros de la consulta
|
||||||
|
hour = int(request.args.get('hour'))
|
||||||
|
dow = int(request.args.get('dow'))
|
||||||
|
total_events = int(request.args.get('total_events', 1)) # valor por defecto si no se pasa
|
||||||
|
|
||||||
|
X_pred = pd.DataFrame([[hour, dow, total_events]], columns=['hour', 'dow', 'total_events'])
|
||||||
|
prob = model.predict_proba(X_pred)[0][1] # Probabilidad de nulo
|
||||||
|
return jsonify({
|
||||||
|
'hour': hour,
|
||||||
|
'dow': dow,
|
||||||
|
'total_events': total_events,
|
||||||
|
'predicted_nulo_prob': float(prob)
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.route('/demand', methods=['GET'])
|
||||||
|
def demand():
|
||||||
|
df = fetch_data()
|
||||||
|
X_pred = df[['hour', 'dow', 'total_events']]
|
||||||
|
df['predicted_nulo_prob'] = model.predict_proba(X_pred)[:,1].astype(float)
|
||||||
|
# Convertimos a JSON
|
||||||
|
result = df.to_dict(orient='records')
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host='0.0.0.0', port=5000)
|
||||||
33
src/pyproject.toml
Normal file
33
src/pyproject.toml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools >= 61.0"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "microservicios"
|
||||||
|
version = "1.0.1"
|
||||||
|
requires-python = ">= 3.8"
|
||||||
|
description = "Microservicios para Estimaciones"
|
||||||
|
license={text = "EULA"}
|
||||||
|
|
||||||
|
authors = [
|
||||||
|
{name = "Diego Fernández", email = "carvajal.diego@gmail.com"}
|
||||||
|
]
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
'flask',
|
||||||
|
'psycopg2-binary',
|
||||||
|
'pandas',
|
||||||
|
'xgboost',
|
||||||
|
'scikit-learn',
|
||||||
|
'joblib'
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
include-package-data = true
|
||||||
|
py-modules = ["flask"]
|
||||||
|
|
||||||
|
[tool.setuptools.packages]
|
||||||
|
find = {}
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Documentation='https://https://gitea.tfmuocdfcarvajal.duckdns.org/TFM/microservicios_python'
|
||||||
Reference in New Issue
Block a user