AWSのEC2にdjangoデプロイ時のトラブルシューティング



EC2へのDjangoデプロイで無茶苦茶時間食ったので、備忘録がてら、トラブル各種まとめてみます。

ちなみにdjango童貞です。「こうしたらええんやで」っていう諸先輩方いらっしゃいましたらコメントいただければ喜びます。

デプロイ一連の流れ

  • EC2インスタンス作成
  • ポート開放
  • SSH接続で環境整備
  • プロジェクトファイルアップロード
  • セッティングファイルあれこれ
  • 完成

EC2インスタンス作成

EC2インスタンス作成
EC2インスタンス作成
AMI選択
AMI選択

なんか二種類ありますが、違いもよくわかりませんし、多分どっちでもいいです。好きな方で。

セキュリティグループの編集(ポート開放)

セキュリティグループ
セキュリティグループ

インバウンドの方をこんな感じの設定にすればOKです。
ちなみに作成時に編集しなくてもあとから変更できます。

あとは大体ディフォルトのまま進めてOKです。

SSH接続

事前にEC2キーペアを作っていたらそれを紐づけ、そうじゃなければEC2インスタンス生成時に項目があるので、鍵ファイル(.pem)をDLしてローカルに保持しておきます。

SSHクライアントは普段使っているものでOKです。

今回はPycharm組み込みのSSHクライアントを例に接続方法を解説。

リモートサーバーの構成
リモートサーバーの構成

サーバーの構成ボタンを押してさらに進みます。

SSH設定
SSH設定
  • 型:SFTP
  • ホスト:EC2インスタンスのパブリックDNS(IPv4)
  • ユーザー名:ec2-user
  • 認証:Key pair
  • Private key path:EC2コンソールからDLした(.pem)ファイルのパス

ここまで入力して接続テストして、OKならルートパスとかはオートで入力できます。

マッピング(やると便利)

ここ設定しておくとPycharmのプロジェクト(ローカル)とリモートのサーバーを自動で同期できます(多分)

ローカルで編集・保存するたびに指定されたリモートサーバーの対応する位置へアップロードされるようです。

Pycharm SSH マッピング
Pycharm SSH マッピング
SSHターミナルで接続成功
SSHターミナルで接続成功

EC2インスタンスの環境整備

SSHで無事接続できたので、必要な環境を整備していきます。
参考: https://qiita.com/pokotsun/items/1272479e36c5146c6609

参考先ではユーザーを作成してますが、管理めんどくさかったのでディフォルトのec2-userのまま環境整備進めました。

ここではyum使ってますが環境によって適宜aptとかに読み替えてください。

yumで下準備

$ sudo -i
$ yum update -y
$ yum install -y vim git

Python3インストール

$ cd ~
$ sudo yum install -y gcc make zlib1g-dev zlib-devel openssl-devel tk-devel sqlite-devel bzip2 bzip2-devel readline-devel
$ wget https://www.python.org/ftp/python/3.7.4/Python-3.7.4.tar.xz
$ tar Jxfv Python-3.7.4.tar.xz
$ cd Python-3.7.4/
$ ./configure --prefix=/home/ec2-user/.local/python
$ make
$ make install
# シンボリックリンクを貼る
$ sudo ln -s ~/.local/python/bin/python3 /usr/local/bin/python
$ sudo ln -s ~/.local/python/bin/pip3.7 /usr/local/bin/pip3
$ echo 'export PATH="/home/ec2-user/.local/python/bin:$PATH"' >> ~/.bashrc
$ echo 'alias pip=pip3' >> ~/.bashrc
# 確認
$ python -V
Python 3.7.4
$ pip -V
pip 19.0.3 from /home/ec2-user/.local/python/lib/python3.7/site-packages/pip (python 3.7)

プロジェクトファイルアップロード

アップロードはgithub使ってcloneしてもいいですし、Pycharmから普通にアップしてもいいですし、煮るなり焼くなりお好きに。

ただ、モジュールのインストール手動でやると面倒なので「requirements.txt」だけつくっておきます。

ローカルのプロジェクト直下で以下のコマンド。

$ pip freeze > requirements.txt

アップ後、依存関係復元

$ pip install -r requirements.txt

django→settings.pyの編集

本番環境用にsettings.pyを編集する必要があります。
以下の箇所を見つけて編集。

DEBUG = False # True => Falseにする

# ローカルホスト、リモートIP、リモートホストを追加
ALLOWED_HOSTS = ['127.0.0.1', 'xx.xx.xxx.xxx', 'ec2-xx-xx-xxx-xxx.ap-northeast-1.compute.amazonaws.com']

