orangain flavor

じっくりコトコト煮込んだみかん2。知らないことを知りたい。

Heroku上のDjangoアプリで静的ファイルをS3から配信する(前編)

HerokuにDjangoアプリケーションを置いて、静的ファイルもHerokuから配信した場合、USまでのレイテンシがあるので1ファイルごとに結構な時間がかかります。

試しに同じアプリケーションをさくらVPS(大阪)と、Heroku(US)に置いた場合、さくらでは 30ms〜150ms程度でロードできるのに対し、Herokuでは200ms以上かかりました。

HTML1ファイルだけならともかく、CSSやJS、画像のロードにも時間がかかるため、体感時間は結構変わります。

そもそも静的ファイルをアプリケーション・サーバーから配信するのはCPU、ディスク、ネットワークなどのリソースの面でも無駄が多く、推奨されていません。

そこで今回は、静的ファイルをAmazon S3のTokyoリージョンから配信してみました。いわゆる Web Storageパターン です。

長くなったので前後編に分けています。前編はHerokuに限らない一般的なDjangoアプリケーションの話をし、後編 ではHeroku特有の話をします。

ライブラリのインストール

$ pip install django-storages boto

django-storages はDjango用のカスタム・ストレージ・バックエンドを集めたライブラリです。

botoAWSPythonインターフェイスを提供するライブラリです。django-storagesのs3-botoバックエンドがbotoを使用する形になります。

requirements.txtにも忘れずに書いておきます。(バージョンはご利用のものに合わせてください)

boto==2.29.1
django-storages==1.1.8

S3バケットの作成

S3に適当な名前でバケットを作成して、Static Website Hostingを有効にしておきます。

今回は static.bus.capybala.com という名前にしました。 日本に置きたかったのでTokyoリージョンを選択しています。

Access Keyの作成

また、このバケットにファイルを追加できる権限を持つAccess KeyとSecret Access Keyを作っておきます。

以下の様なポリシーで良いでしょう。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::static.bus.capybala.com",
        "arn:aws:s3:::static.bus.capybala.com/*"
      ]
    }
  ]
}

Djangoの設定

settings.pyに以下の設定を追加します。

INSTALLED_APPS = (
    ...
    'storages',  # 行を追加
    ...
)

# S3バケットのURL(サブドメインのURLを使ってもどちらでもいいです。)
STATIC_URL = 'https://s3-ap-northeast-1.amazonaws.com/static.bus.capybala.com/'

# collectstaic時にS3を使う
STATICFILES_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
# 作成したS3バケットの名前
AWS_STORAGE_BUCKET_NAME = 'static.bus.capybala.com'
# これをTrueにしたほうがファイル変更のチェックが速くなる
AWS_PRELOAD_METADATA = True

以下のようにDEBUGが有効かどうかによってSTATIC_URLをローカル配信と切り替えても良いでしょう。

if DEBUG:
    STATIC_URL = '/static/'
else:
    STATIC_URL = 'https://s3-ap-northeast-1.amazonaws.com/static.bus.capybala.com/'

AWSのクレデンシャル

AWSAccess KeyとSecret Access Keyは秘密の値なので、settings.pyには書きません。Twelve Factor に従って、環境変数AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYで渡します。

.envファイルに書いておきます。

AWS_ACCESS_KEY_ID=***
AWS_SECRET_ACCESS_KEY=***

アップロードのテスト

ここまでの設定をした上で、以下のようにcollectstaticを実行すると、静的ファイルがS3バケットにアップロードされるはずです。

$ foreman run python manage.py collectstatic --noinput

細かい話(アップロード時のエラー)

S3への大きなファイルのアップロード時にBroken pipeというエラーが発生する問題に遭遇したので、コメントにあるように~/.botoというファイルを作って対処しました。

$ cat ~/.boto
[s3]
host = s3-ap-northeast-1.amazonaws.com

なお、後編のHerokuでは特に必要ありませんでした。

前編はここまでです。後編ではHerokuを使います。

参考