deployml-core 0.0.56__tar.gz → 0.0.58__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {deployml_core-0.0.56 → deployml_core-0.0.58}/PKG-INFO +1 -1
- {deployml_core-0.0.56 → deployml_core-0.0.58}/pyproject.toml +1 -1
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/cli/cli.py +0 -1
- deployml_core-0.0.58/src/deployml/docker/fastapi/.dockerignore +11 -0
- deployml_core-0.0.58/src/deployml/docker/fastapi/Dockerfile +37 -0
- deployml_core-0.0.58/src/deployml/docker/fastapi/main.py +127 -0
- deployml_core-0.0.58/src/deployml/docker/fastapi/requirements.txt +7 -0
- deployml_core-0.0.58/src/deployml/docker/grafana-container/Dockerfile +23 -0
- deployml_core-0.0.58/src/deployml/docker/grafana-container/README.md +3 -0
- deployml_core-0.0.58/src/deployml/docker/grafana-container/entrypoint.sh +10 -0
- deployml_core-0.0.58/src/deployml/docker/mlflow/Dockerfile +39 -0
- deployml_core-0.0.58/src/deployml/docker/mlflow/requirements.txt +4 -0
- deployml_core-0.0.58/src/deployml/notebook/docker.py +224 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/gcp/cloud_run/main.tf.j2 +14 -0
- deployml_core-0.0.58/src/deployml/terraform/modules/bigquery/cloud/gcp/main.tf +38 -0
- deployml_core-0.0.58/src/deployml/terraform/modules/bigquery/cloud/gcp/outputs.tf +3 -0
- deployml_core-0.0.58/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/drift_metrics.json +9 -0
- deployml_core-0.0.58/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/ground_truth.json +5 -0
- deployml_core-0.0.58/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/offline_features.json +7 -0
- deployml_core-0.0.58/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/predictions.json +8 -0
- deployml_core-0.0.58/src/deployml/terraform/modules/bigquery/cloud/gcp/variables.tf +11 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/utils/helpers.py +1 -1
- {deployml_core-0.0.56 → deployml_core-0.0.58}/LICENSE +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/README.md +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/__init__.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/api.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/cli/__init__.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/diagnostics/__init__.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/diagnostics/doctor.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/enum/__init__.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/enum/cloud_provider.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/enum/deployment_type.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/notebook/__init__.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/notebook/deployment.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/notebook/display.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/notebook/stack.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/notebook/urls.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/notebook.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/gcp/cloud_run/base_main.tf.j2 +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/gcp/cloud_run/mlflow_main.tf.j2 +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/gcp/cloud_run/terraform.tfvars.j2 +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/gcp/cloud_run/variables.tf.j2 +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/gcp/cloud_run/wandb_main.tf.j2 +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/gcp/cloud_vm/main.tf.j2 +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/gcp/cloud_vm/terraform.tfvars.j2 +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/gcp/cloud_vm/variables.tf.j2 +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/kubernetes_local/deployment.yaml.j2 +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/kubernetes_local/mlflow-deployment.yaml.j2 +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/kubernetes_local/mlflow-service.yaml.j2 +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/kubernetes_local/service.yaml.j2 +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/cloud_sql_postgres/main.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/cloud_sql_postgres/outputs.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/cloud_sql_postgres/variables.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/cron/cloud/gcp/cloud_run/main.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/cron/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/cron/cloud/gcp/cloud_run/variables.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/explainability_monitoring/cloud/gcp/cloud_run/main.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/explainability_monitoring/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/explainability_monitoring/cloud/gcp/cloud_run/variables.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/fairness_monitoring/cloud/gcp/cloud_run/main.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/fairness_monitoring/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/fairness_monitoring/cloud/gcp/cloud_run/variables.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/fastapi/cloud/gcp/cloud_run/main.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/fastapi/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/fastapi/cloud/gcp/cloud_run/variables.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_run/main.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_run/variables.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_vm/feast_env.tpl +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_vm/main.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_vm/outputs.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_vm/variables.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/grafana/cloud/gcp/cloud_run/main.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/grafana/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/grafana/cloud/gcp/cloud_run/variables.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_run/main.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_run/variables.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_vm/main.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_vm/variables.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/offline_scoring/cloud/gcp/cloud_run/main.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/offline_scoring/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/offline_scoring/cloud/gcp/cloud_run/variables.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/teardown/cloud/gcp/cloud_function/main.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/teardown/cloud/gcp/cloud_function/requirements.txt +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/teardown/cloud/gcp/cloudbuild.yaml +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/teardown/cloud/gcp/main.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/teardown/cloud/gcp/outputs.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/teardown/cloud/gcp/teardown_script.sh +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/teardown/cloud/gcp/variables.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_run/main.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_run/variables.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_vm/main.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_vm/variables.tf +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/utils/__init__.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/utils/banner.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/utils/constants.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/utils/infracost.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/utils/kubernetes_gke.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/utils/kubernetes_local.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/utils/menu.py +0 -0
- {deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/utils/teardown.py +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Build for Linux/amd64 platform (not Apple Silicon)
|
|
2
|
+
FROM --platform=linux/amd64 python:3.9-slim
|
|
3
|
+
|
|
4
|
+
# Set working directory
|
|
5
|
+
WORKDIR /app
|
|
6
|
+
|
|
7
|
+
# Install system dependencies
|
|
8
|
+
RUN apt-get update && apt-get install -y \
|
|
9
|
+
curl \
|
|
10
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
11
|
+
|
|
12
|
+
# Install Python dependencies
|
|
13
|
+
RUN pip install --no-cache-dir --upgrade pip setuptools wheel
|
|
14
|
+
|
|
15
|
+
# Copy requirements and install
|
|
16
|
+
COPY requirements.txt .
|
|
17
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
18
|
+
|
|
19
|
+
# Copy application code
|
|
20
|
+
COPY main.py .
|
|
21
|
+
|
|
22
|
+
# Create non-root user
|
|
23
|
+
RUN useradd -m -s /bin/bash fastapi && \
|
|
24
|
+
chown -R fastapi:fastapi /app
|
|
25
|
+
|
|
26
|
+
# Switch to non-root user
|
|
27
|
+
USER fastapi
|
|
28
|
+
|
|
29
|
+
# Expose port
|
|
30
|
+
EXPOSE 8000
|
|
31
|
+
|
|
32
|
+
# Health check
|
|
33
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
|
|
34
|
+
CMD curl -f http://localhost:8000/health || exit 1
|
|
35
|
+
|
|
36
|
+
# Run the application
|
|
37
|
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from fastapi import FastAPI, HTTPException
|
|
2
|
+
from fastapi.responses import JSONResponse
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from typing import List, Dict, Any, Optional
|
|
5
|
+
import os
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import mlflow
|
|
9
|
+
import mlflow.pyfunc
|
|
10
|
+
|
|
11
|
+
app = FastAPI(
|
|
12
|
+
title="FastAPI Demo",
|
|
13
|
+
description="Simple FastAPI application for Kubernetes deployment demo",
|
|
14
|
+
version="1.0.0"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Hardcoded MLflow configuration
|
|
18
|
+
MLFLOW_TRACKING_URI = "http://mlflow-service:5000"
|
|
19
|
+
MODEL_NAME = "HousingPriceModel"
|
|
20
|
+
|
|
21
|
+
# Global model variable
|
|
22
|
+
model = None
|
|
23
|
+
|
|
24
|
+
# Pydantic models
|
|
25
|
+
class PredictionRequest(BaseModel):
|
|
26
|
+
features: Dict[str, float]
|
|
27
|
+
|
|
28
|
+
class PredictionResponse(BaseModel):
|
|
29
|
+
prediction: float
|
|
30
|
+
timestamp: str
|
|
31
|
+
model_used: Optional[str] = None
|
|
32
|
+
|
|
33
|
+
class HealthResponse(BaseModel):
|
|
34
|
+
status: str
|
|
35
|
+
timestamp: str
|
|
36
|
+
port: int
|
|
37
|
+
mlflow_connected: bool
|
|
38
|
+
model_loaded: bool
|
|
39
|
+
|
|
40
|
+
def load_model_from_mlflow():
|
|
41
|
+
"""Load model from MLflow Model Registry"""
|
|
42
|
+
global model
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
mlflow.set_tracking_uri(MLFLOW_TRACKING_URI)
|
|
46
|
+
model = mlflow.pyfunc.load_model(f"models:/{MODEL_NAME}/Production")
|
|
47
|
+
print(f"✓ Loaded model '{MODEL_NAME}' from Production stage")
|
|
48
|
+
return True
|
|
49
|
+
except Exception as e:
|
|
50
|
+
print(f"⚠️ Could not load model from MLflow: {e}")
|
|
51
|
+
model = None
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
# Load model at startup
|
|
55
|
+
@app.on_event("startup")
|
|
56
|
+
async def startup_event():
|
|
57
|
+
"""Load MLflow model on startup"""
|
|
58
|
+
load_model_from_mlflow()
|
|
59
|
+
|
|
60
|
+
@app.get("/")
|
|
61
|
+
async def root():
|
|
62
|
+
"""Root endpoint"""
|
|
63
|
+
return {
|
|
64
|
+
"service": "FastAPI Demo",
|
|
65
|
+
"version": "1.0.0",
|
|
66
|
+
"model_loaded": model is not None,
|
|
67
|
+
"model_name": MODEL_NAME,
|
|
68
|
+
"mlflow_uri": MLFLOW_TRACKING_URI,
|
|
69
|
+
"endpoints": {
|
|
70
|
+
"health": "/health",
|
|
71
|
+
"predict": "/predict",
|
|
72
|
+
"docs": "/docs"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@app.get("/health", response_model=HealthResponse)
|
|
77
|
+
async def health():
|
|
78
|
+
"""Health check endpoint"""
|
|
79
|
+
port = int(os.getenv("FASTAPI_PORT", "8000"))
|
|
80
|
+
return HealthResponse(
|
|
81
|
+
status="healthy",
|
|
82
|
+
timestamp=datetime.now().isoformat(),
|
|
83
|
+
port=port,
|
|
84
|
+
mlflow_connected=True,
|
|
85
|
+
model_loaded=model is not None
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
@app.post("/predict", response_model=PredictionResponse)
|
|
89
|
+
async def predict(request: PredictionRequest):
|
|
90
|
+
"""Prediction endpoint using MLflow model"""
|
|
91
|
+
global model
|
|
92
|
+
|
|
93
|
+
# If model is not loaded, return -1
|
|
94
|
+
if model is None:
|
|
95
|
+
return PredictionResponse(
|
|
96
|
+
prediction=-1.0,
|
|
97
|
+
timestamp=datetime.now().isoformat(),
|
|
98
|
+
model_used=None
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
# Convert features dict to DataFrame in correct order
|
|
103
|
+
# Features: bedrooms, bathrooms, area_sqft, lot_size, year_built, city, state
|
|
104
|
+
feature_order = ['bedrooms', 'bathrooms', 'area_sqft', 'lot_size', 'year_built', 'city', 'state']
|
|
105
|
+
feature_values = [request.features.get(f, 0.0) for f in feature_order]
|
|
106
|
+
X = pd.DataFrame([feature_values], columns=feature_order)
|
|
107
|
+
|
|
108
|
+
prediction = float(model.predict(X)[0])
|
|
109
|
+
model_used = f"MLflow: {MODEL_NAME}"
|
|
110
|
+
|
|
111
|
+
return PredictionResponse(
|
|
112
|
+
prediction=prediction,
|
|
113
|
+
timestamp=datetime.now().isoformat(),
|
|
114
|
+
model_used=model_used
|
|
115
|
+
)
|
|
116
|
+
except Exception as e:
|
|
117
|
+
# On any error, return -1
|
|
118
|
+
return PredictionResponse(
|
|
119
|
+
prediction=-1.0,
|
|
120
|
+
timestamp=datetime.now().isoformat(),
|
|
121
|
+
model_used=None
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if __name__ == "__main__":
|
|
125
|
+
import uvicorn
|
|
126
|
+
port = int(os.getenv("FASTAPI_PORT", "8000"))
|
|
127
|
+
uvicorn.run(app, host="0.0.0.0", port=port)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
FROM grafana/grafana:10.4.5
|
|
2
|
+
|
|
3
|
+
# Switch to root to copy files and change permissions
|
|
4
|
+
USER root
|
|
5
|
+
|
|
6
|
+
# Configure Grafana to listen on Cloud Run's expected port
|
|
7
|
+
ENV GF_SERVER_HTTP_PORT=8080 \
|
|
8
|
+
GF_SECURITY_ADMIN_USER=admin \
|
|
9
|
+
GF_SECURITY_ADMIN_PASSWORD=admin
|
|
10
|
+
|
|
11
|
+
# Copy entrypoint script and set permissions
|
|
12
|
+
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
13
|
+
RUN chmod 755 /usr/local/bin/entrypoint.sh
|
|
14
|
+
|
|
15
|
+
# Create provisioning directories
|
|
16
|
+
RUN mkdir -p /etc/grafana/provisioning/datasources \
|
|
17
|
+
/etc/grafana/provisioning/dashboards
|
|
18
|
+
|
|
19
|
+
# Switch back to the default grafana user for runtime
|
|
20
|
+
USER grafana
|
|
21
|
+
|
|
22
|
+
# Set custom entrypoint
|
|
23
|
+
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# Map Cloud Run's PORT → Grafana's expected var
|
|
5
|
+
export GF_SERVER_HTTP_PORT="${PORT:-${GF_SERVER_HTTP_PORT:-8080}}"
|
|
6
|
+
|
|
7
|
+
echo "Starting Grafana on port ${GF_SERVER_HTTP_PORT}..."
|
|
8
|
+
[ -n "${GF_DATABASE_URL:-}" ] && echo "Using database: ${GF_DATABASE_URL}"
|
|
9
|
+
|
|
10
|
+
exec /run.sh
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Build for Linux/amd64 platform (not Apple Silicon)
|
|
2
|
+
FROM --platform=linux/amd64 python:3.11-slim
|
|
3
|
+
|
|
4
|
+
# Set working directory
|
|
5
|
+
WORKDIR /app
|
|
6
|
+
|
|
7
|
+
# Install system dependencies
|
|
8
|
+
RUN apt-get update && apt-get install -y \
|
|
9
|
+
curl \
|
|
10
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
11
|
+
|
|
12
|
+
# Install Python dependencies
|
|
13
|
+
RUN pip install --no-cache-dir --upgrade pip setuptools wheel
|
|
14
|
+
|
|
15
|
+
# Copy requirements and install
|
|
16
|
+
COPY requirements.txt .
|
|
17
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
18
|
+
|
|
19
|
+
# Create non-root user
|
|
20
|
+
RUN useradd -m -s /bin/bash mlflow && \
|
|
21
|
+
chown -R mlflow:mlflow /app
|
|
22
|
+
|
|
23
|
+
# Create directories for MLflow data
|
|
24
|
+
RUN mkdir -p /mlflow-artifacts && \
|
|
25
|
+
chown -R mlflow:mlflow /mlflow-artifacts
|
|
26
|
+
|
|
27
|
+
# Switch to non-root user
|
|
28
|
+
USER mlflow
|
|
29
|
+
|
|
30
|
+
# Expose port
|
|
31
|
+
EXPOSE 5000
|
|
32
|
+
|
|
33
|
+
# Health check
|
|
34
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
|
|
35
|
+
CMD curl -f http://localhost:5000/health || exit 1
|
|
36
|
+
|
|
37
|
+
# Default command - MLflow server
|
|
38
|
+
CMD ["mlflow", "server", "--host", "0.0.0.0", "--port", "5000"]
|
|
39
|
+
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
class ImageBuildError(Exception):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
def build_images(
|
|
9
|
+
docker_root: Path,
|
|
10
|
+
gcp_project_id: Optional[str] = None,
|
|
11
|
+
region: str = "us-west1",
|
|
12
|
+
repository: str = "mlops-images",
|
|
13
|
+
tag: str = "latest",
|
|
14
|
+
create_repo: bool = False,
|
|
15
|
+
dry_run: bool = False,
|
|
16
|
+
) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Build all Docker images located in subdirectories of docker_root.
|
|
19
|
+
|
|
20
|
+
Each subdirectory containing a Dockerfile will be built as a separate image.
|
|
21
|
+
The image name will match the subdirectory name.
|
|
22
|
+
|
|
23
|
+
Local mode:
|
|
24
|
+
Builds images locally using Docker.
|
|
25
|
+
|
|
26
|
+
GCP mode:
|
|
27
|
+
Uses Cloud Build and pushes images to Artifact Registry.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
docker_root: Root folder containing subfolders with Dockerfiles.
|
|
31
|
+
gcp_project_id: If provided, use Cloud Build in this GCP project.
|
|
32
|
+
region: GCP region for Artifact Registry.
|
|
33
|
+
repository: Artifact Registry repository name.
|
|
34
|
+
tag: Docker image tag.
|
|
35
|
+
create_repo: Whether to create Artifact Registry repository (GCP mode only).
|
|
36
|
+
dry_run: If True, print commands without executing them.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
docker_root = Path(docker_root)
|
|
40
|
+
|
|
41
|
+
if not docker_root.exists():
|
|
42
|
+
raise ValueError(f"Docker root does not exist: {docker_root}")
|
|
43
|
+
|
|
44
|
+
# Discover services
|
|
45
|
+
services = [
|
|
46
|
+
d for d in docker_root.iterdir()
|
|
47
|
+
if d.is_dir() and (d / "Dockerfile").exists()
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
if not services:
|
|
51
|
+
print("No Dockerfiles found.")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
print(f"Found {len(services)} service(s):")
|
|
55
|
+
for s in services:
|
|
56
|
+
print(f" - {s.name}")
|
|
57
|
+
|
|
58
|
+
print()
|
|
59
|
+
|
|
60
|
+
# ----------------------------------------
|
|
61
|
+
# GCP MODE
|
|
62
|
+
# ----------------------------------------
|
|
63
|
+
if gcp_project_id:
|
|
64
|
+
|
|
65
|
+
image_base = f"{region}-docker.pkg.dev/{gcp_project_id}/{repository}"
|
|
66
|
+
|
|
67
|
+
# Create Artifact Registry repo if requested
|
|
68
|
+
if create_repo:
|
|
69
|
+
create_cmd = [
|
|
70
|
+
"gcloud", "artifacts", "repositories", "create", repository,
|
|
71
|
+
"--repository-format=docker",
|
|
72
|
+
"--location", region,
|
|
73
|
+
"--project", gcp_project_id,
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
if dry_run:
|
|
77
|
+
print("Would create Artifact Registry repository:")
|
|
78
|
+
print(" " + " ".join(create_cmd))
|
|
79
|
+
print()
|
|
80
|
+
else:
|
|
81
|
+
print("Ensuring Artifact Registry repository exists...")
|
|
82
|
+
subprocess.run(create_cmd, check=False) # safe if already exists
|
|
83
|
+
print()
|
|
84
|
+
|
|
85
|
+
# Build each service
|
|
86
|
+
for service_dir in services:
|
|
87
|
+
service_name = service_dir.name
|
|
88
|
+
image_uri = f"{image_base}/{service_name}:{tag}"
|
|
89
|
+
|
|
90
|
+
build_cmd = [
|
|
91
|
+
"gcloud", "builds", "submit",
|
|
92
|
+
str(service_dir),
|
|
93
|
+
"--tag", image_uri,
|
|
94
|
+
"--project", gcp_project_id,
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
if dry_run:
|
|
98
|
+
print("Would submit Cloud Build:")
|
|
99
|
+
print(" " + " ".join(build_cmd))
|
|
100
|
+
print()
|
|
101
|
+
else:
|
|
102
|
+
print(f"Building {service_name} via Cloud Build...")
|
|
103
|
+
subprocess.run(build_cmd, check=True)
|
|
104
|
+
print(f"Pushed: {image_uri}")
|
|
105
|
+
print()
|
|
106
|
+
|
|
107
|
+
# ----------------------------------------
|
|
108
|
+
# LOCAL MODE
|
|
109
|
+
# ----------------------------------------
|
|
110
|
+
else:
|
|
111
|
+
for service_dir in services:
|
|
112
|
+
service_name = service_dir.name
|
|
113
|
+
image_name = f"{service_name}:{tag}"
|
|
114
|
+
|
|
115
|
+
build_cmd = [
|
|
116
|
+
"docker", "build",
|
|
117
|
+
"-t", image_name,
|
|
118
|
+
str(service_dir),
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
if dry_run:
|
|
122
|
+
print("Would build locally:")
|
|
123
|
+
print(" " + " ".join(build_cmd))
|
|
124
|
+
print()
|
|
125
|
+
else:
|
|
126
|
+
print(f"Building {service_name} locally...")
|
|
127
|
+
subprocess.run(build_cmd, check=True)
|
|
128
|
+
print(f"Built: {image_name}")
|
|
129
|
+
print()
|
|
130
|
+
|
|
131
|
+
if dry_run:
|
|
132
|
+
print("Dry run complete. No commands were executed.")
|
|
133
|
+
|
|
134
|
+
# -----------------------------
|
|
135
|
+
# Local Docker Build
|
|
136
|
+
# -----------------------------
|
|
137
|
+
|
|
138
|
+
def _validate_docker():
|
|
139
|
+
try:
|
|
140
|
+
subprocess.run(
|
|
141
|
+
["docker", "--version"],
|
|
142
|
+
check=True,
|
|
143
|
+
stdout=subprocess.PIPE,
|
|
144
|
+
stderr=subprocess.PIPE,
|
|
145
|
+
)
|
|
146
|
+
except Exception:
|
|
147
|
+
raise ImageBuildError("Docker is not installed or not available in PATH.")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _build_locally(service_dirs: list[Path], tag: str):
|
|
151
|
+
print("Building images locally with Docker...\n")
|
|
152
|
+
|
|
153
|
+
for service_dir in service_dirs:
|
|
154
|
+
service_name = service_dir.name
|
|
155
|
+
image_name = f"{service_name}:{tag}"
|
|
156
|
+
|
|
157
|
+
print(f"Building {image_name} ...")
|
|
158
|
+
|
|
159
|
+
subprocess.run(
|
|
160
|
+
[
|
|
161
|
+
"docker",
|
|
162
|
+
"build",
|
|
163
|
+
"-t",
|
|
164
|
+
image_name,
|
|
165
|
+
str(service_dir),
|
|
166
|
+
],
|
|
167
|
+
check=True,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
print(f"Successfully built {image_name}\n")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# -----------------------------
|
|
174
|
+
# GCP Cloud Build
|
|
175
|
+
# -----------------------------
|
|
176
|
+
|
|
177
|
+
def _validate_gcloud():
|
|
178
|
+
try:
|
|
179
|
+
subprocess.run(
|
|
180
|
+
["gcloud", "--version"],
|
|
181
|
+
check=True,
|
|
182
|
+
stdout=subprocess.PIPE,
|
|
183
|
+
stderr=subprocess.PIPE,
|
|
184
|
+
)
|
|
185
|
+
except Exception:
|
|
186
|
+
raise ImageBuildError("gcloud CLI is not installed or not available in PATH.")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _build_with_cloud_build(
|
|
190
|
+
service_dirs: list[Path],
|
|
191
|
+
gcp_project_id: str,
|
|
192
|
+
region: str,
|
|
193
|
+
repository: str,
|
|
194
|
+
tag: str,
|
|
195
|
+
):
|
|
196
|
+
print(f"Building images using Cloud Build in project '{gcp_project_id}'...\n")
|
|
197
|
+
|
|
198
|
+
for service_dir in service_dirs:
|
|
199
|
+
service_name = service_dir.name
|
|
200
|
+
|
|
201
|
+
image_uri = (
|
|
202
|
+
f"{region}-docker.pkg.dev/"
|
|
203
|
+
f"{gcp_project_id}/"
|
|
204
|
+
f"{repository}/"
|
|
205
|
+
f"{service_name}:{tag}"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
print(f"Submitting Cloud Build for {image_uri} ...")
|
|
209
|
+
|
|
210
|
+
subprocess.run(
|
|
211
|
+
[
|
|
212
|
+
"gcloud",
|
|
213
|
+
"builds",
|
|
214
|
+
"submit",
|
|
215
|
+
str(service_dir),
|
|
216
|
+
"--tag",
|
|
217
|
+
image_uri,
|
|
218
|
+
"--project",
|
|
219
|
+
gcp_project_id,
|
|
220
|
+
],
|
|
221
|
+
check=True,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
print(f"Successfully built and pushed {image_uri}\n")
|
{deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/gcp/cloud_run/main.tf.j2
RENAMED
|
@@ -216,6 +216,20 @@ module "{{ stage_name }}_{{ tool.name }}" {
|
|
|
216
216
|
{% endfor %}
|
|
217
217
|
{% endfor %}
|
|
218
218
|
|
|
219
|
+
# Bigquery table for features
|
|
220
|
+
{% for stage in stack %}
|
|
221
|
+
{% for stage_name, tool in stage.items() %}
|
|
222
|
+
{% if stage_name == "data_store" and tool.name == "bigquery" %}
|
|
223
|
+
module "{{ stage_name }}_{{ tool.name }}" {
|
|
224
|
+
source = "./modules/{{ tool.name }}/cloud/gcp"
|
|
225
|
+
project_id = var.project_id
|
|
226
|
+
region = var.region
|
|
227
|
+
dataset_id = var.dataset_id
|
|
228
|
+
}
|
|
229
|
+
{% endif %}
|
|
230
|
+
{% endfor %}
|
|
231
|
+
{% endfor %}
|
|
232
|
+
|
|
219
233
|
# Workflow orchestration (cron scheduling) module
|
|
220
234
|
{% for stage in stack %}
|
|
221
235
|
{% for stage_name, tool in stage.items() %}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
resource "google_bigquery_dataset" "mlops" {
|
|
2
|
+
project = var.project_id
|
|
3
|
+
dataset_id = var.dataset_id
|
|
4
|
+
location = var.region
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
resource "google_bigquery_table" "drift_metrics" {
|
|
8
|
+
dataset_id = google_bigquery_dataset.mlops.dataset_id
|
|
9
|
+
table_id = "drift_metrics"
|
|
10
|
+
project = var.project_id
|
|
11
|
+
|
|
12
|
+
schema = file("${path.module}/schemas/drift_metrics.json")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
resource "google_bigquery_table" "ground_truth" {
|
|
16
|
+
dataset_id = google_bigquery_dataset.mlops.dataset_id
|
|
17
|
+
table_id = "ground_truth"
|
|
18
|
+
project = var.project_id
|
|
19
|
+
|
|
20
|
+
schema = file("${path.module}/schemas/ground_truth.json")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
resource "google_bigquery_table" "offline_features" {
|
|
24
|
+
dataset_id = google_bigquery_dataset.mlops.dataset_id
|
|
25
|
+
table_id = "offline_features"
|
|
26
|
+
project = var.project_id
|
|
27
|
+
|
|
28
|
+
schema = file("${path.module}/schemas/offline_features.json")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
resource "google_bigquery_table" "predictions" {
|
|
32
|
+
dataset_id = google_bigquery_dataset.mlops.dataset_id
|
|
33
|
+
table_id = "predictions"
|
|
34
|
+
project = var.project_id
|
|
35
|
+
|
|
36
|
+
schema = file("${path.module}/schemas/predictions.json")
|
|
37
|
+
}
|
|
38
|
+
|
deployml_core-0.0.58/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/drift_metrics.json
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
[
|
|
2
|
+
{"name": "metric_timestamp", "type": "TIMESTAMP", "mode": "REQUIRED"},
|
|
3
|
+
{"name": "metric_type", "type": "STRING", "mode": "REQUIRED"},
|
|
4
|
+
{"name": "feature_name", "type": "STRING", "mode": "NULLABLE"},
|
|
5
|
+
{"name": "value", "type": "FLOAT", "mode": "NULLABLE"},
|
|
6
|
+
{"name": "window_start", "type": "TIMESTAMP", "mode": "NULLABLE"},
|
|
7
|
+
{"name": "window_end", "type": "TIMESTAMP", "mode": "NULLABLE"},
|
|
8
|
+
{"name": "model_version", "type": "STRING", "mode": "NULLABLE"}
|
|
9
|
+
]
|
deployml_core-0.0.58/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/offline_features.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
[
|
|
2
|
+
{"name": "entity_id", "type": "STRING", "mode": "REQUIRED"},
|
|
3
|
+
{"name": "event_timestamp", "type": "TIMESTAMP", "mode": "REQUIRED"},
|
|
4
|
+
{"name": "feature_1", "type": "FLOAT", "mode": "NULLABLE"},
|
|
5
|
+
{"name": "feature_2", "type": "FLOAT", "mode": "NULLABLE"},
|
|
6
|
+
{"name": "feature_3", "type": "FLOAT", "mode": "NULLABLE"}
|
|
7
|
+
]
|
deployml_core-0.0.58/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/predictions.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
[
|
|
2
|
+
{"name": "prediction_id", "type": "STRING", "mode": "REQUIRED"},
|
|
3
|
+
{"name": "entity_id", "type": "STRING", "mode": "REQUIRED"},
|
|
4
|
+
{"name": "prediction_timestamp", "type": "TIMESTAMP", "mode": "REQUIRED"},
|
|
5
|
+
{"name": "predicted_value", "type": "FLOAT", "mode": "NULLABLE"},
|
|
6
|
+
{"name": "model_version", "type": "STRING", "mode": "NULLABLE"},
|
|
7
|
+
{"name": "feature_snapshot_timestamp", "type": "TIMESTAMP", "mode": "NULLABLE"}
|
|
8
|
+
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/gcp/cloud_run/base_main.tf.j2
RENAMED
|
File without changes
|
{deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/gcp/cloud_run/mlflow_main.tf.j2
RENAMED
|
File without changes
|
|
File without changes
|
{deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/gcp/cloud_run/variables.tf.j2
RENAMED
|
File without changes
|
{deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/gcp/cloud_run/wandb_main.tf.j2
RENAMED
|
File without changes
|
{deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/gcp/cloud_vm/main.tf.j2
RENAMED
|
File without changes
|
|
File without changes
|
{deployml_core-0.0.56 → deployml_core-0.0.58}/src/deployml/templates/gcp/cloud_vm/variables.tf.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|