概要
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
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',
},
}
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が指定したサービスがヘルスチェックで定義された条件を満たす場合にのみ、依存関係の解決を行うように指定できます。
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サービスの起動が待機します。
これで再度ビルドしてコンテナを立ち上げるとエラーが無くなりました。
補足
なお、一点補足。
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」とエラーが起きる
解決方法としては、pg_isreadyコマンドに-dオプションを使用してデータベース名を明示的に指定してあげること。
test: ["CMD-SHELL", "pg_isready -U myuser -d myproject_db"]
これにより、pg_isreadyコマンドはmyuserユーザーでmyproject_dbデータベースに接続を試みるようになり、エラーは解決しました。