タイトルは釣りです
1677万案件は試してません
対象読者
- 開発環境を docker-compose で構築しているWebプログラマの人々
- 複数案件を抱え、ホスト側ポート番号を考えるのに疲弊している人々
- HTTP
80
->80
はとっておいて8080
にしたろ! - あの案件で
8080
を使ったからこっちは10080
にしたろ! -
10080
の次だからとりあえず20080
にしたろ! - うーん、
8000
!w - HMRで
8080
使いたいけれど使われてるから8081
にしたろ!
- HTTP
問題提起
docker-compose.ymlの例
version: "3"
services:
web:
image: "nginx"
ports:
- "80:80" # ...... 1
...
app:
image: "php-fpm"
...
db:
image: "mysql:5.7"
ports:
- "3306:3306" # ...... 2
environment:
- "MYSQL_ROOT_PASSWORD=root"
secure-db:
image: "mysql:5.7"
ports:
- "3307:3306" # ...... 3
environment:
- "MYSQL_ROOT_PASSWORD=root"
- ホストOSのブラウザで動作確認するためにHTTPサーバーのポートを公開する(
80
) - 同じくホストから繋ぐためにDBのポートを公開する(
3306
) - DBは複数立っていたりする(
3307
)
さて別の案件が降ってきました。
version: "3"
services:
app:
image: "httpd:2.4"
ports:
- "8080:80" # ...... 1
volumes:
- "./public/:/usr/local/apache2/htdocs/"
db:
image: "mysql:5.7"
ports:
- "13306:3306" # ...... 2
environment:
- "MYSQL_ROOT_PASSWORD=root"
secure-db:
image: "mysql:5.7"
ports:
- "13307:3306" # ...... 3
environment:
- "MYSQL_ROOT_PASSWORD=root"
先の案件で振ったポート番号とぶつからないように、well-knownポートの亜種をいい感じに考えて割り当てます
-
80
は別の案件で使ったので8080
-
3306
は別の案件で使ったので13306
-
3307
は別の案件で使ったので13307
さて最初の案件でHMRを導入したくなりました。
version: "3"
services:
web:
image: "nginx"
ports:
- "80:80"
...
app:
image: "php-fpm"
...
db:
image: "mysql:5.7"
ports:
- "3306:3306"
environment:
- "MYSQL_ROOT_PASSWORD=root"
secure-db:
image: "mysql:5.7"
ports:
- "3307:3306"
environment:
- "MYSQL_ROOT_PASSWORD=root"
+ node:
+ build:
+ ...
+ ports:
+ - "8080:8080" # ...... 4
HMR用のNode.jsサーバーを立てたくなりました。
んじゃ8080で…
...
app:
image: "httpd:2.4"
ports:
- "8080:80"
...
あ!8080を既に使っていました…
version: "3"
services:
web:
image: "nginx"
ports:
- "80:80"
...
app:
image: "php-fpm"
...
db:
image: "mysql:5.7"
ports:
- "3306:3306"
environment:
- "MYSQL_ROOT_PASSWORD=root"
secure-db:
image: "mysql:5.7"
ports:
- "3307:3306"
environment:
- "MYSQL_ROOT_PASSWORD=root"
+ node:
+ build:
+ ...
+ ports:
+ - "8081:8080" # ...... 4
8081にしておこう…と、こうなります。
別々の案件を抱える複数の人間が絡むと混乱は加速します。
そのうちポート番号込みでgit管理するのが嫌になってきて、
「ホスト側portはもう各自の.envで管理してくれ」
となります。
version: "3"
services:
web:
image: "nginx"
ports:
- "${WEB_HOST_PORT}:80"
...
app:
image: "php-fpm"
...
db:
image: "mysql:5.7"
ports:
- "${DB_HOST_PORT}:3306"
environment:
- "MYSQL_ROOT_PASSWORD=root"
secure-db:
image: "mysql:5.7"
ports:
- "${SECURE_DB_HOST_PORT}:3306"
environment:
- "MYSQL_ROOT_PASSWORD=root"
node:
build:
...
ports:
- "${NODE_HMR_HOST_PORT}:8080"
WEB_HOST_PORT=80
DB_HOST_PORT=3306
SECURE_DB_HOST_PORT=3307
NODE_HMR_HOST_PORT=8080
version: "3"
services:
app:
image: "httpd:2.4"
ports:
- "${APP_HOST_PORT}:80"
volumes:
- "./public/:/usr/local/apache2/htdocs/"
db:
image: "mysql:5.7"
ports:
- "${DB_HOST_PORT}:3306"
environment:
- "MYSQL_ROOT_PASSWORD=root"
secure-db:
image: "mysql:5.7"
ports:
- "${SECURE_DB_HOST_PORT}:3306"
environment:
- "MYSQL_ROOT_PASSWORD=root"
APP_HOST_PORT=80
DB_HOST_PORT=3306
SECURE_DB_HOST_PORT=3307
幾分マシになった感じがしますが、しばらくすると
- 「何箇所設定するね〜んww」
- 「何もしてないのに
docker-compose up -d
が立ち上がらなくなった」- env_exampleの更新を.envへ反映し忘れている
等々の問題が生じてきます。
解決案 -- ポートをバインドするIPを指定する
-
サンプルrepository
- 名前をdocker-compose r にしてしまう痛恨のミス
公式docに書いてありますが、docker run -p
ではホスト側ポートをバインドするIPアドレスを指定できます。
1つは docker run の実行時、 -p IP:ホスト側ポート:コンテナ側ポート か
-p IP::ポート を指定し、特定の外部インターフェースをバインドする指定ができます。
「IPアドレス」というのは、は論理的なアドレスです。
物理的なホストに対して複数の論理的なアドレスが割り当て可能です。
往来で
「D.Horiyamaさんの80番ポートさん、HTMLファイルをください」
と言われたらHTMLファイルを返しますが、
「‡漆黒の堕天使‡さんの80番ポートさん、HTMLファイルをください」
と言われたら、こっ恥ずかしくて聞いてないふりしますよね。そういうことです。
「この名前で呼ばれた時だけ応答する」ということができるわけです。
さて、このホスト側IP指定、docker-compose.yml でも設定可能です:
version: "3"
services:
web:
image: "httpd:2.4"
ports:
- "${IP}:80:80"
volumes:
- "./public/:/usr/local/apache2/htdocs/"
db:
image: "mysql:5.7"
ports:
- "${IP}:3306:3306"
environment:
- "MYSQL_ROOT_PASSWORD=root"
secure-db:
image: "mysql:5.7"
ports:
- "${IP}:3307:3306"
environment:
- "MYSQL_ROOT_PASSWORD=root"
IP=127.0.0.1
こんな風にします。
127.*.*.*
は ローカルループバックアドレス と呼ばれる特別なアドレスで、ホスト自身を指します。
127.0.0.1
〜127.255.255.254
は全て別のIPアドレスであり、個々にポートをバインドできます。
これを使って、
「IP=127.0.0.1は案件1用」
というようにして、80
や3306
といったwell-known portを惜しげもなくじゃぶじゃぶ使うことができます。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
36d5b663edf6 mysql:5.7 "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 127.0.0.2:3306->3306/tcp, 33060/tcp anken-2_db_1
c347fe183ad6 httpd:2.4 "httpd-foreground" 3 seconds ago Up 2 seconds 127.0.0.2:80->80/tcp anken-2_web_1
8ad0a5ae018d mysql:5.7 "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 33060/tcp, 127.0.0.2:3307->3306/tcp anken-2_secure-db_1
6955a89c43d9 httpd:2.4 "httpd-foreground" 29 seconds ago Up 28 seconds 127.0.0.1:80->80/tcp anken-1_web_1
f13161e0dfef mysql:5.7 "docker-entrypoint.s…" 29 seconds ago Up 28 seconds 33060/tcp, 127.0.0.1:3307->3306/tcp anken-1_secure-db_1
c1905356e589 mysql:5.7 "docker-entrypoint.s…" 29 seconds ago Up 28 seconds 127.0.0.1:3306->3306/tcp, 33060/tcp anken-1_db_1
↑80
,3306
,3307
をanken-1, anken-2それぞれで利用できています。
127.0.0.1:80
, 127.0.0.2:80
にリクエストしてみると:
$ curl 127.0.0.1:80
<html>案件1</html>
$ curl 127.0.0.2:80
<html>案件2</html>
ちゃんと、それぞれ別々のサーバーに処理されていることがわかります。
さて、IPv4発明当初の仕様策定陣は、これほどまでにインターネットが隆盛を極めるとは考えていなかったのでしょう。
全アドレス空間のうち1/256 -- 約1677万個 -- もの膨大なアドレス空間をローカルループバックアドレスに宛ててしまいました。
(グローバルIPアドレスとして使えなくなってしまいました)
おかげさまで、この方法で、理論的には約1677万案件ぶんの docker-compose 環境を同時に抱えられます!(タイトル回収)
生のIPアドレスはつらいので、/etc/hosts
を書いたりDNSを立てたりするとより便利になるでしょう:
...
127.0.0.1 anken-1
127.0.0.2 anken-2
...
curl
$ curl anken-1:80
<html>案件1</html>
mysql
$ mysql -h anken-1 -u root -proot
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.27 MySQL Community Server (GPL)
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> \q
nginx-proxyとの違い
?「nginx-proxyでいいんじゃ?」
nginx-proxyはHTTPにしか使えないと聞きます。(使ったことない)
対して、本記事の方法はHTTP以外にも適用可能です。
macOS固有の問題
Docker Desktop for mac 2.2.x の不具合
Docker Desktop for macでは、2.2.x系にて不具合があり、127.0.0.1(デフォルト)以外のIPアドレスのポートをバインドできなかったようです。
さっさと2.3系に上げましょう。
127.0.0.1 以外のローカルループバックアドレスがデフォルトで使えない
デフォルトでping 127.0.0.2
すら通りません。ifconfig
でエイリアス定義する必要があります。
下記のようなスクリプトを .zshrc
などで実行するとよいでしょう:
for ((i=2;i<256;i++))
do
sudo ifconfig lo0 alias 127.0.0.$i up
done