10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-09-28

はじめに

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ファイルへのパスが異なっている。

今日はここまで。

10
7
0

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
  3. You can use dark theme
What you can do with signing up
10
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?