deployml-core 0.0.59__tar.gz → 0.0.60__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.
Files changed (106) hide show
  1. deployml_core-0.0.60/PKG-INFO +117 -0
  2. deployml_core-0.0.60/README.md +92 -0
  3. {deployml_core-0.0.59 → deployml_core-0.0.60}/pyproject.toml +1 -1
  4. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/cli/cli.py +35 -13
  5. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/fastapi/main.py +76 -36
  6. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/fastapi/requirements.txt +3 -1
  7. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/mlflow/Dockerfile +1 -1
  8. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_run/mlflow_main.tf.j2 +1 -0
  9. deployml_core-0.0.60/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/offline_features.json +12 -0
  10. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/fastapi/cloud/gcp/cloud_run/main.tf +5 -1
  11. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/fastapi/cloud/gcp/cloud_run/variables.tf +7 -1
  12. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_run/main.tf +4 -0
  13. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/helpers.py +4 -10
  14. deployml_core-0.0.59/PKG-INFO +0 -111
  15. deployml_core-0.0.59/README.md +0 -87
  16. deployml_core-0.0.59/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/offline_features.json +0 -7
  17. {deployml_core-0.0.59 → deployml_core-0.0.60}/LICENSE +0 -0
  18. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/__init__.py +0 -0
  19. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/api.py +0 -0
  20. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/cli/__init__.py +0 -0
  21. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/diagnostics/__init__.py +0 -0
  22. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/diagnostics/doctor.py +0 -0
  23. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/fastapi/.dockerignore +0 -0
  24. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/fastapi/Dockerfile +0 -0
  25. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/grafana-container/Dockerfile +0 -0
  26. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/grafana-container/README.md +0 -0
  27. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/grafana-container/entrypoint.sh +0 -0
  28. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/mlflow/requirements.txt +0 -0
  29. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/enum/__init__.py +0 -0
  30. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/enum/cloud_provider.py +0 -0
  31. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/enum/deployment_type.py +0 -0
  32. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/notebook/__init__.py +0 -0
  33. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/notebook/deployment.py +0 -0
  34. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/notebook/display.py +0 -0
  35. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/notebook/docker.py +0 -0
  36. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/notebook/stack.py +0 -0
  37. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/notebook/urls.py +0 -0
  38. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/notebook.py +0 -0
  39. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_run/base_main.tf.j2 +0 -0
  40. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_run/main.tf.j2 +0 -0
  41. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_run/terraform.tfvars.j2 +0 -0
  42. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_run/variables.tf.j2 +0 -0
  43. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_run/wandb_main.tf.j2 +0 -0
  44. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_vm/main.tf.j2 +0 -0
  45. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_vm/terraform.tfvars.j2 +0 -0
  46. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_vm/variables.tf.j2 +0 -0
  47. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/kubernetes_local/deployment.yaml.j2 +0 -0
  48. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/kubernetes_local/mlflow-deployment.yaml.j2 +0 -0
  49. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/kubernetes_local/mlflow-service.yaml.j2 +0 -0
  50. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/kubernetes_local/service.yaml.j2 +0 -0
  51. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/bigquery/cloud/gcp/main.tf +0 -0
  52. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/bigquery/cloud/gcp/outputs.tf +0 -0
  53. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/drift_metrics.json +0 -0
  54. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/ground_truth.json +0 -0
  55. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/predictions.json +0 -0
  56. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/bigquery/cloud/gcp/variables.tf +0 -0
  57. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/cloud_sql_postgres/main.tf +0 -0
  58. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/cloud_sql_postgres/outputs.tf +0 -0
  59. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/cloud_sql_postgres/variables.tf +0 -0
  60. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/cron/cloud/gcp/cloud_run/main.tf +0 -0
  61. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/cron/cloud/gcp/cloud_run/outputs.tf +0 -0
  62. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/cron/cloud/gcp/cloud_run/variables.tf +0 -0
  63. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/explainability_monitoring/cloud/gcp/cloud_run/main.tf +0 -0
  64. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/explainability_monitoring/cloud/gcp/cloud_run/outputs.tf +0 -0
  65. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/explainability_monitoring/cloud/gcp/cloud_run/variables.tf +0 -0
  66. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/fairness_monitoring/cloud/gcp/cloud_run/main.tf +0 -0
  67. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/fairness_monitoring/cloud/gcp/cloud_run/outputs.tf +0 -0
  68. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/fairness_monitoring/cloud/gcp/cloud_run/variables.tf +0 -0
  69. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/fastapi/cloud/gcp/cloud_run/outputs.tf +0 -0
  70. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_run/main.tf +0 -0
  71. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_run/outputs.tf +0 -0
  72. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_run/variables.tf +0 -0
  73. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_vm/feast_env.tpl +0 -0
  74. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_vm/main.tf +0 -0
  75. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_vm/outputs.tf +0 -0
  76. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_vm/variables.tf +0 -0
  77. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/grafana/cloud/gcp/cloud_run/main.tf +0 -0
  78. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/grafana/cloud/gcp/cloud_run/outputs.tf +0 -0
  79. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/grafana/cloud/gcp/cloud_run/variables.tf +0 -0
  80. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_run/outputs.tf +0 -0
  81. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_run/variables.tf +0 -0
  82. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_vm/main.tf +0 -0
  83. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_vm/variables.tf +0 -0
  84. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/offline_scoring/cloud/gcp/cloud_run/main.tf +0 -0
  85. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/offline_scoring/cloud/gcp/cloud_run/outputs.tf +0 -0
  86. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/offline_scoring/cloud/gcp/cloud_run/variables.tf +0 -0
  87. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/teardown/cloud/gcp/cloud_function/main.py +0 -0
  88. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/teardown/cloud/gcp/cloud_function/requirements.txt +0 -0
  89. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/teardown/cloud/gcp/cloudbuild.yaml +0 -0
  90. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/teardown/cloud/gcp/main.tf +0 -0
  91. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/teardown/cloud/gcp/outputs.tf +0 -0
  92. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/teardown/cloud/gcp/teardown_script.sh +0 -0
  93. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/teardown/cloud/gcp/variables.tf +0 -0
  94. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_run/main.tf +0 -0
  95. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_run/outputs.tf +0 -0
  96. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_run/variables.tf +0 -0
  97. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_vm/main.tf +0 -0
  98. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_vm/variables.tf +0 -0
  99. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/__init__.py +0 -0
  100. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/banner.py +0 -0
  101. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/constants.py +0 -0
  102. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/infracost.py +0 -0
  103. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/kubernetes_gke.py +0 -0
  104. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/kubernetes_local.py +0 -0
  105. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/menu.py +0 -0
  106. {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/teardown.py +0 -0
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: deployml-core
3
+ Version: 0.0.60
4
+ Summary: Infra for academia
5
+ License-File: LICENSE
6
+ Author: Drew Hoang
7
+ Author-email: codentell@gmail.com
8
+ Requires-Python: >=3.11
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Requires-Dist: google-cloud-storage (>=3.1.1,<4.0.0)
15
+ Requires-Dist: ipython (>=9.0.0)
16
+ Requires-Dist: jinja2
17
+ Requires-Dist: jupyter (>=1.0.0)
18
+ Requires-Dist: pandas (>=2.2.1)
19
+ Requires-Dist: pyyaml
20
+ Requires-Dist: requests (>=2.28.0)
21
+ Requires-Dist: rich
22
+ Requires-Dist: typer
23
+ Description-Content-Type: text/markdown
24
+
25
+ # deployml
26
+
27
+ A CLI tool that deploys a production MLOps stack on GCP with a single command. Built for academic ML courses so students can focus on building models, not infrastructure.
28
+
29
+ ## What you get
30
+
31
+ - **MLflow** — experiment tracking, artifact storage, and model registry (backed by Cloud SQL Postgres + GCS)
32
+ - **FastAPI** — model serving endpoint that loads the latest registered model from MLflow automatically
33
+ - **Grafana** — monitoring dashboard connected to your metrics database
34
+ - **BigQuery** — `mlops` dataset with tables for features, predictions, ground truth, and drift metrics
35
+
36
+ All running on GCP Cloud Run — no servers to manage, scales to zero when idle.
37
+
38
+ ## Quick Start
39
+
40
+ **1. Install**
41
+
42
+ ```bash
43
+ pip install deployml-core
44
+ ```
45
+
46
+ **2. Initialize your GCP project** (enables APIs, creates Artifact Registry)
47
+
48
+ ```bash
49
+ deployml init --provider gcp --project-id YOUR_PROJECT_ID
50
+ ```
51
+
52
+ **3. Configure**
53
+
54
+ ```bash
55
+ cp config.example.yaml config.yaml
56
+ # Edit config.yaml and set your project_id
57
+ ```
58
+
59
+ **4. Build images**
60
+
61
+ ```bash
62
+ deployml build-images
63
+ ```
64
+
65
+ **5. Deploy**
66
+
67
+ ```bash
68
+ deployml deploy --verbose
69
+ ```
70
+
71
+ First deploy takes ~20 minutes (Cloud SQL provisioning). Subsequent deploys are 1–2 minutes.
72
+
73
+ **6. Get your URLs**
74
+
75
+ ```bash
76
+ deployml get-urls
77
+ ```
78
+
79
+ Prints service URLs and writes a `.env` file with `MLFLOW_URL`, `FASTAPI_URL`, `GRAFANA_URL`, `BIGQUERY_PROJECT`, and `BIGQUERY_DATASET`.
80
+
81
+ ## End-to-End Example
82
+
83
+ Once deployed, the `example/` directory walks through a complete MLOps workflow using a synthetic housing price dataset:
84
+
85
+ ```bash
86
+ pip install mlflow scikit-learn pandas numpy google-cloud-bigquery db-dtypes python-dotenv requests
87
+
88
+ python example/scripts/01_load_training_data.py # load 500 rows into BigQuery
89
+ python example/scripts/02_train_model.py # train RandomForest, log to MLflow
90
+ python example/scripts/03_register_model.py # register model as Production
91
+ python example/scripts/04_make_predictions.py # serve 50 predictions via FastAPI
92
+ python example/scripts/05_generate_ground_truth.py # simulate actual outcomes
93
+ python example/scripts/06_compute_drift_metrics.py # compute feature drift + MAE
94
+ python example/scripts/07_setup_grafana.py # provision monitoring dashboard
95
+ ```
96
+
97
+ See [example/README.md](example/README.md) for details.
98
+
99
+ ## Teardown
100
+
101
+ ```bash
102
+ deployml destroy
103
+ ```
104
+
105
+ Deletes all Cloud Run services, Cloud SQL, GCS bucket, and BigQuery dataset. Does not delete Artifact Registry images or the GCP project.
106
+
107
+ ## Full Tutorial
108
+
109
+ See [docs/tutorials/gcp-cloud-run.md](docs/tutorials/gcp-cloud-run.md) for a step-by-step walkthrough.
110
+
111
+ ## Requirements
112
+
113
+ - Python 3.10+
114
+ - `gcloud` CLI (authenticated)
115
+ - Docker (running)
116
+ - Terraform
117
+
@@ -0,0 +1,92 @@
1
+ # deployml
2
+
3
+ A CLI tool that deploys a production MLOps stack on GCP with a single command. Built for academic ML courses so students can focus on building models, not infrastructure.
4
+
5
+ ## What you get
6
+
7
+ - **MLflow** — experiment tracking, artifact storage, and model registry (backed by Cloud SQL Postgres + GCS)
8
+ - **FastAPI** — model serving endpoint that loads the latest registered model from MLflow automatically
9
+ - **Grafana** — monitoring dashboard connected to your metrics database
10
+ - **BigQuery** — `mlops` dataset with tables for features, predictions, ground truth, and drift metrics
11
+
12
+ All running on GCP Cloud Run — no servers to manage, scales to zero when idle.
13
+
14
+ ## Quick Start
15
+
16
+ **1. Install**
17
+
18
+ ```bash
19
+ pip install deployml-core
20
+ ```
21
+
22
+ **2. Initialize your GCP project** (enables APIs, creates Artifact Registry)
23
+
24
+ ```bash
25
+ deployml init --provider gcp --project-id YOUR_PROJECT_ID
26
+ ```
27
+
28
+ **3. Configure**
29
+
30
+ ```bash
31
+ cp config.example.yaml config.yaml
32
+ # Edit config.yaml and set your project_id
33
+ ```
34
+
35
+ **4. Build images**
36
+
37
+ ```bash
38
+ deployml build-images
39
+ ```
40
+
41
+ **5. Deploy**
42
+
43
+ ```bash
44
+ deployml deploy --verbose
45
+ ```
46
+
47
+ First deploy takes ~20 minutes (Cloud SQL provisioning). Subsequent deploys are 1–2 minutes.
48
+
49
+ **6. Get your URLs**
50
+
51
+ ```bash
52
+ deployml get-urls
53
+ ```
54
+
55
+ Prints service URLs and writes a `.env` file with `MLFLOW_URL`, `FASTAPI_URL`, `GRAFANA_URL`, `BIGQUERY_PROJECT`, and `BIGQUERY_DATASET`.
56
+
57
+ ## End-to-End Example
58
+
59
+ Once deployed, the `example/` directory walks through a complete MLOps workflow using a synthetic housing price dataset:
60
+
61
+ ```bash
62
+ pip install mlflow scikit-learn pandas numpy google-cloud-bigquery db-dtypes python-dotenv requests
63
+
64
+ python example/scripts/01_load_training_data.py # load 500 rows into BigQuery
65
+ python example/scripts/02_train_model.py # train RandomForest, log to MLflow
66
+ python example/scripts/03_register_model.py # register model as Production
67
+ python example/scripts/04_make_predictions.py # serve 50 predictions via FastAPI
68
+ python example/scripts/05_generate_ground_truth.py # simulate actual outcomes
69
+ python example/scripts/06_compute_drift_metrics.py # compute feature drift + MAE
70
+ python example/scripts/07_setup_grafana.py # provision monitoring dashboard
71
+ ```
72
+
73
+ See [example/README.md](example/README.md) for details.
74
+
75
+ ## Teardown
76
+
77
+ ```bash
78
+ deployml destroy
79
+ ```
80
+
81
+ Deletes all Cloud Run services, Cloud SQL, GCS bucket, and BigQuery dataset. Does not delete Artifact Registry images or the GCP project.
82
+
83
+ ## Full Tutorial
84
+
85
+ See [docs/tutorials/gcp-cloud-run.md](docs/tutorials/gcp-cloud-run.md) for a step-by-step walkthrough.
86
+
87
+ ## Requirements
88
+
89
+ - Python 3.10+
90
+ - `gcloud` CLI (authenticated)
91
+ - Docker (running)
92
+ - Terraform
@@ -1,7 +1,7 @@
1
1
 
2
2
  [project]
3
3
  name = "deployml-core"
4
- version = "0.0.59"
4
+ version = "0.0.60"
5
5
  description = "Infra for academia"
6
6
  authors = [
7
7
  {name = "Drew Hoang", email = "codentell@gmail.com"},
@@ -4,6 +4,7 @@ import typer
4
4
  import shutil
5
5
  import subprocess
6
6
  import re
7
+ import importlib.resources as pkg_resources
7
8
  from deployml.utils.banner import display_banner
8
9
  from deployml.utils.menu import prompt, show_menu
9
10
  from deployml.utils.constants import (
@@ -780,7 +781,7 @@ def terraform(
780
781
  @cli.command()
781
782
  def deploy(
782
783
  config_path: Path = typer.Option(
783
- ..., "--config-path", "-c", help="Path to YAML config file"
784
+ Path("config.yaml"), "--config-path", "-c", help="Path to YAML config file"
784
785
  ),
785
786
  yes: bool = typer.Option(
786
787
  False, "--yes", "-y", help="Skip confirmation prompts and deploy"
@@ -1456,7 +1457,7 @@ def deploy(
1456
1457
  @cli.command()
1457
1458
  def get_urls(
1458
1459
  config_path: Path = typer.Option(
1459
- ..., "--config-path", "-c", help="Path to YAML config file"
1460
+ Path("config.yaml"), "--config-path", "-c", help="Path to YAML config file"
1460
1461
  ),
1461
1462
  env_path: Path = typer.Option(
1462
1463
  Path(".env"), "--env-path", help="Path to write .env file"
@@ -1471,6 +1472,7 @@ def get_urls(
1471
1472
 
1472
1473
  config = yaml.safe_load(config_path.read_text())
1473
1474
  workspace_name = config.get("name") or "development"
1475
+ project_id = config.get("provider", {}).get("project_id", "")
1474
1476
  terraform_dir = Path.cwd() / ".deployml" / workspace_name / "terraform"
1475
1477
 
1476
1478
  if not terraform_dir.exists():
@@ -1527,6 +1529,10 @@ def get_urls(
1527
1529
  else:
1528
1530
  typer.echo(f" {key}: {output_val}")
1529
1531
 
1532
+ if project_id:
1533
+ env_lines.append(f"BIGQUERY_PROJECT={project_id}")
1534
+ typer.echo(f" bigquery_project: {project_id}")
1535
+
1530
1536
  env_path.write_text("\n".join(env_lines) + "\n")
1531
1537
  typer.echo(f"\n .env written to {env_path.resolve()}")
1532
1538
 
@@ -1534,7 +1540,7 @@ def get_urls(
1534
1540
  @cli.command()
1535
1541
  def destroy(
1536
1542
  config_path: Path = typer.Option(
1537
- ..., "--config-path", "-c", help="Path to YAML config file"
1543
+ Path("config.yaml"), "--config-path", "-c", help="Path to YAML config file"
1538
1544
  ),
1539
1545
  workspace: Optional[str] = typer.Option(
1540
1546
  None, "--workspace", help="Override workspace name from config"
@@ -2434,15 +2440,17 @@ def gke_apply(
2434
2440
 
2435
2441
  @cli.command("build-images")
2436
2442
  def build_images_command(
2437
- docker_root: Path = typer.Option(
2438
- ...,
2443
+ config_path: Path = typer.Option(
2444
+ Path("config.yaml"),
2445
+ "--config-path",
2446
+ "-c",
2447
+ help="Path to YAML config file. Used to infer project and region automatically.",
2448
+ ),
2449
+ docker_root: Optional[Path] = typer.Option(
2450
+ None,
2439
2451
  "--docker-root",
2440
2452
  "-d",
2441
- help="Path to folder containing subfolders with Dockerfiles.",
2442
- exists=True,
2443
- file_okay=False,
2444
- dir_okay=True,
2445
- resolve_path=True,
2453
+ help="Path to folder containing subfolders with Dockerfiles. Defaults to the built-in deployml docker directory.",
2446
2454
  ),
2447
2455
  gcp_project: Optional[str] = typer.Option(
2448
2456
  None,
@@ -2450,10 +2458,10 @@ def build_images_command(
2450
2458
  "-p",
2451
2459
  help="If provided, images will be built using Cloud Build in this GCP project.",
2452
2460
  ),
2453
- region: str = typer.Option(
2454
- "us-central1",
2461
+ region: Optional[str] = typer.Option(
2462
+ None,
2455
2463
  "--region",
2456
- help="GCP region for Artifact Registry.",
2464
+ help="GCP region for Artifact Registry. Inferred from config if not set.",
2457
2465
  ),
2458
2466
  repository: str = typer.Option(
2459
2467
  "mlops-images",
@@ -2489,6 +2497,16 @@ def build_images_command(
2489
2497
  --dry-run prints commands without executing them.
2490
2498
  """
2491
2499
 
2500
+ if config_path and config_path.exists():
2501
+ config = yaml.safe_load(config_path.read_text())
2502
+ if not gcp_project:
2503
+ gcp_project = config.get("provider", {}).get("project_id")
2504
+ if not region:
2505
+ region = config.get("provider", {}).get("region", "us-central1")
2506
+
2507
+ if not region:
2508
+ region = "us-central1"
2509
+
2492
2510
  if create_repo and not gcp_project:
2493
2511
  typer.secho(
2494
2512
  "--create-repo can only be used with --gcp-project.",
@@ -2496,6 +2514,10 @@ def build_images_command(
2496
2514
  )
2497
2515
  raise typer.Exit(code=1)
2498
2516
 
2517
+ if docker_root is None:
2518
+ docker_root = Path(str(pkg_resources.files("deployml") / "docker"))
2519
+ typer.echo(f"Using built-in docker directory: {docker_root}")
2520
+
2499
2521
  try:
2500
2522
  build_images(
2501
2523
  docker_root=docker_root,
@@ -1,9 +1,10 @@
1
- from fastapi import FastAPI, HTTPException
1
+ from fastapi import FastAPI
2
2
  from fastapi.responses import JSONResponse
3
3
  from pydantic import BaseModel
4
- from typing import List, Dict, Any, Optional
4
+ from typing import Dict, Optional
5
5
  import os
6
- from datetime import datetime
6
+ import uuid
7
+ from datetime import datetime, timezone
7
8
  import pandas as pd
8
9
  import mlflow
9
10
  import mlflow.pyfunc
@@ -14,21 +15,28 @@ app = FastAPI(
14
15
  version="1.0.0"
15
16
  )
16
17
 
17
- # MLflow configuration — override via MLFLOW_TRACKING_URI env var
18
18
  MLFLOW_TRACKING_URI = os.getenv("MLFLOW_TRACKING_URI", "http://mlflow-service:5000")
19
19
  MODEL_NAME = os.getenv("MODEL_NAME", "HousingPriceModel")
20
+ BIGQUERY_PROJECT = os.getenv("GOOGLE_CLOUD_PROJECT", "")
21
+ BIGQUERY_DATASET = os.getenv("BIGQUERY_DATASET", "mlops")
20
22
 
21
- # Global model variable
22
23
  model = None
24
+ bq_client = None
25
+
26
+ FEATURE_ORDER = ['bedrooms', 'bathrooms', 'area_sqft', 'lot_size', 'year_built', 'city', 'state']
27
+
23
28
 
24
- # Pydantic models
25
29
  class PredictionRequest(BaseModel):
30
+ entity_id: Optional[str] = None
26
31
  features: Dict[str, float]
27
32
 
33
+
28
34
  class PredictionResponse(BaseModel):
29
35
  prediction: float
30
36
  timestamp: str
31
37
  model_used: Optional[str] = None
38
+ entity_id: Optional[str] = None
39
+
32
40
 
33
41
  class HealthResponse(BaseModel):
34
42
  status: str
@@ -37,10 +45,9 @@ class HealthResponse(BaseModel):
37
45
  mlflow_connected: bool
38
46
  model_loaded: bool
39
47
 
48
+
40
49
  def load_model_from_mlflow():
41
- """Load model from MLflow Model Registry"""
42
50
  global model
43
-
44
51
  try:
45
52
  mlflow.set_tracking_uri(MLFLOW_TRACKING_URI)
46
53
  model = mlflow.pyfunc.load_model(f"models:/{MODEL_NAME}/Production")
@@ -51,32 +58,59 @@ def load_model_from_mlflow():
51
58
  model = None
52
59
  return False
53
60
 
54
- # Load model at startup
61
+
62
+ def init_bigquery():
63
+ global bq_client
64
+ if not BIGQUERY_PROJECT:
65
+ return
66
+ try:
67
+ from google.cloud import bigquery
68
+ bq_client = bigquery.Client(project=BIGQUERY_PROJECT)
69
+ print(f"✓ BigQuery client initialized for project {BIGQUERY_PROJECT}")
70
+ except Exception as e:
71
+ print(f"⚠️ Could not initialize BigQuery client: {e}")
72
+ bq_client = None
73
+
74
+
75
+ def log_prediction_to_bigquery(entity_id: str, predicted_value: float, model_version: str):
76
+ if bq_client is None:
77
+ return
78
+ try:
79
+ table_id = f"{BIGQUERY_PROJECT}.{BIGQUERY_DATASET}.predictions"
80
+ row = {
81
+ "prediction_id": str(uuid.uuid4()),
82
+ "entity_id": entity_id,
83
+ "prediction_timestamp": datetime.now(timezone.utc).isoformat(),
84
+ "predicted_value": predicted_value,
85
+ "model_version": model_version,
86
+ "feature_snapshot_timestamp": datetime.now(timezone.utc).isoformat(),
87
+ }
88
+ bq_client.insert_rows_json(table_id, [row])
89
+ except Exception as e:
90
+ print(f"⚠️ Could not log prediction to BigQuery: {e}")
91
+
92
+
55
93
  @app.on_event("startup")
56
94
  async def startup_event():
57
- """Load MLflow model on startup"""
58
95
  load_model_from_mlflow()
96
+ init_bigquery()
97
+
59
98
 
60
99
  @app.get("/")
61
100
  async def root():
62
- """Root endpoint"""
63
101
  return {
64
102
  "service": "FastAPI Demo",
65
103
  "version": "1.0.0",
66
104
  "model_loaded": model is not None,
67
105
  "model_name": MODEL_NAME,
68
106
  "mlflow_uri": MLFLOW_TRACKING_URI,
69
- "endpoints": {
70
- "health": "/health",
71
- "predict": "/predict",
72
- "docs": "/docs"
73
- }
107
+ "endpoints": {"health": "/health", "predict": "/predict", "docs": "/docs"}
74
108
  }
75
109
 
110
+
76
111
  @app.get("/health", response_model=HealthResponse)
77
112
  async def health():
78
- """Health check endpoint"""
79
- port = int(os.getenv("FASTAPI_PORT", "8000"))
113
+ port = int(os.getenv("PORT", "8000"))
80
114
  return HealthResponse(
81
115
  status="healthy",
82
116
  timestamp=datetime.now().isoformat(),
@@ -85,43 +119,49 @@ async def health():
85
119
  model_loaded=model is not None
86
120
  )
87
121
 
122
+
88
123
  @app.post("/predict", response_model=PredictionResponse)
89
124
  async def predict(request: PredictionRequest):
90
- """Prediction endpoint using MLflow model"""
91
125
  global model
92
-
93
- # If model is not loaded, return -1
126
+
127
+ entity_id = request.entity_id or str(uuid.uuid4())
128
+
129
+ if model is None:
130
+ load_model_from_mlflow()
131
+
94
132
  if model is None:
95
133
  return PredictionResponse(
96
134
  prediction=-1.0,
97
135
  timestamp=datetime.now().isoformat(),
98
- model_used=None
136
+ model_used=None,
137
+ entity_id=entity_id
99
138
  )
100
-
139
+
101
140
  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
-
141
+ feature_values = [request.features.get(f, 0.0) for f in FEATURE_ORDER]
142
+ X = pd.DataFrame([feature_values], columns=FEATURE_ORDER)
143
+
108
144
  prediction = float(model.predict(X)[0])
109
- model_used = f"MLflow: {MODEL_NAME}"
110
-
145
+ model_version = f"MLflow: {MODEL_NAME}"
146
+
147
+ log_prediction_to_bigquery(entity_id, prediction, model_version)
148
+
111
149
  return PredictionResponse(
112
150
  prediction=prediction,
113
151
  timestamp=datetime.now().isoformat(),
114
- model_used=model_used
152
+ model_used=model_version,
153
+ entity_id=entity_id
115
154
  )
116
155
  except Exception as e:
117
- # On any error, return -1
118
156
  return PredictionResponse(
119
157
  prediction=-1.0,
120
158
  timestamp=datetime.now().isoformat(),
121
- model_used=None
159
+ model_used=None,
160
+ entity_id=entity_id
122
161
  )
123
162
 
163
+
124
164
  if __name__ == "__main__":
125
165
  import uvicorn
126
- port = int(os.getenv("FASTAPI_PORT", "8000"))
127
- uvicorn.run(app, host="0.0.0.0", port=port)
166
+ port = int(os.getenv("PORT", "8000"))
167
+ uvicorn.run(app, host="0.0.0.0", port=port)
@@ -4,4 +4,6 @@ pydantic==2.5.2
4
4
  mlflow>=2.8.0
5
5
  scikit-learn>=1.0.0
6
6
  pandas>=1.3.0
7
- numpy>=1.21.0
7
+ numpy>=1.21.0
8
+ google-cloud-bigquery>=3.0.0
9
+ google-cloud-storage>=2.0.0
@@ -36,5 +36,5 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
36
36
 
37
37
  # Default command - MLflow server
38
38
  # Uses PORT env var so it works on Cloud Run (PORT=8080) and locally (PORT=5000)
39
- CMD mlflow server --host 0.0.0.0 --port ${PORT:-5000} --dev
39
+ CMD mlflow server --host 0.0.0.0 --port ${PORT:-5000} --dev --allowed-hosts '*'
40
40
 
@@ -151,6 +151,7 @@ module "{{ stage_name }}_{{ tool.name }}" {
151
151
  {% endfor %}
152
152
  feast_service_url = {{ 'length(module.feature_store_feast) > 0 ? module.feature_store_feast[0].service_url : ""' if feast_exists.value else '""' }}
153
153
  enable_feast_connection = {{ 'true' if feast_exists.value else 'false' }}
154
+ bigquery_dataset = "mlops"
154
155
  {% if feast_exists.value %}
155
156
  depends_on = [module.feature_store_feast]
156
157
  {% endif %}
@@ -0,0 +1,12 @@
1
+ [
2
+ {"name": "entity_id", "type": "STRING", "mode": "REQUIRED"},
3
+ {"name": "event_timestamp", "type": "TIMESTAMP", "mode": "REQUIRED"},
4
+ {"name": "bedrooms", "type": "FLOAT", "mode": "NULLABLE"},
5
+ {"name": "bathrooms", "type": "FLOAT", "mode": "NULLABLE"},
6
+ {"name": "area_sqft", "type": "FLOAT", "mode": "NULLABLE"},
7
+ {"name": "lot_size", "type": "FLOAT", "mode": "NULLABLE"},
8
+ {"name": "year_built", "type": "FLOAT", "mode": "NULLABLE"},
9
+ {"name": "city", "type": "FLOAT", "mode": "NULLABLE"},
10
+ {"name": "state", "type": "FLOAT", "mode": "NULLABLE"},
11
+ {"name": "price", "type": "FLOAT", "mode": "NULLABLE"}
12
+ ]
@@ -51,6 +51,10 @@ resource "google_cloud_run_service" "fastapi" {
51
51
  name = "GOOGLE_CLOUD_PROJECT"
52
52
  value = var.project_id
53
53
  }
54
+ env {
55
+ name = "BIGQUERY_DATASET"
56
+ value = var.bigquery_dataset
57
+ }
54
58
  resources {
55
59
  limits = {
56
60
  cpu = var.cpu_limit
@@ -116,7 +120,7 @@ resource "google_project_iam_member" "fastapi_storage_object_admin" {
116
120
 
117
121
  resource "google_project_iam_member" "fastapi_bigquery_user" {
118
122
  project = var.project_id
119
- role = "roles/bigquery.user"
123
+ role = "roles/bigquery.dataEditor"
120
124
  member = "serviceAccount:${data.google_project.current.number}-compute@developer.gserviceaccount.com"
121
125
  }
122
126
 
@@ -93,4 +93,10 @@ variable "mlflow_bucket_exists" {
93
93
  type = bool
94
94
  description = "Whether the MLflow artifact bucket already exists"
95
95
  default = false
96
- }
96
+ }
97
+
98
+ variable "bigquery_dataset" {
99
+ type = string
100
+ description = "BigQuery dataset name for storing predictions"
101
+ default = "mlops"
102
+ }
@@ -50,6 +50,10 @@ resource "google_cloud_run_service" "mlflow" {
50
50
  name = "MLFLOW_SERVER_ALLOWED_HOSTS"
51
51
  value = "*"
52
52
  }
53
+ env {
54
+ name = "MLFLOW_SERVER_DISABLE_SECURITY_MIDDLEWARE"
55
+ value = "true"
56
+ }
53
57
 
54
58
  # Backend store URI
55
59
  dynamic "env" {
@@ -196,22 +196,16 @@ def generate_unique_bucket_name(base_name: str, project_id: str) -> str:
196
196
 
197
197
  def generate_bucket_name(project_id: str) -> str:
198
198
  """
199
- Generate a random, human-readable GCS bucket name for the given project.
199
+ Generate a stable, deterministic GCS bucket name for the given project.
200
+ Uses project_id (globally unique on GCP) so the name is consistent across deploys.
200
201
 
201
202
  Args:
202
203
  project_id (str): The GCP project ID.
203
204
 
204
205
  Returns:
205
- str: A generated bucket name.
206
+ str: A deterministic bucket name.
206
207
  """
207
- if random.random() < 0.7:
208
- word = random.choice(ANIMAL_NAMES)
209
- else:
210
- word = random.choice(FALLBACK_WORDS)
211
- suffix = "".join(
212
- random.choices(string.ascii_lowercase + string.digits, k=4)
213
- )
214
- return f"{word}-bucket-{project_id}-{suffix}".replace("_", "-")
208
+ return f"mlflow-artifacts-{project_id}".replace("_", "-")
215
209
 
216
210
 
217
211
  def estimate_terraform_time(plan_output: str, operation: str = "apply") -> str:
@@ -1,111 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: deployml-core
3
- Version: 0.0.59
4
- Summary: Infra for academia
5
- License-File: LICENSE
6
- Author: Drew Hoang
7
- Author-email: codentell@gmail.com
8
- Requires-Python: >=3.11
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.11
11
- Classifier: Programming Language :: Python :: 3.12
12
- Classifier: Programming Language :: Python :: 3.13
13
- Classifier: Programming Language :: Python :: 3.14
14
- Requires-Dist: google-cloud-storage (>=3.1.1,<4.0.0)
15
- Requires-Dist: ipython (>=9.0.0)
16
- Requires-Dist: jinja2
17
- Requires-Dist: jupyter (>=1.0.0)
18
- Requires-Dist: pandas (>=2.2.1)
19
- Requires-Dist: pyyaml
20
- Requires-Dist: requests (>=2.28.0)
21
- Requires-Dist: rich
22
- Requires-Dist: typer
23
- Description-Content-Type: text/markdown
24
-
25
- # deployml
26
- Infrastructure for academia with cost analysis
27
-
28
- ## Features
29
-
30
- - 🏗️ **Infrastructure as Code**: Deploy ML infrastructure using Terraform
31
- - 💰 **Cost Analysis**: Integrated infracost analysis before deployment
32
- - ☁️ **Multi-Cloud Support**: GCP, AWS, and more
33
- - 🔬 **ML-Focused**: Pre-configured for MLflow, experiment tracking, and model registry
34
- - 🛡️ **Production Ready**: Security best practices and service account management
35
-
36
- ## Instructions
37
-
38
- ```bash
39
- poetry install
40
- poetry run deployml doctor
41
- poetry run deployml deploy --config-path your-config.yaml
42
- ```
43
-
44
- docker build --platform=linux/amd64 -t gcr.io/mlops-intro-461805/mlflow/mlflow:latest .
45
-
46
- gcloud auth configure-docker docker push gcr.io/PROJECT_ID/mlflow-app:latest
47
-
48
- ## Cost Analysis Integration
49
-
50
- deployml integrates with [infracost](https://www.infracost.io/) to provide cost estimates before deployment:
51
-
52
- ### Installation
53
- ```bash
54
- brew install infracost
55
- ```
56
- Once installed you will need to create a free [infracost](https://www.infracost.io/) account before creating your API key.
57
-
58
- To generate your infracost API key run the following command:
59
-
60
- ```bash
61
- infracost auth login
62
- ```
63
- If you want to retrieve your API key use:
64
- ```bash
65
- infracost configure get api_key
66
- ```
67
-
68
- ### Cost Analysis Configuration
69
- Add cost analysis settings to your YAML configuration:
70
-
71
- ```yaml
72
- name: "my-mlops-stack"
73
- cost_analysis:
74
- enabled: true # Enable/disable cost analysis (default: true)
75
- warning_threshold: 100.0 # Warn if monthly cost exceeds this amount
76
- currency: "USD" # Currency for cost display
77
- ```
78
-
79
- ## Cloud Run Service Account Setup
80
-
81
- When deploying MLflow, you must specify the service account email that Cloud Run will use. This service account must have permission to access the artifact bucket.
82
-
83
- ### How to get a service account email
84
-
85
- 1. List your service accounts:
86
- ```sh
87
- gcloud iam service-accounts list
88
- ```
89
- 2. (Recommended) Create a dedicated service account for MLflow if you don't have one:
90
- ```sh
91
- gcloud iam service-accounts create mlflow-cloudrun --display-name "MLflow Cloud Run Service Account"
92
- ```
93
- 3. Find the email for your service account (it will look like `mlflow-cloudrun@YOUR_PROJECT.iam.gserviceaccount.com`).
94
-
95
- ### Grant the service account permissions
96
-
97
- The Terraform module will automatically grant the service account the required permissions on the artifact bucket.
98
-
99
- ### Deploying with Terraform
100
-
101
- Pass the service account email as a variable:
102
-
103
- ```sh
104
- terraform apply -var="cloud_run_service_account=mlflow-cloudrun@YOUR_PROJECT.iam.gserviceaccount.com"
105
- ```
106
-
107
- Or, if using the CLI, ensure it passes this variable to Terraform.
108
-
109
- ### Why this is needed
110
-
111
- The MLflow server (running on Cloud Run) needs permission to list and read artifacts in the GCS bucket. This setup ensures the MLflow UI works for all users without manual permission fixes.
@@ -1,87 +0,0 @@
1
- # deployml
2
- Infrastructure for academia with cost analysis
3
-
4
- ## Features
5
-
6
- - 🏗️ **Infrastructure as Code**: Deploy ML infrastructure using Terraform
7
- - 💰 **Cost Analysis**: Integrated infracost analysis before deployment
8
- - ☁️ **Multi-Cloud Support**: GCP, AWS, and more
9
- - 🔬 **ML-Focused**: Pre-configured for MLflow, experiment tracking, and model registry
10
- - 🛡️ **Production Ready**: Security best practices and service account management
11
-
12
- ## Instructions
13
-
14
- ```bash
15
- poetry install
16
- poetry run deployml doctor
17
- poetry run deployml deploy --config-path your-config.yaml
18
- ```
19
-
20
- docker build --platform=linux/amd64 -t gcr.io/mlops-intro-461805/mlflow/mlflow:latest .
21
-
22
- gcloud auth configure-docker docker push gcr.io/PROJECT_ID/mlflow-app:latest
23
-
24
- ## Cost Analysis Integration
25
-
26
- deployml integrates with [infracost](https://www.infracost.io/) to provide cost estimates before deployment:
27
-
28
- ### Installation
29
- ```bash
30
- brew install infracost
31
- ```
32
- Once installed you will need to create a free [infracost](https://www.infracost.io/) account before creating your API key.
33
-
34
- To generate your infracost API key run the following command:
35
-
36
- ```bash
37
- infracost auth login
38
- ```
39
- If you want to retrieve your API key use:
40
- ```bash
41
- infracost configure get api_key
42
- ```
43
-
44
- ### Cost Analysis Configuration
45
- Add cost analysis settings to your YAML configuration:
46
-
47
- ```yaml
48
- name: "my-mlops-stack"
49
- cost_analysis:
50
- enabled: true # Enable/disable cost analysis (default: true)
51
- warning_threshold: 100.0 # Warn if monthly cost exceeds this amount
52
- currency: "USD" # Currency for cost display
53
- ```
54
-
55
- ## Cloud Run Service Account Setup
56
-
57
- When deploying MLflow, you must specify the service account email that Cloud Run will use. This service account must have permission to access the artifact bucket.
58
-
59
- ### How to get a service account email
60
-
61
- 1. List your service accounts:
62
- ```sh
63
- gcloud iam service-accounts list
64
- ```
65
- 2. (Recommended) Create a dedicated service account for MLflow if you don't have one:
66
- ```sh
67
- gcloud iam service-accounts create mlflow-cloudrun --display-name "MLflow Cloud Run Service Account"
68
- ```
69
- 3. Find the email for your service account (it will look like `mlflow-cloudrun@YOUR_PROJECT.iam.gserviceaccount.com`).
70
-
71
- ### Grant the service account permissions
72
-
73
- The Terraform module will automatically grant the service account the required permissions on the artifact bucket.
74
-
75
- ### Deploying with Terraform
76
-
77
- Pass the service account email as a variable:
78
-
79
- ```sh
80
- terraform apply -var="cloud_run_service_account=mlflow-cloudrun@YOUR_PROJECT.iam.gserviceaccount.com"
81
- ```
82
-
83
- Or, if using the CLI, ensure it passes this variable to Terraform.
84
-
85
- ### Why this is needed
86
-
87
- The MLflow server (running on Cloud Run) needs permission to list and read artifacts in the GCS bucket. This setup ensures the MLflow UI works for all users without manual permission fixes.
@@ -1,7 +0,0 @@
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
- ]
File without changes