orangain flavor

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

#isucon 2014の予選をほぼ一人で戦うハメになった話

まとめ

  • リモートでタイムゾーンが違うと大変
  • Pythonを選んだ
  • Redisのみを使う実装に書き換えた
  • 提出スコアは32710

準備したこと

事前にチームで昨年の問題を解きました。プロファイリング方法を全然わかってなかったので、制限時間内にほとんどスコアが伸びませんでした。1週間かけて勉強し、MySQLのスロークエリログやApache/nginxのログを集計する方法を知りました。

また、継続的にプロファイリングできるようにするのが大事だと痛感したので、以下の様なツールを作りました。

  • ベンチマークを回すと同時に、以下の様なデータを収集する。
  • 収集したデータを集計し、Jenkins上で成果物として保存し、スコアのグラフをプロットする。

f:id:mi_kattun:20140930001731p:plain

やったこと

10:00-

開始時刻になっても他の2人が現れないので、一人で作業することに。最初のところで、案内された手順でRuby実装が終了しない問題や、isuconユーザーでSSH接続できない問題などでかなり手間取ってしまいました。

ブラウザでの挙動、DBの構造、稼働中のミドルウェアを確認をした上で、事前に準備したツールでリクエストログやスロークエリログの集計などを行いました。

Python実装での初期スコアは 1534 でした。

11:00-

アプリのソースコードを読み、データ構造を変えたらBAN/Lockの判別はO(1)にできるし、JOINとかないからKVS向きだと判断しました。が、上位陣はMySQLのままで高いスコアを出していたので読みが甘かったかもしれません。

インメモリで高速に処理できてかつ永続化もできるKVS、ということでRedisを選択しました。Redisはこれまで話を聞くだけで実際に使ったことなかったですが、Pythonから簡単に使えました。tagomorisさんがISUCONは新しいことに挑戦できる場だと仰っていたのはほんとそう思います。 *1

12:00-

ご飯を食べ、Redisをインストールしました。yumでインストール出来ないのは辛かったです。Redisの使い方を勉強しながらアプリを書き換えました。ちゃんと書き換えられるか自信がなかったので、少しづつ書き換えて確認する戦略をとりました。

とりあえずlogin_logの処理とBAN/Lockのチェックだけを書き換えて、Failしつつもスコアが 8864 に上がったのが13:30頃でした。

14:00-

14:00になってようやく2人目のメンバーが参加しました。が、すぐに状況を把握できるわけではありません。。

login_logに初期データがあることに気づき、reportが通るようになったのが14:20頃。レポートの出力順をチェックされていたらどうしようとドキドキしてましたが、無事SUCCESS。スコアは 9250

Redisのデータがちゃんと永続化されているか自信がなかったので、サーバーを再起動して問題ないことを確認。

nginxで静的ファイルを返すよう設定してもらいました。

15:00-

最後までMySQLで処理していたログイン時のユーザー情報取得もRedisに移し、ついでにパスワードは平文で比較するようにして、スコアは 9623 に。

初期化処理でMySQLからRedisにデータをロードする処理が遅くてイライラしてきたので、予め初期化済みのaofファイルを用意して置き換えるようにしました。これによって軽快にベンチを回すことができるようになりました。

nginxで静的ファイルを返すとContent-Typeが正しく設定されないという罠に引っかかって二人で直しました。

16:00-

この辺でworkloadを増やすとスコアが上がることに気づくものの、cannot assign requested addressというエラーが出るようになります。

カーネルパラメータを調整し、workload 10でfailしなくなったのが16:30で、スコアは 30694

RedisをUnix Domain Socketで繋いだら速くなるかなと設定してみてもそれほど変わらず、スコアは 31059。 

nginxのworker_processesを4に増やすと 31650 に。この辺からスコアが伸びなくなります。

17:00-

nginxとgunicornをUnix Domain Socketで繋ぐも、さほど変わらず。

セッションクッキーがない場合だけnginxで静的ファイルを返すよう設定を試みましたが、二人共詳しくなかったため、なかなかうまく設定できず。

