Docker + NestJS (WSL) 環境で "Connection reset by peer" と格闘した話
はじめに
Docker Compose を使用して WSL2 上で NestJS アプリケーションを開発していた際、複数のネットワークエラーに見舞われました。最初はデータベース接続エラー、その後は Connection reset by peer
という不可解なネットワークエラーです。この記事では、その原因調査と解決に至るまでのトラブルシューティングを備忘録的に記録していきます。
環境
- OS: Windows (WSL2 Ubuntu 環境を利用)
- バックエンド: NestJS
- データベース: PostgreSQL
- コンテナ管理: Docker Compose
- その他: Prisma
発生した問題
1. Prisma データベース認証エラー (P1000)
開発環境を docker compose up
で起動したところ、NestJS アプリのログに以下のエラーが出力されました。
PrismaClientInitializationError: Authentication failed against database server at `database`, the provided database credentials for `ユーザー名` are not valid.
...
errorCode: 'P1000'
.env
ファイルと compose.yml
に設定したデータベースの認証情報が一致していない、またはデータベースユーザーが存在しない可能性が考えられました。
2. "Connection reset by peer"
データベース認証エラーを解決し、NestJS アプリのログには Nest application successfully started
と表示されるようになりました。しかし、curl
コマンドで動作確認しようとすると、以下のエラーが発生し、アプリケーションに接続できませんでした。
$ curl http://localhost:3000/hello
curl: (56) Recv failure: Connection reset by peer
不思議なことに、このエラーが発生しても NestJS アプリのログには何も出力されませんでした。
調査と試行錯誤
データベース認証エラー (P1000) の解決
- 設定ファイルの確認:
- そもそもの設定ファイルの誤りの可能性があったのでそこから確認します。
-
.env
のDATABASE_URL
とcompose.yml
のdatabase
サービスのPOSTGRES_USER
,POSTGRES_PASSWORD
が一致していることを確認。
-> 一致していた。
-
データベースボリューム/データの確認と削除:
-
docker compose down -v
を試したが、DB ログでSkipping initialization
と表示され、ユーザーが作成されていない様子。 -
compose.yml
で バインドマウント (./data/db:/var/lib/postgresql/data
) を使用していたため、名前付きボリュームではなくホスト側の./data/db
にデータが残っていると推測。 -
sudo rm -rf ./data/db/*
で中身を削除
->Skipping initialization
が表示されてしまう -
sudo rm -rf ./data/db
で ディレクトリごと削除 してからdocker compose up
-> これで初期化が実行され、DB ログにRole "choseidoro" does not exist.
エラーが出なくなり、認証エラーは解決。
-
原因
今回のプロジェクトでは、既存のプロジェクトを下敷きに諸々開発した後、古いプロジェクトの文言を軒並み置換していきました。
その際、POSTGRES_USER
,POSTGRES _DB
などの環境変数も置換していたため、本来はDBが初期化されていなければいけませんでした。
しかし、バインドマウント先に./data/db/
フォルダがあったため、データ有と判断したDocker君は初期化をスキップしてしまい、接続できなくなっていたようでした。
フォルダを削除した結果、データ無と判断したDocker君は、初期化をスキップせず、ユーザー作成など必要な処理をしてくれたのでした。
"Connection reset by peer" の解決
curl
での接続確認 (WSL 内部):
-
curl http://localhost:3000/hello
やcurl http://localhost:3000/health
など、ヘルスチェック用のAPIを叩くも、"Connection reset by peer" となり、NestJS ログには変化なし。
-
NestJS アプリケーションコードの確認:
-
main.ts
を確認。bodyParser
の明示的な使用が怪しいと考えコメントアウト -> 効果なし。 - CORS 設定 (
origin: true
) も確認したが、WSL 内部からのcurl
では関係なさそう。
-
-
ポート競合調査:
- WSL 内部:
ss -tulnp | grep 3000
-> Docker プロセスのみ。問題なし。 - Windows 側: 管理者権限で
netstat -ano | findstr "LISTEN" | findstr ":3000"
を実行。TCP 0.0.0.0:3000 0.0.0.0:0 LISTENING 14732 # Dockerと思われる TCP 127.0.0.1:3000 0.0.0.0:0 LISTENING 12360 # 謎のプロセス TCP [::]:3000 [::]:0 LISTENING 14732 # Dockerと思われる TCP [::1]:3000 [::]:0 LISTENING 12360 # 謎のプロセス
- タスクマネージャーで PID
12360
を確認 ->wslrelay.exe
(WSL の内部プロセス) であることが判明。
- WSL 内部:
原因
Windows 側で wslrelay.exe
という WSL の内部プロセスが、何らかの理由で localhost:3000
を確保してしまっていた。これにより、Windows のブラウザや WSL 内部からの curl
が本来の Docker コンテナ (NestJS アプリ) に到達できず、接続がリセットされていたと考えられます。
解決策
WSL を完全に再起動することで、ポートを掴んでいた wslrelay.exe
プロセスを終了させ、問題を解決しました。
-
管理者権限の PowerShell/コマンドプロンプトで
wsl --shutdown
を実行。 - (Docker Desktop を使用している場合) Docker Desktop を再起動。
-
docker compose up -d
でコンテナを起動。 -
netstat -ano | findstr "LISTEN" | findstr ":3000"
でwslrelay.exe
がポートを掴んでいないことを確認。 -
curl http://localhost:3000/api/hello
で接続確認 -> 成功!
WSL2 環境で Docker を使って開発する場合、Windows 側と WSL 側の両方でポートの競合が発生する可能性があります。特に localhost
への接続で問題が発生した場合、netstat
コマンドを Windows 側 (管理者権限) で実行し、意図しないプロセス (時には WSL 自体のプロセス) がポートを掴んでいないか確認することが重要です。wsl --shutdown
は、WSL 関連のネットワーク問題をリセットするのに有効な手段でした。
まとめ
今回は既存のプロジェクトを下敷きにした新規開発ということもあり、特殊?条件や開発環境の変更タイミングにより、初歩的なはまりポイントにことごとくはまってしまった感がありました。
開発環境系の理解の足りなさを恥じつつも、未来の自分への備忘録としての記事を残しておきます。
おまけ:弊社では一緒に働く仲間を募集しています!
弊社では一緒に働く仲間を募集しています!
株式会社コードユニットは 「エンジニアが自由に挑戦し、成長できる環境を創る」 をビジョンに掲げる札幌のIT企業です。
一緒に働く仲間を募集しているので、もし一緒に働く仲間となってもいいと思った方は、一緒に働く仲間になりましょう!
一緒に働く仲間に興味がある方は、どんな手段でもいいのでお声がけください!
コーポレートサイト
Discussion