# staticファイル関係、必要であれば
STATIC_URL = '/static/' # 多分元々こうなってる
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

django童貞の僕としてはなぜdjangoのstaticファイルの構成がこんなめんどい感じなのかわからないまま・・・。

とりあえず、開発時にプロジェクト共通のstaticフォルダとか作ってなくて、app直下にstatic作って対応してた方は上記で問題ないと思います。

staticのファイルを集約する

$ python manage.py collectstatic

なぜこんな面倒なことさせるのか知りませんが、app直下のstaticファルダ内のファイルを集約してproject直下のstaticフォルダにコピーするコマンドです。

これやらないとリモートで見たときにstaticファイルが見つからず404になります。

さらに面倒なことにcollectstaticをやってても403になる場合があります。

サーバーミドルウェアインストールと設定

サーバーはnginxです。なうい(別になうくはないのか?)です。

$ sudo amazon-linux-extras install nginx1.12
$ sudo service nginx start

さらにこれも必要なよう。

pip install gunicorn

gunicornはなんかnginxで受け取った処理をさらに中継するのに使うようです。

ここでdjango童貞の僕はこう思いました。

「いや、何でディフォルトのサーバー使わんのや?」

manage.py runserver

でええんちゃうのん?ただただめんどいだけちゃうんか、と。
回答は以下。

参考: https://blog.hirokiky.org/entry/2018/10/06/151830

要約すると、

なんかわからんけど、とりあえず早いらしい

なら、まぁ、使っとくか。

nginx→gunicornの流れ

$ sudo vi /etc/nginx/nginx.conf

からの。

# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {


    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    index   index.html index.htm;

    upstream app_server {
       server 127.0.0.1:8000 fail_timeout=0;
    }

    server {

        ## ここを書き換える
        listen    80;
        server_name     (EC2のドメイン or IPアドレス);
        client_max_body_size    6G;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
            # 以下4行を追加
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_redirect off;
            proxy_pass   http://app_server;
        }

        location /static {
            alias (アプリケーションのstaticファイルの絶対パスを記入);
            expires 5h;
        }

        # redirect server error pages to the static page /40x.html
        #
        error_page 404 /404.html;
            location = /40x.html {
        }

        # redirect server error pages to the static page /50x.html
        #
        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ .php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ .php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /.ht {
        #    deny  all;
        #}
    }

}

考えるのめんどくさかったので、設定ファイル全部消してからの完コピです。
マジでありがとうございます。

参考: https://qiita.com/pokotsun/items/1272479e36c5146c6609

サーバーの起動

ここまで設定が完了したらあとはサーバーを立ち上げてアクセスするのみ。

$ sudo service nginx restart
$ gunicorn MyAppName.wsgi --bind=0.0.0.0:8000

MyAppNameはプロジェクト直下の最上位アプリケーション名(settings.pyとかが入っているapp)です。

確認してみればわかりますが、wsgi.pyというファイルがあります。

この状態でAWSのEC2コンソールで確認できるパブリックDNSのアドレスへアクセスするとローカルで見ていたのと同じようにdjangoで作ったWebアプリが確認できるはずです。

ちなみにgunicornはこの状態だとSSHの接続を切ると落ちてしまいますので、普段使いで常時待機状態にするならデーモンとして立ち上げる必要があります。

$ gunicorn MyAppName.wsgi --bind=0.0.0.0:8000 -D

-D オプションをつけるだけです。これでバックグラウンド起動になります。

デーモンで走ってるgunicornサーバーをkillするときは以下コマンド

$ pkill gunicorn

nginxを止めるときはこうです。

$ sudo service nginx stop

ちょっとハマったトラブル集

いくつかハマった箇所があるので紹介します。

SQliteのバージョンエラー

django.core.exceptions.ImproperlyConfigured: SQLite 3.8.3 or later is required (found 3.7.17).

3.8以上のSQliteのバージョンを要求されます。
SQliteってバージョンもクソもあるんかよ・・・。とか思いながら、調べました。

参考: https://qiita.com/rururu_kenken/items/8202b30b50e3bfa75821

と、まぁ色々弄くり回してという方法もあるようですが、一番簡単なのはこれです。

$ pip install django==2.1

どうやらdjango2.2以上から出現するエラーのようですので、特に2.2にこだわる理由もなかったので僕はdjangoのバージョンを下げることで対応しました。

Staticファイル群の403

CSSとかJSファイルが403でちゃんと動いてないなってなったらこのパターンだと思います。

ec2-userにパーミッションを振る

参考: https://qiita.com/xKxAxKx/items/f43d1bddbc4fb31013b1

$ sudo chmod o+x /home/ec2-user/

これで一発解決。