Django
nginx
AWS
deploy
gunicorn

AWSにDjangoアプリケーションをデプロイ(Nginx, gunicorn, postgresql)

デプロイ時に詰まりどころがいくつかあったのでとりあえずHTTPSで動くところまで

1. AWSインスタンスを作成

EC2インスタンスを作成。
セキュリティグループを追加して22, 80, 443ポートが開いているのを確認。

下画像のようになっていればたぶん大丈夫。

Screenshot from 2018-05-16 13-57-11.png

取得した鍵(*.pemてやつ)を使って

$ ssh -i 取得した鍵 インスタンスのパブリックドメイン or ipアドレス

でインスタンスにssh接続する。毎回コマンド打って接続するのが面倒だったらconfigに書いてもよし

2. Amazon Linuxに作業用環境を導入

$ sudo -i
$ yum update -y
$ yum install -y vim git
$ useradd app-user
$ passwd app-user
$ usermod -aG wheel app-user
$ cp -r /home/ec2-user/.ssh /home/app-user/.ssh
$ chown -R app-user:app-user /home/app-user/.ssh
$ sudo su app-user
$ chmod 0600 ~/.ssh/authorized_keys

以下app-user内で作業を行う(その前にvisudoでwheelに権限を与える必要あり)

3. Python導入

公式から目的のバージョンのPython(今回は3.6.6)のtarファイルを持ってきてインストールする

python3.6.6のインストール
$ 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.6.6/Python-3.6.6.tar.xz
$ tar Jxfv Python-3.6.6.tar.xz
$ cd Python-3.6.6/
$ ./configure --prefix=/home/app-user/.local/python
$ make
$ make install
# シンボリックリンクを貼る
$ sudo ln -s ~/.local/python/bin/python3 /usr/local/bin/python3
$ sudo ln -s ~/.local/python/bin/pip3.6 /usr/local/bin/pip3
$ python3 -c "print('Hello, Python36')"
$ echo 'export PATH="/home/app-user/.local/python/bin:$PATH"' >> ~/.bashrc
# 以下のように帰ってきたらOK
Hello, Python36
# 以下のようにモジュールをインストールできる
$ pip3 install (インストールしたいモジュール名)

4. 目的のDjango applicationをgitで持ってくる

git cloneしてデプロイしたいアプリケーションをとってくる。

アプリケーションのルートに移動したあと

$ pip3 install pipenv
$ pipenv install
$ pipenv shell

上記コマンドで必要モジュールをインストール,環境に移動

以下コマンドを叩いて無事動いたらdjangoは正常に動いている

$ ./manage.py runserver 0:8000

5. djangoアプリケーションをproduction環境にする

settings.pyをいじる

/application_name/settings.py
#(省略)
DEBUG = False # True => Falseにする

ALLOWED_HOSTS = ["(EC2のドメイン or IPアドレス)"] # Hostを追記
#(省略)
STATIC_URL = '/static/' # 基本これで決め打ち
STATIC_ROOT = '(staticディレクトリの絶対パス)'
#(省略)

以下コマンドでstaticファイルを一箇所にまとめる

$ ./manage.py collectstatic

これしないとcssやjsが読み込まれないのでAPIの場合は特に問題ないが、せっかくのDjango Contributorsの方々のAdminページに対する情熱を4,5割ほど無駄にするので注意(Nginxで静的ファイルの処理を行うまでcssは効かないが気にせず先に進む)

(補足) STATIC_URL, STATIC_ROOTの違い

気になったので調べてみた

変数名 意味
STATIC_URL Djangoアプリケーション内で読み込む静的ファイルの場所 defaultはNone
STATIC_ROOT collectstaticしたときに集めたstaticファイルを置く場所 絶対パス

STATIC_URL はアプリケーションのルートからのパス
STATIC_ROOT は絶対パス
というのが味噌

6. Nginx導入

$ sudo yum install -y nginx
# もし上記でうまくリポジトリが登録されていない場合以下コマンドでインストール
$ sudo amazon-linux-extras install nginx1.12
$ sudo service nginx start

これでEC2のドメインをブラウザで開いてみてNignxが動いているかの確認

7. gunicorn導入

Wsgiサーバーとしてgunicornを導入する

$ pipenv install gunicorn

以下コマンドでwsgiと接続できているかを確認
(project_name).wsgiファイルはsettings.pyと同階層にいる

$ gunicorn (project_name).wsgi --bind=0.0.0.0:8000

ここでstatic fileが読み込まれないかもしれませんがそれは仕様なので気にせず次に行く

8. nginxとgunicornを接続

nginxのリバースプロキシ設定をする

/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;
        #}
    }

}

nginxの設定ファイルをいじり終えたら再起動

$ sudo service nginx restart

これで
Client -> Server80番ポート(Nginx) -> Server8000番ポート(gunicorn) -> Djangoアプリケーション
の流れを作れる

