一年ほど前からさくらのVPSにCentOS7を入れ、DjangoアプリをNginx+uWSGI環境で動かしていました。
今回アプリをReact(+Redux)+Django REST Frameworkに移行してデプロイしてみたのでそこで詰まった点などを残しておきます。
Contents
前提条件
OS | CentOS 7 .6 |
Webサーバ | Nginx 1.15 |
Django | 2.1 |
uWSGI | 2.0 |
React | 16.12 |
元からDjangoを動かしていたのでReactのバックエンドをDjango REST Frameworkで動かすことにしました。
今回の記事ではすでにDjangoは動かすことが出来るという状態からの記事とします。Django+uWSGI+Nginxのデプロイに関してはこちらの記事がオススメです。
補足
サポート状況から考えるとDjangoは2.2以上のバージョンで運用すべきです。今回Django2.2へアップグレードしようとしたところ、sqlite3のバージョンが合わなくなったためアップグレードを保留しましたが、本来は2.2以上のバージョンで運用すべきです。
手順
手順としては以下の順番で行いました。
- サーバー側でソースコードをGitHubからClone
- python manage.py collectstaticで静的ファイルを配置
- Nginx + uWSGIで動かす (ここまでDjango側)
- React側でyarn buildでビルドし、ファイルを公開フォルダへ配置
- Nginxの設定ファイルでReact用のセッティングを行う
- 動作確認
Django REST Framerokセットアップ
1 ~3についてはDjango+uWSGI+Nginxと同じでrest framework特有の問題などは起こりませんでした。初めての方は上で紹介したリンクを見てみて下さい。
忘れがちなこととして、サーバー側に必要なライブラリがインストールされているかは確認しましょう。django rest framework自体や、django-cors-headersなど追加する必要があると思います。
ポイント
バックエンドはAPIを使うだけだからcollectstaticする必要ある?と思うかもしれませんが、adminページのcss等あるため必ずやっておきましょう。
Reactファイル配置
yarn buildでビルドします。私はサーバー側にnode.jsの環境を作るのが面倒だったのでローカルでビルドしてファイルをscpで送りました。
ビルドすると/buildフォルダが出来るので、その中身をNginxで公開しているディレクトリに配置します。
私の場合公開ディレクトリ直下にindex.html等のビルドファイルを配置しましたが、ディレクトリ構成を変える場合はnginxの設定ファイルでディレクトリの指定をする必要があります。
ポイント
Reduxの開発ツールを使っている場合は以下のようにbuild時は適用されないようにしましょう。
const composeEnhancers = process.env.NODE_ENV === "development" ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : null || compose;
この条件分岐を利用してAPIのURLなどもdevelopmentとproductionで分けられます。
Nginx設定
Nginxのsites-availableディレクトリにファイルを作り、以下のように設定します。
server { listen 80 default_server; root ここに公開ディレクトリへのパスを記入; index index.html index.htm; location / { try_files $uri $uri/ /index.html; } }
ポイントはtry_filesのところ。
通常のWebサーバーはアクセスされたURLに対応するhtmlファイルを返しますが、Reactの場合、全てのアクセスが一つのindex.htmlによって処理されるようにします。それがtry_filesの箇所です。
ここまで設定し、Nginxをrestartすれば全て動くようになっているはずです。
が、私の場合動きませんでしたので、ここからは起こった問題と対応策を書いておきます。
詰まった点 & 解決法
Reactのビルドファイルを公開フォルダへ配置してもForbiddenになる。
ReactのビルドファイルをNginxの公開用フォルダに移しても、ページを見ることができず「Forbidden」が表示されるだけでした。
Nginxをインストールしたときに初期配置されているファイルはブラウザ上でも表示されています。ただしその初期ファイルと同じ権限にReactのファイルを設定してもReactのファイルの方は見られない、という状態でした。
error.logを見てもPermission Deniedになっています。
大体CentOSではSELinuxのせいで問題が起こるので、一時的にSELinuxをPermissiveにして(setenforce 0して)試してみると今度はアクセス出来ます。
調べてみたところ、SELinuxのコンテキストと呼ばれるものが原因であると判明しました。(ディレクトリをmvしたりすると起こるっぽい?)
ざっくり言うとSELinuxはプロセスやファイルをラベル付けしているらしく、それをコンテキストと呼ぶらしい。
ls -Z フォルダ名
とやるとSELinuxコンテキストが見られます。(よく覚えてないのですが、たぶん)unconfined_u:object_r:user_なんちゃらみたいになっていたので(Nginxの初期ファイルと違っていたので)、さらに調べてみます。
ここやここなどを見る限り、restoreconというコマンドでコンテキストを復元すればよさそうだったので、やってみます。
sudo restorecon (公開ディレクトリのパス) -R
としたところ、SELinuxがEnforcingでもReactのindex.htmlにアクセスできるようになりました。
Cross Originの設定(CORS)でWhitelistを設定しても、ReactからのAPIが ブロックされる。
厄介だったのがCross Originの設定で、ローカル環境では以下のように設定しておけば問題なかったのですが、本番環境では当然変える必要があります。
CORS_ORIGIN_WHITELIST = ( 'http://localhost:3000', 'http://127.0.0.1:3000', )
これを変更しホワイトリストにVPSのIPアドレスを指定したのですが、CORSポリシーによってアクセスがブロックされてしまいます。
「おかしいな?」と思いながらも、今度は"http://127.0.0.1"を指定するもまだブロックされます。
CORS_ORIGIN_ALLOW_ALL = True
とすると動くのですが、明らかにセキュリティ的に問題があります。
ここからどこを調べても解決策が見つからず迷走したのですが、結局以下のようにIPアドレスと127.0.0.1を両方指定すると、きちんと動くようになりました。
CORS_ORIGIN_WHITELIST = ( # 'http://localhost:3000', 'http://127.0.0.1', 'http://ここにIP' )
どちらか一方でもダメで両方指定しないとアクセスがブロックされます。なぜかは不明。
まとめ & 感想
Django REST Framework + NginxでのReactのデプロイを紹介しました。Django REST FrameworkとReactの組み合わせはそこまで多くないと思うので誰かの役に立てればと思います。
===
やはり実環境では中々思った通りにいかないですね。特にCentOSではSELinuxがあるので何かとつまずきます。
不思議なのですが、調べていると何かに付けて「SELinuxを無効にすればいいよ」とか「Cross Origin設定はALLOW ALLでいいよ」とか言っている人がいるのですが、大丈夫なのでしょうか?
(ダメでしょ)