方言を話すおしゃべり猫型ロボット『ミーア』をリリースしました(こちらをクリック)

【Docker】ポートマッピング。port番号がたくさん登場するので整理してみた。

この記事は約12分で読めます。

Docker Port Mappingに関して

いくつもportが出現するので、メチャクチャややこしいので、整理する。

上記図を例に記載していく。

Docker化して、コンテナ内部のサービスにアクセスする場合は、Dockerコンテナ自体がホストマシンからも独立した環境((独自のネットワークインターフェース、IPアドレス、ルーティングテーブルなど)なので、コンテナportを介さないとアクセスができない。

さらに、コンテナ内に作られたflaskアプリケーションがそもそも、外部からのアクセスを許可するという明言をしておかないと、仮にDockerコンテナにアクセスできたとしてもアプリを起動できないという二重の制約がある。

flaskアプリでの外部開放:host='0.0.0.0'

まずflaskアプリに関しては、アプリを実行するpythonファイル内にhost='0.0.0.0' の設定を付け加えないといけない。

仮に下記のように記載すれば、Flaskアプリケーションを3000番ポートで外部に接続を開放して外部(ホストマシン、ホスト以外の全てを含む)からの接続を待ち受けますという状態になる。ちなみにデフォルトポートは5000が割り当てられているので、portを記載しなくても良い。

Python
if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=3000)

Docker port mappingの設定方法2つ

さらに、dockerのコンテナにもportがあり、こちらはport mappingというやり方で、外部からアクセスできるようにする。

port mappingを行う方法は主に2つある。

docker run コマンドを使用する場合

docker run コマンドで -p オプションを使用すると、ホストのポートをコンテナのポートにマッピングできる。書式は -p <ホストポート>:<コンテナポート>

例えば、上記図のようにホストマシンのポート8080をコンテナのポート3000にマッピングする場合は、下記のようになる。

Zsh
docker run -p 8080:3000 your-image-name

docker-compose.yml ファイルを使用する場合

docker-compose.yml ファイルでは、services セクション内でポートマッピングを定義する。ここでの書式も <ホストポート>:<コンテナポート> となる。

YAML
version: '3'
services:
  webapp:
    image: your-image-name
    ports:
      - "8080:3000"

のようになる。上記の設定は、docker-compose up コマンドを実行すると、webapp サービスで指定されたイメージを起動し、ホストの8080ポートをコンテナの3000ポートにマッピングする。

毎回、docker run -p 8080:5001 your-image-name とターミナルでコマンド実行するのは手間なので、docker-compose.ymlに定義してしまって、docker-compose upコマンドでマッピングも済ませてしまおうということ。

Dockerfile内のEXPOSE命令は必須ではない

Dockerfile内のExpose命令で、下記のような記載を見かけたりする。

これは、Dockerに対して、コンテナが3000番ポートでリクエストを受け付けますよと示している。しかし、これはあくまでドキュメンテーションの目的であり、実際にポートを外部に公開するわけではない。

EXPOSE 命令自体はポートを外部に公開しないので、コンテナを起動する際には、先ほどの -p フラグ(例:docker run -p 8080:5001 image_name)を使用して、ホストマシンのポートをコンテナのポートにバインドする必要がある。

YAML
# 基本イメージとしてPythonを使用
FROM python:3.8-slim

# 作業ディレクトリを設定
WORKDIR /app

# 必要なファイルをコンテナにコピー
COPY . /app

# 必要なパッケージをインストール
RUN pip install -r requirements.txt

# ポート3000を開放
EXPOSE 3000

# アプリケーションの実行コマンド
CMD ["python", "app.py"]

DockerのコンテナポートとDockerコンテナ内で実行するサービスのport番号は一致させないといけない

ここで一つ疑問になったのが、dockerコンテナのポート番号と、flaskアプリケーションで指定するポート番号を一致させる必要があるかどうかということ。

結論から言うと、一致させないといけない。一致していないと、外部からコンテナ内のアプリケーションにアクセスすることはできない。

  • コンテナ内の Flask アプリケーションのポート: 例えば、Flask アプリケーションが app.run(host="0.0.0.0", port=5001) としてポート 5001 でリッスンしているとする。
  • Docker のポートマッピング: この場合、Docker コマンドで p 8080:5001 のように設定する必要がある。これにより、ホストマシンのポート 8080 からのリクエストがコンテナのポート 5001 に転送される。
  • アクセス: この設定により、外部からホストマシンのポート 8080 にアクセスすると、そのリクエストはコンテナ内のポート 5001 にリダイレクトされ、Flask アプリケーションが処理を受け取る。

もしコンテナのポートと Flask アプリケーションがリッスンしているポートが一致していない場合(例えば、Flask がポート 3100 でリッスンしているが、Docker は 8080:5001 でマッピングしている)、外部から Flask アプリケーションにアクセスすることはできない。なぜなら、ホストマシンのポート 8080 からのリクエストはコンテナのポート 5001 に転送されますが、Flask は別のポート(この場合は 3100)で待ち受けているから。

Docker のコンテナポートと Flask アプリケーションのポートを異なる番号で設定し、それらを自動的にバインディングさせるような仕組みは現状ないある必要がないとも言えるが。

Running onで記載されるport番号に関して