そんな中3人目がようやく登場するも、なにができるわけでもなしw もう諦めようかと言いながらも、どうにかして設定したのが 17:40。スコアは 32710。これが最終スコアになりました。

この後、いろいろなworkload、gunicornのプロセス数を試すも、スコアは上がりませんでした。高速化できる場所も思いつかずに終了しました。

反省点

後半ちゃんとボトルネックを特定できておらず、ほとんどスコアが上がらなかったのは反省点です。サーバー起動時にオンメモリにデータを持つようにすればもっと上がった気がします。

ツールを用意したものの、肝心のnginxのレスポンスタイムを出力するのを忘れていたりと全然活用できませんでした>< 自分が直前に作ったコマンドは使い慣れてないし、微妙に使いやすくないし、心の何処かで自分を信用できてなくてほんとダメでした。

というか、Redisへの書き換えに3時間ほど集中したおかげで色々吹っ飛びました。こういう時に冷静な仲間が居るといいんだろうなと思います。

まとめ

今回はじめて参加しましたが、非常に楽しい一日を過ごすことができました。運営の皆様、チームのメンバー、ありがとうございました!

*1:supervisordも去年の問題を練習で解いた時に初めて使い方を知りました。

プロフェッショナルのための実践Heroku入門 の紹介

以前公開した、 The Twelve-Factor App日本語訳が書籍に収録されるということで、一冊頂けました。ありがとうございます。せっかくなので簡単に紹介します。

f:id:mi_kattun:20140917204846j:plain

Herokuの哲学

私が初めてHerokuを触ったとき*1に感動したのはそのオープンさです。Heroku独自の仕様は少なく、Git, Procfile, Buildpackなどのオープンな仕様に加え、各言語のデファクトスタンダードな方法(Pythonであればpipとrequirements.txt)で依存関係を管理すれば良いことにとても好感を抱きました。

そんなHerokuを支える哲学として気に入った部分を2つ書籍から引用します。

Herokuはアプリケーション開発者の生産性を最大化することに常に焦点を当てています。世界を変える新しいアイデアをソフトウェアという形で世に送り出し、実際に世界を変えていくことを可能にするために、アプリケーション開発者自身がその創造力、時間、コストを本当に価値のある活動だけに100%集中できるようなプラットフォームでありたいと願っています。
Herokuは、アプリケーション開発者にとって、生産性が高い開発環境とは、彼らの手に馴染んだツールや経験値を生かすことができる環境―すなわち各アプリケーション開発者が自分たちの手元に構築している自分のための開発環境―こそがアプリケーション開発者の生産性を最大化させるのだと判断しました。

プロフェッショナル向けのガイド

もちろん本書は哲学だけでなく、実践的な書籍です。

最初の一歩であるHerokuアカウントの作成から、Dynoの冗長化・カスタムドメインSSL・データベース運用などの本番環境での利用までを丁寧に解説しているため、これから本格的に触ってみるという人にはオススメです。

また、デプロイ時のSlugコンパイラの動作やDynoの動作環境など、Herokuの具体的なアーキテクチャについては知らなかったため、勉強になりました。

最初に書いたとおり、The Twelve-Factor Appの日本語訳も掲載されています。これはHeroku上で強制されるある種の制約が、スケーラビリティやポータビリティを生み出す方法論であることを言語化して教えてくれる文書です。

目次

最後に目次を載せておきます。 

はじめに
Herokuの哲学
プロフェッショナルなアプリケーション開発者
Herokuの歴史
本書の構成

第1章 Herokuの概要
1.1 本章の内容
1.2 Herokuとは
1.3 さまざまな種類のクラウドサービス
1.4 Herokuの特徴

第2章 Herokuの利用準備
2.1 本章の内容
2.2 Heroku利用準備
2.3 アプリケーションの作成からデプロイまで
2.4 Ruby開発環境の構築
2.5 Node.js開発環境の構築
2.6 Scala開発環境の構築
2.7 Java開発環境の構築

