💨

EC2でwebアプリを作成したメモ

2023/02/04に公開

概要

AWS EC2+Nginx+gunicorn+Django+PostgreSQLでシンプルなwebアプリを作成したログ.
以下の内容は,すでにAWS EC2へSSH接続していることを前提としている.

環境構築

今回の記事で使用するパッケージなどのインストール方法をまとめて記載する.

$ sudo yum install git
$ sudo yum install openssl-devel
$ sudo yum install python-psycopg2

$ sudo amazon-linux-extras install nginx1
$ sudo amazon-linux-extras install postgresql14

Pythonのversionを管理するために,pyenvをインストールする.

$ git clone https://github.com/pyenv/pyenv.git ~/.pyenv

$ echo '# pyenv' >> ~/.bashrc
$ echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
$ echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
$ echo 'eval "$(pyenv init --path)"' >> ~/.bashrc

$ source .bashrc

$ pyenv -v
pyenv 2.3.6-15-g13d85686

プロジェクトディレクトリを作成する.
Pythonバージョンを3.9.13とし,venvで仮想環境を作成する.

$ mkdir ~/django_app
$ cd ~/django_app/

$ pyenv install 3.9.13

$ python -V
Python 3.9.13

# 仮想環境`venv`を作成する
$ python -m venv venv

# 仮想環境に入る
source activate venv/bin/activate

# pipを最新バージョンに更新する
pip install --upgrade pip

pipで必要なpythonモジュールをインストールする.

# Djano関連
$ pip install django
$ pip install django-environ
$ pip install dj-database-url
$ pip install djangorestframework
$ pip install mojimoji
# PostgreSQL関連
$ pip install -U setuptools
$ pip install psycopg2
# Gunicorn
$ pip install gunicorn

Webサーバ構築

WebサーバとしてNginxを利用する.

# バージョン確認
$ sudo nginx -v
nginx version: nginx/1.22.0

Nginxの設定

Nginxの初期設定ファイルのバックアップを取る

$ sudo cp -a /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup

Nginxの通信データをhttp://127.0.0.1:8000へ送るように設定する.
これにより,サーバの80番ポートに来たアクセスをDjango側へ転送できる.

$ sudo vim /etc/nginx/nginx.conf

以下のように記述.

~
http{
  ~
  server{
    ~
    location / {
      # `http://xx.xx.xx.xx/`へ来たアクセスをDjango側へ転送する.
      proxy_set_header Host $http_host;                                   # host name
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;        # source address
      proxy_set_header X_Forwarded-Proto $scheme;                         # URL scheme
      # localhostの8000番ポートへ転送する.
      proxy_pass http://127.0.0.1:8000;
    }
    # ここまで
    ~
  }
  ~
}
~

Nginxを起動する.

$ sudo systemctl start nginx

インスタンス起動時にNginxも自動で起動させる.

$ sudo systemctl enable nginx
Created symlink from /etc/systemd/system/multi-user.target.wants/nginx.service to /usr/lib/systemd/system/nginx.service.

サービスステータス表示する.

$ systemctl status nginx
● nginx.service - The nginx HTTP and reverse proxy server
   Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; vendor preset: disabled)
   Active: active (running) since {DayOfWeek} {Year}-{Month}-{Day} {Hour}:{Minute}:{Seconds} UTC; 28s ago
 Main PID: 26497 (nginx)
   CGroup: /system.slice/nginx.service
           ├─26497 nginx: master process /usr/sbin/nginx
           ├─26498 nginx: worker process
           └─26499 nginx: worker process

PostgreSQLの設定

Djangoアプリケーションで利用するためのDBを構築する.

初期化

まず,データベースサーバを初期化する.

$ sudo /sbin/service postgresql initdb
Hint: the preferred way to do this is now "postgresql-setup initdb"
Initializing database ... OK

サービス起動設定

今回の記事では,サーバ内でDBサーバを構築する方法を取った.
データベースサーバを起動する.

$ sudo /sbin/service postgresql start

