search
LoginSignup
4
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Organization

何も考えずにEC2にDjango×SQLite3×nginx×gunicornな環境を構築する

はじめに

AWSのEC2上でDjangoプロジェクトを展開しようとして丸一日悩んでしまったので、手順をまとめました。あくまでも自己責任でお願いします。

やったこと

EC2インスタンスを立てて何も考えずにDjangoを動かして、ネット上からアクセスできるようにする。
今回はSQLite3,nginx,gunicornを使います。

注意

  • ec2-userをそのまま使っていたり、セキュリティ的に怪しい所があるため注意すること。今回はAWSでdjangoプロジェクトをとりあえず動かせるようにすることを目的とする。
  • VPC、サブネット、インターネットゲートウェイの設定はすでに完了していて、EC2自体はネットから参照できる状態になっている所からスタートする
  • ssh(22)、HTTP(80)とDjango用(8000)のポートが開放されている状態を前提とする
  • DBはSQLite3を使用する。また、SQLite3のバージョンを最新にするため既存のソフトウェアでSQLite3を使用している際は注意すること。
  • Djangoの管理者画面がスタイルシートなどを含めて読み込めたところで本記事のゴールとする

参考にした文献

必要なものを揃える

本記事ではvenvなどは使わず、そのままpython3を使っていくため注意すること。

install
# 後述処理でパスワードを求められるため設定する。また、rootを使えるようにする
$ sudo passwd ec2-user
$ sudo passwd root
$ sudo yum update
$ sudo yum install python3
# Amazon Linux 2で$sudo yum install nginx と入力するとコマンドを催促されるので、それを入力する。
$ sudo amazon-linux-extras install nginx1
$ sudo pip3 install django
$ sudo pip3 install gunicorn

Amazon Linux 2にもデフォルトでSQLite3がインストールされていたが、Djangoで求めるバージョン(3.8.3以降)にマッチしないため、SQLiteのアプデを実施する。

SQLite3のinstall
# 後述のSQLite3インストール時のmakeファイルのビルドに使用する。
$ sudo yum install gcc
# 最新版のSQLite3をインストールする。執筆当時は3.33.0が最新だった。(https://www.sqlite.org/download.html)
# 展開した後にmakeファイルをビルドしてmake installまで実施する
$ wget https://www.sqlite.org/2020/sqlite-autoconf-3330000.tar.gz
$ tar zxvf sqlite-autoconf-3330000.tar.gz
$ cd sqlite-autoconf-3330000
# makeファイルのビルド
$ ./configure --prefix=/usr/local
$ sudo make
$ sudo make install

次に、過去に入っていたSQLite3から現行のSQLite3を使用するように設定を通す。

SQLite3のinstallつづき
# すでにインストール済みのsqlite3を確認する
$ sudo find / -name sqlite3
# もともとあったやつは多分これ。バージョンが古いことを一応確認しておく。
/usr/bin/sqlite3 --version
# 今回インストールしたのは多分これ。バージョンが古いことを一応確認しておく。
/usr/local/bin/sqlite3 --version
# 古い方のsqlite3のフォルダ名を変え、また新しい方のsqlite3へパスを設定しておく
sudo mv /usr/bin/sqlite3 /usr/bin/sqlite3_old
sudo ln -s /usr/local/bin/sqlite3 /usr/bin/sqlite3
# ライブラリへのパスを通しておく
su
sudo touch /etc/ld.so.conf.d/sqlite3.conf
sudo echo /usr/local/lib >> sqlite3.conf
# rootからログアウト
exit
# 過去のsqlite3へのリンクとディレクトリを削除
$ sudo rm /lib64/libsqlite3.so.0
$ sudo rm -rf /lib64/libsqlite3.so.[過去のバージョン]
# ライブラリのパスを更新する
sudo ldconfig
# 新しいライブラリへのパス(/usr/local/lib)が通っているか確認する
$ ldconfig -p | grep sqlite
# 一応バージョンも確認する
sqlite3 --version

最後のバージョン確認で現行のSQlite3のものが出力されれば、次に進む。