第3章 アプリケーション開発のポイント
3.1 本章の内容
3.2 データベースの選定
3.3 外部ストレージ
3.4 レスポンス時間制限
3.5 Slug制限
3.6 IPアドレス制限
3.7 Procfile
3.8 Foremanによるローカル環境デプロイ
3.9 ステージング環境構築
3.10 ステージング環境のBasic認証設定

第4章 アドオンによる機能追加
4.1 本章の内容
4.2 アドオンとは
4.3 アドオンの基本操作
4.4 代表的なアドオンの紹介

第5章 本番環境への移行
5.1 本章の内容
5.2 Production Check
5.3 Cederスタックの利用
5.4 Dynoの冗長化
5.5 プロダクションレベルのデータベースの利用
5.6 カスタムドメインの利用
5.7 SSLの導入
5.8 カスタムエラーページの設置

第6章 Heroku Postgres
6.1 本章の内容
6.2 Heroku Postgresのサービス
6.3 基本機能
6.4 Heroku Postgresの拡張機能

第7章 トラブルシューティング
7.1 本章の内容
7.2 Herokuのオフィシャルサポート
7.3 よくあるトラブル
7.4 Herokuのエラーコード

第8章 Herokuのアーキテクチャ
8.1 本章の内容
8.2 Herokuの仕組み

第9章 The Twelve Factor App
9.1 はじめに
9.2 背景
9.3 このドキュメントの対象者
9.4 The Twelve Factors

*1:私が触ったときには既にCederスタックでした。Herokuの歴史も書かれてて興味深いです。

PyCon JP 2014に参加しました

2014年12月23日 編集:資料・動画の埋め込みは重かったのでリンクにし、いくつかの資料へのリンクを追加しました。

毎回行きたいと思いながらも用事があって行けなかったPyConに初めて参加しました。

f:id:mi_kattun:20140915221906j:plain

感想

Kenneth Reitzさんによる1日目の基調講演は、言語、そしてインターネットによって人は他の人と交流できるようになったのに、Pythonは2と3の二つの言語に分かれてしまっているという話でした。

個人的にはUnicode周りが面倒なPython 2にはもう戻れないと感じていますが、利用状況は2のほうが圧倒的でした。確かに英語圏の人にはメリットが感じにくいので、メリットを享受しやすい非英語圏(含む日本)の人が積極的にPython 3に移行していくと幸せになれるのではないでしょうか。 

西尾泰和さんによる2日目の基調講演は、人間は人工物、言語、方法論、教育の4要素によって増強することができ、新しいものを発見するためには、盲点を知るための方法論(比較、歴史、経験、抽象化、会話)が大切という話でした。

今回PyConに参加したことで自分の知らない領域の話をいろいろ聴くことができ、自分の盲点を認識できました。資料を見返して復習するとともに、聴けなかったセッション資料・動画も観て勉強していきたいです。

以下、参加したセッションの資料・動画です。

基調講演1日目:Python 2.7 and Python 3: A Sacred Love Story

Python 2.7 and Python 3: A Sacred Love Story // Speaker Deck

CH01 Opening~Keynote: Kenneth Reitz - YouTube

Deep Learning for Image Recognition in Python

Deep Learning for Image Recognition in Python

MH01 Deep Learning for Image Recognition in Python (ja) - YouTube

Gunicorn, the thundering herd and other concurrency programming challenges

Gunicorn, the thundering herd and other concurrency programming challenges // Speaker Deck

CR02 Gunicorn, the thundering herd and other concurrency programming challenges (en) - YouTube

Pythonの実装系総ざらい

Pyconjp2014_implementations

CH04 Pythonの実装系総ざらい (ja) - YouTube

最新リリースCMSツール Plone 5 のモダンUIとテクノロジーの進化

PyCon JP 2014 plone terada

CH05 最新リリースCMSツール Plone 5 のモダンUIとテクノロジーの進化 (ja) - YouTube

XML-RPC : Pythonが「電池付属」と呼ばれる理由

XML-RPC : Pythonが「電池付属」と呼ばれる理由

