[04]  jwilder/dockerize を使ってセットアップ処理を改良する

# はじめに

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

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

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

・[[01] デフォルト環境 runserver + SQLite3 の構築](https://qiita.com/robozushi10/items/fc8e40ad7ecc29131ea3/)
・[[02] SQLite3 を PostgreSQL へ置き換える](https://qiita.com/robozushi10/items/7dc07ab6412b285c75da)
・[[03] Django アプリの立ち上げもさせる](https://qiita.com/robozushi10/items/bda4e5f1c743c4c26af5)
・本記事の内容

# 概要

今回は下図・下表の環境を構築する. (前章からの変更点は🔥である)
前章からの具体的な変更点としては次である.
**・セットアップ時の Django や PostgreSQL コンテナの起動待ちの sleep を廃止する.**
**・sleep の代わりに「[jwilder/dockerize](https://github.com/jwilder/dockerize)」を使って Django や PostgreSQL コンテナ起動を即座に検知させる.**

## 概念図

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246056/b5d732d1-1866-5612-8ab8-9cf034234b0a.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||ホスト上の `./web/assets/settings.py` が、<br/>コンテナ内に `/usr/src/app/config/settings.py` <br/>として配置される.|
|Django admin ページ||管理者アカウントは次の通り<br/>・アカウント「`admin`」<br/>・パスワード「`admin`」|
|アプリケーション|「shop」|**各ユーザが使用したいアプリに置き換えてほしい**|
|コンテナ起動確認ツール🔥|[jwilder/dockerize](https://github.com/jwilder/dockerize)🔥||

## 特記事項

### jwilder/dockerize についての参考情報

jwilder/dockerize はコンテナの準備完了を検知するソフトウェアである.

|参考 URL|
|:--|
|https://labs.cybozu.co.jp/blog/akky/2017/12/let-dockerize-command-wait-other-container/|
|https://qiita.com/Aruneko/items/971e65a945d23f7bb8b6|

# セットアップコード

## 全文

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

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

## ファイル構成

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

```bash
.
|-- db
|   `-- Dockerfile
|-- docker-compose.yml 🏷️
|-- setupapp.sh 🏷️........... セットアップスクリプト
`-- web
    |-- Dockerfile
    `-- assets .............. コンテナへコピーされるデータ
        |-- entrypoint.sh
        |-- requirements.txt
        |-- sample
        |   `-- shop
        |       |-- admin.py
        |       `-- models.py
        `-- settings.py
```

## 解説

### docker-compose.yml

・`jwilder/dockerize` は docker-compose.yml に書くことでコンテナ間のネットワーク連携が容易になる

```diff
--- docker-compose.yml.ORIG
+++ docker-compose.yml
@@ -27,3 +27,7 @@
         POSTGRES_USER: 'admin'
         POSTGRES_PASSWORD: 'admin'
         PGDATA: '/data'
+  dockerize:
+    image: jwilder/dockerize
+    container_name: jwilder_dockerize
```

### setupapp.sh

■ 「`jwilder/dockerize`」コンテナから Django コンテナに対して疎通確認をする.
&nbsp;&nbsp;&nbsp;・リクエスト先は「ルート」すなわち `http://localhost:8000` とする.
&nbsp;&nbsp;&nbsp;・Django が起動したか否かの判定条件として manage.py の有無を見る必要が無くなる.
&nbsp;&nbsp;&nbsp;・Django 起動までの待機時間を推測しなくて済む.

■ 「`jwilder/dockerize`」コンテナから PostgreSQL コンテナに対して疎通確認をする.
&nbsp;&nbsp;&nbsp;・リクエスト先は `tcp://db:5432` とする.

■ 「`jwilder/dockerize`」が所定時間内に Django や PostgreSQL と疎通できなければエラー終了させる.
&nbsp;&nbsp;&nbsp;・「`jwilder/dockerize`」は疎通できなかった場合は **非0** を返す.
&nbsp;&nbsp;&nbsp;・そこで、エラー終了を実現するために「`set -e`」を用いる.

■ 「`jwilder/dockerize`」の通信周期、上限時間などは下記コード内のコメントを参照してほしい.

```diff
--- setupapp.sh.ORIG
+++ setupapp.sh
@@ -27,20 +27,14 @@
 # Docker コンテナ起動を行う. 
 docker-compose up -d
 
-# 起動完了を待つ. (== entrypoint.sh の成功を待つ)
-sleep 2
+# 次の jwilder/dockerize でタイムアウトになった場合にセットアップスクリプトを停止させるようにする.
+set -e
 
-# manage.py が作成されていなければ Django のプロジェクト作成に失敗したとみなす
-test ! -f ./web/app/manage.py && {
-  echo "ERROR: Django のプロジェクト作成に失敗した"
-  exit 2
-}
+# 0.5秒周期で Django と PostgreSQL コンテナから応答があるまで待機する. (上限 1分間)
+docker-compose run --rm dockerize sh -c 'dockerize -wait http://web:8000 -wait tcp://db:5432 -timeout 1m -wait-retry-interval 0.5s'
 
-# コンテナ内でのマイグレーション終了を待つ.
-# なお、ここで 待機時間を取らないと次のエラーが出現してしまう.
-#「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
+# jwilder/dockerize の処理が済んだので、set -e を無効にする.
+set +e
 
 # 「Django管理サイト」の管理者アカウントを作成する. (ID「admin」, PW「admin」とする)
 docker-compose exec web bash -c "python manage.py makemigrations"

```

# セットアップ方法

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

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

# セットアップ後

## ファイル構成

```bash
$ tree . --charset=c -a 
.
|-- db
|   |-- Dockerfile
|   |-- data [error opening dir]
|   `-- init
|-- docker-compose.yml
|-- setupapp.sh
`-- web
    |-- Dockerfile
    |-- app
    |   |-- .settings.py
    |   |-- config
    |   |   |-- __init__.py
    |   |   |-- asgi.py
    |   |   |-- settings.py
    |   |   |-- urls.py
    |   |   `-- wsgi.py
    |   |-- entrypoint.sh
    |   |-- manage.py
    |   `-- shop
    |       |-- __init__.py
    |       |-- admin.py
    |       |-- apps.py
    |       |-- migrations
    |       |   |-- 0001_initial.py
    |       |   `-- __init__.py
    |       |-- models.py
    |       |-- tests.py
    |       `-- views.py
    `-- assets
        |-- entrypoint.sh
        |-- requirements.txt
        |-- sample
        |   `-- shop
        |       |-- admin.py
        |       `-- models.py
        `-- settings.py
```

## 動作確認方法

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

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

コンテナ「`jwilder_dockerize`」は使い捨て型なので、Exit 2 は意図した結果であり問題無し.

```bash
$ docker-compose ps
      Name                     Command               State             Ports         
-------------------------------------------------------------------------------------
jwilder_dockerize   dockerize --help                 Exit 2                          
mydjango307         /usr/src/app/entrypoint.sh ...   Up       0.0.0.0:8000->8000/tcp 
mypostgres12        docker-entrypoint.sh postgres    Up       0.0.0.0:53432->5432/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/>

以上.