{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Copyright (c) Microsoft Corporation. All rights reserved.\n",
        "\n",
        "Licensed under the MIT License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Deploying a web service to Azure Kubernetes Service (AKS)\n",
        "This notebook shows the steps for deploying a service: registering a model, creating an image, provisioning a cluster (one time action), and deploying a service to it. \n",
        "We then test and delete the service, image and model."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "from azureml.core import Workspace\n",
        "from azureml.core.compute import AksCompute, ComputeTarget\n",
        "from azureml.core.webservice import Webservice, AksWebservice\n",
        "from azureml.core.image import Image\n",
        "from azureml.core.model import Model"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "import azureml.core\n",
        "print(azureml.core.VERSION)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Get workspace\n",
        "Load existing workspace from the config file info."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "from azureml.core.workspace import Workspace\n",
        "\n",
        "ws = Workspace.from_config()\n",
        "print(ws.name, ws.resource_group, ws.location, ws.subscription_id, sep = '\\n')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Register the model\n",
        "Register an existing trained model, add descirption and tags. Prior to registering the model, you should have a TensorFlow [Saved Model](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/saved_model/README.md) in the `resnet50` directory. You can download a [pretrained resnet50](https://github.com/tensorflow/models/tree/master/official/resnet#pre-trained-model) and unpack it to that directory."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "#Register the model\n",
        "from azureml.core.model import Model\n",
        "model = Model.register(model_path = \"resnet50\", # this points to a local file\n",
        "                       model_name = \"resnet50\", # this is the name the model is registered as\n",
        "                       tags = {'area': \"Image classification\", 'type': \"classification\"},\n",
        "                       description = \"Image classification trained on Imagenet Dataset\",\n",
        "                       workspace = ws)\n",
        "\n",
        "print(model.name, model.description, model.version)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Create an image\n",
        "Create an image using the registered model the script that will load and run the model."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "%%writefile score.py\n",
        "import tensorflow as tf\n",
        "import numpy as np\n",
        "import ujson\n",
        "from azureml.core.model import Model\n",
        "from azureml.contrib.services.aml_request import AMLRequest, rawhttp\n",
        "from azureml.contrib.services.aml_response import AMLResponse\n",
        "\n",
        "def init():\n",
        "    global session\n",
        "    global input_name\n",
        "    global output_name\n",
        "    \n",
        "    session = tf.Session()\n",
        "\n",
        "    model_path = Model.get_model_path('resnet50')\n",
        "    model = tf.saved_model.loader.load(session, ['serve'], model_path)\n",
        "    if len(model.signature_def['serving_default'].inputs) > 1:\n",
        "        raise ValueError(\"This score.py only supports one input\")\n",
        "    if len(model.signature_def['serving_default'].outputs) > 1:\n",
        "        raise ValueError(\"This score.py only supports one input\")\n",
        "    input_name = [tensor.name for tensor in model.signature_def['serving_default'].inputs.values()][0]\n",
        "    output_name = [tensor.name for tensor in model.signature_def['serving_default'].outputs.values()][0]\n",
        "    \n",
        "\n",
        "@rawhttp\n",
        "def run(request):\n",
        "    if request.method == 'POST':\n",
        "        reqBody = request.get_data(False)\n",
        "        resp = score(reqBody)\n",
        "        return AMLResponse(resp, 200)\n",
        "    if request.method == 'GET':\n",
        "        respBody = str.encode(\"GET is not supported\")\n",
        "        return AMLResponse(respBody, 405)\n",
        "    return AMLResponse(\"bad request\", 500)\n",
        "\n",
        "def score(data):\n",
        "    result = session.run(output_name, {input_name: [data]})\n",
        "    return ujson.dumps(result[0])\n",
        "\n",
        "if __name__ == \"__main__\":\n",
        "    init()\n",
        "    with open(\"test_image.jpg\", 'rb') as f:\n",
        "        content = f.read()\n",
        "        print(score(content))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "from azureml.core.conda_dependencies import CondaDependencies \n",
        "\n",
        "myenv = CondaDependencies.create(conda_packages=['tensorflow-gpu==1.12.0','numpy','ujson','azureml-contrib-services'])\n",
        "\n",
        "with open(\"myenv.yml\",\"w\") as f:\n",
        "    f.write(myenv.serialize_to_string())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "from azureml.core.image import ContainerImage\n",
        "\n",
        "image_config = ContainerImage.image_configuration(execution_script = \"score.py\",\n",
        "                                                  runtime = \"python\",\n",
        "                                                  conda_file = \"myenv.yml\",\n",
        "                                                  gpu_enabled = True\n",
        "                                                 )\n",
        "\n",
        "image = ContainerImage.create(name = \"GpuImage\",\n",
        "                              # this is the model object\n",
        "                              models = [model],\n",
        "                              image_config = image_config,\n",
        "                              workspace = ws)\n",
        "\n",
        "image.wait_for_creation(show_output = True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Provision the AKS Cluster\n",
        "This is a one time setup. You can reuse this cluster for multiple deployments after it has been created. If you delete the cluster or the resource group that contains it, then you would have to recreate it."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Use the default configuration (can also provide parameters to customize)\n",
        "prov_config = AksCompute.provisioning_configuration(vm_size=\"Standard_NC6\")\n",
        "\n",
        "aks_name = 'my-aks-9' \n",
        "# Create the cluster\n",
        "aks_target = ComputeTarget.create(workspace = ws, \n",
        "                                  name = aks_name, \n",
        "                                  provisioning_configuration = prov_config)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Create AKS Cluster in an existing virtual network (optional)\n",
        "See code snippet below. Check the documentation [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-enable-virtual-network#use-azure-kubernetes-service) for more details."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "'''\n",
        "from azureml.core.compute import ComputeTarget, AksCompute\n",
        "\n",
        "# Create the compute configuration and set virtual network information\n",
        "config = AksCompute.provisioning_configuration(vm_size=\"Standard_NC6\", location=\"eastus2\")\n",
        "config.vnet_resourcegroup_name = \"mygroup\"\n",
        "config.vnet_name = \"mynetwork\"\n",
        "config.subnet_name = \"default\"\n",
        "config.service_cidr = \"10.0.0.0/16\"\n",
        "config.dns_service_ip = \"10.0.0.10\"\n",
        "config.docker_bridge_cidr = \"172.17.0.1/16\"\n",
        "\n",
        "# Create the compute target\n",
        "aks_target = ComputeTarget.create(workspace = ws,\n",
        "                                  name = \"myaks\",\n",
        "                                  provisioning_configuration = config)\n",
        "'''"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Enable SSL on the AKS Cluster (optional)\n",
        "See code snippet below. Check the documentation [here](https://docs.microsoft.com/en-us/azure/machine-learning/service/how-to-secure-web-service) for more details"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# provisioning_config = AksCompute.provisioning_configuration(ssl_cert_pem_file=\"cert.pem\", ssl_key_pem_file=\"key.pem\", ssl_cname=\"www.contoso.com\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "%%time\n",
        "aks_target.wait_for_completion(show_output = True)\n",
        "print(aks_target.provisioning_state)\n",
        "print(aks_target.provisioning_errors)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Optional step: Attach existing AKS cluster\n",
        "\n",
        "If you have existing AKS cluster in your Azure subscription, you can attach it to the Workspace."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "'''\n",
        "# Use the default configuration (can also provide parameters to customize)\n",
        "resource_id = '/subscriptions/92c76a2f-0e1c-4216-b65e-abf7a3f34c1e/resourcegroups/raymondsdk0604/providers/Microsoft.ContainerService/managedClusters/my-aks-0605d37425356b7d01'\n",
        "\n",
        "create_name='my-existing-aks' \n",
        "# Create the cluster\n",
        "attach_config = AksCompute.attach_configuration(resource_id=resource_id)\n",
        "aks_target = ComputeTarget.attach(workspace=ws, name=create_name, attach_configuration=attach_config)\n",
        "# Wait for the operation to complete\n",
        "aks_target.wait_for_completion(True)\n",
        "'''"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Deploy web service to AKS"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "#Set the web service configuration (using default here)\n",
        "aks_config = AksWebservice.deploy_configuration()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "%%time\n",
        "aks_service_name ='aks-service-1'\n",
        "\n",
        "aks_service = Webservice.deploy_from_image(workspace = ws, \n",
        "                                           name = aks_service_name,\n",
        "                                           image = image,\n",
        "                                           deployment_config = aks_config,\n",
        "                                           deployment_target = aks_target)\n",
        "aks_service.wait_for_deployment(show_output = True)\n",
        "print(aks_service.state)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Test the web service\n",
        "We test the web sevice by passing the test images content."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "%%time\n",
        "import requests\n",
        "key1, key2 = aks_service.get_keys()\n",
        "\n",
        "headers = {'Content-Type':'application/json', 'Authorization': 'Bearer ' + key1}\n",
        "test_sampe = open('test_image.jpg', 'rb').read()\n",
        "resp = requests.post(aks_service.scoring_uri, test_sample, headers=headers)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Clean up\n",
        "Delete the service, image, model and compute target"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "%%time\n",
        "aks_service.delete()\n",
        "image.delete()\n",
        "model.delete()\n",
        "aks_target.delete()"
      ]
    }
  ],
  "metadata": {
    "authors": [
      {
        "name": "aashishb"
      }
    ],
    "kernelspec": {
      "display_name": "Python 3.6",
      "language": "python",
      "name": "python36"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.7.0"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 2
}