概要
Djangoで作ったアプリをEC2上にデプロイしましたが、色々とハマったので備忘録として記事にまとめました。
とりあえずEC2を立てて独自ドメインで動くところまで書きます。
環境
- Macbook pro (M1 2020)
- Python 3.10.8
- Django 3.2
- EC2 (Ubuntu 22.04)
- Nginx 1.18
手順
- Djangoアプリを作る
- EC2を立てる
- ブラウザからDjangoアプリにアクセスできることを確認する
- Elastic IPでグローバルIPアドレスを固定する
- Nginxを設定する
- Route53で独自ドメインを取得
- SSL証明取得
- wsgiをデーモン化
1. Djangoアプリをつくる
ここでは簡易なDjangoプロジェクトのみ作成します。
Djangoの詳細な開発方法については書籍や他記事を参照してください。
A. 仮想環境を構築する
B. Djangoのプロジェクト・アプリを生成する
C. .envに機密情報を集約する
D. settingsファイルを分割する
E. インストールしたライブラリをrequirements.txt
に出力する
F. Githubにpushする
A. 仮想環境を構築する
Pythonのデフォルトで使えるvenvで環境を作ります。
$ python -m venv django_env
仮想環境に入ります。
$ source django_env/bin/activate
必要なライブラリをインストールします。
ここでは2つだけですが、他に必要なライブラリがあれば適宜インストールしてください。
$ pip install django==3.2
$ pip install uwsgi==2.0.21
B. Djangoのプロジェクト・アプリを生成する
Djangoプロジェクトを作り、その下にアプリを作ります。
$ django-admin startproject sample_project .
$ django-admin startapp sample_app
末尾の .
を忘れた場合はプロジェクトのディレクトリに移動すればOKです。
$ django-admin startproject sample_project
$ cd sample_project
$ django-admin startapp sample_app
C. .envに機密情報を集約する
ライブラリをインストールします。
$ pip install django-environ
.env
ファイルを manage.py
と同じで階層に作成してください。
ここにシークレットキーなどを格納します。
DBのパスワードなどもあればここに書いておきます。
環境によって変える変数もここに書いてもOKです (環境変数でもOK) 。
manage.py
と同じ階層に作成してください。
SECRET_KEY=django-insecure-xxxxxxxxxxxxxxxxxxxxxxx
DEBUG=True
ALLOWED_HOSTS=*
settings.py
の該当部分を置き換えます。
import os
import environ
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# env系の設定
env = environ.Env()
env.read_env(os.path.join(BASE_DIR, '.env'))
SECRET_KEY = env('SECRET_KEY')
DEBUG = env('DEBUG') == 'True'
ALLOWED_HOSTS = [env('ALLOWED_HOSTS')]
...
Githubにアップロードされないように .gitignore
に載せておきます。
.env
D. settingsファイルを分割する
settings
ディレクトリ作成し、以下のような構成にします。
settings.py
は common.py
に名前を変更します。
├── settings
│ ├── common.py
│ ├── development.py
│ └── production.py
common.py
に共通の設定を書いておき、 ログ設定など環境で変わる設定を development.py
や production.py
などに書きます。
環境ごとに読み込むファイルを変えればそれぞれの設定が反映されます。
from .common import *
...
from .common import *
...
settings
ディレクトリで階層を深くしたので、 BASE_DIR
の階層を修正します。
parent
を1つ増やします。
BASE_DIR = Path(__file__).resolve().parent.parent.parent
settings.py
を呼び出していたところを修正します。
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sample_project.settings.development')
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sample_project.settings.production')
E. インストールしたライブラリをrequirements.txt
に出力する
EC2上で同じライブラリをインストールできるように出力しておきます。
$ pip freeze > requirements.txt
F. Githubにpushする
ここまでの変更内容をGithubにpushしておきます。
manage.py
と同じ階層で実行します。
push先のリポジトリは事前にGithub上で作成しておきます。
$ git init
$ git remote add origin git@github.com:xxxxxxxxxxxxxxxxx.git
$ git add .
$ git commit -m 'create django project'
$ git push origin master
2. EC2を立てる
A. ubuntu22.04でEC2を立てる
B. EC2にsshで接続する
C. EC2でpyenvでPythonのバージョンを開発環境と合わせる
D. EC2上でGithubからpullする
E. EC2上に仮想環境を構築する
A. ubuntu22.04でEC2を立てる
AWSコンソールでEC2ページにアクセスします。
右上の「インスタンスを起動」からインスタンスを作成します。
サーバーをに名前をつけます。
何でもよければ雑に アプリ名-server
あたりにします。
OSを選択します。
今回はUbuntu22.04でを選択します。
インスタンスタイプを選択します。
t2.micro
は1CPU・メモリ1GBなので、デプロイするアプリの規模が大きい場合は高性能なインスタンスを選択します。
ssh接続などでEC2にアクセスするときに必要です。必ず生成しましょう。
また、再発行ができないので大切に保管しましょう。
EC2へのアクセス制御を行います。
セキュリティグループを作成する場合、sshトラフィックの許可はプルダウンから「自分のIP」を選択しておきます。
右下の「インスタンスを起動」ボタンを押下するとEC2インスタンスが立ち上がります。
B. EC2にsshで接続する
EC2のコンソールから先程作成したインスタンスを選択してください。
インスタンス概要ページの「接続」を選択すると、SSHクライアントの接続情報が表示されます。
.pem
は先程生成したキーペアです。保存した先のPathも含めてください。
$ ssh -i "/to/your/pem_path/xxxxx.pem" ubuntu@ec2-IPアドレス.ap-northeast-1.compute.amazonaws.com
C. EC2でpyenvでPythonのバージョンを開発環境と合わせる
local環境のPythonバージョンを確認します。
$ python -V
Python 3.10.8
EC2にssh接続し、必要なライブラリをインストールします。
$ sudo apt update; sudo apt install build-essential libssl-dev zlib1g-dev \
libbz2-dev libreadline-dev libsqlite3-dev curl \
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev
pyenvをpullします。
$ git clone https://github.com/pyenv/pyenv.git ~/.pyenv
pathを通します。
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
$ echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
$ echo 'eval "$(pyenv init -)"' >> ~/.bashrc
変更内容を反映します。
$ source .bashrc
pyenvがインストールできていることを確認します。
$ pyenv --version
localのPythonと同じバージョンがインストール可能かチェックします。
$ pyenv install -list
localのPythonと同じバージョンをインストールします。
pyenv install 3.10.8
指定したバージョンがインストールされていることを確認します。
$ pyenv versions
Pythonのバージョンを変更します。
$ pyenv global 3.10.8
指定したバージョンに変更されていることを確認します。
$ python -V
Python 3.10.8
D. EC2上でGithubからpullする
sshのディレクトリに移動します。
$ cd ~/.ssh
鍵を作ります。
$ ssh-keygen -t rsa
id_rsa.pub
の中身をコピーし、↓で New SSH key
として登録します。
接続確認をします。 You've successfully authenticated
と出力されればOKです。
$ ssh -T git@github.com
Djangoアプリをpullします。
$ cd ~
$ git clone git@github.com:xxxxxxxxxxxxx.git
E. EC2上に仮想環境を構築する
localで作ったときと同様に構築します。
$ python -m venv django_env
$ source django_env/bin/activate
ライブラリをインストールします。
$ pip install -r requirements.txt
3. ブラウザからDjangoアプリにアクセスできることを確認する
A. とりあえずmanage.pyで動かす
B. wsgiで動かす
A. とりあえずmanage.pyで動かす
EC2インスタンスに割り当てているセキュリティグループで8000番ポートを解放します。
インバウンドルールで以下のように設定します。
manage.py
のあるディレクトリ移動して次を実行します。
$ python manage.py runserver 0.0.0.0:8000
ブラウザからEC2インスタンスのグローバルIPアドレスで接続できるか確認します。
http://グローバルIP:8000
B. wsgiで動かす
manage.py
を停止し、同じ階層で実行します。
$ uwsgi --http :8000 --module sample_project.wsgi
同じくブラウザからグローバルIPアドレスで接続できるか確認します。
http://グローバルIP:8000
4. Elastic IPでグローバルIPアドレスを固定する
A. Elastic IPを割り当てる (= 取得する)
B. Elastic IPをEC2に関連付ける
C. EC2の .env
にElastic IPで固定したグローバルIPアドレスを追加
A. Elastic IPを割り当てる
EC2のコンソールで左のメニューから Elastic IP
を選択します。
右上オレンジの Elastic IP アドレスを割り当てる
を押下します。
設定は特に何も触れず右下の 割り当て
を押下するとElastic IPが割り当てられます。
B. Elastic IPをEC2に関連付ける
右上オレンジの Elastic IP アドレスの関連付け
を押下します。
インスタンス
の検索ウインドウから先程つくったEC2を選択します。
プライベート IP アドレス
の検索ウインドウで選択したインスタンスのIPアドレスが選択できるようになるので、それを選択します。
右下の 関連付ける
を押下するとEC2インスタンスにElastic IPがグローバルIPとして関連付けられます。
関連付けが完了するとEC2のホスト名が変わるので、ssh接続する際は改めてEC2コンソールから接続情報を確認してください。
C. EC2の .env
にElastic IPで固定したグローバルIPアドレスを追加
ALLOWED_HOSTS=固定したグローバルIPアドレス
5. Nginxを設定する
A. nginx.confを編集する
B. uwsgiファイルをコピーする
C. 静的ファイルをNginxに配置する
D. nginxを動かす
A. nginx.confを編集する
設定を書くファイルを作成します。
$ sudo touch /etc/nginx/sites-available/sample_project
中身は以下のようにします。
upstream django {
server unix:///home/ubuntu/sample_project/sample_project.sock;
}
server {
listen 80;
server_name グローバルIPアドレス;
root /usr/share/nginx/html;
location /static {
alias /usr/share/nginx/html/static;
}
location /media {
alias /usr/share/nginx/html/media;
}
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarder-Proto $scheme;
uwsgi_pass django;
include /home/ubuntu/sample_project/uwsgi_params;
}
}
シンボリックリンクを張って nginx.conf
に読み込ませます。
$ sudo ln -s /etc/nginx/sites-available/sample_project /etc/nginx/sites-enabled/sample_project
B. uwsgiファイルをコピーする
uwsgi_params
ファイルをNginxからプロジェクトのmanage.py
と同じ階層のディレクトリにコピーします。
$ sudo cp /etc/nginx/uwsgi_params /home/ubuntu/sample_project/
C. 静的ファイルをNginxに配置する
Djangoプロジェクトの静的ファイル (cssとjavascriptなど) をNginxに配置します。
配置するディレクトリを作成します。
$ sudo mkdir /usr/share/nginx/html/static
$ sudo mkdir /usr/share/nginx/html/media
ディレクトリの所有者を書き換えます。
$ sudo chown ubuntu:ubuntu /usr/share/nginx/html/static
$ sudo chown ubuntu:ubuntu /usr/share/nginx/html/media
settingsの設定を書き換えます。
from .common import *
STATIC_ROOT = BASE_DIR / "static"
...
from .common import *
STATIC_ROOT = '/usr/share/nginx/html/static'
MEDIA_ROOT = '/usr/share/nginx/html/media'
...
manage.py
の階層に行き、以下コマンドを実行します。
$ python manage.py collectstatic
静的ファイルが配置されたか確認します。
$ ls -lrt /usr/share/nginx/html/static
D. Nginxを動かす
Nginxを有効化・起動します。
$ sudo systemctl enable nginx
$ sudo systemctl start nginx
EC2インスタンスのセキュリティグループからインバウンドでhttpリクエストを有効にします。
8000番ポートを有効にしたインバウンドルールは削除しておきます。
manage.py
のあるディレクトリで以下を実行し、ブラウザからグローバルIPでアクセスできることを確認します。
$ uwsgi --socket /home/ubuntu/sample_project/sample_project.sock --module sample_project.wsgi --chmod-socket=666
6. Route53で独自ドメインを取得
A. ドメイン名を決めて購入
B. 固定したグローバルIPを紐付ける
C. ドメイン名をNginxとDjangoに反映する
A. ドメイン名を決めて購入
AWSコンソールでRoute53にアクセスします。
ドメインの登録
に取得したいドメイン名を入力し、チェックを押下します。
次ページで購入するドメイン名を確定し、以降のページで購入手続きを完了してください。
個人利用の場合、whoisで調べても個人情報の連絡先等はすべてAWSの連絡先になっていました。
筆者は購入完了後20分程度でドメインが有効化されました。
B. 固定したグローバルIPを紐付ける
Route53のダッシュボードからホストゾーンを選択します。
有効になったドメイン名を選択し、レコードを作成
を押下します。
以下を入力し、レコードを作成
を押下します。
これでドメイン名が固定したグローバルIPに紐付けられました。
C. ドメイン名をNginxとDjangoに反映する
Nginxにドメイン名を追加
server_name グローバルIPアドレス 取得したドメイン名;
...
ALLOWED_HOSTS = ['取得したドメイン名', env('ALLOWED_HOSTS')]
...
これでドメイン名でアクセスできるようになります。
http://取得したドメイン名
7. SSL証明取得
A. Let’s Encryptをインストール
B. Nginxで443へのアクセスを受け付ける
A. Let’s Encryptをインストール
nginxを停止し、Let’s Encryptをインストールします。
$ sudo systemctl stop nginx
$ sudo apt-get install letsencrypt
SSL化に必要な証明書を取得します。
$ sudo certbot certonly --standalone -d 取得したドメイン名
証明書の実行権限を書き換えます。
$ sudo chmod 700 /etc/letsencrypt/live/
B. Nginxで443へのアクセスを受け付ける
upstream django {
server unix:///home/ubuntu/sample_project/sample_project.sock;
}
server {
listen 80;
server_name ドメイン名;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name ドメイン名;
root /usr/share/nginx/html;
ssl_certificate /etc/letsencrypt/live/ドメイン名/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ドメイン名/privkey.pem;
location /static {
alias /usr/share/nginx/html/static;
}
location /media {
alias /usr/share/nginx/html/media;
}
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarder-Proto $scheme;
uwsgi_pass django;
include /home/ubuntu/sample_project/uwsgi_params;
}
}
EC2インスタンスのセキュリティグループでインバウンドルールにhttpsリクエストを追加します。
Nginxを再起動します。
$ sudo systemctl start nginx
これでhttpsでアクセスできるようになります。
https://ドメイン名
8. wsgiをデーモン化
A. wsgi.iniファイルを作成
B. バックグラウンドで動かす
A. wsgi.iniファイルを作成
いままでDjangoを実行していた↓のコマンドをファイルに転記します。
$ uwsgi --socket /home/ubuntu/sample_project/sample_project.sock --module sample_project.wsgi --chmod-socket=666
[uwsgi]
socket = /home/ubuntu/sample_project/sample_project.sock
module = sample_project.wsgi
chmod-socket = 666
動作確認をします。
$ uwsgi --ini uwsgi.ini
B. バックグラウンドで動かす
バックグラウンドで動けばssh接続を切ってもアプリが動き続けます。
$ uwsgi --ini uwsgi.ini &