$ sudo systemctl status postgresql
● postgresql.service - PostgreSQL database server
   Loaded: loaded (/usr/lib/systemd/system/postgresql.service; disabled; vendor preset: disabled)
   Active: active (running) since ** **-**-** **:**:** UTC; **min **s ago
  Process: 9585 ExecStart=/usr/bin/pg_ctl start -D ${PGDATA} -s -o -p ${PGPORT} -w -t 300 (code=exited, status=0/SUCCESS)
  Process: 9580 ExecStartPre=/usr/bin/postgresql-check-db-dir ${PGDATA} (code=exited, status=0/SUCCESS)
 Main PID: 9589 (postgres)
   CGroup: /system.slice/postgresql.service
           ├─9589 /usr/bin/postgres -D /var/lib/pgsql/data -p 5432
           ├─9590 postgres: logger process   
           ├─9592 postgres: checkpointer process   
           ├─9593 postgres: writer process   
           ├─9594 postgres: wal writer process   
           ├─9595 postgres: autovacuum launcher process   
           └─9596 postgres: stats collector process   

自動起動設定をする.

$ sudo systemctl enable postgresql
Created symlink from /etc/systemd/system/multi-user.target.wants/postgresql.service to /usr/lib/systemd/system/postgresql.service.

DB作成

Djangoアプリが利用するDBを作成する.

$ sudo -u postgres psql
psql (9.2.24)
Type "help" for help.

postgres=# 

以下のように入力し,DBを作成した.

-- 新規にDBを作成する.
postgres=# CREATE DATABASE djangodb;
CREATE DATABASE
-- ユーザ名とパスワードを設定する.
postgres=# CREATE USER django WITH PASSWORD '****';
CREATE ROLE
-- 文字コードを`UTF-8`にする.
postgres=# ALTER ROLE django SET client_encoding TO 'utf8';
ALTER ROLE
-- トランザクション分離レベルを設定する.
postgres=# ALTER ROLE django SET default_transaction_isolation TO 'read committed';
ALTER ROLE
-- タイムゾーンを東京/日本とする.
postgres=# ALTER ROLE django SET timezone TO 'UTC+9';
ALTER ROLE
-- `django`ユーザにDBの全ての権限を付与する.
postgres=# GRANT ALL PRIVILEGES ON DATABASE djangodb TO django;
GRANT
-- psqlを修了する.
postgres-# \q

postgresqlの設定ファイルのバックアップを取り,認証方法を変更する.

$ sudo cp /var/lib/pgsql/data/pg_hba.conf /var/lib/pgsql/data/pg_hba.conf.backup

$ sudo vim /var/lib/pgsql/data/pg_hba.conf

変更箇所は以下の通り.認証方法をidentからmd5へ変更する.

# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     peer
# IPv4 local connections:
host    all             all             127.0.0.1/32            md5  # <-- here!
# IPv6 local connections:
host    all             all             ::1/128                 md5  # <-- here!
# Allow replication connections from localhost, by a user with the
# replication privilege.
local   replication     postgres                                peer
host    replication     postgres        127.0.0.1/32            ident
host    replication     postgres        ::1/128                 ident

設定ファイルの変更を適用するため,serviceを再起動する.

$ sudo systemctl restart postgresql

簡易的なWebアプリ作成

Djangoプロジェクトの作成

Djanoでプロジェクトを作成する.

$ cd ~/django_app/

$ django-admin startproject website .

~/django_app/website/settings.pyを修正する.

# DBへのパス
DATABASE_URL=postgres://django:{password}@localhost:/djangodb
# アクセスを許可するホスト
ALLOWED_HOSTS={Your IP},localhost,127.0.0.1
# URLの末尾に`/`がない時,`/`を付けたURLへリダイレクトしない
APPEND_SLASH=False
# タイムゾーンを日本に設定
TIME_ZONE = 'Asia/Tokyo'
# 日本語
LANGUAGE_CODE = 'ja'
# 静的ファイルが集約されるパス
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

gunicornを起動

  • gunicornを起動する.
    • 127.0.0.1:8000をバインドし,Nginxからのデータを受け取る.
    • Djangoプロジェクトのwsgi設定ファイル~/django_app/website/wsgi.pyを指定する.
    • バックグラウンドで実行する.
# Djangoプロジェクトのrootに移動する
$ cd ~/django_app/

# 仮想環境に入る(既に入っていたら必要なし)
$ source venv/bin/activate

$ gunicorn --bind 127.0.0.1:8000 website.wsgi -D

簡単なテスト

現在,EC2のIPアドレスへHTTPリクエストを送信すると,ポート80でリクエストを受信したNginxがローカルホストのポート8000へデータを転送し,gunicornが転送されてきたデータをdjango_appのwsgiに紐づけて,Djangoアプリがリクエストを処理する,という仕組みになっている.

試しに,別のホストからcurlでEC2サーバにHTTP GET リクエストを送信してみる.

