Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Santhosha Rajashekar committed Jun 10, 2024
2 parents 848c1fd + 573f9dc commit b014a20
Show file tree
Hide file tree
Showing 13 changed files with 335 additions and 9 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/deploy-model-manual.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Deploy Model Manually #intrdouced for quick testing

on:
pull_request_target:
types: [labeled]

env:
workspace-name: aml-ws-mlops
resource-group: rg-mlops
job-name: amusing_jackal_9w9m3ls2b2
model-name: azureml_amusing_jackal_9w9m3ls2b2_output_mlflow_log_model_583397521

permissions:
id-token: write
contents: read

jobs:

deploy-model:
name: Deploy Model
if: github.event.label.name == 'deploy-model'
runs-on: ubuntu-latest
environment:
name: prod
steps:
- name: Check out repo
uses: actions/checkout@main
- name: Install az ml extension
run: az extension add -n ml -y
- name: "Az CLI login"
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Trigger Azure ML job
run: az ml online-deployment create -f src/deploy-endpoint.yml --all-traffic --workspace-name ${{ env.workspace-name }} --resource-group ${{ env.resource-group }}
40 changes: 40 additions & 0 deletions .github/workflows/deploy-webapp.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
on:
workflow_dispatch:

permissions:
id-token: write
contents: read

env:
AZURE_WEBAPP_NAME: mlops-skaroti-appservice # set this to your web application's name
AZURE_WEBAPP_PACKAGE_PATH: 'src/web/myapp' # set this to the path to your web app project, defaults to the repository root

jobs:
build:
environment:
name: 'prod'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

- name: Set up Python 3.x
uses: actions/setup-python@v4
with:
python-version: 3.x
- name: Install dependencies
run: |
pip install -r src/web/myapp/requirements.txt
- name: Deploy App
uses: azure/webapps-deploy@v2
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }}
package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
- name: logout
run: |
az logout
5 changes: 3 additions & 2 deletions .github/workflows/train-model.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name: Train Model

on:
pull_request_target:
types: [labeled]
workflow_dispatch:
branches:
- main

env:
workspace-name: aml-ws-mlops
Expand Down
7 changes: 4 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pytest==7.4.0
pytest==7.3.1
mlflow==2.6.0
pandas==2.0.3
pandas==2.0.1
sklearn==0.0
scikit-learn==1.3.0
scikit-learn==1.3.0
azureml-inference-server-http==1.2.2
7 changes: 4 additions & 3 deletions src/deploy-endpoint.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
$schema: https://azuremlschemas.azureedge.net/latest/managedOnlineDeployment.schema.json
name: mlflow-deployment
name: mlops-mlflow-predication-v3
endpoint_name: mlops-mlflow-endpoint-v1
model: azureml:azureml_amusing_jackal_9w9m3ls2b2_output_mlflow_log_model_583397521:1
model: azureml:azureml_polite_apple_qkbzxkd1kg_output_mlflow_log_model_299177315:1
instance_type: Standard_F4s_v2
instance_count: 1
instance_count: 1
environment: azureml:AzureML-sklearn-0.24-ubuntu18.04-py37-cpu@latest
76 changes: 76 additions & 0 deletions src/infra/main.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
param location string = resourceGroup().location
param keyVaultName string = 'amlwsmlokeyvault7251ed5b'

@description('Reference an existing Key Vault')
resource kv 'Microsoft.KeyVault/vaults@2023-02-01' existing = {
name: keyVaultName
}

@description('Create an App Service Plan with a Basic SKU')
resource exampleAppServicePlan 'Microsoft.Web/serverfarms@2022-09-01' = {
name: 'mlflow-ASP'
location: location
kind: 'Linux'
properties: {
reserved: true
}
sku: {
tier: 'Basic'
name: 'B1'
}
}

@description('Create the App Service with a system-assigned identity')
resource exampleAppService 'Microsoft.Web/sites@2022-09-01' = {
name: 'mlops-skaroti-appservice' // must be globally unique
location: location
kind: 'linux'
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: exampleAppServicePlan.id
reserved: true
siteConfig: {
linuxFxVersion: 'PYTHON|3.11'
}
}
}

@description('Configure the App Service with the Key Vault secrets')
resource exampleAppServiceConfig 'Microsoft.Web/sites/config@2022-09-01' = {
name: 'web'
parent: exampleAppService
properties: {
appSettings: [
{
name: 'API_KEY'
value: '@Microsoft.KeyVault(SecretUri=https://${kv.name}.vault.azure.net/secrets/apiKey/)'
}
{
name: 'AZURE_ML_ENDPOINT_URL'
value: '@Microsoft.KeyVault(SecretUri=https://${kv.name}.vault.azure.net/secrets/endpointUrl/)'
}
{
name: 'SCM_DO_BUILD_DURING_DEPLOYMENT'
value: '1'
}
]
}
}

@description('This is the built-in Key Vault Secrets User role. See https://docs.microsoft.com/azure/role-based-access-control/built-in-roles')
resource keyVaultSecretsUser 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
name: '4633458b-17de-408a-b874-0445c86b69e6'
}

@description('Assign the Key Vault Secrets User role to the App Service')
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(kv.id, exampleAppService.id)
properties: {
principalId: exampleAppService.identity.principalId
roleDefinitionId: keyVaultSecretsUser.id
principalType: 'ServicePrincipal'
}
scope: kv
}
2 changes: 1 addition & 1 deletion src/job-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ inputs:
path: azureml:diabetes-prod-folder:1
reg_rate: 0.01
environment: azureml:AzureML-sklearn-0.24-ubuntu18.04-py37-cpu@latest
compute: azureml:aml-ws-compute-ds11-v2
compute: azureml:aml-ws-compute-f4s-v2
experiment_name: diabetes-classification-production
description: Train a classification model to predict diabetes
Empty file added src/myapp/myapp/__init__.py
Empty file.
114 changes: 114 additions & 0 deletions src/myapp/myapp/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
from flask import Flask, request, render_template
import json
import os
import ssl
import urllib.request

