【Django x Docker】エラー「Is the server running on that host and accepting TCP/IP connections?」の原因

# 概要
DockerでDjangoとPostgresのコンテナをローカル環境で立ち上げようとしたら、以下のエラーに遭遇しました。

```
django.db.utils.OperationalError: connection to server at "db" (172.22.0.2), port 5432 failed: Connection refused

Is the server running on that host and accepting TCP/IP connections?
```

# 環境・前提
以下で内容でコンテナ起動を試みました。

```
Django==4.2.9
psycopg2==2.9.9
```

```settings.py
DATABASES = {
    'default': {
        # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': DATABASE,
        'USER': USER,
        'PASSWORD': PASSWORD,
        'HOST': HOST,
        'PORT': '5432',
    },
}
```

```docker-compose.yaml
version: "3.9"

services:

  db:
    image: postgres:13
    environment:
      POSTGRES_DB: myproject_db
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
    ports:
      - "5432:5432"

  web:
    build:
      context: .
      dockerfile: Dockerfile
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - ./myprojects:/code
    ports:
      - "8000:8000"
    depends_on:
      - db
```

すると上述の通り、以下のエラーが発生しました。

```
django.db.utils.OperationalError: connection to server at "db" (172.22.0.2), port 5432 failed: Connection refused

Is the server running on that host and accepting TCP/IP connections?
```

# 原因
原因は、webサービスがdbサービスよりも先に起動し、PostgreSQLサーバーがまだ接続を受け付ける準備ができていない状態で接続を試みたためです。

`docker-compose.yaml`で`depends_on`を使ってサービス間の依存関係を定義しても、依存先のサービスが完全に起動し接続を受け付ける状態になるまで待ってくれるわけではないようです。

PostgreSQLサーバーが接続を受け付ける準備ができるまで待つようにする必要があります。
対応方法としては、`runserver`コマンド実行前にスクリプトを追加して待機させるような方法でも良いですし、ヘルスチェックを追加してあげる方法でも良いと思います。

今回はヘルスチェックを追加して解決しました。

## 解決方法
以下の通り、ヘルスチェックを追加。
ヘルスチェックとは、Docker Composeがサービスが正常に起動したかどうかを確認するための仕組みで、サービスがHealthyとみなされるまで、他のサービスの依存を待つことができます。

`depends_on`を使ってサービスの起動順序を指定しただけだと単にサービスの起動を待つだけですが、`condition: service_healthy`を加えることで、`depends_on`が指定したサービスがヘルスチェックで定義された条件を満たす場合にのみ、依存関係の解決を行うように指定できます。

```yaml
version: "3.9"

services:

  db:
    image: postgres:13
    environment:
      POSTGRES_DB: myproject_db
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myuser -d myproject_db"] 
      interval: 5s
      timeout: 5s
      retries: 5

  web:
    build:
      context: .
      dockerfile: Dockerfile
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - ./myprojects:/code
    ports:
      - "8000:8000"
    depends_on:
      db:
        condition: service_healthy
```

上記のヘルスチェックでは、`pg_isready`コマンド（クエリに応答しているかどうかを確認するツール）を使用してPostgreSQLデータベースが利用可能かどうかを確認しています。各項目の意味は以下の通り。

- `interval`:ヘルスチェックの実行間隔
- `timeout: 5s`: ヘルスチェックがタイムアウトするまでの時間
- `retries: 5`: ヘルスチェックが失敗した場合に、リトライ（再試行）する回数

また、`condition: service_healthy`によって、dbサービスがヘルスチェックに成功するまで、webサービスの起動が待機します。

これで再度ビルドしてコンテナを立ち上げるとエラーが無くなりました。

## 補足
なお、一点補足。

```yaml
healthcheck:
  test: ["CMD-SHELL", "pg_isready -U myuser"] 
  interval: 5s
  timeout: 5s
  retries: 5
```

上記のように`"pg_isready -U myuser"`だけだと、

  ```
  2024-01-13 13:41:33.730 UTC [52] FATAL:  database "myuser" does not exist
  ```

というエラーになります。

`pg_isready`コマンドは、PostgreSQLサーバーが起動していて接続を受け付ける準備ができているかどうかをチェックするものですが、`-U`オプションで指定したユーザー名がデータベース名として解釈されてしまっているからです。

どうやら`pg_isready`コマンドはデータベースに接続と勘違いしてしまうことがあるらしい。今回は、`myuser`という名前のデータベースに接続を試みてしまっています。

以下でも同じような事象の方がいました。
[PostgreSQLに「psql -U <ユーザ名> -h <ホスト名>」で接続しようとすると、「psql: FATAL: database "<ユーザ名>" does not exist」とエラーが起きる](https://qiita.com/nasuB7373/items/fecbc93c5484474c378f)

解決方法としては、`pg_isready`コマンドに`-d`オプションを使用してデータベース名を明示的に指定してあげること。

```yaml
  test: ["CMD-SHELL", "pg_isready -U myuser -d myproject_db"]
```

これにより、`pg_isready`コマンドは`myuser`ユーザーで`myproject_db`データベースに接続を試みるようになり、エラーは解決しました。