$ curl http://{Your IP}/

レスポンスとして,Djangoのロケットのページを見ることができる.

簡単なAPIの実装

今回は,DBへのデータの登録と呼び出し,削除機能を実装する.

まず,API実装のためのrest_frameworkをプロジェクトへ追加する.~/django_app/website/settings.pyを編集する.

INSTALLED_APPS = [
  ...
  'rest_framework', #new
]

DB操作を実現するためのアプリを作成する.

  • アプリ名:crud_app
$ python manage.py startapp crud_app

新規にアプリを作成したため,~/django_app/website/settings.pyへ追記する.

INSTALLED_APPS = [
   ...
    'rest_framework',
    'crud_app', #new
]

今回は,以下に示す簡易的なテーブルを作成する.

テストテーブル
-----------------
名前      (char)
数量      (int)
作成日時   (date)

~/django_app/crud_app/models.pyを以下のように記載し,DBモデルを作成する.

from django.db import models

class DjangoModel(models.Model):
    name    = models.CharField("Name", max_length=240)  # 名前
    amount  = models.IntegerField("Amount", default=1)  # 数量
    created = models.DateField(auto_now_add=True)

    def __str__(self):
        return self.name

プロジェクトのrootで,マイグレーションを適用し,dbカラムを作成する.

$ python manage.py makemigrations

$ python manage.py migrate

~/django_app/crud_app/views.pyを編集し,リクエストに応じてDBを操作する.

from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
from django.http.response import JsonResponse
import json

from .models import DjangoModel


@csrf_exempt
def control_data(request):
    """データの新規作成,確認,削除を行うインターフェース"""

    # 確認
    if request.method == 'GET':
        # `name`で昇順にソートし,全件取得
        models = DjangoModel.objects.order_by('name')
        # レスポンス作成
        all_models_dict = {}
        for model in models:
            all_models_dict[model.name] = model.amount
        return JsonResponse(all_models_dict)

    # 新規作成
    if request.method == 'POST':
        # HTTPリクエストbodyのデータをjson形式で取得する
        data = json.loads(request.body)
        # `amount`がinputになければ,デフォルト値を代入する
        if not 'amount' in data:
            data['amount'] = 1
        # 新規作成
        model = DjangoModel(name=data['name'], amount=data['amount'])
        model.save()
        # レスポンス作成
        response = HttpResponse('created!')
        return response

    # 全て削除
    elif request.method == 'DELETE':
        # レコード削除
        DjangoModel.objects.all().delete()
        # レスポンス作成
        response = HttpResponse('deleted!')
        return response

    # 未実装のHTTP methodへの処理
    else: 
        return HttpResponse('Not Implemented.')

特定のURLにアクセスしたとき,呼び出す処理を記載する.
~/django_app/crud_app/urls.pyを作成する.

from django.urls import include, path
from .views import control_data

urlpatterns = [
    path('', control_data, name='control-data'),
]

rootのurls.pyからdjango_appアプリのurls.pyへ接続するようにする.
~/django_app/website/urls.pyを編集する.

from django.urls import path, include

urlpatterns = [
    path('/', include('django_app.urls')),
]

以上により,簡易なAPIの実装を終えた.
Djangoアプリの機能がいくつか変更・追記されたので,gunicornを再度起動し,変更を適用する.

$ pkill gunicorn

$ gunicorn --bind 127.0.0.1:8000 website.wsgi -D

APIのテスト

実装したAPIが正常に動作確認するために,curlでテストできる.

# コマンド例

## 新規登録
$ curl -d '{"name": "computer", "amount": 3}' -H 'Content-Type: application/json' http://{Your IP}/
created!

## 確認
$ curl http://{Your IP}/
{"computer":3}

## 削除
$ curl -X DELETE http://{Your IP}/
deleted!

## 確認
$ curl http://{Your IP}/
{}

改善点など

この記事の設定では,nginxとDjangoはHTTPで通信するが,UNIXソケットを使用するようにすると高速な通信が期待できる.おそらく,多くの同時アクセスを想定する場合は,UNIXソケットを使用した方が良いという予想.
他にも改善できる設定は存在すると思うので,勉強中.

参考

  1. Request and response objects, Documentation, django (https://docs.djangoproject.com/en/4.1/ref/request-response/#httpresponse-objects)
  2. リクエストとレスポンスのオブジェクト, Django 4.0.6 ドキュメント (https://man.plustar.jp/django/ref/request-response.html)

Discussion