app = Flask(__name__)


def allowSelfSignedHttps(allowed):
# bypass the server certificate verification on client side
if (
allowed
and not os.environ.get("PYTHONHTTPSVERIFY", "")
and getattr(ssl, "_create_unverified_context", None)
):
ssl._create_default_https_context = ssl._create_unverified_context


allowSelfSignedHttps(True)
# this line is needed if you use self-signed certificate
# in your scoring service.


@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "POST":
# Get user input
patient_id = request.form["PatientID"]
pregnancies = request.form["Pregnancies"]
plasma_glucose = request.form["PlasmaGlucose"]
diastolic_blood_pressure = request.form["DiastolicBloodPressure"]
triceps_thickness = request.form["TricepsThickness"]
serum_insulin = request.form["SerumInsulin"]
bmi = request.form["BMI"]
diabetes_pedigree = request.form["DiabetesPedigree"]
age = request.form["Age"]

# Format input data
input_data = {
"input_data": {
"columns": [
"PatientID",
"Pregnancies",
"PlasmaGlucose",
"DiastolicBloodPressure",
"TricepsThickness",
"SerumInsulin",
"BMI",
"DiabetesPedigree",
"Age",
],
"index": [0],
"data": [
[
patient_id,
pregnancies,
plasma_glucose,
diastolic_blood_pressure,
triceps_thickness,
serum_insulin,
bmi,
diabetes_pedigree,
age,
]
],
}
}

body = str.encode(json.dumps(input_data))
# Get the AZURE_ML_ENDPOINT_URL environment variable passed from App Service Application settings
url = os.environ["AZURE_ML_ENDPOINT_URL"]
# Get the API_KEY environment variable passed from App Service Application settings
api_key = os.environ["API_KEY"]
if not api_key:
raise Exception("A key should be provided to invoke the endpoint")

# The azureml-model-deployment header will force the request
# to go to a specific deployment.
# Remove this header to have the request observe the endpoint
# traffic rules
headers = {
"Content-Type": "application/json",
"Authorization": ("Bearer " + api_key),
"azureml-model-deployment": "mlflow-deployment",
}

req = urllib.request.Request(url, body, headers)

try:
response = urllib.request.urlopen(req)

result = json.loads(response.read())

# Display result
if result[0] == 1:
return render_template("result.html", result="The patient is diabetic.")
else:
return render_template(
"result.html", result="The patient is not diabetic."
)
except urllib.error.HTTPError as error:
print("The request failed with status code: " + str(error.code))

# Print the headers - they include the requert ID and the
# timestamp, which are useful for debugging the failure
print(error.info())
print(error.read().decode("utf8", "ignore"))
else:
return render_template("index.html")


if __name__ == "__main__":
app.run()
2 changes: 2 additions & 0 deletions src/myapp/myapp/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Flask==2.2.5
requests==2.31.0
30 changes: 30 additions & 0 deletions src/myapp/myapp/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<title>Diabetes Prediction</title>
</head>
<body>
<h1>Diabetes Prediction</h1>
<form method="post">
<label for="PatientID">Patient ID:</label><br>
<input type="text" id="PatientID" name="PatientID"><br><br>
<label for="Pregnancies">Pregnancies:</label><br>
<input type="text" id="Pregnancies" name="Pregnancies"><br><br>
<label for="PlasmaGlucose">Plasma Glucose:</label><br>
<input type="text" id="PlasmaGlucose" name="PlasmaGlucose"><br><br>
<label for="DiastolicBloodPressure">Diastolic Blood Pressure:</label><br>
<input type="text" id="DiastolicBloodPressure" name="DiastolicBloodPressure"><br><br>
<label for="TricepsThickness">Triceps Thickness:</label><br>
<input type="text" id="TricepsThickness" name="TricepsThickness"><br><br>
<label for="SerumInsulin">Serum Insulin:</label><br>
<input type="text" id="SerumInsulin" name="SerumInsulin"><br><br>
<label for="BMI">BMI:</label><br>
<input type="text" id="BMI" name="BMI"><br><br>
<label for="DiabetesPedigree">Diabetes Pedigree:</label><br>
<input type="text" id="DiabetesPedigree" name="DiabetesPedigree"><br><br>
<label for="Age">Age:</label><br>
<input type="text" id="Age" name="Age"><br><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
10 changes: 10 additions & 0 deletions src/myapp/myapp/templates/result.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>Diabetes Prediction Result</title>
</head>
<body>
<h1>Diabetes Prediction Result</h1>
<p>{{ result }}</p>
</body>
</html>
14 changes: 14 additions & 0 deletions src/myapp/myapp/web.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified" />
</handlers>
<httpPlatform processPath="%INTERPRETERPATH%" arguments="%INTERPRETER_ARGUMENTS%" stdoutLogEnabled="true" stdoutLogFile="\\?\%home%\LogFiles\python.log" startupTimeLimit="60" processesPerApplication="16">
<environmentVariables>
<environmentVariable name="PORT" value="%HTTP_PLATFORM_PORT%" />
<environmentVariable name="PYTHONPATH" value="%HOME%\site\wwwroot" />
</environmentVariables>
</httpPlatform>
</system.webServer>
</configuration>

0 comments on commit b014a20

Please sign in to comment.