Python
Django
Azure
python3

AzureにPython3.6/Django/PostgresのWebアプリをデプロイする

More than 1 year has passed since last update.

Microsoft AzureのApp ServiceにPostgreSQLを使ったDjangoアプリをデプロイしようしたのですが、けっこう苦労したのでわかったことを書いておきます。

基礎知識

  • Microsoft Azure
    • Microsoftが提供するクラウドプラットフォーム(PaaS/IaaS)
  • App Service
    • AzureでWebアプリを提供するためのプラットフォーム (PaaS)

わかったこと

ドキュメントと色々試行錯誤してわかったことです。

  • App ServiceのOSはデフォルトではWindows
  • OSはLinuxも選択できるけど現在はプレビュー版という扱いで制限がある
  • LinuxではDockerでのデプロイが可能
  • デフォルトのデプロイメントスクリプトではDjangoの静的ファイル配置に対応している
  • 選択可能なPythonバージョンは2.7または3.4のみ
  • WindowsのPython3.4ではpipでインストールできないモジュールがある(psycopg2など)
  • 拡張機能を使えば他のPythonバージョンをインストールできる
  • この場合デプロイコマンド(pipを実行したりするコマンド)は自分で作る必要がある

OSがWindowsなのはMicrosoftなので当たり前なのですが、運用環境はLinuxというのが常識の世界で育った人だと見落としがちです。

公式ドキュメント

Azure Web Apps のドキュメント

メニューからPythonに関するページを探すと以下4つがありました。

まずは1をやってみてから4を見るのがいいと思います。
2はチュートリアルとなってますが、プレビュー版であるLinuxにDockerイメージでデプロイするという特殊な使い方なので注意が必要です。
3はVisualStudioを使わないなら見なくてもいいです。

1. クイック スタート > Python アプリの作成

Azure に Python Web アプリを作成する

  • Flaskを使ったシンプルなアプリをデプロイする手順が書かれています。
  • 必要なファイルは全てGitのサンプルコードに含まれています。
  • Azure上の操作はAzure CLI を使って行います。
  • デプロイはローカルGitという方法で、git push するとデプロイが実行されます。

2. Tutorials (チュートリアル) > 1 - Python と PostgreSQL

Azure で Docker Python と PostgreSQL アプリを構築する

  • FlaskでPostgreSQLを使ったアプリの例です。
  • Dockerイメージでデプロイする方法が書かれています。
  • サンプルコード

3. ハウツー ガイド > アプリの開発 > Python > MySQL を使用した Django アプリ

Python Tools 2.2 for Visual Studio を使用した Azure 上の Django と MySQL

  • Python Tools 2.2 for Visual Studio(PTVS)を使った開発方法が書かれています。

4. ハウツー ガイド > アプリの開発 > ランタイムの構成 > Python

Azure App Service Web Apps による Python の構成

  • デプロイに必要な以下のファイルの説明が書かれています。
    • requirements.txt
    • runtime.txt
    • web.config
    • ptvs_virtualenv_proxy.py
    • .deployment
    • deploy.cmd

Python3.6/Django/PostgreSQLのWebアプリをデプロイする

公式ドキュメント+αの情報で次のような手順でデプロイすることができました。

  1. ローカルでDjangoアプリを作成する
  2. AzureにPostgreSQLデータベースを作成する
  3. Azureに App Service を作成する
  4. App Service に拡張機能でPython3.6をインストールする
  5. デプロイ用ファイルを作成する
  6. Djangoアプリをデプロイする

※Azure では PostgreSQL の使用は有償になります。

1. ローカルでDjangoアプリを作成する

ふつうにDjangoアプリを作ります。
デプロイする際には以下の設定が必要です。

settings.py
DEBUG = False
ALLOW_HOST = ['*']
STATIC_ROOT = 'static'

2. AzureにPostgreSQLデータベースを作成する

Azure で Docker Python と PostgreSQL アプリを構築する の「運用 PostgreSQL データベースを作成する」を参考に AzureにPostgreSQLデータベースを作成します。

AZ_GROUP="<resource_group>"
AZ_LOCATION="japanwest"
AZ_PG="<postgresql_name>"
AZ_PG_USER="<admin_username>"
AZ_PG_PASS="<admin_password>"

