AWS Amazon Linux2 で Django 2.2以降の環境を構築する

# 概要

　この記事は初心者の自分がRESTful なAPIとswiftでiPhone向けのクーポン配信サービスを開発した手順を順番に記事にしています。技術要素を1つずつ調べながら実装したため、とても遠回りな実装となっています。

前回の [Djangoで画像を配信できるwebAPIを作る](https://qiita.com/Ajyarimochi/items/b7278c357b2ffca183b5) で実装したAPIを外部のサーバへデプロイし、パグリックな環境からAPIを利用できる状態にします。

作業は

1. クラウドのインスタンスを立てて必要なソフトやパッケージをインストール
2. RestAPIのアプリをデプロイ

になります。この記事では「1. クラウドのインスタンスを立てて必要なソフトやパッケージをインストール」を纏めます。

### 構成

　今回はAmazonのEC2を使うことにしました。OSはAmazon Linux 2 を選びました。構築に失敗したり環境が合わなかったらインスタンスごと作り直せば良いと考えたので、docker等のコンテナは使わず、OSに直接インストールをする事にしました。

djangoで開発したwebAPIは nginx、uwsgi、djangoの構成で動かします。



**構成図**
　こんな感じになるイメージです

<img width="980" alt="system-stack.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526594/6b4e99fc-e9a6-c280-1edb-91e7e1375754.png">




### 参考

[Amazon Linux 2でSQLite3を最新バージョンにする](https://qiita.com/kai_kou/items/c18b68a7916251231f6d)
[Django2.2で開発サーバー起動時にSQLite3のエラーが出た場合の対応](https://qiita.com/rururu_kenken/items/8202b30b50e3bfa75821)
[Django + uWSGI + nginx (uWSGIチュートリアルの和訳)](https://qiita.com/Ningensei848/items/e91fbeef5f03685a7a0b)
[【django】SQLiteバージョンエラー](https://teratail.com/questions/194159)
[Linuxやってみる！ (6) ldconfigでライブラリパスを更新](https://www.dogrow.net/linux/blog6/)
[Amazon Linux 2 ExtrasレポジトリのNginx、トピック名が「nginx1」になりました](https://dev.classmethod.jp/cloud/aws/al2-nginx1-new-extras-topic/)


### 環境

Amazon Linux 2
Python 3.7.4
Django 3.0.3
SQLite 3.31.1


# 手順

* AWSのアカウントを作成して（無料枠で）インスタンスを作成
* 必要なソフトをインストール
 * python 3
 * python のパッケージ 
  * django
  * Django-filter
  * djangorestframework
  * djangorestframework-jwt
  * wheel
  * uwsgi
 * Development Tools
 * python3-devel
 * git
 * nginx
* SQLite3 のアップデート


## AWSのアカウントを作成して（無料枠で）インスタンスを作成

　クラウドサービスでよく使われている AWS、Azure、GCP の中から、今回は自分にとって操作経験があり且つその経験がまだ中途半端なAWSを選びました。無料枠を使ってAPIを公開させようと思います。

　AWSのWebページにアクセスしてアカウントを登録するか、Googleで 「aws 無料枠」と入力すると　“AWS クラウド無料利用枠 | AWS - Amazon Web Services”　というリンクが出てくると思います（2020/1/5 時点）ので、そのページの「まずは無料で始める」からアカウントを登録をします。

　アカウントの登録が完了したらインスタンスを作成します。自分はOSをAmazon Linux、インスタンスタイプは無料枠のt2.micro を選びました。インスタンスの作り方はAWSのチュートリアルがわかりやすいので、そちらをご覧ください。途中で作成するキーペアのキーはアクセス時に使うことになるので、保存場所に気をつけてください。


　インスタンスが一つ立ち上がりました。用途が分かり易いように api_server という名前を付けました。
<img width="917" alt="instance-start.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526594/cb861346-7cda-bd0e-e751-cf612d513e7b.png">



　インスタンスが立ち上がったのでSSHでの接続を試します。今後インスタンスに設定を加えたり必要なソフトをインストールするのにSSHでの操作が必要になるからです。（デフォルトでSSH接続ができるセキュリティ設定になっています。）

　Macの場合はターミナルを使ってSSH接続ができます。Windowsの場合はエディタのコンソールから操作するか、TeraTarmのようなツールを使います。

　ここからはキーペアによる認証で接続する場合の方法を記載します。Macからキーペアでアクセスする場合、事前にキー（ XXX.pem ）のアクセス権限を変更しておかないとエラーになりました。所有者のみ参照できるように権限を変更します。下記のコマンドで変更します。（AWSのチュートリアルにも出てきます）

```
chmod 400 [キー（ XXX.pem ）を格納したフォルダのパス]/[キーのファイル名].pem
```

SSH接続します。Macの場合コマンドはこちらです。`ec2-user` の部分はログインユーザの指定で、デフォルトは `ec2-user`です。

```
ssh -i [インスタンスを作成する際に作ったキーペアのキー（ XXX.pem ）の絶対パス] ec2-user@[インスタンスのグローバルIP]
```

自分の場合はこんなコマンドになりました。

```
ssh -i ~/xxxxxx/yyyyy/MyKeyPair.pem ec2-user@zz.zzz.zz.zzz
```

以下のように接続を続けるか確認される事があるので、`yes` を入力してEnterします。

```
The authenticity of host '13.230.91.101 (13.230.91.101)' can't be established.
ECDSA key fingerprint is SHA256:ldQ3z2WraZy3xCGfL1taOnixN9baFpgnN3qYXfZLah0.
Are you sure you want to continue connecting (yes/no)? yes
```

こちらの表示が出たら接続が成功です。

```
       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
8 package(s) needed for security, out of 17 available
Run "sudo yum update" to apply all updates.
```


## 必要なソフトをインストール

　ここからはインスタンスにSSH接続した状態でコマンドを使ってインストール作業を進めます。また開発したローカル環境とできる限り同じバージョンをインストールします。

　最初にyum（パッケージの統合管理システム）をアップデートしておきます。

```
$ sudo yum update
```

　途中で `Is this ok [y/d/N]:` と聞かれたら`y`を入力してEnter。なおyumの後ろに`-y`を付けると自動的にyesと答えてくれるので聞かれなくなります。この後、yumでインストールする際に頻繁に出てきます。


### Python3 のインストール

　Amazon Linux 2 はデフォルトで Python がインストールされていますが、2系なので 3系のPythonを別途インストールします。なお、インストールされているpythonのバージョンを確認するコマンドは`$ Python -V`です。

　インストールする際のパッケージ名（バージョンの指定）がMacOSやCentOSの場合と異なったので、最初に少し困りました。今回のAmazon Linux 2 のケースでは `Python3` になります。

Python 3 のインストール。

```
$ sudo yum install python3
```

###  Development Tools と python3のdevパッケージのインストール

　uwsgiをインストールする際に必要なのでインストールしておきます。なお、python3のdevパッケージのパッケージ名は、CentOSやUbuntuでは `python 3.x-dev`のようになりますが、Amazon Linux 2では`python3-devel`となります。ネットの情報を頼りにインストールのコマンドを実行したらパッケージを引けず、小ハマりしました。パッケージ名を確認してからインストールを実行する習慣が大切です。

**Development Tools のインストール**

```
$ sudo yum groupinstall "Development Tools"
```

**python3のdevパッケージのインストール**

```
$ sudo yum install python3-devel
```


### Python3 のパッケージのインストール

　djangoなど必要なパッケージをpythonの環境にインストールします。Python3のインストールが成功していれば pip3 コマンドが使えます。現在インストールされているパッケージを確認します。まだ何もインストールしていないので何も表示されないはずです。

```
$ pip3 freeze
```

**djangoのインストール**

```
$ sudo pip3 install django
```

`$ pip3 freeze`を実行するとdjangoも含めいくつかのパッケージがインストールされています。

```
$ pip3 freeze
asgiref==3.2.3
Django==3.0.3
pytz==2019.3
sqlparse==0.3.0
```

この調子で他のパッケージもインストールしていきます。

**Django Filter のインストール**

```
$ sudo pip3 install django-filter
```

**Django Restframework のインストール**

```
$ sudo pip3 install djangorestframework
```

**Django Restframework JWT のインストール**

　Json Web Token（JWT）を使ったAPIの認証機能を実装している場合に必要です。

```
$ sudo pip3 install djangorestframework-jwt
```

**wheel のインストール**

　uwisgi のインストールに必要です。

```
$ sudo pip3 install wheel
```

**uwisgi のインストール**

```
$ sudo pip3 install uwsgi
```


### git のインストール

　ソースコードからデプロイする際に必要です。

```
$ sudo yum install git
```


### nginx のインストール

　Amazon Linux 2 の場合 nginx は yum では無く`amazon-linux-extras`でインストールします。なお、2020年2月現在でトピック名の指定は「nginx1」だそうです。（以前は「nginx1.12」）

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

　インストールが完了したら起動してみます。

```
# nginxの起動
$ systemctl start nginx

# active (running) と表示されていれば起動しています
● nginx.service - The nginx HTTP and reverse proxy server
   Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; vendor preset: disabled)
   Active: active (running) 
```


　nginxの操作を纏めました。（CentOS7と同じです）

```
# 起動
$ systemctl start nginx

# 再起動
$ systemctl restart nginx

# 停止
$ systemctl stop nginx
```



## SQLite3 のアップデート

 Djangoを使うのにSQLite3が必要になります。
python3をインストールした際にSQLite3もインストールされるようですが、デフォルトでインストールされるSQLite3のバージョンが古く、Django2.2以降ではサーバ起動時などにバージョンチェックでエラーとなってしまいます。そこでSQLite3のアップデートが必要です。

バージョンは`$ sqlite3 ―version`で確認できます。デフォルトは `3.7.17` でした。

```エラー時のスタックトレース（抜粋）
# SQLite 3.8.3 より新しいバージョンを要求される

  File "/home/ec2-user/.local/share/virtualenvs/ami-folvMrTj/lib/python3.7/site-packages/django/db/backends/sqlite3/base.py", line 65, in check_sqlite_version
    raise ImproperlyConfigured('SQLite 3.8.3 or later is required (found %s).' % Database.sqlite_version)
django.core.exceptions.ImproperlyConfigured: SQLite 3.8.3 or later is required (found 3.7.17).

```

2020年1月時点でyum でのアップデートに対応していないようで、ファイルをダウンロードするところから手動でアップデートが必要です。

### SQLite3 のファイルをダウンロードして解答する
 
SQLiteの公式サイト（https://www.sqlite.org/download.html）で最新版を確認して、そのバージョンのgzファイルのパスを取得します。2020年1月時点の最新版は `https://www.sqlite.org/2020/sqlite-autoconf-3310100.tar.gz` でした。

```
# ファイルをダウンロード

$ wget https://www.sqlite.org/2020/sqlite-autoconf-3310100.tar.gz


# ファイルを解凍

$ tar xvfz sqlite-autoconf-3310100.tar.gz 



# ダウンロードしたファイルと、解凍したファイルが入ったフォルダができています。

$ ls
sqlite-autoconf-3310100  sqlite-autoconf-3310100.tar.gz

```


### ビルドをしてインストールする

```
# 解凍したフォルダへ移動します。
$ cd sqlite-autoconf-3310100/

# ビルドしてインストール
# ./configure　→　make　→　make install の流れは定番らしいです　
$ ./configure --prefix=/usr/local
$ sudo make
$ sudo make install
```

### 新しくインストールしたSQLite3のパスを通す

　最新版のインストールは出来ましたがパスが通っていないので、実行時に古いバージョンのSQLite3の方が参照/実行されてしまいます。不要なファイルを削除したり新しいSQLite3にパスを通す作業をします。

```
# SQLite3のインストールに使ったファイルを削除
$ rm sqlite-autoconf-3310100.tar.gz 
$ rm -rf sqlite-autoconf-3310100
```

　次にSQLiteがインストールされたか確認します。

```
# sqlite3で検索
$ sudo find / -name sqlite3
/usr/bin/sqlite3
/usr/lib64/python2.7/sqlite3
/usr/lib64/python3.7/sqlite3
/usr/local/bin/sqlite3
/usr/local/lib64/python3.7/site-packages/django/db/backends/sqlite3

# /usr/lib64/python2.7/sqlite3、/usr/lib64/python3.7/sqlite3、～django/db/backends/sqlite3　はディレクトリ
# /usr/bin/sqlite3 と /usr/local/bin/sqlite3　のバージョンを確認
$ /usr/bin/sqlite3 --version
3.7.17 2013-05-20
$ /usr/local/bin/sqlite3 --version
3.31.1 
```
　`/usr/local/bin/sqlite3`の方が新しくインストールしたSQLite3 なので、古い`/usr/bin/sqlite3`が参照されないようにします。

```
# 古い方のsqlite3のファイル名を変更
$ sudo mv /usr/bin/sqlite3 /usr/bin/sqlite3_old

# ディレクトリに新しいsqlite3へのシンボリックリンクを作成
$ sudo ln -s /usr/local/bin/sqlite3 /usr/bin/sqlite3

# sqlite3のバージョンを参照。新しい方が参照される事を確認
$ sqlite3 --version
3.31.1
```

　次に、共通ライブラリ(`ld.so.conf`)へパスを通します。

```
# ld.so.conf があるディレクトリへ移動
$ cd /etc

# ls か ll でld.so.confファイルの存在を確認
$ ls

# ld.so.confをVimで開いてパスを追加
# 追加するパスは　/usr/local/lib
$ sudo vi ld.so.conf

# 編集を反映
$ sudo ldconfig

# パスが通ったか確認する
# /usr/local/lib/～のパスが表示されれば通っている
$ ldconfig -p | grep sqlite
	libsqlite3.so.0 (libc6,x86-64) => /usr/local/lib/libsqlite3.so.0
	libsqlite3.so.0 (libc6,x86-64) => /lib64/libsqlite3.so.0
	libsqlite3.so (libc6,x86-64) => /usr/local/lib/libsqlite3.so
```

　さて、参考にさせて頂いた方の情報によるとこれでSQLite3のアップデートは完了のはずですが、何故かdjangoのサーバ起動時のバージョンチェックのエラーが消えず、3日ほどハマりました。

　アップデートをして共通ライブラリにパスも通したはずなのに、バージョン3.7.17 が参照されるのです。

　いろいろと試した結果（インスタンスごと作り直しました）、元から存在したライブラリ（`/lib64/libsqlite3.so.0`）が優先的に読み込まれているのが原因らしいと分かりました。

```再掲：エラー時のスタックトレース（抜粋）
# SQLite 3.8.3 より新しいバージョンを要求される

  File "/home/ec2-user/.local/share/virtualenvs/ami-folvMrTj/lib/python3.7/site-packages/django/db/backends/sqlite3/base.py", line 65, in check_sqlite_version
    raise ImproperlyConfigured('SQLite 3.8.3 or later is required (found %s).' % Database.sqlite_version)
django.core.exceptions.ImproperlyConfigured: SQLite 3.8.3 or later is required (found 3.7.17).

```

　そこで、`/lib64/libsqlite3.so.0`を削除すると、問題無く起動しました。
（他の箇所に影響を及ぼす可能性があるため自己責任でお願いいたします）

```
# 該当するファイルを探す
$ cd /lib64
$ ls

# リンクとディレクトリを削除
$ sudo rm libsqlite3.so.0
$ sudo rm -rf libsqlite3.so.0.8.6

# 削除を反映
$ sudo ldconfig

# 古いライブラリが参照されないか確認
$ ldconfig -p | grep sqlite
	libsqlite3.so.0 (libc6,x86-64) => /usr/local/lib/libsqlite3.so.0
	libsqlite3.so (libc6,x86-64) => /usr/local/lib/libsqlite3.so
```


ここまでで環境の準備は出来ました。

次はnginxのインストールと、APIのデプロイをします。