From 23cb81a52589ee36f246fde2cdd1b971cd596fa3 Mon Sep 17 00:00:00 2001
From: airsimcloud <67428553+airsimcloud@users.noreply.github.com>
Date: Tue, 30 Jun 2020 14:42:36 -0400
Subject: [PATCH] Azure development environment and documentation (#2816)
* add azure doc to index
* initial move from external repo to airsim
* updated doc to new repo
* fixed paths for new repo and .vscode info
* added missing .vscode dir
* fixed link
* wording
* message after starting
* feedback
---
azure/.gitignore | 1 +
azure/.vscode/extensions.json | 7 +
azure/.vscode/launch.json | 15 ++
azure/.vscode/tasks.json | 14 +
azure/app/multirotor.py | 42 +++
azure/app/requirements.txt | 1 +
azure/app/settings.json | 19 ++
azure/azure-env-creation/configure-vm.ps1 | 37 +++
azure/azure-env-creation/vm-arm-template.json | 250 ++++++++++++++++++
azure/docker/Dockerfile | 63 +++++
azure/docker/docker-entrypoint.sh | 11 +
azure/start-airsim.ps1 | 23 ++
docs/azure.md | 84 ++++++
mkdocs.yml | 1 +
14 files changed, 568 insertions(+)
create mode 100644 azure/.gitignore
create mode 100644 azure/.vscode/extensions.json
create mode 100644 azure/.vscode/launch.json
create mode 100644 azure/.vscode/tasks.json
create mode 100644 azure/app/multirotor.py
create mode 100644 azure/app/requirements.txt
create mode 100644 azure/app/settings.json
create mode 100644 azure/azure-env-creation/configure-vm.ps1
create mode 100644 azure/azure-env-creation/vm-arm-template.json
create mode 100644 azure/docker/Dockerfile
create mode 100644 azure/docker/docker-entrypoint.sh
create mode 100644 azure/start-airsim.ps1
create mode 100644 docs/azure.md
diff --git a/azure/.gitignore b/azure/.gitignore
new file mode 100644
index 0000000000..f0f1cccf7f
--- /dev/null
+++ b/azure/.gitignore
@@ -0,0 +1 @@
+!.vscode
\ No newline at end of file
diff --git a/azure/.vscode/extensions.json b/azure/.vscode/extensions.json
new file mode 100644
index 0000000000..88a39fc08c
--- /dev/null
+++ b/azure/.vscode/extensions.json
@@ -0,0 +1,7 @@
+{
+ "recommendations": [
+ "ms-python.python",
+ "ms-azuretools.vscode-docker",
+ "ms-vscode.powershell"
+ ]
+}
diff --git a/azure/.vscode/launch.json b/azure/.vscode/launch.json
new file mode 100644
index 0000000000..17e15f27ec
--- /dev/null
+++ b/azure/.vscode/launch.json
@@ -0,0 +1,15 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Python: Current File",
+ "type": "python",
+ "request": "launch",
+ "program": "${file}",
+ "console": "integratedTerminal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/azure/.vscode/tasks.json b/azure/.vscode/tasks.json
new file mode 100644
index 0000000000..f4294d52df
--- /dev/null
+++ b/azure/.vscode/tasks.json
@@ -0,0 +1,14 @@
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=733558
+ // for the documentation about the tasks.json format
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "Start AirSim",
+ "type": "shell",
+ "options": {"shell": {"executable": "powershell.exe"}},
+ "command": ".\\start-airsim.ps1"
+
+ }
+ ]
+}
\ No newline at end of file
diff --git a/azure/app/multirotor.py b/azure/app/multirotor.py
new file mode 100644
index 0000000000..8fe53c6420
--- /dev/null
+++ b/azure/app/multirotor.py
@@ -0,0 +1,42 @@
+import airsim
+import pprint
+
+def print_state(client):
+ state = client.getMultirotorState()
+ s = pprint.pformat(state)
+ print("state: %s" % s)
+
+ imu_data = client.getImuData()
+ s = pprint.pformat(imu_data)
+ print("imu_data: %s" % s)
+
+ barometer_data = client.getBarometerData()
+ s = pprint.pformat(barometer_data)
+ print("barometer_data: %s" % s)
+
+ magnetometer_data = client.getMagnetometerData()
+ s = pprint.pformat(magnetometer_data)
+ print("magnetometer_data: %s" % s)
+
+ gps_data = client.getGpsData()
+ s = pprint.pformat(gps_data)
+ print("gps_data: %s" % s)
+
+# connect to the AirSim simulator
+client = airsim.MultirotorClient()
+client.confirmConnection()
+client.enableApiControl(True)
+client.armDisarm(True)
+
+print_state(client)
+print('Takeoff')
+client.takeoffAsync().join()
+
+while True:
+ print_state(client)
+ print('Go to (-10, 10, -10) at 5 m/s')
+ client.moveToPositionAsync(-10, 10, -10, 5).join()
+ client.hoverAsync().join()
+ print_state(client)
+ print('Go to (0, 10, 0) at 5 m/s')
+ client.moveToPositionAsync(0, 10, 0, 5).join()
diff --git a/azure/app/requirements.txt b/azure/app/requirements.txt
new file mode 100644
index 0000000000..ca180bf3ec
--- /dev/null
+++ b/azure/app/requirements.txt
@@ -0,0 +1 @@
+airsim
\ No newline at end of file
diff --git a/azure/app/settings.json b/azure/app/settings.json
new file mode 100644
index 0000000000..44b5a20009
--- /dev/null
+++ b/azure/app/settings.json
@@ -0,0 +1,19 @@
+{
+ "SeeDocsAt": "https://github.com/Microsoft/AirSim/blob/master/docs/settings_json.md",
+ "SettingsVersion": 1.2,
+ "SimMode": "Multirotor",
+ "ClockSpeed": 1.0,
+ "Vehicles": {
+ "SimpleFlight": {
+ "VehicleType": "SimpleFlight",
+ "DefaultVehicleState": "Armed",
+ "EnableCollisionPassthrogh": false,
+ "EnableCollisions": true,
+ "AllowAPIAlways": true,
+ "RC": {
+ "RemoteControlID": 0,
+ "AllowAPIWhenDisconnected": false
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/azure/azure-env-creation/configure-vm.ps1 b/azure/azure-env-creation/configure-vm.ps1
new file mode 100644
index 0000000000..3910298c07
--- /dev/null
+++ b/azure/azure-env-creation/configure-vm.ps1
@@ -0,0 +1,37 @@
+$airSimInstallPath = "C:\AirSim\"
+$airSimBinaryZipUrl = "https://github.com/microsoft/AirSim/releases/download/v1.3.1-windows/Blocks.zip"
+$airSimBinaryZipFilename = "Blocks.zip"
+$airSimBinaryPath = $airSimInstallPath + "blocks\blocks\binaries\win64\blocks.exe"
+$airSimBinaryName = "Blocks"
+
+$webClient = new-object System.Net.WebClient
+
+# Install the OpenSSH Client
+Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
+# Install the OpenSSH Server
+Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
+# Enable service
+Start-Service sshd
+Set-Service -Name sshd -StartupType 'Automatic'
+
+#Install Chocolatey
+Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iwr https://chocolatey.org/install.ps1 -UseBasicParsing | iex
+# Bypass confirmation in scripts.
+choco feature enable --name="'allowGlobalConfirmation'"
+choco install python --version=3.8.2
+choco install git
+# Run time c++
+choco install vcredist-all
+choco install directx
+
+#Create new folder & set as default directory
+New-Item -ItemType directory -Path $airSimInstallPath
+cd $airSimInstallPath
+
+# Get AirSim
+$webClient.DownloadFile($airSimBinaryZipUrl, $airSimInstallPath + $airSimBinaryZipFilename)
+# Unzip AirSim
+Expand-Archive $airSimBinaryZipFilename
+
+# Firewall rule for AirSim
+New-NetFirewallRule -DisplayName $airSimBinaryName -Direction Inbound -Program $airSimBinaryPath -Action Allow
diff --git a/azure/azure-env-creation/vm-arm-template.json b/azure/azure-env-creation/vm-arm-template.json
new file mode 100644
index 0000000000..e87babba6d
--- /dev/null
+++ b/azure/azure-env-creation/vm-arm-template.json
@@ -0,0 +1,250 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "VmName": {
+ "type": "string",
+ "metadata": {
+ "description": "Name for the VM can be whatever you want, but can't be changed once the VM is created."
+ }
+ },
+ "AdminUsername": {
+ "type": "string",
+ "metadata": {
+ "description": "Admin user name for the VM."
+ },
+ "defaultValue": "AzureUser"
+ },
+ "AdminPassword": {
+ "type": "securestring",
+ "metadata": {
+ "description": "Admin user password for the VM."
+ }
+ },
+ "VmSize": {
+ "type": "string",
+ "metadata": {
+ "description": "Desired Size of the VM (NV-series installs NVIDIA GPU drivers in WDDM mode for graphical display by default)."
+ },
+ "defaultValue": "Standard_NV6",
+ "allowedValues": [
+ "Standard_NV6_Promo",
+ "Standard_NV6",
+ "Standard_NV12",
+ "Standard_NV24"
+ ]
+ },
+ "ScriptLocation": {
+ "type": "string",
+ "metadata": {
+ "description": "Location of the setup script"
+ },
+ "defaultValue": "https://raw.githubusercontent.com/microsoft/airsim/master/azure/azure-env-creation"
+ },
+ "ScriptFileName": {
+ "type": "string",
+ "metadata": {
+ "description": "Name of the setup script"
+ },
+ "defaultValue": "configure-vm.ps1"
+ }
+
+ },
+ "variables": {
+ "NetworkInterfaceCardName": "[concat(parameters('VmName'),'-nic')]",
+ "PublicIPAddressName": "[concat(parameters('VmName'),'-ip')]",
+ "NetworkSecurityGroupName": "[concat(parameters('VmName'),'-nsg')]",
+ "VirtualNetworkName": "[concat(parameters('VmName'),'-vnet')]"
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Network/networkSecurityGroups",
+ "apiVersion": "2019-12-01",
+ "name": "[variables('NetworkSecurityGroupName')]",
+ "location": "[resourceGroup().location]",
+ "properties": {
+ "securityRules": [
+ {
+ "name": "RDP",
+ "properties": {
+ "protocol": "Tcp",
+ "sourcePortRange": "*",
+ "destinationPortRange": "3389",
+ "sourceAddressPrefix": "*",
+ "destinationAddressPrefix": "*",
+ "access": "Allow",
+ "priority": 300,
+ "direction": "Inbound"
+ }
+ },
+ {
+ "name": "SSH",
+ "properties": {
+ "protocol": "Tcp",
+ "sourcePortRange": "*",
+ "destinationPortRange": "22",
+ "sourceAddressPrefix": "*",
+ "destinationAddressPrefix": "*",
+ "access": "Allow",
+ "priority": 320,
+ "direction": "Inbound"
+ }
+ }
+
+ ]
+ }
+ },
+ {
+ "type": "Microsoft.Network/virtualNetworks/subnets",
+ "apiVersion": "2019-12-01",
+ "name": "[concat(variables('VirtualNetworkName'), '/default')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Network/virtualNetworks', variables('VirtualNetworkName'))]"
+ ],
+ "properties": {
+ "addressPrefix": "10.0.0.0/24"
+ }
+ },
+ {
+ "type": "Microsoft.Network/virtualNetworks",
+ "apiVersion": "2019-12-01",
+ "name": "[variables('VirtualNetworkName')]",
+ "location": "[resourceGroup().location]",
+ "properties": {
+ "addressSpace": {
+ "addressPrefixes": [
+ "10.0.0.0/24"
+ ]
+ },
+ "subnets": [
+ {
+ "name": "default",
+ "properties": {
+ "addressPrefix": "10.0.0.0/24"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "type": "Microsoft.Network/publicIPAddresses",
+ "apiVersion": "2019-12-01",
+ "name": "[variables('PublicIPAddressName')]",
+ "location": "[resourceGroup().location]",
+ "properties": {
+ "publicIPAllocationMethod": "Dynamic"
+ }
+ },
+ {
+ "type": "Microsoft.Network/networkInterfaces",
+ "apiVersion": "2019-12-01",
+ "name": "[variables('NetworkInterfaceCardName')]",
+ "location": "[resourceGroup().location]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Network/publicIPAddresses', variables('PublicIPAddressName'))]",
+ "[resourceId('Microsoft.Network/networkSecurityGroups', variables('NetworkSecurityGroupName'))]",
+ "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('VirtualNetworkName'), 'default')]"
+ ],
+ "properties": {
+ "ipConfigurations": [
+ {
+ "name": "ipconfig1",
+ "properties": {
+ "publicIPAddress": {
+ "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('PublicIPAddressName'))]"
+ },
+ "subnet": {
+ "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('VirtualNetworkName'), 'default')]"
+ }
+ }
+ }
+ ],
+ "networkSecurityGroup": {
+ "id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('NetworkSecurityGroupName'))]"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Compute/virtualMachines",
+ "apiVersion": "2019-07-01",
+ "name": "[parameters('VmName')]",
+ "location": "[resourceGroup().location]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Network/networkInterfaces', variables('NetworkInterfaceCardName'))]"
+ ],
+ "properties": {
+ "hardwareProfile": {
+ "vmSize": "[parameters('vmSize')]"
+ },
+ "storageProfile": {
+ "imageReference": {
+ "publisher": "MicrosoftWindowsDesktop",
+ "offer": "Windows-10",
+ "sku": "rs5-pro",
+ "version": "latest"
+ },
+ "osDisk": {
+ "osType": "Windows",
+ "name": "[concat(parameters('VmName'), '_OsDisk')]",
+ "createOption": "FromImage",
+ "caching": "ReadWrite"
+ }
+ },
+ "osProfile": {
+ "computerName": "[parameters('VmName')]",
+ "adminUsername": "[parameters('AdminUsername')]",
+ "adminPassword": "[parameters('AdminPassword')]"
+ },
+ "networkProfile": {
+ "networkInterfaces": [
+ {
+ "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('NetworkInterfaceCardName'))]"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "name": "[concat(parameters('VmName'),'/GPUDrivers')]",
+ "type": "Microsoft.Compute/virtualMachines/extensions",
+ "location": "[resourceGroup().location]",
+ "apiVersion": "2019-07-01",
+ "dependsOn": [
+ "[resourceId('Microsoft.Compute/virtualMachines/', parameters('VmName'))]"
+ ],
+ "properties": {
+ "publisher": "Microsoft.HpcCompute",
+ "type": "NvidiaGpuDriverWindows",
+ "typeHandlerVersion": "1.3",
+ "autoUpgradeMinorVersion": true,
+ "settings": {
+ }
+ }
+ },
+ {
+ "name": "[concat(parameters('VmName'),'/SetupScript')]",
+ "apiVersion": "2019-07-01",
+ "type": "Microsoft.Compute/virtualMachines/extensions",
+ "location": "[resourceGroup().location]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VmName'), 'GPUDrivers')]"
+ ],
+ "properties": {
+ "publisher": "Microsoft.Compute",
+ "type": "CustomScriptExtension",
+ "typeHandlerVersion": "1.10",
+ "autoUpgradeMinorVersion": true,
+ "settings": {
+ "fileUris": [
+ "[concat(parameters('ScriptLocation'), '/' , parameters('ScriptFileName'))]"
+ ],
+ "commandToExecute": "[concat('powershell.exe -ExecutionPolicy bypass -File ./', parameters('ScriptFileName'))]"
+ },
+ "protectedSettings": {
+ }
+ }
+ }
+ ],
+ "outputs": {
+ }
+}
\ No newline at end of file
diff --git a/azure/docker/Dockerfile b/azure/docker/Dockerfile
new file mode 100644
index 0000000000..27766f87f7
--- /dev/null
+++ b/azure/docker/Dockerfile
@@ -0,0 +1,63 @@
+FROM nvidia/cudagl:9.0-devel
+
+RUN apt-get update
+RUN apt-get install \
+ sudo \
+ libglu1-mesa-dev \
+ xdg-user-dirs \
+ pulseaudio \
+ sudo \
+ x11-xserver-utils \
+ unzip \
+ wget \
+ software-properties-common \
+ -y --no-install-recommends
+
+RUN apt-add-repository ppa:deadsnakes/ppa
+RUN apt-get update
+RUN apt-get install -y \
+ python3.6 \
+ python3-pip
+
+RUN python3.6 -m pip install --upgrade pip
+
+RUN python3.6 -m pip install setuptools wheel
+
+RUN adduser --force-badname --disabled-password --gecos '' --shell /bin/bash airsim_user && \
+ echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \
+ adduser airsim_user sudo && \
+ adduser airsim_user audio && \
+ adduser airsim_user video
+
+USER airsim_user
+WORKDIR /home/airsim_user
+
+# Change the following values to use a different AirSim binary
+# Also change the AIRSIM_EXECUTABLE variable in docker-entrypoint.sh
+ENV AIRSIM_BINARY_ZIP_URL=https://github.com/microsoft/AirSim/releases/download/v1.3.1-linux/Blocks.zip
+ENV AIRSIM_BINARY_ZIP_FILENAME=Blocks.zip
+
+ENV SDL_VIDEODRIVER_VALUE=offscreen
+ENV SDL_HINT_CUDA_DEVICE=0
+
+# Download and unzip the AirSim binary
+RUN wget -c $AIRSIM_BINARY_ZIP_URL
+RUN unzip $AIRSIM_BINARY_ZIP_FILENAME
+RUN rm $AIRSIM_BINARY_ZIP_FILENAME
+
+# Add the Python app to the image
+ADD ./app /home/airsim_user/app
+
+WORKDIR /home/airsim_user
+RUN mkdir -p /home/airsim_user/Documents/AirSim
+ADD ./app/settings.json /home/airsim_user/Documents/AirSim
+ADD ./docker/docker-entrypoint.sh /home/airsim_user/docker-entrypoint.sh
+
+RUN sudo chown -R airsim_user /home/airsim_user
+
+WORKDIR /home/airsim_user/app
+
+# Install Python requirements
+RUN python3.6 -m pip install -r requirements.txt
+
+ENTRYPOINT /home/airsim_user/docker-entrypoint.sh
\ No newline at end of file
diff --git a/azure/docker/docker-entrypoint.sh b/azure/docker/docker-entrypoint.sh
new file mode 100644
index 0000000000..436736918c
--- /dev/null
+++ b/azure/docker/docker-entrypoint.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+AIRSIM_EXECUTABLE=/home/airsim_user/Blocks/Blocks.sh
+
+echo Starting AirSim binary...
+$AIRSIM_EXECUTABLE &
+
+echo Waiting 10 seconds before starting app...
+sleep 10
+
+echo Starting Python app
+python3.6 /home/airsim_user/app/multirotor.py
\ No newline at end of file
diff --git a/azure/start-airsim.ps1 b/azure/start-airsim.ps1
new file mode 100644
index 0000000000..6f52061501
--- /dev/null
+++ b/azure/start-airsim.ps1
@@ -0,0 +1,23 @@
+# Script parameters
+$airSimExecutable = "c:\AirSim\Blocks\blocks.exe"
+$airSimProcessName = "Blocks"
+
+# Ensure proper path
+$env:Path =
+ [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" +
+ [System.Environment]::GetEnvironmentVariable("Path","User")
+
+# Install python app requirements
+pip3 install -r .\app\requirements.txt
+
+# Overwrite AirSim configuration
+New-Item -ItemType Directory -Force -Path $env:USERPROFILE\Documents\AirSim\
+copy .\app\settings.json $env:USERPROFILE\Documents\AirSim\
+
+# Kill previous AirSim instance
+Stop-Process -Name $airSimProcessName -Force -ErrorAction SilentlyContinue
+sleep 2
+
+# Start new AirSim instance
+Start-Process -NoNewWindow -FilePath $airSimExecutable -ArgumentList "-RenderOffScreen"
+echo "Starting the AirSim environment has completed."
diff --git a/docs/azure.md b/docs/azure.md
new file mode 100644
index 0000000000..4e6baf83bb
--- /dev/null
+++ b/docs/azure.md
@@ -0,0 +1,84 @@
+# AirSim Development Environment on Azure
+
+This document explains how to automate the creation of a development environment on Azure and code and debug a Python application connected to AirSim using Visual Studio Code
+
+## Automatically Deploy Your Azure VM
+Use [this](../azure/azure-env-creation/vm-arm-template.json) template to create, deploy and configure an Azure VM to work with AirSim
+
+*Note: the VM deployment and configuration process may take 20+ minutes to complete*
+
+
+
+
+
+### Regarding the deployment of the Azure VM
+- When using an Azure Trial account, the default vCPU quota may not be enough to allocate the required VM to run AirSim. If that's the case, you will see an error when trying to create the VM and will have to make a support request to increase the default quota.
+
+- To avoid charges for the Virtual Machine usage while not in use, remember to deallocate its resources from the [Azure Portal](https://portal.azure.com) or use the following command from the Azure CLI:
+ ```bash
+ az vm deallocate --resource-group MyResourceGroup --name MyVMName
+ ```
+
+## Code and debug from Visual Studio Code and Remote SSH
+- Install Visual Studio Code
+- Install the *Remote - SSH* extension
+- Press `F1` and run the `Remote - SSH: Connect to host...` command
+- Add the recently create VM details. For instance, `AzureUser@11.22.33.44`
+- Run the `Remote - SSH: Connect to host...` command again, and now select the newly added connection.
+- Once connected, click on the `Clone Repository` button in Visual Studio Code, and either clone this repository in the remote VM and open *just the `azure` folder*, or create a brand new repository, clone it and copy the contents of the `azure` folder from this repository in it. It is important to open that directory so Visual Studio Code can use the specific `.vscode` directory for the scenario and not the general AirSim `.vscode` directory. It contains the recommended extensions to install, the task to start AirSim remotely and the launch configuration for the Python application.
+- Install all the recommended extensions
+- Press `F1` and select the `Tasks: Run Task` option. Then, select the `Start AirSim` task from Visual Studio Code to execute the `start-airsim.ps1` script from Visual Studio Code.
+- Open the `multirotor.py` file inside the `app` directory
+- Start debugging with Python
+- When finished, remember to stop an deallocate the Azure VM to avoid extra charges
+
+## Code and debug from a local Visual Studio Code and connect to AirSim via forwarded ports
+
+*Note: this scenario, will be using two Visual Studio Code instances.
+The first one will be used as a bridge to forward ports via SSH to the Azure VM and execute remote processes, and the second one will
+be used for local Python development.
+To be able to reach the VM from the local Python code, it is required to keep the `Remote - SSH` instance of Visual Studio Code opened, while working with the local Python environment on the second instance*
+
+- Open the first Visual Studio Code instance
+- Follow the steps in the previous section to connect via `Remote - SSH`
+- In the *Remote Explorer*, add the port `41451` as a forwarded port to localhost
+- Either run the `Start AirSim` task on the Visual Studio Code with the remote session as explained in the previous scenario or manually start the AirSim binary in the VM
+- Open a second Visual Studio Code instance, without disconnecting or closing the first one
+- Either clone this repository locally and open *just the `azure` folder* in Visual Studio Code, or create a brand new repository, clone it and copy the contents of the `azure` folder from this repository in it.
+- Run `pip install -r requirements.txt` inside the `app` directory
+- Open the `multirotor.py` file inside the `app` directory
+- Start debugging with Python
+- When finished, remember to stop an deallocate the Azure VM to avoid extra charges
+
+## Running with Docker
+Once both the AirSim environment and the Python application are ready, you can package everything as a Docker image. The sample project inside the `azure` directory is already prepared to run a prebuilt AirSim binary and Python code using Docker.
+
+This would be a perfect scenario when you want to run the simulation at scale. For instance, you could have several different configurations for the same simulation and execute them in a parallel, unattended way using a Docker image on Azure Container Services
+
+Since AirSim requires access to the host GPU, it is required to use a Docker runtime that supports it. For more information about running AirSim in Docker, click [here](https://github.com/microsoft/AirSim/blob/master/docs/docker_ubuntu.md).
+
+When using Azure Container Services to run this image, the only extra-requirement is to add GPU support to the Container Group where it will be deployed.
+
+It can use either public docker images from DockerHub or images deployed to a private Azure Container Registry
+
+### Building the docker image
+
+```bash
+docker build -t / -f ./docker/Dockerfile .`
+```
+
+## Using a different AirSim binary
+
+To use a different AirSim binary, first check the official documentation on [How to Build AirSim on Windows](build_windows.md) and [How to Build AirSim on Linux](build_linux.md) if you also want to run it with Docker
+
+Once you have a zip file with the new AirSim environment (or prefer to use one from the [Official Releases](https://github.com/microsoft/AirSim/releases)), you need to modify some of the scripts in the `azure` directory of the repository to point to the new environment:
+- In [`azure/azure-env-creation/configure-vm.ps1`](../azure/azure-env-creation/configure-vm.ps1), modify all the parameters starting with `$airSimBinary` with the new values
+- In [`azure/start-airsim.ps1`](../azure/start-airsim.ps1), modify `$airSimExecutable` and `$airSimProcessName` with the new values
+
+If you are using the docker image, you also need a linux binary zip file and modify the following Docker-related files:
+- In [`azure/docker/Dockerfile`](../azure/docker/Dockerfile), modify the `AIRSIM_BINARY_ZIP_URL` and `AIRSIM_BINARY_ZIP_FILENAME` ENV declarations with the new values
+- In [`azure/docker/docker-entrypoint.sh`](../azure/docker/docker-entrypoint.sh), modify `AIRSIM_EXECUTABLE` with the new value
+
+## Maintaining this development environment
+
+Several components of this development environment (ARM templates, initialization scripts and VSCode tasks) directly depend on the current directory structures file names and repository locations. When planning to modify/fork any of those, make sure to check every script and template to make any required adjustment.
diff --git a/mkdocs.yml b/mkdocs.yml
index e8385608a1..40d7eadc8c 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -37,6 +37,7 @@ nav:
- "Build on Windows": 'build_windows.md'
- "Build on Linux": 'build_linux.md'
- "Docker on Linux": 'docker_ubuntu.md'
+ - "AirSim on Azure": 'azure.md'
- "Custom Unreal Environment": 'unreal_custenv.md'
- "AirSim with Unity": "Unity.md"
- "Custom Unity Environment": "custom_unity_environments.md"