CR05 XML-RPC : Pythonが「電池付属」と呼ばれる理由 (ja) - YouTube

Open Data for Taiwan's Roadways

Open Data for Taiwan's Roadways // Speaker Deck

MH06 Open Data for Taiwan's Roadways (en) - YouTube

Python を支える技術: ディスクリプタ

Python を支える技術 ディスクリプタ編 // Speaker Deck

CH08 Python を支える技術: ディスクリプタ編 (ja) - YouTube

Lightning Talks

CH09 Lightning Talks - YouTube

基調講演2日目:Rediscover with Python

PyConJP Keynote Speech (Japanese version)

CH10 Opening~Keynote: Hirokazu Nishio - YouTube

聴きたかったけど聴けなかったセッション

自分が後で観るために書いておきます。

Micro Python で組み込み Python

Micro Python で組み込み Python

CR04 Micro Python で組み込み Python (ja) - YouTube

PyCharm活用術

CH06 PyCharm活用術 (ja) - YouTube

PyCharm 活用術 (ja)

リファクタリングツールあれこれ (May the force be with you)

リファクタリングツールあれこれ — pyconjp2014 1 documentation

CH07 リファクタリングツールあれこれ (May the force be with you) (ja) - YouTube

パッケージングの今

パッケージングの今

MH12 パッケージングの今 (ja) - YouTube

その他の資料も含め、以下の場所でまとめられています。

PyCon JP 2014 - 資料一覧 - connpass

RequireJSを始めて戸惑ったこと

公式のGetting Started単純すぎでは?

自作のhelper/util.jsをAMD使って書いてるような意識高い人は、今さらGetting Startedを読まないと思うのです。。

Examplesからリンクされている以下のサンプルを眺めるのがオススメです。

 requireとrequirejsとrequire.configとrequirejs.configのどれ使えばいいの?

ドキュメントでは混在してて戸惑いますが、requirerequirejs は基本的に同じものなので、気にしなくて良いです。主観では requirejs より require を使っている人のほうが多いです。

require.config({})require({}) とも書けます。ただ、設定をする場合は require.config({}) 、モジュールを読み込むときは require と使い分けたほうがわかりやすいでしょう。

configの書き方いろいろありすぎでは?

気にしないで良いです。以下の書き方を使いましょう。

HTML

<script src="require.js" data-main="main.js">

main.js(エントリポイント)

require.config({
  // モジュールIDからモジュールのファイルを探すときにベースとなるパス
  baseUrl: '',
  paths: {
    // モジュールIDでは探せないモジュールのパス
  },
  shim: {
    // AMDに対応してないモジュールを読み込むための設定
  }
});


require(['jquery'], function($) {
     // $を使った処理
});

require.config の引数はConfiguration Optionsに解説があります。

そういえばモジュールって何?

公開用オブジェクトを、グローバルスコープを汚さずに定義するためのスクリプトです。公開用のオブジェクトとは、jQuery$や、Underscore.jsの_、Backbone.jsのBackboneのようなオブジェクトです。

jQueryプラグインのように、既存のオブジェクトに変更を加えるだけで何も公開しないモジュールもあります。1ファイル1モジュールのみを定義できます。

モジュールは jquery.jsmy/module.js のようなパスで管理することもできますが、 jquerymy/moduleのような モジュールID で管理する方が良いです。

baseUrlはどこにすべき?

最初はベースっぽいところを適当に選べばよいかと思います。モジュールをファイルパスから独立したモジュールIDで管理することを意識しておけば、後から変更してもそんなに困りません。

BackboneとUnderscoreってshim使う必要あるの?

もはや必要ありません。RequireJS公式を含む世の中のドキュメントには、Backbone.jsやUnderscore.jsがshimの使用例としてよく出てきますが、 2014年2月リリースの Backbone.js 1.1.1、Underscore.js 1.6.0以降はAMDに対応しています。

ちなみにjQuery UIも1.11(2014年6月リリース)からAMDに対応しています。

r.js難しくない?

