🙆

Docker ポートは公開しなくても実は空いている

2023/12/22に公開

例えばnginxをdockerで起動するとする。とくに小細工をしなければ次のようは指定をするだろう

% docker run -p 8080:80 -d nginx

docker ps をみると次のようになってるはずである。

03b554062f52 nginx "/docker-entrypoint.…" 7 seconds ago Up 6 seconds 0.0.0.0:8080->80/tcp, :::8080->80/tcp gifted_golick

お馴染みであろう 0.0.0.0:8080->80/tcp の表示が視える。

大抵の場合は、ここで localhost:8080 への接続を試して気が済んでしまうと思われる。

0.0.0.0:*

だがまってほしい。0.0.0.0:8080->80/tcp の表示の意味を。

実はあなたのPCの外からでも接続できるようになってしまってるのだ。
それが「ポート"公開"」たる所以である。

多くの場合は問題ない。たとえばwifiから接続して、スマフォでテストしたい場合もあるだろう。
その場合はホストOSのネットワークカードのIPアドレスで接続できる。

Docker on cloud

だが最近はdockerfileをそのままデプロイにつかえるパブリッククラウドが多いため、利用したくなることだろう。代表的なものは次のものがある。

https://devcenter.heroku.com/ja/articles/build-docker-images-heroku-yml

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/create-container-image.html

https://cloud.google.com/build/docs/build-push-docker-image?hl=ja

だがローカル開発でつかったdockerfileをそのままデプロイに使うとえらいことになる。

おあつらえむきに失敗談を語ってるひとがいたので使わせてもらおう。

https://qiita.com/isso_719/items/c22e617986c821b2f624

ポート公開をやめる。

もちろん回避策はある。 port foward 指定をしなければよいだけだ。

% docker run -d nginx

先の例に比べて物足りないが、無事に起動してしまう。docker psを確認してみる。

※以下全て小生の環境で実行した場合の表示である。ID表示等の差異は適宜読み替えてほしい。

1d012c58c212 nginx "/docker-entrypoint.…" 4 seconds ago Up 3 seconds 80/tcp vigorous_buck

いつもの localhost:8080 ではつながらない。だが、80/tcpという表示は見つかる。

何が違うのか? 実は後者でも接続はできる。いつものlocalhostではないだけで。

"公開"しなくても接続できてしまう

container-idがわかればinspectサブコマンドで、dockerブリッジネットワークのIPアドレスを確認できる。
あまり賢くはないがてっとり早くgrepで探す。

$ sudo docker inspect 1d012c58c212 | grep -Fw IPAddress
            "IPAddress": "172.17.0.2",
                    "IPAddress": "172.17.0.2",

172.27.0.2とある。

これはじつは http://172.27.0.2/ で、dockerインスタンスに接続できることを意味している。

172.27.*.* は dockerがホストOSとの接続のために用意した特殊なネットワークである。
オフィシャルのブリッジネットワークの項に詳しい。

https://docs.docker.jp/engine/userguide/networking/dockernetworks.html

環境によって2ニブル目が微妙にちがう可能性があるが、概ねクラスBプライベートを使ってると思って良い。
ここならとりあえずウッカリ外につながる心配はない。

しかも同じdockerホスト上のdockerインスタンス同士なら、172.27.*.* で接続できるということでもあるのだ。

このIPアドレス空間はdockerデーモンの全体設定によって変更できるが、
家庭用wifiルータは概ねクラスCプライベートの 192.168.*.* を使ってるため、
大きく隔離したほうが混乱が減らせると思われる。

ちなみにdockerインスタンスからホストOSへは、172.*.0.1 で接続できる。

複数dockerインスタンスの構成

docker-compose等で複数インスタンスを使う場合、それぞれ相互に接続したいことだろう。
rubyやPHPからはmysqlに接続したいだろう。

現状、多くの場合はそれはポート公開により行われてきたが、そうでなくてもやっていけるという話をしたばかりだ。

だがブリッジネットワークへの接続はdockerの裁量によって行われる。
起動してみるまでは、IPアドレスに何がつくかはわからない。

だが回避策はもちろんある。

あまり知られてないようなのだが、docker-composeを使う場合、実は定義したサービス名そのものによってそのままインスタンス相互に接続ができる。
例えば linux インスタンスであれば、/etc/hosts を相応に書き換えてくれている。

下記オフィシャルの例をみれば一目瞭然だろう。

https://docs.docker.jp/compose/networking.html

これで各コンテナはホスト名 web や db で探せるようになり、適切なコンテナの IP アドレスが帰ってきます。たとえば、 web アプリケーションのコードが URL postgres://db:5432 に接続すると、Postgres データベースを使用開始します。

機構についてのオフィシャルの説明が発見できなかったので、仕方ないのでリポジトリの該当文字列を探した。

https://github.com/docker/docs/blob/main/content/network/links.md?plain=1#L353-L374

大多数の場合では、webサーバのみポート公開すればよく、mysql等の接続はブリッジネットワーク経由でよいだろう。あくまで同じdockerホスト上であればだが。

docker inspectでIPアドレスを賢く調べる

docker inspectの出力はじつはJSONであり、inspect自身も出力のフィルタリング&表示フォーマット指定機能をもつため、docker cli 単独でIPアドレスの抽出はできる。

https://docs.docker.jp/engine/reference/commandline/inspect.html

$ docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $INSTANCE_ID

前述の例にあてはめてみよう。

$ sudo docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'  1d012c58c212
172.17.0.2

覚えにくいけどね。

Discussion