Djangoの設定

とりあえずプロジェクトを作る。プロジェクト名は公式ドキュメントでおなじみのmysiteにする。

django-admin
# /home/ec2-userなどの任意のユーザのhomeディレクトリへ移動
$ cd /~
$ django-admin startproject mysite

今回はadmin画面が見えればOKなので、最低限の設定だけをする。

django-admin
$ cd mysite/mysite
$ vi settings.py

setting.pyにおいて設定するのは

  • 末尾にSTATIC_ROOTを追加(あわせてimport osも追記)
  • ALLOWED_HOSTSに自分のパブリックドメインやパブリックIPアドレスを設定

ぐらい。一応全文を載せておく

setting.py
import os
from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.                                                                                        
BASE_DIR = Path(__file__).resolve().parent.parent

# SECURITY WARNING: keep the secret key used in production secret!                                                                                      
SECRET_KEY = 'hogepugefugahogepugefuga...'

# SECURITY WARNING: don't run with debug turned on in production!                                                                                       
DEBUG = True

# 自分のドメインやパブリックIPアドレスとか
ALLOWED_HOSTS = ['*.*.*.*',]


# Application definition                                                                                                                                

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'mysite.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'mysite.wsgi.application'

# Database                                                                                                                                              
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases                                                                                         

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}


# Password validation                                                                                                                                   
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators                                                                          

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization                                                                                                                                  
# https://docs.djangoproject.com/en/3.1/topics/i18n/                                                                                                    

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)                                                                                                                
# https://docs.djangoproject.com/en/3.1/howto/static-files/                                                                                             

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR,'static')

終わったら、djangoプロジェクトの静的ファイルをまとめたり、
管理者ページに入るまでの設定をしておく。

django
$ cd ~/mysite
# 自身の情報をここで入れておく。パスは忘れないように
$ python3 manage.py makemigrations
$ python3 manage.py migrate
$ python3 manage.py createsuperuser
# STATICファイルをとりまとめておく
$ python3 manage.py collectstatic

もし、python3 manage.py collectstaticでcheck_sqlite_versionと書かれたエラーを吐いた場合はSQLite3の設定を見直すこと。過去のSQLite3を見に行っている可能性が高い。

Djangoそのものの設定がうまくできているか確認するために、gunicornから動かしてみる。

django
# 必ずmanage.pyなどが存在しているフォルダへ移動すること
$ cd ~/mysite
$ sudo gunicorn mysite.wsgi --bind=0.0.0.0:8000

ブラウザでパブリックIPあるいはドメインへアクセスした際に、例のロケットが表示されればOK。確認できたらgunicornをCtrl+Xなどで終了する。

gunicornの設定

以下の設定ファイルを作成(編集)する。

  • /etc/systemd/system/gunicorn.service
  • /etc/systemd/system/gunicorn.socket

なお、gunicorn.serviceのExecStartのパスはwhichコマンドで確認する。

gunicorn.serviceのExecStartに設定するパスのチェック
which gunicorn

ユーザー名(グループ名)、WorkingDirectory、ExecStartのwsgiファイル名とListenStreamは各自の環境に揃えておくこと。(特に工夫がなければ、ユーザー名はnginxと一致させておく。)

gunicorn.service
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target

[Service]
Type=notify
# the specific user that our service will run as
# ここのユーザ名とグループ名はデーモンを実行するユーザを入れる。
# 特に何も考えていなければec2-userにする。
User=ec2-user
Group=ec2-user
# another option for an even more restricted service is
# DynamicUser=yes
# see http://0pointer.net/blog/dynamic-users-with-systemd.html
RuntimeDirectory=gunicorn
# Djangoプロジェクトのパスを入れる
WorkingDirectory=/home/ec2-user/mysite
# gunicornの実行ファイルのあるパスと、プロジェクト名.wsgi
ExecStart=/usr/local/bin/gunicorn mysite.wsgi
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true

[Install]
WantedBy=multi-user.target
gunicorn.socket
[Unit]
Description=gunicorn socket