はい。shimのライブラリが動かなくなったりするので頑張りましょう。 shimの解説 にいろいろ注意書きがあります。

最初は optimize: 'none' で様子を見るのが良いです。

r.js遅くない?

Rhino版は遅いので、Node版を使いましょう。

ちなみに私は django-require というライブラリを使って、Herokuにpushしたときにr.jsが走るようにしましたが、あまりに遅かったのでr.jsを使うのやめました。Python環境にはNodeが入っておらず、Javaしか入ってないので、簡単に使えるのはRhino版のみでした。

まとめ

始めるときに参考にさせていただいたサイト

Amazon S3のUS Standardリージョンって何?

EC2などのサービスでは US East (Northern Virginia) というリージョンが存在しますが、S3だけは US East の代わりに、US Standard というリージョンが存在します。何故なのか気になるのでちょっと調べてみました。

f:id:mi_kattun:20140607200533p:plain

AWS公式資料から

US Standardへのリクエストは自動的に Northern Virginia または Pacific Northwest *1 にルーティングされるとのことです。

この認識が正しければ、S3で自動的に作られるオブジェクトの3つ以上の複製が、これら2つのリージョンにまたがって保管されていると考えられます。

 This region automatically routes requests to facilities in Northern Virginia or the Pacific Northwest using network maps.

Buckets and Regions - Amazon Simple Storage Service

転送量課金の面では、US East (Northern Virginia) と同じリージョンという扱いです。

 リージョン間でコピーリクエストを通じて転送されるデータは、S3 詳細ページの料金表セクションに指定された料金で課金されます。同じリージョン内にある Amazon EC 2 と Amazon S3 間、もしくは米国東部の Amazon EC2 と米国スタンダードの Amazon S3 間でデータ転送を行った場合、請求は発生しません。

よくある質問 - Amazon S3 (クラウドストレージサービス Amazon Simple Storage Service) | アマゾン ウェブ サービス(AWS 日本語)

ちなみに、US Standardのみデータ整合性モデルが異なります。想像ですが、2つのリージョンにまたがって複製されるため、強い整合性を採用できなかったのかもしれません。

米国西部(オレゴン)、米国西部(北カリフォルニア)、欧州(アイルランド)、アジアパシフィック(シンガポール)、アジアパシフィック(東京)、アジアパシフィック(シドニー)、および南米(サンパウロ)リージョンの Amazon S3 バケットは、新しいオブジェクトの PUTS に対して、「書き込み後の読み込み」整合性を提供します。また、PUTS および DELETES の上書きについて、結果整合性を提供します。米国スタンダードリージョンの Amazon S3 バケットは、結果整合性を提供します。

よくある質問 - Amazon S3 (クラウドストレージサービス Amazon Simple Storage Service) | アマゾン ウェブ サービス(AWS 日本語)

中の人に聞いた方のブログ記事から

Shlomo Swidler氏が、AWSのJeff Barr氏にRead-After-Write ConsistencyがUS Standardで提供されない理由を聞いたというブログ記事です。

その理由は「US Standardが米両岸にまたがる論理リージョンだから」と書かれています。やはり上での想像は正しかったようです。

Read-after-write consistency for AWS S3 is was only available in the US-west and EU regions, not the US-Standard region. I asked Jeff Barr of AWS blogging fame why, and his answer makes a lot of sense:

This is a feature for EU and US-West. US Standard is bi-coastal and doesn’t have read-after-write consistency.

Aha! I had forgotten about the way Amazon defines its S3 regions. US-Standard has servers on both the east and west coasts (remember, this is S3 not EC2) in the same logical “region”. The engineering challenges in providing read-after-write consistency in a smaller geographical area are greatly magnified when that area is expanded. The fundamental physical limitation is the speed of light, which takes at least 16 milliseconds to cross the US coast-to-coast (that’s in a vacuum – it takes at least four times as long over the internet due to the latency introduced by routers and switches along the way).

Read-After-Write Consistency in Amazon S3

結論

