Python 3.6 deployments on Azure Function Apps

Using Python on Azure Function Apps, by default, we have two options: Python 2.7 or Python 3.4 (it is not possible to set Python version from Azure portal yet, but it is an another issue I will describe in another post).

For Azure Web Apps it is quite simple to install Python 3.6 - we can use Site Extension Gallery (link).

Azure Web App Extension

The problem is, that Azure Function Apps has extension feature disabled in "Platform features" tab in Function App blade (in Azure Portal). There is workaround for no extension feature in the blade - we can use Kudu (scm) for installing it.

alt

It will succeed but it will be installed exactly in the way that extension maintainer wanted it to be installed - it means, that it will be installed as Web App extension, not Function App extension.
If we will extract the extension, we will see that there are two important files in there: install.cmd and applicationHost.xdt.
The first one (install.cmd) is determining what will happen at the installation stage:

taskkill /F /IM python.exe
if exist D:\home\python361x64 rmdir /Q /S D:\home\python361x64
xcopy /I /S /Y python361x64 D:\home\python361x64\

So, we will have a new python.exe in D:\home\python361x64\.

The second one (applicationHost.xdt) is going to be used to transform D:\local\Config\applicationhost.config file:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <system.webServer>
    <fastCgi>
      <application
    fullPath="D:\home\python361x64\python.exe"
    arguments="D:\home\python361x64\wfastcgi.py"
        maxInstances="16"
        idleTimeout="21600"
        instanceMaxRequests="10000000"
        signalBeforeTerminateSeconds="60"
        xdt:Transform="InsertIfMissing"
        xdt:Locator="Match(fullPath)">
        <environmentVariables>
          <environmentVariable name="PYTHONHOME" value="D:\home\python361x64" />
        </environmentVariables>
      </application>
    </fastCgi>
  </system.webServer>
</configuration>

So after this extension installation we will have Python 3.6.1 but we will not be able to use it from Function App - it is dedicated for IIS.
(Beside of this post's topic - notice that extensions' configuration is fixing some problems I described here - cool!)

If we want to have Python 3.6 up and running and also usable in Function Apps, we need to install it at the deployment stage and have it accessible from web jobs (functions are based on WebJob). The best option to do that is to have Python 3.6 in PATH. For more than one year right now, thanks for changes in scripts for Azure WebJob SDK, we have whole D:\home\site\tools content in PATH.
So what we need to do is to get extension we used above, but extracted to D:\home\site\tools - not installed as maintainer wanted us to have.

The place where we can do this "magic" is Web App deployment script (which can be - and, in fact, need to be - used also during Function Apps deployments). The default Python deployment script for Azure Web App can be obtained using old (v1) azure-cli in ASM mode (you don't need to be logged in at all):

azure site deploymentscript --python

This command is generating two files: .deployment and deploy.cmd.

In .deployment you will find a simple definition of command to be performed at deployment stage, when pushing to Azure Web App or Function App:

[config]
command = deploy.cmd

In deploy.cmd there is a BATCH script used for Python deployments. We can tweak it a little bit for our needs... LINK

The most important change is here:

:: 2. Create virtual environment
IF NOT EXIST "D:\home\site\tools\python36*" (

  echo Creating Python 3.6.1 x64 virtual environment...
  nuget.exe install -Source https://www.siteextensions.net/api/v2/ -OutputDirectory D:\home\site\tools python361x64
  mv /d/home/site/tools/python3*/content/python*/* /d/home/site/tools/
  IF !ERRORLEVEL! NEQ 0 goto error

  ) ELSE (
    echo Found compatible virtual environment.
  )

:: 3. Install packages
echo Pip install requirements...
D:\home\site\tools\python.exe -m pip install -r "%DEPLOYMENT_TARGET%\requirements.txt"
IF !ERRORLEVEL! NEQ 0 goto error

Using nuget.exe we are downloading the same extension we used from Kudu, but we are extracting it to D:\home\site\tools.

nuget.exe install -Source https://www.siteextensions.net/api/v2/ -OutputDirectory D:\home\site\tools python361x64

Next, we are moving the content of the extracted directory (/d/home/site/tools/python3*/content/python*/) to /d/home/site/tools/.

mv /d/home/site/tools/python3*/content/python*/* /d/home/site/tools/

After that, we are installing all required Python modules using new Python distribution from /d/home/site/tools/.

D:\home\site\tools\python.exe -m pip install -r "%DEPLOYMENT_TARGET%\requirements.txt"

All you need to do is to place .deployment and tweaked deploy.cmd in the root directory (where host.json file is placed) of the Function Apps deployed using source control - like GIT. After every PUSH our deployment script will run.

REMEMBER! To always have requirements.txt in your Function Apps root directory - even if it will be empty! Or just change it in deploy.cmd.

It works:

Python 3.6.1 on Azure Function App

Feel free to make your own changes in this deployment script. It is only a base you can use to develop your own deployment scripts.

comments powered by Disqus