@kotyのブログ

.NETとかJavaとかPythonとか勉強会のこととかを、田舎者SEがつづります。記事のライセンスは"CC BY"でお願いします。

AWS Lambda+Serverless FrameworkでDjangoを使う

この記事は Django Advent Calendar 2017 - Qiita の17日目の記事です。

ここ数年サーバーレスアーキテクチャが盛り上がっているものの、Djangoと組み合わせた事例がググってもあまりないので書いてみました。ググっても出てこないということは需要が無いということかもしれませんが。。。みなさんFlaskといった軽量なwebフレームワークとDynamoって組み合わせで使ってるんですかね。

私はLambda+Djangoという組み合わせで本番投入した経験はありません。本記事は、運用に使えそうか?という観点で検証しました。

本記事のサンプルコードは以下に置いてあります。各種バージョンはgithub内のrequirements.txtを参照してください。

GitHub - koty/dj-lambda-sample: A sample of Django app on AWS Lambda using severless framework.

Djangoプロジェクトの作成

適当な名前でDjangoプロジェクトを作ります。プロジェクト構成は前述のgithubリポジトリをご確認ください。標準的なものだと思います。

Serverless Frameworkの導入

Lambdaを運用するにはこれがないとやってられません。各種リソースをzipにまとめてLambdaにdeployしてくれます。またAPI Gatewayなどの周辺リソースの設定もできます。pythonアプリを作るのにnpmモジュールを導入するのはどうなんだとも思いますけども。

www.npmjs.com

serverless コマンドを使うために -g つきでinstallします。

npm install -g serverless

VPCやIAM role等の設定が必要ですが、serverless framework自体は情報が多くありますので、ここではこれ以上触れません。

serverless-wsgi の導入

Djangoを使うにはwsgiという規格に沿う必要があります。普段EC2等で動かす場合はuwsgiやgunicornを使えばwsgiに沿うことができますが、Lambdaの場合は以下を使います。

www.npmjs.com

npm install serverless-wsgi --save-dev

serverless.ymlに以下のように追記します。

functions:
  api:
    events:  # この記述により、ルーティングをwsgi側(Django)に移譲できる。 {proxy+} の意味は不明。。documentにそう書いてある。
      - http: ANY /
      - http: ANY {proxy+}

plugins:
  - serverless-wsgi

custom:
  wsgi:  # appにはwsgi.py へのパスを記述。 serverless-python-requirementsにパッケージングを任せるため packRequirements: false を記述
    app: dj-lambda-sample.wsgi.application
    packRequirements: false

serverless-python-requirements の導入

EC2互換のdockerコンテナ上でpip install〜deployパッケージを作ってくれます。lxml等のpip install時にコンパイルをするパッケージを使っていてもEC2で問題なく動くようパッケージできます。

www.npmjs.com

npm install serverless-python-requirements --save-dev

serverless.ymlには以下のように設定します。

plugins:
  - serverless-python-requirements

custom:
  pythonRequirements:
    dockerizePip: true

Djangoの設定

DB の設定

使うRDBMSPostgreSQLです。MySQLよほどの理由がない限りは使わない方が良さそうです。

事前にRDSを立てておきます(EC2上に立てても当然OK)。検証が済んだら落としておくのを忘れずに。。

serverless.env.ymlを以下のように作ります。

SECURITY_GROUP_ID: 'sg-xxxxxxx'
SUBNET_ID1: 'subnet-xxxxxxx'
SUBNET_ID2: 'subnet-xxxxxxx'
DATABASE_NAME: 'xxxxx'
DATABASE_USER: 'xxxxx'
DATABASE_PASSWORD: 'xxxxxx'
DATABASE_HOST: 'xxxxxx.xxxxxxxx.ap-northeast-1.rds.amazonaws.com'
DATABASE_PORT: 5432

環境変数を作るために、serverless.ymlに以下のように設定します。外部ファイルから読み込むわけです。

provider:
  environment:
    DATABASE_NAME: ${file(./serverless.env.yml):DATABASE_NAME}
    DATABASE_USER: ${file(./serverless.env.yml):DATABASE_USER}
    DATABASE_PASSWORD: ${file(./serverless.env.yml):DATABASE_PASSWORD}
    DATABASE_HOST: ${file(./serverless.env.yml):DATABASE_HOST}
    DATABASE_PORT: ${file(./serverless.env.yml):DATABASE_PORT}

Djangoのsettings.pyに以下のように記述します。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.getenv('DATABASE_NAME'),
        'USER': os.getenv('DATABASE_USER'),
        'PASSWORD': os.getenv('DATABASE_PASSWORD'),
        'HOST': os.getenv('DATABASE_HOST'),
        'PORT': os.getenv('DATABASE_PORT'),
    },
}

最後にEC2等から ./manage.py migrate します。EC2を用意するのが面倒だったので、

class MigrateView(APIView):
    def get(self, request):
        from django.core import management
        management.call_command('migrate')
        return Response({})

というviewでmigrateしました。。。現実的にはVPC内に立てたEC2からmigrateするってもんでしょう。

そのた

このへんで力尽きてきました。Lambdaでは静的ファイルはホストできないので、S3を使います。django-storageを使って、S3に向かってcollectstaticします。

まとめ

簡単なAPIであればこの構成で運用できそうです。Lambdaのスケーラビリティは魅力です。バッチ処理が入ってくるとEC2がほしくなりそうですが。。。繰り返しますが、使うRDBMSMySQLはやめた方が良いです。職場で私の斜め前の人が、どハマりしてました。

残課題

  • デプロイ時に接続が切れるか?
  • Lambdaのパッケージ容量制限はどうする?site-packagesの容量が増えるとどうなる?

もっとcoolな方法があるぜ!という方はぜひ教えてください。。

Two Scoops of Django 1.11: Best Practices for the Django Web Framework (English Edition)

Two Scoops of Django 1.11: Best Practices for the Django Web Framework (English Edition)

Vue.js 2 Cookbook: Build modern, interactive web applications with Vue.js

Vue.js 2 Cookbook: Build modern, interactive web applications with Vue.js