himanago

Azure・C#などのMS系技術やLINE関連技術など、好きな技術について書くブログ

Azure Functions に Django で作った Web アプリをホストする

はじめに

ちょっとおもしろい記事を見つけました。

Pythonフルスタックな Web アプリケーション開発フレームワークである「Django」で作ったアプリケーションを、まるごと Azure Functions に乗せてしまうというものです。

szwarc.ai

フルスタックな Web アプリケーションフレームワークで作った重厚なアプリが、サーバーレスな関数上で動くということで、なかなかおもしろそうです。

実際にやってみた

この記事をベースに、実際に Azure 上で動くか試してみます。

Azure SQL Database の作成

まず DB を Azure 上に作ります。元記事と同じように、サーバーレスで作っていきます。

f:id:himanago:20220203002826p:plain

サーバーは新規作成で、構成は以下の通り。

f:id:himanago:20220203002711p:plain

データベースの構成は下記のような感じで。

f:id:himanago:20220203002507p:plain

f:id:himanago:20220203002557p:plain

作成されたら、ファイアウォール設定を変えておきます。

これから作る Django のアプリケーションをローカルで動かす際もこの Azure 上の DB を見に行くので、ローカルからも接続できるようにしておきます。

f:id:himanago:20220203004410p:plain

ローカルで Azure Functions のプロジェクト作成

VS Code を開き、Azure Functions 拡張機能から Python の Function App を作っていきます。

f:id:himanago:20220203003825p:plain

関数の名前は DjangoTrigger で。

f:id:himanago:20220203003838p:plain

承認レベルは必ず Anonymous にします。

f:id:himanago:20220203003920p:plain

Functions プロジェクト内に Django アプリ作成

つづいて Django アプリを作っていきます。

venv を作ってから、

python -m venv venv

requirements.txt を編集します。下3行を追記。

azure-functions
django==4.0.2
mssql-django
whitenoise

まとめて pip install します。

pip install -r requirements.txt

Django プロジェクトをつくります。

. を末尾につければ直下に直接作ってくれます。

django-admin startproject config .

ここまででこのようになっていれば OK です。

f:id:himanago:20220203005929p:plain

Function → Django をつなぐ設定

Functions から Django が呼ばれるように設定していきます。このあたりは元記事にかいてあるとおりです。

DjangoTrigger/function.json を以下のようにします("route": の行を追加)。

{
  "scriptFile": "__init__.py",
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "get",
        "post"
      ],
      "route": "DjangoTrigger/{*path_info}"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    }
  ]
}

DjangoTrigger/__init__.py を丸ごと書き換えます。

import azure.functions as func
from config.wsgi import application

def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
    return func.WsgiMiddleware(application).handle(req, context)

ここでいったん動作確認してみます。

func host start

ブラウザで開いてみても、まだ動きません。

f:id:himanago:20220203010658p:plain

より細かい設定をしていきます。Azure の SQL DB への接続もやってしまいます。

config/settings.py の内容を編集していきます。

# (略)

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

FUNCTION_APP_PATH = 'api/DjangoTrigger'   # 追加

# (略)

ALLOWED_HOSTS = ['*']  # 変更

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'whitenoise.runserver_nostatic',  # 追加
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',  # 追加
]

# (略)

# 変更
DATABASES = {
    'default': {
        'ENGINE': 'mssql',
        'NAME': 'xxxx-xxxx-xxxx',       # SQL DB の名称
        'USER': 'xxxxxxxx',                   # サーバー管理者ログイン
        'PASSWORD': '************',    # サーバー管理者パスワード
        'HOST': 'xxxxxxx.database.windows.net',  # ホスト名
        'PORT': '',
        'OPTIONS': {
            'driver': 'ODBC Driver 17 for SQL Server',
        },
    }
}

# (略)

STATIC_URL = '/' + FUNCTION_APP_PATH + '/static/'   # 変更

つづいて config/urls.py を書き換えます。

from django.contrib import admin
from django.urls import path
from config.settings import FUNCTION_APP_PATH   # <- import our FUNCTION_APP_PATH constant from settings.py file

urlpatterns = [
    path(FUNCTION_APP_PATH + '/admin/', admin.site.urls),  # <- change "path" to include FUNCITION_APP_PATH
]

