Dockerによるポートオープン

ポート番号空間

Dockerコンテナは、そのコンテナの中だけでホスト側とは異なるポート番号空間を持っている。例えば、コンテナAが80をオープンし、コンテナBが80番をオープンし、ホスト側でもまた80番をオープンできる。

それぞれがウェブサービスを提供しているとして、それらを外部から使いたいとすると、どうすれば良いか。

まず、ホストの側で80番を開けているプログラムは、そのままでは使えない。ファイアウォールが閉じているからだ。これをfirewall-cmdで開けてやらなければ、このサービスに到達することはできない。通常はこのようにして、ホストの80番で動作させているサービスを外部に公開する。

では、Dockerコンテナについてはどうか。例えば、コンテナAに関しては、例えばdocker-compose.ymlの場合、次のように記述したりする。

  ports
    - 8080:80

これは、コンテナAの80番ポートをホスト側では8080番に変換して公開するという意味だ。ホスト側のポート番号空間は一つのため、同じ80番を使うことはできず、8080番として使うことにする。

これで、外部からは、80番、8080番としてアクセスすることができる。

Dockerによるiptablesの操作

最初にDockerを使ったとき、なぜこれですんなりうまく行くのかわからなかった。なぜなら、8080番はfirewallで閉じられているはずだからだ。8080番に関しては、外部へのポートオープンはしていないにもかかわらず、外部からアクセスできてしまうのである。実は、Dockerはfirewall-cmdを無視して、その下請けのiptablesで勝手にホストのポートをオープンしているのである。

このあたりはDocker と iptablesで説明されている。

Dockerはfirewall-cmdを使わず、直接的にその下請けのiptablesでポートオープンしてしまうため、Dockerによるポートオープン操作は、例えば「firewall-cmd –list-all」等のコマンドでは、「8080番ポートが開いている状態」を全く見ることはできない。

この点は注意が必要である。Dockerの設定を間違えると、意図せずポートオープンしてしまい、それはfirewall-cmdではわからない。

外部には公開しない場合

ともあれ、外部インターフェースを通じてポートを外部に大公開する場合にはこれで良いのだが、しかし、コンテナが開いているポートを内部的に使いたい場合にはどうすれば良いか?以下のケースがある。

  • あるコンテナでリバースプロキシを動作させており、そのコンテナがホスト側の80番を使って外部公開するので、他コンテナは勝手に外部公開せず、このリバースプロキシ配下にする。
  • ホスト側でVPNを動作させており、そのVPN経由のものだけが、コンテナの80番に接続したい。

コンテナが開いている80番は、そのままでは他コンテナからも、ホスト側からも見ることができない。かといって、単純に以下を行うと、外部に対して全開してしまうことになる。

  ports
    - 8080:80

リバースプロキシを使う場合

単純に言えば、このケースとしては、「一つのサーバの中に複数のウェブサービスを入れたい」場合である。コンテナAが「example.com」用、コンテナBが「sample.com」、コンテナCが「foobar.com」をサービスしたいとする。とりあえずhttpのみのサービスだとして、すべてが80番を使う。

リバースプロキシのコンテナとしては、https-portalでもtraefikでも良いのだが、これが代表してホスト側の80番をオープンし、外部に全開する。つまり以下の設定だ。

  ports
    - 80:80

外部からのリクエストを受け、リクエストに記述されたドメインによって、A,B,Cのコンテナに振り分けるものとする。

しかし、この場合、この代表コンテナからコンテナA,B,Cのポートには、そのままではアクセスできないのである。A,B,Cを外部に公開してしまえば、代表コンテナからもアクセス可能と思われるが、それでは、色々と困ることがある。https-portalであれ、traefikであれ、実際には443ポートを開き、https通信を行い、その暗号解除後にコンテナA,B,Cに振り向けたいからだ。

このやり方としては何種類か考えられるが最も簡単なやり方としては、代表コンテナ、A,B,Cが内部で使用するネットワークを統一化してしまうことである。デフォルトでは、それぞれのコンテナでそれぞれのネットワークが自動作成されるため、各コンテナの間では何のやりとりもできないのだが、単一のネットワークにすべてを属させてしまえば、あたかも「同一ネットワークにいる別々のマシン」として扱うことができ、マシン名を指定してそのポート番号80番を指定すれば、そのマシンとのやりとりができるようになる。

docker network create common-network

としておき、docker-compose.ymlに他のネットワーク定義を入れていない場合には、以下のみでよい。

networks:
  default:
    external:
      name: common-network

すると、例えば、https-portalからは次のようにアクセスできる。

    environment:
      DOMAINS: >-
        example.com -> http://container-a:80
        ,sample.com -> http://container-b:80
        ,foobar.com -> http://container-c:80

container-aなどは、それぞれのコンテナの名前である。同じネットワーク上にある「マシン」をその名前で参照し、そのマシンの目的とするポート番号を指定すればよい。

VPSを使う場合

時には、80番のウェブサービスとは言っても、どこにも公開したくないことがある。例えば、cockpit、traefikのGUI画面、portainer等は、管理者のみが使うものなので、特にネット上に「入り口」が公開されている必要はない。VPNを介して、「あたかも自分のPCと同じLANにいる」かのように扱いたい、そしてこの場合、面倒なパスワード入力は不要である。誰も「入り口」にたどりつくことさえできないのである。

サーバにVPNを入れると、サーバ中に仮想的なNICが作成され、そのIPアドレスが設定される。このサーバに本来的に備わっている物理的NICとは全く別のIPアドレスになる。

同じVPNに属する、例えば手元のPCからは、この仮想NICとやりとりを行えばよく、上記のウェブサービスも、この仮想NICのみにポートを公開すればよい。

仮に、上のコンテナA,B,Cを三つともこの仮想NICにのみ公開する場合には、例えば以下を行う。仮想NICのIPが、100.123.123.123だとすると、

A

  ports
    - 100.123.123.123:80:80

B

  ports
    - 100.123.123.123:8080:80

C

  ports
    - 100.123.123.123:8000:80

当然だが、この仮想NIC上でポート番号の重複は許されないので、適当に80番以外の番号に変更しなければいけない。

Dockerでのポート公開の方式

Dockerでのポート公開の仕方は、Docker Compose】ports / expose、書式別の挙動まとめ【version 3】に詳しい記述がある。

まとめてみると、「8080:80」の場合には、このポートを8080番として大公開してしまい、どこからでもアクセスを可能にしている。「100.123.123.123:8080:80」の場合には、ホスト側として8080番を使い、そこにアクセスできるのは100.123.123.123のみということになる。

さて、この「100.123.123.123」というIPアドレスを「IPアドレス範囲」にできないものかと思った。なぜなら、この同じ設定ファイルをコピーして別のマシンに入れると、そこでの仮想NICのアドレスは、「100.200.200.200」かもしれない。例えば、Tailscaleを使った場合には、必ず100...*といったアドレスになるからだ。しかし、これはできないらしい。複数の設定を書くことはできるが、アドレス範囲は指定できないようだ。

  ports
    - 100.123.123.123:8080:80
    - 100.200.200.200:8080:80

つまり、IPアドレス指定がなければ、完全な大公開だが、指定する場合は一つずつ書くほかないらしい。

このあたりの話がDocker-compose “ports”: listen on multiple IP addresses / IP rangeにある。