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を記載しなくても良い。
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にマッピングする場合は、下記のようになる。
docker run -p 8080:3000 your-image-name
docker-compose.yml ファイルを使用する場合
docker-compose.yml
ファイルでは、services
セクション内でポートマッピングを定義する。ここでの書式も <ホストポート>:<コンテナポート>
となる。
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
)を使用して、ホストマシンのポートをコンテナのポートにバインドする必要がある。
# 基本イメージとして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番号で指定
if __name__ == "__main__":
app.run(debug=True, port=3100)
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'
をつけて実行した場合
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した場合は下記のようになる
$ 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をクリックしてもアクセスできるようになる。
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コンテナで構築している場合に、コンテナ間通信を設定してサービス間連携できるようにする方法はこちら。
コメント
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!