ここまでできたら、もう一度起動してみます。

func host start

このタイミングで DB につなぐので、ODBC Driver がインストールされていないと起動できません。

インストールされていなければ、入れておきましょう。

docs.microsoft.com

自分が Mac でやったときは Xcode を最新化する必要があったりで結構面倒でした。

エラーなく起動したら、http://localhost:7071/api/DjangoTrigger/admin/ をブラウザで開いてみます。

以下のようなログイン画面が表示されれば、OK です(Django が動いてますね!)。

f:id:himanago:20220203092807p:plain

データベースへのテーブル作成

より本格的に使えるか試すため、SQL DB にテーブルを作っていきます。

python manage.py migrate

Mac でやったときはここで

[08001] [Microsoft][ODBC Driver 17 for SQL Server]Client unable to establish connection (0) (SQLDriverConnect)

というエラーが出てしまいました。どうも Mac に入っている OpenSSL が悪いらしく、LibreSSL から OpenSSL@1.1 にしたり

github.com

を参考にいろいろやったりしてなんとか解消しました(ちなみに Windows の環境でやったときはすんなりでした)。

ログインユーザー作成

うまく動いたら、続いてアプリの管理者ユーザーを作ります。

コマンドで作成します。

python manage.py createsuperuser

指示に従って、ユーザー名、メールアドレス、パスワードを設定します。

モデルクラス&テーブル作成

元記事にはないですが、新しいモデルクラスとテーブルも作ってみます。

作るテーブルは公式チュートリアルのものを使います。

docs.djangoproject.com

python manage.py startapp polls

ファイルが作られるので、その中の polls/models.py を以下のようにします。

from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

config/settings.py にさらに追記します。

INSTALLED_APPS = [
    'polls.apps.PollsConfig',  # これを追加
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'whitenoise.runserver_nostatic',
]

コマンドを実行します。

python manage.py makemigrations polls
python manage.py sqlmigrate polls 0001
python manage.py migrate

Azure 上の SQL DB に、作ったモデルクラスにあったテーブルが作成されます。

最後に polls/admin.py を以下のようにします。

from django.contrib import admin
from .models import Question
from .models import Choice

admin.site.register(Question)
admin.site.register(Choice)

ローカルでの動作確認

func host start でローカルで実行して、以下の http://localhost:7071/api/DjangoTrigger/admin/ にアクセスしてみます。

createsuperuser で作った ユーザー名とパスワードでログインすると、以下のようになります。

f:id:himanago:20220203181751p:plain

ちゃんと動いてるっぽいです!

Function App のデプロイ

ではいよいよ Azure に Function App としてデプロイしてみます。

VS Code拡張機能を使っていきます。

Azure にサインインします。

f:id:himanago:20220204142809p:plain

「Deploy to Function App...」をクリック。

f:id:himanago:20220204142957p:plain

あとはプロジェクト、サブスクリプション、リージョンなどを指示通りに指定していけば作成・デプロイされます。

Azure 上での動作確認

デプロイ後は、Azure ポータルにいかずとも VS Code 上で関数 URL を得られます。

f:id:himanago:20220204143546p:plain

コピーした URL をもとにして、https://xxxxxxxxxx.azurewebsites.net/api/DjangoTrigger/admin/ となるように編集(大文字小文字に注意!)してアクセスしてみます。

ログイン画面 f:id:himanago:20220204144007p:plain

管理画面 f:id:himanago:20220204144300p:plain

データ追加 f:id:himanago:20220204144723p:plain

f:id:himanago:20220204144733p:plain

f:id:himanago:20220204144743p:plain

f:id:himanago:20220204144753p:plain

ちゃんと動いてます!

おわりに

このままでは URL に api などが入っていていまいちなのですが、Web アプリがまるごと Function App に乗るのはなかなかおもしろいですね。

Consumption プランで乗せてしまえば、コールドスタートの問題はあるものの低価格&スケーラブルに Django アプリが運用できます。

ちなみにログイン機能で使われるセッションキーについてですが、サーバー側はデータベースに保存されるようになっているので、サーバーレスでも運用できそうです。