az postgres server create -g $AZ_GROUP -n $AZ_PG -l $AZ_LOCATION -u $AZ_PG_USER -p $AZ_PG_PASS
az postgres server firewall-rule create -g $AZ_GROUP --server-name $AZ_PG --start-ip-address=0.0.0.0 --end-ip-address=255.255.255.255 --name AllowAllIPs

データベースとユーザーを作成します。

$ psql -h ${AZ_PG}.postgres.database.azure.com -U ${AZ_PG_USER}@${AZ_PG} postgres
CREATE DATABASE <database_name>;
CREATE USER <user> WITH PASSWORD '<password>';
GRANT ALL PRIVILEGES ON DATABASE <database_name> TO <user>;

3. Azureに App Service を作成する

Azure に Python Web アプリを作成する を参考に以下のコマンドを実行します。

AZ_GROUP="<resource_group>"
AZ_LOCATION="japanwest"
AZ_APPSERVICE_PLAN="<plan_name>"
AZ_APPSERVICE_PLAN_TYPE="Free"
AZ_APP_NAME="<app_name>"

az group create -n $AZ_GROUP -l $AZ_LOCATION
az appservice plan create -n $AZ_APPSERVICE_PLAN -g $AZ_GROUP --sku $AZ_APPSERVICE_PLAN_TYPE
az webapp create -n $AZ_APP_NAME -g $AZ_GROUP --plan $AZ_APPSERVICE_PLAN --deployment-local-git

4. App Service に拡張機能でPython3.6をインストールする

Upgrading Python on Azure App Service – Python Engineering at Microsoft

Azure Portal の画面から作成した App Service を選択 > 拡張機能を選択 > インストールしたいPythonバージョンを選択してインストールします。

5. デプロイ用ファイルを作成する

以下のファイルを作成します。

  • requirements.txt
  • .deployment
  • deploy.cmd
  • web.config
requirements.txt
Django
psycopg2
.deployment
[config]
command = deploy.cmd

deploy.cmdはデフォルトのものをコピーしてpython.exeやpipのパスをバージョンに合わせて変更します。

deploy.cmd
@if "%SCM_TRACE_LEVEL%" NEQ "4" @echo off

:: ----------------------
:: KUDU Deployment Script
:: Version: 1.0.15
:: ----------------------

SET PYTHON_HOME=D:\home\python361x86
SET PYTHON=%PYTHON_HOME%\python.exe
SET PIP=%PYTHON% -m pip

:: Prerequisites
:: -------------

:: Verify node.js installed
where node 2>nul >nul
IF %ERRORLEVEL% NEQ 0 (
  echo Missing node.js executable, please install node.js, if already installed make sure it can be reached from current environment.
  goto error
)

:: Setup
:: -----

setlocal enabledelayedexpansion

SET ARTIFACTS=%~dp0%..\artifacts

IF NOT DEFINED DEPLOYMENT_SOURCE (
  SET DEPLOYMENT_SOURCE=%~dp0%.
)

IF NOT DEFINED DEPLOYMENT_TARGET (
  SET DEPLOYMENT_TARGET=%ARTIFACTS%\wwwroot
)

IF NOT DEFINED NEXT_MANIFEST_PATH (
  SET NEXT_MANIFEST_PATH=%ARTIFACTS%\manifest

  IF NOT DEFINED PREVIOUS_MANIFEST_PATH (
    SET PREVIOUS_MANIFEST_PATH=%ARTIFACTS%\manifest
  )
)

IF NOT DEFINED KUDU_SYNC_CMD (
  :: Install kudu sync
  echo Installing Kudu Sync
  call npm install kudusync -g --silent
  IF !ERRORLEVEL! NEQ 0 goto error

  :: Locally just running "kuduSync" would also work
  SET KUDU_SYNC_CMD=%appdata%\npm\kuduSync.cmd
)
goto Deployment

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Deployment
:: ----------

:Deployment
echo Handling python deployment.