早速gunicornを起動

$ gunicorn app_name.wsgi --bind=0.0.0.0:8000

-DオプションをつけることでDeamon起動も可

もしjqueryやcssが403 forbiddenエラーで効いていない場合は
以下コマンドで権限を与えてあげることでうまくいくはず

$ sudo chown o+x app-user

9. DBをPostgreSQLに変更

SQLiteのままなのでPostgreSQLに変更
postgresqlをインストール

$ sudo yum install -y postgresql93 postgresql93-devel postgresql93-server
$ sudo service postgresql initdb
$ sudo service postgresql start
$ sudo useradd postgres
$ sudo passwd postgres
$ sudo su - postgres
$ psql
postgresql内での設定
> ALTER USER postgres with encrypted password 'ここにパスワード';  # パスワード変更
> CREATE DATABASE your-db-name; # アプリケーション用のDB作成

PostgreSQLでユーザー認証がデフォルトだとPeer認証になっているため変更

/var/lib/pgsql93/data/pg_hba.conf
local   all             postgres                                md5  # <- peerからmd5に変更
local   all             all                                     peer
# IPv4 local connections:
host    all             all             127.0.0.1/32            md5 # <- peerからmd5に変更

(補足) Peer認証とは

Peer Authentication(以下Peer認証) とは postgresql に ローカルから接続する際に, 接続を試みるプロセスの実効ユーザ(OS側のユーザ)とデータベースへのログインユーザ(データベース側のユーザ)が一致している場合のみ接続を許可する認証方法である.
PostgreSQL の Peer Authentication と 他の認証方法への変更についてのメモ書き. Peer Authentication とは

ちなみにtrustにすればパスワード無しで入れる

configファイルをいじったので再起動

$ sudo service postgresql93 restart

再起動できたらPostgreSQLの方はこれで終わり

djangoでpostgresqlを使うためにモジュールを導入

$ pipenv install psycopg2

djangoの方にPostgresqlを使うことを明示

settings.py
# (省略)
DATABASES = {
    'default': {
        'ENGINE':'django.db.backends.postgresql_psycopg2',
        'NAME': 'データベース名',
        'USER': 'データベースユーザー名',
        'PASSWORD': 'パスワード',
        'HOST': 'ホスト(今回はlocalhost)',
        'PORT': '',
    }
}

以上書き終わればまたWsgiサーバー起動してみてエラーが出なければうまくいってる

$ gunicorn app_name.wsgi --bind=0.0.0.0:8000

10. SSL認証させる

秘密鍵(.key)と証明書を(.crt)用意
オレオレで良いなら下記記事がまとめてくれている
SSLを利用するための自己証明書(オレオレ証明書)の設定メモ

Let's encryptで証明書を用意する場合

let'sEncryptの用意
$ sudo service nginx stop
$ sudo su -
$ curl https://dl.eff.org/certbot-auto -o /usr/bin/certbot-auto
$ chmod 700 /usr/bin/certbot-auto

/etc/nginx/nginx.confに下記を追加して

/etc/nginx/nginx.conf
server {
    listen 80;
    # 省略 (以下追記)
    location ^~ /.well-known/acme-challenge/ { 
    root /usr/share/nginx/html;
} 

以下を叩く

$ certbot-auto certonly --standalone -t --no-bootstrap

以上で証明書が手に入るはず

証明書が切れたら

証明書が切れたら
$ certbot-auto renew --force-renewal

でOK

Nginxの変更

NginxのConfファイルに追記

/etc/nginx/nginx.conf
#(省略)

server {
        listen    80;
        server_name     (証明書で証明したドメイン名);
        rewrite ^ https://$server_name$request_uri? permanent; # ここを追記
}

# 以下追記 SSL用ポートの設定
server {
        listen 443;
        ssl on;
        server_name  (EC2のドメイン or IPアドレス);
        client_max_body_size 250M;

        # let's encryptの場合 fullchain.pemてやつ
        # オレオレの場合 server.crt てやつ
        ssl_certificate "(証明書のフルパス)";

        # let's encryptの場合 privkey.pemてやつ
        # オレオレの場合 server.key てやつ
        ssl_certificate_key "秘密鍵のフルパス";

        keepalive_timeout 70;

        location / {
        # リバースプロキシの設定
        ## Load configuration files for the default server block.
            proxy_pass   http://app_server;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_redirect off;
            proxy_set_header Host $http_host;
            include /etc/nginx/default.d/*.conf;
        }

        location /static {
                alias (Djangoアプリケーションのstaticファイルへのフルパス);
                expires 5h;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
}

confファイルの設定が文法的に間違ってないか確認

$ sudo nginx -t

うまくいったらNginx再起動, 鍵のパスワードが求められるのでちゃんと打ち込む

$ sudo service nginx restart

https://ホニャララでアクセスできればOK

参考にさせていただいた記事