Dockerコンテナにネットワークアクセスする


はじめに

『Docker/Kubernetes 実践コンテナ開発入門』読書会という勉強会に参加したのですが、その最初の例でこんなのが出てきました。

$ docker run -t -p 9000:8080 gihyodocker/echo:latest

この -p 9000:8080の部分をちゃんと理解するための図が欲しいなと思い描いてみたので、それを使って外部プログラムからどのようにDockerコンテナにネットワークを介してアクセスするのかを説明してみたいと思います。

TCP/IP接続とポート

Dockerの話の前にまずはTCP/IP接続とポートについて。知っている方は読み飛ばして下さい。

例えばWebブラウザで https://qiita.com に接続することを考えてみましょう。普段はあまり気にしていませんが、ブラウザに表示するデータを qiita.com から取得するために https接続しますが、その土台となっているのがTCP/IP接続。その際に必要なのが、「IPアドレス」と「ポート番号」です。

IPアドレスは「ホスト名」からDNSという仕組みによって知ることができます。コマンドラインで確認すると、このような感じ。

$ nslookup qiita.com
Server:     xxx
Address:    xxx#53

Non-authoritative answer:
Name:   qiita.com
Address: 13.112.220.124
Name:   qiita.com
Address: 52.199.237.49
Name:   qiita.com
Address: 18.179.247.240

一方でポート番号はサービスごとに決まった値が割りあたっています。これを Well-Known port(よく知られたポート)といい、ここで一覧が見られます。例えばhttpプロトコルでは80番、httpsプロトコルでは443番が使われます。

したがって、https://qiita.com の場合は、13.112.220.12452.199.237.4918.179.247.240のいずれかのホストのポート番号443への接続を行います。

サーバ側のqiita.comでは443番へ誰かが接続しているのを待っていて(listen)、接続してきたら予め設定されているアプリケーション(この場合はhttpd)に引き渡しを行います。

なお通常は気にすることがありませんが、ポート番号は接続先のサーバ側だけでなく接続元のクライアント側にも割り当てられます。サーバ側から見た時に、どのIPアドレスのどのポートから接続しに来ているということを特定するためですが、この一時的に割り当てられるポートをエフェメラルポートといいます。

Dockerコンテナとポート

DockerコンテナはLinux(macOS/WindowsのHypervisor上で動いているLinuxを含む)の上で可動する仮想化環境です。カーネルはコンテナ間で共有しているものの、プロセス、ファイルシステム、ネットワークはそれぞれ独立していて個別に隔離されています。

そのため、コンテナの中であるポートを待ち受けしているサーバーアプリケーションを動かしていたとしても、そのままでは外部からはアクセスできません。

これを外部からアクセスできるようにするのが -p (--publish)オプションになります。 コンテナのポートを外部に「公開」するという感じですかね。その際に、元々のコンテナが指定しているポート番号と外部公開するポート番号を変えることができ、これをポートマッピングあるいはポートフォワーディングと呼んでいます。

例えば、 -p 9000:8080を指定した場合には、外部から来た9000番へのアクセスをコンテナの8080番へ振り替えるということになります。

$ docker run -t -p 9000:8080 -d --rm gihyodocker/echo:latest
$ curl http://localhost:9000/
Hello Docker!!

複数のコンテナを可動させる場合

Dockerは同じイメージファイルから複数のコンテナを起動することができます。ただし、ポートマッピングを行う場合は少し注意が必要です。上記のコマンドで既に一つのコンテナが可動している状況でもう一つを可動させてみます。

$ docker run -t -p 9000:8080 -d --rm gihyodocker/echo:latest
....
docker: Error response from daemon: driver failed programming external connectivity on endpoint sad_blackwell (....): Bind for 0.0.0.0:9000 failed: port is already allocated.

よく考えたら当たり前ですが、ホスト側のポート9000番が既に使われているので起動できない、というエラーが出ます。その場合には別のポートを指定すればよいだけで、例えば -p 9001:8080とすると以下のようになります。

このように、同じイメージの複数のコンテナも別々のポートで可動させることで共存させることができます。

まとめ

Dockerの -p/--publishオプションについてまとめてみました。実際には、複数のコンテナを管理する場合は docker-composeを利用することが多く、その場合にはポートマッピングの指定はdocker-compose.yml(docker-composeの設定ファイル)に書かれることになりますが、それに関してはまた機会のある時に。