:: 1. KuduSync
IF /I "%IN_PLACE_DEPLOYMENT%" NEQ "1" (
  call :ExecuteCmd "%KUDU_SYNC_CMD%" -v 50 -f "%DEPLOYMENT_SOURCE%" -t "%DEPLOYMENT_TARGET%" -n "%NEXT_MANIFEST_PATH%" -p "%PREVIOUS_MANIFEST_PATH%" -i ".git;.hg;.deployment;deploy.cmd"
  IF !ERRORLEVEL! NEQ 0 goto error
)

IF NOT EXIST "%DEPLOYMENT_TARGET%\requirements.txt" goto postPython

pushd "%DEPLOYMENT_TARGET%"

:: 4. Install packages
echo Pip install requirements.

%PIP% install -r requirements.txt
IF !ERRORLEVEL! NEQ 0 goto error

REM Add additional package installation here
REM -- Example --
REM env\scripts\easy_install pytz
REM IF !ERRORLEVEL! NEQ 0 goto error

:: 5. Copy web.config
IF EXIST "%DEPLOYMENT_SOURCE%\web.%PYTHON_VER%.config" (
  echo Overwriting web.config with web.%PYTHON_VER%.config
  copy /y "%DEPLOYMENT_SOURCE%\web.%PYTHON_VER%.config" "%DEPLOYMENT_TARGET%\web.config"
)

:: 6. Django collectstatic
IF EXIST "%DEPLOYMENT_TARGET%\manage.py" (
  echo Collecting Django static files. You can skip Django specific steps with a .skipDjango file.
  IF NOT EXIST "%DEPLOYMENT_TARGET%\static" (
    MKDIR "%DEPLOYMENT_TARGET%\static"
  )
  %PYTHON% manage.py collectstatic --noinput --clear
)

popd

:postPython

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
goto end

:: Execute command routine that will echo out when error
:ExecuteCmd
setlocal
set _CMD_=%*
call %_CMD_%
if "%ERRORLEVEL%" NEQ "0" echo Failed exitCode=%ERRORLEVEL%, command=%_CMD_%
exit /b %ERRORLEVEL%

:error
endlocal
echo An error has occurred during web site deployment.
call :exitSetErrorLevel
call :exitFromFunction 2>nul

:exitSetErrorLevel
exit /b 1

:exitFromFunction
()

:end
endlocal
echo Finished successfully.
web.config
<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="PYTHONPATH" value="D:\home\site\wwwroot" />
    <add key="WSGI_HANDLER" value="django.core.wsgi.get_wsgi_application()" />
    <add key="WSGI_LOG" value="D:\home\LogFiles\wfastcgi.log" />
    <add key="DJANGO_SETTINGS_MODULE" value="project.settings" />
  </appSettings>
  <system.webServer>
    <handlers>
      <remove name="Python27_via_FastCGI" />
      <remove name="Python34_via_FastCGI" />
      <add name="Python FastCGI"
           path="handler.fcgi"
           verb="*"
           modules="FastCgiModule"
           scriptProcessor="D:\home\python361x86\python.exe|D:\home\python361x86\wfastcgi.py"
           resourceType="Unspecified"
           requireAccess="Script" />
    </handlers>
    <rewrite>
      <rules>
        <rule name="Static Files" stopProcessing="true">
          <conditions>
            <add input="true" pattern="false" />
          </conditions>
        </rule>
        <rule name="Configure Python" stopProcessing="true">
          <match url="(.*)" ignoreCase="false" />
          <conditions>
            <add input="{REQUEST_URI}" pattern="^/static/.*" ignoreCase="true" negate="true" />
          </conditions>
          <action type="Rewrite" url="handler.fcgi/{R:1}" appendQueryString="true" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

DJANGO_SETTINGS_MODULEの部分はDjangoプロジェクトに合わせて変更してください。

6. Djangoアプリをデプロイする

AZ_USER="<user>"
AZ_PASS="<password>"
az webapp deployment user set --user-name $AZ_USER --password $AZ_PASS
git remote add azure <git_url>
git push azure master

gitのURLはaz webapp createしたときに表示されます。
ポータルの App Service の概要でも確認できます。

ブラウザで<app_name>.azurewebsites.net/adminにアクセスしてadminのログイン画面が表示されれば完了です。

感想

公式ドキュメントの構成のせいでかなり振り回されました。
チュートリアルの部分が通常と全く違うやり方を説明しているので混乱しました。。。