ここが、docker化しないで直接flaskアプリを実行した時と、docker化した時とで異なり、私は初見で混乱したので備忘録として残しておく。

Docker化しないで直接pythonファイルを実行する場合:実行ファイルのport番号

まず、host='0.0.0.0' を設定せずに実行した場合

理解のために、portも敢えて、普段設定しないようなport番号で指定

Python
if __name__ == "__main__":
    app.run(debug=True, port=3100)
Zsh
env  ~/dev/ python3 app/routes.py 
 * Serving Flask app 'routes'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:3100
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 801-553-609

URLは、http://127.0.0.1:3100 という、ローカルホスト(localhost、つまり自身のコンピュータ=127.0.0.1)からのアクセス用のURLのみが表示されている。localhostのIPアドレス:port番号という記載。

127.0.0.1 が localhost の IP アドレスとして使われるのは、インターネットのプロトコル標準によって決まっているもの。ループバックアドレスと言われる。コンピュータが自分自身と通信するためのアドレスであり、文字通り、ループがバックしているのでループバックアドレス。そういう規約なので、割り切る。

次に、host='0.0.0.0' をつけて実行した場合

Python
python3 app/routes.py
 * Serving Flask app 'routes'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:3100
* Running on http://192.168.x.x:3100

Running on all addresses (0.0.0.0)という記載が追加され、先ほどのローカルホストからのアクセス用のURLの他に、別のアドレスも表示されている。

http://192.168.x.x:5000 は、ローカルネットワーク内の他のデバイス(または同じネットワークに接続された外部デバイス)からアクセスするためのURL。これは、サーバーの実行中のマシン(ホストマシン)がネットワーク内で使用しているIPアドレス。

ただし、portの方は、host='0.0.0.0' をつけようがつけまいが同じで、flaskアプリケーションの実行ファイルで設定したport番号になる。まぁ、これは当たり前という感じ。他に出現しうるportがそもそもない。

Docker化してコンテナを実行する場合:port番号はホストのport番号だが、ログは…

docker化するとdockerコンテナという独立環境が新たに誕生するので、ゲートウェイでのportが一気に増える。

docker化したflaskアプリケーションをdocker runした場合は下記のようになる

Zsh
 $ docker-compose up
 * Serving Flask app 'routes'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:3100
 * Running on http://192.168.128.2:3100

え?ここに表示されるport番号は、flaskアプリケーションに設定したport番号ではなく、ホストportのはずなのだが。。。理解間違っているのか?と思い、クリックしてURLを開こうとしたら、案の定エラー。

http://127.0.0.1:8080 と入力し直すと、無事表示された。

  • 127.0.0.1: これはローカルホストアドレスで、コンテナ自体に対してのみ有効。
  • 192.168.128.2: これはDockerコンテナが割り当てられたプライベートネットワーク内のIPアドレスで、他のコンテナとの通信に使われることがある。

なぜこのようなログ表示になってしまったのか?

Flask のログは、コンテナ内部のコンテキストで生成されるため、コンテナ内部のネットワークインターフェースとIPアドレスに基づいているため。

  • ログに表示されるアドレスは:コンテナの内部ネットワークにおけるアドレス
  • ログに表示されるポート:flaskアプリケーションが実際にリッスンしているポート

を反映しているだけで、それが Docker 内部であるか、通常のホスト環境であるかは関係ない。外部からのアクセスには Docker のポートマッピングに従ったアドレス(ホストマシンのIPとポート)を使用する必要がある。

だから、portバインディングでよく、同じport番号を記載していることが多いのか。よく、docker-compose.ymlで、ホストportもコンテナport番号も同じにしてバインディングしているのを見るが、これだと、docker-compose upした時のログに記載されたURLをクリックしてもアクセスできるようになる。

YAML
version: '3'
services:
  webapp:
    image: your-image-name
    ports:
      - "5000:5000"

となると、結局、ホストマシンのport番号=dockerコンテナのport番号=flaskアプリ実行ファイルでのport番号で全部同じ番号に設定したほうが便利となるが、逆に言えば、全部同じ番号なので、どういう原理に基づいて外部からサイトへのアクセスがなされているかの理解が難しくもなると感じた。

その場合のport番号記載は、dockerコンテナ内には複数のサービスを入れるので、サービスごとのデフォルトのポート番号をimageごとに記載するという着地になるのだろう。

なので、今回の場合だと、docker-compose.ymlに記載するportバインディング番号も、flaskアプリで設定するport番号も全て5000になる。MySQLのコンテナを立てたときは、全部3306になる。

と、ここまで記載したが、この私の理解が合っているかどうか少し不安。。。

もし理解に間違いがありましたら、コメントいただけますと幸いです。

【Docker備忘録】DockerFile・docker-compose.yml・Dockerコマンドに関しては、こちら。

また、異なる機能を持つ複数のサービスを別々のDockerコンテナで構築している場合に、コンテナ間通信を設定してサービス間連携できるようにする方法はこちら。

コメント

  1. latestmodapks より:

    This post is incredibly helpful in clarifying Docker’s port mapping concept. I was getting overwhelmed with all the port numbers and not knowing what each one was used for. Your breakdown and examples are super clear and easy to understand. Thanks for taking the time to write this!

タイトルとURLをコピーしました