【Docker】docker-composeのホスト側ポートを同じポート番号で使い回す方法

Web開発ShellScript,Docker,docker-compose

先日、ローカルのDocker環境にてDjangoとDjango REST frameworkを使ってAPIとそのAPIからデータを取得してDBに保存するという簡単なアプリを作成していました。

しかし、なぜかアプリからAPIに接続することができない….
その時に陥った問題と原因、解決方法について解説します!

こんな人におすすめ
  • ローカルのDocker環境でlocalhostに通信できず困っている人

localhostでAPIに接続できない

作成したアプリとAPIで想定していたURLはそれぞれ以下の通りでした

  • アプリ
    • http://localhost/
  • API(保存)
    • http://localhost/api/v1/save/

まずはAPIから作成し、ブラウザからアクセス。
コンソールが表示され、GETでDBからデータの一覧が取得されている。
フォームからPOSTするとDBに正常に保存されることを確認まで完了できました。

続いてアプリを作成。
APIへのPOST処理を作って、フォームから送信できるようにしました。

ともに完成し、いざアプリからAPIへのデータ送信を実行するとなぜか接続できない…

原因は『localhost』

想定していたアプリやAPIのURLをlocalhostにしており、ブラウザでも問題なく表示されていたため、アプリのPOST先も同様に http://localhost/api/v1/save/ と指定していましたが、これが問題でした。

アプリ自体はDockerコンテナ上で動作しているため、アプリでlocalhostの指定は、コンテナ内を指しているということがわかりました。

ブラウザからのlocalhostはホストマシンを指しているため、接続先がないとなったわけですね。

Dockerコンテナからホストマシンのlocalhostに接続する

Dockerコンテナからホストマシンのlocalhostに接続するには、接続先をホストマシンのIPアドレスを指定することで可能になります。

Dockerコンテナ内から見たホストマシンのIPアドレスを確認

ここでは docker-compose を使用していますので、使用されていない方は適宜読み替えてください

# Dockerコンテナの中に入る
# "web"は、docker-compose.ymlで指定しているアプリやAPIを使用しているコンテナのサービス名
$ docker-compose exec web bash

# 入れたら以下のコマンドを実行
$ cat /etc/hosts | awk 'END{print $1}' | sed -e 's/[0-9]+$/1/g'
192.168.96.1

cat /etc/hosts
/etc/hosts ファイルの内容を確認

awk 'END{print $1}'
– 最後の行を取り出す

sed -e 's/[0-9]+$/1/g'
– 取り出したIPアドレスと末尾を1に変える

IPアドレスの設定をdocker-compose.ymlに追加

Dockerコンテナは起動ごとに動的にIPアドレスが割り当てられるため、毎回 /etc/hosts ファイルの内容が書き換わってしまいます。

なので、docker-compose.ymlの extra_hosts オプションを使って、先程確認したIPアドレスをDockerコンテナないの /etc/hosts に追加するようにします。

docker-compose.yml

version: '3'

services:
    web:
        container_name: django-docker
        { ... }
        depends_on:
            - db
        extra_hosts:                  # 追加
            - localhost:192.168.96.1   # 追加

Dockerコンテナを再ビルドして再起動

# Dockerコンテナを起動している場合
$ docker-compose down

# 再ビルド&再起動
$ docker-compose build; docker-compose up -d

# Dockerコンテナの中に入る
$ docker-compose exec web bash

# /etc/hostsを確認
$ cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
192.168.96.1     localhost     # ←これが追加されていたらOK
192.168.96.3    96062a57a93d

これで無事APIへ接続と処理が成功しました。


以上になります。

初めてDjangoを使用していたときに起こったことだったため、てっきりDjangoやPythonの使い方が間違っているのか?と見当違いなことを調査してしまっていて、原因がDockerに関する事だったのは盲点でした。

自分が実際に困ったことだったので記事にしました。

ご参考になればと思います☀️