Skip to content

Add best model artifacts for deployment #3

Add best model artifacts for deployment

Add best model artifacts for deployment #3

Workflow file for this run

name: Deploy Model API
on:
push:
branches:
- main
- develop
paths:
- 'api/**'
- 'mlruns/best_model_artifacts/**'
- 'Data/preprocessor.pkl'
- '.github/workflows/cd.yml'
pull_request:
branches:
- main
paths:
- 'api/**'
- '.github/workflows/cd.yml'
workflow_dispatch:
inputs:
deployment_target:
description: 'Choose deployment target'
required: true
default: 'local'
type: choice
options:
- local
- staging
- production
jobs:
# Verify prerequisites
check-prerequisites:
runs-on: self-hosted
outputs:
model-exists: ${{ steps.check.outputs.model }}
preprocessor-exists: ${{ steps.check.outputs.preprocessor }}
defaults:
run:
shell: powershell
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check required artifacts
id: check
run: |
$modelExists = $false
$preprocessorExists = $false
# Check for best model artifacts
if (Test-Path "mlruns\best_model_artifacts\customer_churn_optimization") {
$modelDirs = Get-ChildItem "mlruns\best_model_artifacts\customer_churn_optimization" -Directory
if ($modelDirs.Count -gt 0) {
Write-Host "✅ Model artifacts found: $($modelDirs[0].Name)"
$modelExists = $true
}
}
# Check for preprocessor
if (Test-Path "Data\preprocessor.pkl") {
Write-Host "✅ Preprocessor found"
$preprocessorExists = $true
}
# Set outputs
"model=$modelExists" >> $env:GITHUB_OUTPUT
"preprocessor=$preprocessorExists" >> $env:GITHUB_OUTPUT
# Fail if required artifacts are missing
if (-not $modelExists) {
Write-Host "❌ No model artifacts found. Run CI pipeline first to train a model."
exit 1
}
if (-not $preprocessorExists) {
Write-Host "❌ Preprocessor not found. Run data preprocessing first."
exit 1
}
Write-Host "✅ All required artifacts are present"
# Test API locally (without Docker for Windows)
test-api:
needs: check-prerequisites
runs-on: self-hosted
defaults:
run:
shell: powershell
env:
BEST_MODEL_METRIC: f1
MLFLOW_EXPERIMENT_NAME: customer_churn_optimization
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python environment
run: |
if (Test-Path ".venv\Scripts\Activate.ps1") {
Write-Host "✅ Virtual environment found"
& ".venv\Scripts\Activate.ps1"
} else {
Write-Host "⚠️ No virtual environment found, using system Python"
}
# Install API dependencies
Write-Host "📦 Installing API dependencies..."
python -m pip install -q -r api/requirements.txt
- name: Start API server in background
run: |
Write-Host "🚀 Starting API server..."
$job = Start-Job -ScriptBlock {
Set-Location "d:\ML 101\ML_101_BootCamp"
python -m uvicorn api.main:app --host 127.0.0.1 --port 8000
}
Write-Host "⏳ Waiting for API to start..."
Start-Sleep -Seconds 10
# Check if job is still running
if ($job.State -eq "Running") {
Write-Host "✅ API server started (Job ID: $($job.Id))"
"API_JOB_ID=$($job.Id)" >> $env:GITHUB_ENV
} else {
Write-Host "❌ API server failed to start"
Receive-Job -Job $job
exit 1
}
- name: Test API endpoints
run: |
Write-Host "🧪 Testing API endpoints..."
# Test health endpoint
Write-Host "Testing /health..."
$health = Invoke-RestMethod -Uri "http://localhost:8000/health" -Method Get
if ($health.status -eq "ok") {
Write-Host "✅ Health check passed"
} else {
Write-Host "❌ Health check failed"
exit 1
}
# Test schema endpoint
Write-Host "Testing /schema..."
$schema = Invoke-RestMethod -Uri "http://localhost:8000/schema" -Method Get
if ($schema.required_columns) {
Write-Host "✅ Schema endpoint working"
Write-Host " Required columns: $($schema.required_columns.Count)"
} else {
Write-Host "❌ Schema endpoint failed"
exit 1
}
# Test prediction endpoint
Write-Host "Testing /predict..."
$body = @{
features = @{
Age = 30
Gender = "Female"
Tenure = 39
"Usage Frequency" = 14
"Support Calls" = 5
"Payment Delay" = 18
"Subscription Type" = "Standard"
"Contract Length" = "Annual"
"Total Spend" = 932
"Last Interaction" = 17
}
} | ConvertTo-Json
$prediction = Invoke-RestMethod -Uri "http://localhost:8000/predict" -Method Post -ContentType "application/json" -Body $body
if ($null -ne $prediction.prediction) {
Write-Host "✅ Prediction endpoint working"
Write-Host " Prediction: $($prediction.prediction)"
Write-Host " Probability: $($prediction.probability)"
} else {
Write-Host "❌ Prediction failed"
exit 1
}
Write-Host "🎉 All API tests passed!"
- name: Stop API server
if: always()
run: |
if ($env:API_JOB_ID) {
Write-Host "🛑 Stopping API server..."
Stop-Job -Id $env:API_JOB_ID -ErrorAction SilentlyContinue
Remove-Job -Id $env:API_JOB_ID -ErrorAction SilentlyContinue
Write-Host "✅ API server stopped"
}
# Deploy to selected environment
deploy:
needs: [check-prerequisites, test-api]
runs-on: self-hosted
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
defaults:
run:
shell: powershell
env:
DEPLOYMENT_TARGET: ${{ inputs.deployment_target || 'local' }}
API_PORT_LOCAL: 8000
API_PORT_STAGING: 8001
API_PORT_PRODUCTION: 8002
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Deploy to Local
if: env.DEPLOYMENT_TARGET == 'local'
run: |
Write-Host "🚀 Deploying to local environment..."
# Check if API is already running on port 8000
$running = Get-Process -Name "python" -ErrorAction SilentlyContinue | Where-Object {
$_.CommandLine -like "*uvicorn*api.main*8000*"
}
if ($running) {
Write-Host "⚠️ Stopping existing API server..."
Stop-Process -Id $running.Id -Force
Start-Sleep -Seconds 2
}
# Start API server as a background service
Write-Host "Starting API server on port ${{ env.API_PORT_LOCAL }}..."
Start-Process powershell -ArgumentList @(
"-NoExit",
"-Command",
"cd 'd:\ML 101\ML_101_BootCamp'; python -m uvicorn api.main:app --host 127.0.0.1 --port ${{ env.API_PORT_LOCAL }}"
) -WindowStyle Minimized
Write-Host "⏳ Waiting for server to start..."
Start-Sleep -Seconds 10
# Verify deployment
try {
$health = Invoke-RestMethod -Uri "http://localhost:${{ env.API_PORT_LOCAL }}/health" -Method Get
if ($health.status -eq "ok") {
Write-Host "✅ Local deployment successful!"
Write-Host "🌐 API available at: http://localhost:${{ env.API_PORT_LOCAL }}"
Write-Host "📚 API Docs: http://localhost:${{ env.API_PORT_LOCAL }}/docs"
}
} catch {
Write-Host "❌ Health check failed: $_"
exit 1
}
- name: Deploy to Staging
if: env.DEPLOYMENT_TARGET == 'staging'
run: |
Write-Host "🚀 Deploying to staging environment..."
# Similar to local but on different port
$running = Get-Process -Name "python" -ErrorAction SilentlyContinue | Where-Object {
$_.CommandLine -like "*uvicorn*api.main*${{ env.API_PORT_STAGING }}*"
}
if ($running) {
Write-Host "⚠️ Stopping existing staging server..."
Stop-Process -Id $running.Id -Force
Start-Sleep -Seconds 2
}
Write-Host "Starting staging server on port ${{ env.API_PORT_STAGING }}..."
Start-Process powershell -ArgumentList @(
"-NoExit",
"-Command",
"cd 'd:\ML 101\ML_101_BootCamp'; python -m uvicorn api.main:app --host 127.0.0.1 --port ${{ env.API_PORT_STAGING }}"
) -WindowStyle Minimized
Start-Sleep -Seconds 10
$health = Invoke-RestMethod -Uri "http://localhost:${{ env.API_PORT_STAGING }}/health" -Method Get
if ($health.status -eq "ok") {
Write-Host "✅ Staging deployment successful!"
Write-Host "🌐 Staging API: http://localhost:${{ env.API_PORT_STAGING }}"
}
- name: Deploy to Production
if: env.DEPLOYMENT_TARGET == 'production' && github.ref == 'refs/heads/main'
run: |
Write-Host "🚀 Deploying to production environment..."
Write-Host "⚠️ Production deployment configured for manual approval only"
# Production deployment steps would include:
# - Deploy to cloud service (Azure/AWS/GCP)
# - Update container registry
# - Configure load balancer
# - Set up monitoring and alerts
# - Run smoke tests
# - Gradual rollout with canary deployment
Write-Host "✅ Production deployment would happen here"
Write-Host "🔧 Configure cloud provider credentials and deployment scripts"
# Post-deployment verification
verify-deployment:
needs: deploy
runs-on: self-hosted
if: always() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
defaults:
run:
shell: powershell
env:
DEPLOYMENT_TARGET: ${{ inputs.deployment_target || 'local' }}
API_PORT_LOCAL: 8000
API_PORT_STAGING: 8001
steps:
- name: Verify Deployment
run: |
Write-Host "🔍 Verifying deployment..."
# Determine port based on environment
$port = if ($env:DEPLOYMENT_TARGET -eq "staging") { ${{ env.API_PORT_STAGING }} } else { ${{ env.API_PORT_LOCAL }} }
$baseUrl = "http://localhost:$port"
Write-Host "Testing $env:DEPLOYMENT_TARGET environment at $baseUrl"
# Extended health checks
$successCount = 0
for ($i = 1; $i -le 5; $i++) {
try {
$health = Invoke-RestMethod -Uri "$baseUrl/health" -Method Get -TimeoutSec 5
if ($health.status -eq "ok") {
Write-Host "✅ Health check $i/5 passed"
$successCount++
}
} catch {
Write-Host "⚠️ Health check $i/5 failed: $_"
}
Start-Sleep -Seconds 3
}
if ($successCount -ge 4) {
Write-Host "🎉 Deployment verified successfully! ($successCount/5 checks passed)"
} else {
Write-Host "❌ Deployment verification failed (only $successCount/5 checks passed)"
exit 1
}
# Test a prediction to ensure model is working
Write-Host "`n🧪 Testing prediction endpoint..."
$body = @{
features = @{
Age = 35
Gender = "Male"
Tenure = 24
"Usage Frequency" = 15
"Support Calls" = 2
"Payment Delay" = 5
"Subscription Type" = "Standard"
"Contract Length" = "Annual"
"Total Spend" = 1200
"Last Interaction" = 30
}
} | ConvertTo-Json
try {
$prediction = Invoke-RestMethod -Uri "$baseUrl/predict" -Method Post -ContentType "application/json" -Body $body
Write-Host "✅ Model prediction working"
Write-Host " Result: $($prediction.prediction) (probability: $($prediction.probability))"
} catch {
Write-Host "❌ Prediction test failed: $_"
exit 1
}
- name: Deployment Summary
if: always()
run: |
$target = if ($env:DEPLOYMENT_TARGET) { $env:DEPLOYMENT_TARGET } else { "local" }
$port = if ($target -eq "staging") { ${{ env.API_PORT_STAGING }} } else { ${{ env.API_PORT_LOCAL }} }
Write-Host "`n# 🚀 Deployment Summary" >> $env:GITHUB_STEP_SUMMARY
Write-Host "" >> $env:GITHUB_STEP_SUMMARY
Write-Host "**Target:** $target" >> $env:GITHUB_STEP_SUMMARY
Write-Host "**Commit:** ${{ github.sha }}" >> $env:GITHUB_STEP_SUMMARY
Write-Host "**Branch:** ${{ github.ref_name }}" >> $env:GITHUB_STEP_SUMMARY
Write-Host "**API URL:** http://localhost:$port" >> $env:GITHUB_STEP_SUMMARY
Write-Host "**Docs URL:** http://localhost:${port}/docs" >> $env:GITHUB_STEP_SUMMARY
Write-Host "" >> $env:GITHUB_STEP_SUMMARY
Write-Host "### 🧪 Quick Test Commands:" >> $env:GITHUB_STEP_SUMMARY
Write-Host '```powershell' >> $env:GITHUB_STEP_SUMMARY
Write-Host "# Health check" >> $env:GITHUB_STEP_SUMMARY
Write-Host "Invoke-RestMethod http://localhost:${port}/health" >> $env:GITHUB_STEP_SUMMARY
Write-Host "" >> $env:GITHUB_STEP_SUMMARY
Write-Host "# Get schema" >> $env:GITHUB_STEP_SUMMARY
Write-Host "Invoke-RestMethod http://localhost:${port}/schema" >> $env:GITHUB_STEP_SUMMARY
Write-Host "" >> $env:GITHUB_STEP_SUMMARY
Write-Host "# Open API documentation" >> $env:GITHUB_STEP_SUMMARY
Write-Host "Start-Process http://localhost:${port}/docs" >> $env:GITHUB_STEP_SUMMARY
Write-Host '```' >> $env:GITHUB_STEP_SUMMARY