[Socket]
# sockファイルを作る場所を入れる。基本はdjangoプロジェクトのパスを入れておく。
ListenStream=/home/ec2-user/mysite/mysite.sock
# Our service won't need permissions for the socket, since it
# inherits the file descriptor by socket activation
# only the nginx daemon will need access to the socket
# gunicorn.serviceと同じユーザを入れる
SocketUser=ec2-user
# Optionally restrict the socket permissions even more.
# SocketMode=600

[Install]
WantedBy=sockets.target

ここで注意として、sockファイルとsocketファイルを混同させないようにすること。あくまでも、sockファイルはnginxとgunicornが共有するファイルである。

終わったら、gunicornのデーモンの起動と自動起動の登録をする。

gunicorn起動と自動起動設定
$ sudo systemctl start gunicorn.service
$ sudo systemctl enable gunicorn
# ちゃんとプロセスが動いていることを確認する。
$ sudo systemctl status gunicorn

プロセスが正常に起動していれば、次に進む。

nginxの設定

まず、nginxが正常に動いていて、ネットからつながることを確認する。つながらない場合は、EC2ダッシュボードのインスタンス→セキュリティからポート80が開放されているか、あるいはEC2のダッシュボード上で示されているパブリックIPアドレスやドメインが正しいか確認する。

nginx
systemctl start nginx
# コマンド実行後にEC2のパブリックIPアドレスにブラウザからアクセスして、
# nginxが動いているページが見えることを確認する。
# 確認できたら、以下のコマンドで一旦終了する。
systemctl stop nginx

確認できたら、nginxの設定をする。設定するファイルは次の通り。(mysite.confは新規作成する。また、拡張子が.confなら好きな名前でOK)

  • /etc/nginx/nginx.conf
  • /etc/nginx/conf.d/mysite.conf
nginx.conf
# 全文は多すぎるので省略。userをnginxからec2-userなどのサービスを動かすユーザーへ変える。
# 思い入れがなければ、gunicorn起動時のユーザと一致させておく
...
user ec2-user;
...
# httpブロック内などに、この一行があるか確認する。書いてあればOK
include /etc/nginx/conf.d/*.conf;
...
mysite.conf
server {
    listen  80;
    # ドメインあるいはIPアドレスを設定する
    server_name     *.*.*.*;

    # djangoの静的ファイルの置き場所を指定する。
    location /static {
        alias /home/ec2-user/mysite/static;
    }

    # djangoのadmin用の静的ファイルの置き場所を指定する。
    location /static/admin {
        alias /home/ec2-user/mysite/static/admin;
    }

    # gunicorn起動時に作成されるsockファイルの場所を指定する。
    location / {
        proxy_pass http://unix:/home/ec2-user/mysite/mysite.sock;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

}

作成したら、デーモンをstart(あるいはrestart)させる。statusでプロセスが問題なく起動していれば次に進む。

nginx
sudo systemctl start nginx
# すでに起動していた場合はsudo systemctl restart nginx
# プロセスが適切に動いていることを確認する
sudo systemctl status nginx

ブラウザから管理者画面を確認してみる

以下コマンドでnginxとgunicornのプロセスがバックグラウンドで動いていることを確認する。動いていなければエラーから各設定を修正してstartしておく。

プロセスのチェック
$ sudo systemctl status nginx
$ sudo systemctl status gunicorn

そのあと、自身のDjangoプロジェクトフォルダ(/home/ec2-user/mysite)内にmysite.sockが作成されていることを確認し、ブラウザで「自身のパブリックIPアドレスorドメイン/admin」で管理者画面を開いて見る。CSSを含めてAdminのログインページが表示されれば完了。

もしCSSが死んでいる場合はこのあたりを疑ってみる。

また、もし502エラー(Bad Gateway)が出てくるようであれば、このあたりを間違えているかも。

  • nginx.confやgunicorn.serviceのユーザーが同じではなかった(意図した権限を持ったユーザじゃなかった)
  • 同ファイルにおいてsockファイルへのパスが異なっている。

今日はここまで。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
4
Help us understand the problem. What are the problem?