Amazon S3のUS Standardリージョンは、アメリカ東西海岸にまたがる論理リージョンです。 ただ転送量課金の面では、US East (Northern Virginia)と同じと考えて問題ないでしょう。

もう少し明確なソースがありそうな気がしますが、見つけられませんでした。ご存じの方は教えて頂けるとありがたいです。

参考:

*1:Oregonのことかと思いましたが、Oregonは2011年に始まった比較的新しいリージョンなので違うかもしれません。わかりません。

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

前編 までで静的ファイルをS3から配信することができました。しかし、実際にHerokuにpushしてみると、毎回全ファイルがアップロードされてしまい、非常に時間がかかります。

原因はこうです。collectstaticはファイルのタイムスタンプを比較してファイルが変更されたかどうかを判定しています。しかし、Herokuではpushの度に全ファイルのタイムスタンプが更新されるので、全ファイルに変更があったと判断されてしまうのです。

参考: Django collectstatic from Heroku pushes to S3 everytime - Stack Overflow

この対策として、前編 のようにcollectstaticをローカルマシンで実行する方法もあります。しかしせっかくならHerokuへのpush時にやってくれたほうがエレガントです。

これを解決するのが collectfast です。collectfastはcollectstatic時にファイルの変更チェックをタイムスタンプではなく、ハッシュ値で行うライブラリです。

collectfastはS3上のファイルのハッシュ値Djangoのキャッシュに保存することで、高速に変更チェックを行います。キャッシュなしでも利用できますが、毎回全ファイルのハッシュ値をS3から取得するので導入効果が薄れます。手元では1分ほどかかりました。

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

$ pip install collectfast pylibmc django-pylibmc-sasl

pylibmcdjango-pylibmc-sasl は後述のmemcachierを使うのに必要です。

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

Collectfast==0.2.0
django-pylibmc-sasl==0.2.4
pylibmc==1.3.0
python-dateutil==2.2
pytz==2014.3
six==1.6.1

Herokuの設定

キャッシュで使うmemcachierアドオンを追加します。

$ heroku addons:add memcachier

また、AWSのクレデンシャルはHerokuの設定に追加しておきます。

$ heroku config:set AWS_ACCESS_KEY_ID=*** AWS_SECRET_ACCESS_KEY=***

Djangoの設定

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

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

os.environ['MEMCACHE_SERVERS'] = os.environ.get('MEMCACHIER_SERVERS', '').replace(',', ';')
os.environ['MEMCACHE_USERNAME'] = os.environ.get('MEMCACHIER_USERNAME', '')
os.environ['MEMCACHE_PASSWORD'] = os.environ.get('MEMCACHIER_PASSWORD', '')

CACHES = {
     'default': {
          'BACKEND': 'django_pylibmc.memcached.PyLibMCCache',
          'TIMEOUT': 1000,
          'BINARY': True,
          'OPTIONS': {
               'tcp_nodelay': True,
               'remove_failed': 4
          }
     }
}

COLLECTFAST_CACHE = 'default'

ここでは説明を簡単にするため、defaultのキャッシュを使ってますが、他の用途で使っている場合は、collectfastなどのキャッシュを作るとよいでしょう。

参考:

デプロイ

あとは普通にHerokuにデプロイするだけです。

$ git push heroku master

1回目はある程度時間がかかりますが、2回目以降は変更のあったファイルだけがアップロードされるのですぐに終わります。

結果

Heroku単体で配信していた時は200ms程度かかっていた静的ファイルの配信が、S3を使うことで30〜100ms程度で終わっていることがわかります。

Heroku(US)で静的ファイル配信 f:id:mi_kattun:20140604002053p:plain Heroku+S3(Tokyo)で静的ファイル配信 f:id:mi_kattun:20140604002038p:plain

(参考)さくらVPS(大阪) f:id:mi_kattun:20140604002105p:plain

今回は日本のみをターゲットとしたのでS3のみを使いましたが、全世界をターゲットとする場合はCloudFrontなどのCDNを使うのが効果的でしょう。

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を使います。

参考