[02] SQLite3 を PostgreSQL へ置き換える

# はじめに

Docker (docker-compose)と Linux に標準で搭載されているシェルを組み合せて、
下記 [概要](#概要)に挙げた Django 環境を 1コマンドで即座に立ち上げるためのコードとその解説です.

本記事の内容は、Zenn で[書籍](https://zenn.dev/robozushi10/books/3d32504b4b5707)として販売している内容と同一です.
Zenn でも書籍の内容は全て無料公開しています.

次の順でコードを作成してください.
動作するコードが作成できるように可能な限り注意を払って執筆しました.

・[[01] デフォルト環境 runserver + SQLite3 の構築](https://qiita.com/robozushi10/items/fc8e40ad7ecc29131ea3/) 
・本記事の内容

# 概要

今回は下図・下表の環境を構築する. (前章からの変更点は🔥である)
具体的には次の通り.
**・SQLite3 ではなく、PostgreSQL を使うようにする**

## 概念図

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246056/ec667920-bce0-6029-8ef1-b09c24c38fae.png)


## コンテナ内の構成

|項目|値|補足|
|:--|:--|:--|
|Python|バージョン3.8.3|`python:3.8.3-slim-buster` を使う|
|Django|バージョン3.0.7||
|アプリサーバ|runserver|8000番ポートを使用する|
|データベース|PostgreSQL 12.0🔥|ポートは次の通り<br/>・ホスト側「53432」<br/>・コンテナ側「5432」<br/>管理者アカウントは次の通り<br/>・アカウント「`admin`」<br/>・パスワード「`admin`」|
|settings.py|「`DATABASE`」の設定を PostgreSQL に変える. 🔥|ホスト上の `./web/assets/settings.py` が、<br/>コンテナ内に `/usr/src/app/config/settings.py` として配置される.|
|Django admin ページ||管理者アカウントは次の通り<br/>・アカウント「`admin`」<br/>・パスワード「`admin`」|


# セットアップコード

## 全文

前章で作成したコードに対して、本記事の内容を適用してください.

:::message alert
web/assets/settings.py より、「DEBUG = True」のみセットアップできることを確認している.
:::

## ファイル構成

上記 GitHub からコードを取得してきた直後の構成である.
前章から変更が生じたファイルには🏷️を、新規作成については🆕を付与している.

```bash
$ tree . --charset=c -a
.
|-- db 🆕
|   `-- Dockerfile 🆕
|-- docker-compose.yml 🏷️
|-- setupapp.sh 🏷️........... セットアップスクリプト
`-- web
    |-- Dockerfile 🏷️
    `-- assets .............. コンテナへコピーされるデータ
        |-- entrypoint.sh 🏷️
        |-- requirements.txt 🏷️
        `-- settings.py 🏷️
```

## 解説

### db/Dockerfile

新規作成.
特筆すべき事項なし.

```dockerfile
FROM postgres:12-alpine
ENV LANG ja_JP.utf8

## プロキシサーバを使う場合
# ENV http_proxy="http://proxy.co.jp:8080"	
# ENV https_proxy="http://proxy.co.jp:8080"

# 時刻を日本に合わせる
RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
```

### setupapp.sh

前章からの変化点は次の通り.
・PostgreSQL 用の永続化ボリュームとして次を作成する.
&nbsp;&nbsp;&nbsp;./db/data
&nbsp;&nbsp;&nbsp;./db/init

・PostgreSQL を使用するよう記述した settings.py をコンテナ内に配置する

以下、前回からの差分である.

```diff
--- ./setupapp.sh.ORIG
+++ ./setupapp.sh
@@ -2,6 +2,8 @@
@@ -2,6 +2,8 @@
 
 # 旧データを削除する
 sudo rm -rf web/app
+sudo rm -rf db/data
+sudo rm -rf db/init
 
 # docker-compose.yml で書かれたサービスのコンテナ、イメージ、ネットワークを削除する
 docker-compose down --rmi all --volumes
@@ -11,9 +13,13 @@
 
 # 必須ディレクトリを作成する
 sudo mkdir -p web/app
+sudo mkdir -p db/data
+sudo mkdir -p db/init
 
 # entrypoint.sh を配置する
 sudo cp ./web/assets/entrypoint.sh ./web/app/
+# settings.py をコンテナ内に .settings.py として配置する
+sudo cp ./web/assets/settings.py ./web/app/.settings.py
 
 # Dockerイメージビルドを行う. 
 docker-compose build
@@ -31,6 +37,9 @@
 }
 
 # コンテナ内でのマイグレーション終了を待つ.
+# なお、ここで 待機時間を取らないと次のエラーが出現してしまう.
+#「django.db.utils.IntegrityError: duplicate key value violates unique constraint "pg_type_typname_nsp_index"」
+# [参考] https://www.odin.hyork.net/write/write0206.html
 sleep 4
 
 # 「Django管理サイト」の管理者アカウントを作成する. (ID「admin」, PW「admin」とする)
```


### docker-compose.yml

前章からの変化点は次の通り.
・ポート (ホスト側 : コンテナ側) = (53432 : 5432) とする.
・PostgreSQL の管理者アカウント「`admin`」、パスワード「`admin`」とする

```diff
--- docker-compose.yml.ORIG
+++ docker-compose.yml
@@ -11,3 +11,19 @@
       - ./web/app/:/usr/src/app/
     ports:
       - 8000:8000
+    depends_on:
+      - db
+  db:
+    build: ./db
+    image: postgres:12
+    container_name: mypostgres12
+    volumes:
+        - ./db/data:/data
+        - ./db/init:/docker-entrypoint-initdb.d/
+    ports:
+        - "53432:5432"
+    environment:
+        POSTGRES_DB: 'django307'
+        POSTGRES_USER: 'admin'
+        POSTGRES_PASSWORD: 'admin'
+        PGDATA: '/data'
```

### web/Dockerfile

前章からの変化点は次の通り.
・build-essential は requirements.txt に追加した psycopg2 のインストールで必要である
・libpq-dev は Python から PostgreSQL を操作するために必要である

```diff
--- web/Dockerfile.ORIG
+++ web/Dockerfile
@@ -11,6 +11,10 @@
 ENV PYTHONDONTWRITEBYTECODE 1
 ENV PYTHONUNBUFFERED 1
  
+RUN apt-get update
+RUN apt-get install -y build-essential
+RUN apt-get install -y libpq-dev
+
 # 時刻を日本に合わせる
 RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
```

### web/assets/entrypoint.sh

前章からの変化点は次の通り.
・事前に PostgreSQL の設定を記述しておいた settings.py を、コンテナ内の /usr/src/app/config/settings.py として配置する

```diff
--- web/assets/entrypoint.sh.ORIG
+++ web/assets/entrypoint.sh
@@ -4,6 +4,8 @@
 test ! -f manage.py && { 
   # プロジェクトを作成する (config/ と manage.py が作成される)
   django-admin.py startproject config .
+  # 事前に用意しておいた PostgreSQL 対応の settings.py に差し替える
+  cp -f .settings.py config/settings.py
   # マイグレーションを実施する
   python manage.py makemigrations
   python manage.py migrate auth
```

### web/assets/requirements.txt

前章からの変化点は次の通り.
・Python から PostgreSQL を操作するために psycopg2 と psycopg2-binary の 2パッケージをインストールする

```diff
--- web/assets/requirements.txt.ORIG
+++ web/assets/requirements.txt
@@ -2,3 +2,5 @@
 django-extensions==2.2.9
 django-filter==2.2.0
 djangorestframework==3.11.0
+psycopg2==2.8.5
+psycopg2-binary==2.8.5
```

### web/assets/settings.py

前章からの変化点は以下である.
なお、前章では Django が生成するデフォルトの settings.py を使用していた.
・PostgreSQL の記述を追加した.
・PostgreSQL の管理者アカウントとパスワードはともに「`admin`」としている.
・使用する Database 名は「`django307`」としている.

```diff
--- ./web/assets/settings.py.ORIG
+++ ./web/assets/settings.py
@@ -75,8 +75,12 @@
 
 DATABASES = {
     'default': {
-        'ENGINE': 'django.db.backends.sqlite3',
-        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+        'ENGINE': 'django.db.backends.postgresql',
+        'NAME': 'django307',
+        'USER': 'admin',
+        'PASSWORD': 'admin',
+        'HOST': 'db',
+        'PORT': 5432,
     }
 }
```

# セットアップ方法

次の通りコマンドを実行する.

```
$ ./setupapp.sh
```

# セットアップ後

## ファイル構成

```
.
|-- db
|   |-- Dockerfile
|   |-- data [error opening dir]
|   `-- init
|-- docker-compose.yml
|-- setupapp.sh
`-- web
    |-- Dockerfile
    |-- app
    |   |-- config
    |   |   |-- __init__.py
    |   |   |-- asgi.py
    |   |   |-- settings.py
    |   |   |-- urls.py
    |   |   `-- wsgi.py
    |   |-- entrypoint.sh
    |   `-- manage.py
    `-- assets
        |-- entrypoint.sh
	|-- requirements.txt
        `-- settings.py

```

## 動作確認方法

次の点を確認すれば良い.

✔ Dockerコンテナが次のように起動していること

```bash
$ docker-compose ps
    Name                  Command               State            Ports         
-------------------------------------------------------------------------------
mydjango307    /usr/src/app/entrypoint.sh ...   Up      0.0.0.0:8000->8000/tcp 
```

<br/>

✔ http://localhost:8000 にアクセスして次の画面が表示されること.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246056/d3ec0713-6dfa-369a-d110-cd8dde62e68f.png)

<br/>

✔ http://localhost:8000/admin にアクセスして、アカウント「`admin`」、パスワード「`admin`」でログインできること.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246056/ccb608ea-fcd5-aa6a-f555-c8200d7337be.png)

<br/>

✔ ./web/app/db.sqlite3 が存在して**いない**こと

もしも存在している場合は、settings.py が正しく配置できていない疑いがある.

<br/>

✔ PostgreSQL コンテナにて Database にログインできること

次のパラメータで PostgreSQL のデータベース「`django307`」にログインする

|項目|値|
|:--|:--|
|アカウント|`admin`|
|パスワード|`admin`|
|ポート|`5432番`|
|データベース|`django307`|

```bash
$ docker-compose exec db bash -c 'PGPASSWORD=admin psql -U admin -h db -p 5432 -w django307'
```

そして、次のように「データベース」一覧が表示されれば良い

```bash
django307-# \l
                                     データベース一覧
   名前    | 所有者 | エンコーディング |  照合順序  | Ctype(変換演算子) |  アクセス権限   
-----------+--------+------------------+------------+-------------------+-----------------
 django307 | admin  | UTF8             | ja_JP.utf8 | ja_JP.utf8        | 
 postgres  | admin  | UTF8             | ja_JP.utf8 | ja_JP.utf8        | 
 template0 | admin  | UTF8             | ja_JP.utf8 | ja_JP.utf8        | =c/admin       +
           |        |                  |            |                   | admin=CTc/admin
 template1 | admin  | UTF8             | ja_JP.utf8 | ja_JP.utf8        | =c/admin       +
           |        |                  |            |                   | admin=CTc/admin
(4 行)

```

<br/>

以上.