概要
Docker for Macを使っていて、80番ポートのコンテナを複数起動したいとする。
この場合、最初に起動したコンテナはデフォルトで0.0.0.0:80で待受するので、2つ目を起動しようとすと、
docker: Error response from daemon: driver failed programming external connectivity on endpoint nginxB (1c90fd41c712e357ef339bfd99ccda3258b20f5955ec8583a52c18bdaf643d91): Bind for 0.0.0.0:80 failed: port is already allocated.
と言ったエラーになって、起動できない。
通常、こうならないように前段に80番で待受するNginxなどを使って、リバースプロキシするか、10080:80の様に待受ポートを変えるのが普通だと思う。
しかし、なんとかして80番ポートを待ち受けするコンテナを複数起動する良い方法がないか探したところ、一応方法があったので記録しておく。
方法1: 割当られているネットワークのIPアドレスを複数使う
例えば、WiFiを使ってインターネットに接続していたとすると、基本的にはDHCPでIPアドレスが振られる。
同じネットワーク帯のIPアドレスをもう一つもらってしまう方法で上記エラーが解決できる。
まず、Macで複数IPを設定する。
WiFiだったら既存のWiFiインタフェースを選択して複製をする。

次に手動でIPアドレスを設定する。
適用すると下記のようにIPアドレスが振られる。(IPは適当)
$ ifconfig
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
ether a8:66:7f:1c:35:e9
inet6 fe80::84b:528c:e2d6:a4cc%en0 prefixlen 64 secured scopeid 0x5
inet 192.168.200.244 netmask 0xffffff00 broadcast 192.168.200.255
inet 192.168.200.254 netmask 0xffffff00 broadcast 192.168.200.255
nd6 options=201<PERFORMNUD,DAD>
media: autoselect
status: active
IPアドレスを2つ所有したら、下記のように実行する。
$ docker run -dti --name nginxA -p 192.168.200.244:80:80 -d nginx
$ docker run -dti --name nginxB -p 192.168.200.254:80:80 -d nginx
そうすると下記のように待ち受けができるようになる。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b1e139ccd611 nginx "nginx -g 'daemon of…" 15 minutes ago Up 15 minutes 192.168.200.254:80->80/tcp nginxB
598a1a2dbcf5 nginx "nginx -g 'daemon of…" 16 minutes ago Up 16 minutes 192.168.200.244:80->80/tcp nginxA
もちろんブラウザでそれぞれのIPアドレスで接続できるようになる。
ただ、IPアドレスを自由に割り振りできる自宅のネットワークだからできるのであって、会社では使えないだろうなと思う。
方法2: lo0にaliasでIPアドレスを割り当てて使う
lo0には127.0.0.1が振られているが、そのインターフェースのaliasにIPアドレスを振ってしまう。
$ sudo ifconfig lo0 alias 192.168.200.1/24
$ sudo ifconfig lo0 alias 192.168.201.1/24
$ ifconfig lo0
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
inet 127.0.0.1 netmask 0xff000000
inet6 ::1 prefixlen 128
inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1
inet 192.168.200.1 netmask 0xffffff00
inet 192.168.201.1 netmask 0xffffff00
nd6 options=201<PERFORMNUD,DAD>
上記ネットワーク帯でdockerのnetworkを作成する。
$ docker network create --subnet 192.168.200.1/24 --gateway 192.168.200.1 mynetworkA
$ docker network create --subnet 192.168.201.1/24 --gateway 192.168.201.1 mynetworkB
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
b607e1b256d3 bridge bridge local
1a9676066305 host host local
c9b3bc8f2c03 mynetworkA bridge local
f4c1995c0da5 mynetworkB bridge local
20562a1fec5d none null local
そうしたら、docker起動時の公開ポートにIPアドレスを指定して、runしてあげる。
$ docker run -dti --name nginxA --net=mynetworkA -p 192.168.200.1:80:80 --ip=192.168.200.2 nginx
$ docker run -dti --name nginxB --net=mynetworkB -p 192.168.201.1:80:80 --ip=192.168.201.2 nginx
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8f3b951723a6 nginx "nginx -g 'daemon of…" 28 seconds ago Up 27 seconds 192.168.200.1:80->80/tcp nginxA
bc36df351589 nginx "nginx -g 'daemon of…" 54 seconds ago Up 53 seconds 192.168.201.1:80->80/tcp nginxB
そうするとエラー無くnginxが80番ポートで2個起動する。もちろん、ブラウザからIPアドレスを指定すれば、接続できるし、各コンテナからもインターネットに出ていくことができる。
$ curl -I http://192.168.200.1
HTTP/1.1 200 OK
Server: nginx/1.15.3
Date: Sun, 23 Sep 2018 11:06:05 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 28 Aug 2018 13:32:13 GMT
Connection: keep-alive
ETag: "5b854edd-264"
Accept-Ranges: bytes
$ curl -I http://192.168.201.1
HTTP/1.1 200 OK
Server: nginx/1.15.3
Date: Sun, 23 Sep 2018 11:06:08 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 28 Aug 2018 13:32:13 GMT
Connection: keep-alive
ETag: "5b854edd-264"
Accept-Ranges: bytes
LoginHookの設定
ifconfigでalias指定した場合は、Macの再起動時に設定が消えてしまう。
Macの場合、LoginHookというログイン時にスクリプトを実行できる便利ワザがあるので、それで起動時にifconfigを実行するようにしてあげる。
$ vim ~/.lo0ip.sh
#!/bin/bash
sudo ifconfig lo0 alias 192.168.100.1/24
sudo ifconfig lo0 alias 192.168.101.1/24
$ chmod u+x ~/.lo0ip.sh
上記スクリプトをホーム直下に置いて、defaultsでwriteする。
$ sudo defaults write com.apple.loginwindow LoginHook ~/.lo0ip.sh
設定確認は下記コマンドで可能。
$ sudo defaults read com.apple.loginwindow LoginHook
/Users/kazuma/.lo0ip.sh
これで、再起動しても常にlo0にaliasが設定されるようになる。
まとめ
会社でちょっと要望があったので、なんとかならないか調べたら、こんな方法でいけそうかなぁという感触を得た。
とはいえ、実際にみんなに同じことをさせるのはいささか仰々しいような気がするので、実際にやるかは応相談だと思う。
しかし、お陰でdockerのnetwork周りが少しだけ勉強になったので、個人的には得るものはあった。
あと、MacのLoginHookも便利だなと思う。
以前Macで定期実行させるのにCronじゃなくてLaunchdを使うという記事で、スクリプトを定期実行させていたけど、LoginHookのほうがはるかに便利だと思った。
おまけ
lo0のaliasを解除するには下記で解除可能。
$ sudo ifconfig -alias 192.168.200.1
LoginHookの解除。
$ sudo defaults delete com.apple.loginwindow LoginHook
$ sudo defaults read com.apple.loginwindow LoginHook
2018-09-23 21:23:43.996 defaults[2379:18698]
The domain/default pair of (com.apple.loginwindow, LoginHook) does not exist
dockerネットワークの確認。
docker network ls
NETWORK ID NAME DRIVER SCOPE
e864f9db3ea2 bridge bridge local
1a9676066305 host host local
c9b3bc8f2c03 mynetworkA bridge local
f4c1995c0da5 mynetworkB bridge local
20562a1fec5d none null local
dockerネットワークの詳細を確認。
docker inspect mynetworkA
[
{
"Name": "mynetworkA",
"Id": "c9b3bc8f2c039fe70cd1359ecf88cf902f66665d27edaf78cdc3beb2c0a6bc10",
"Created": "2018-09-23T10:57:41.803838Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "192.168.200.0/24",
"Gateway": "192.168.200.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"8f3b951723a69ba97fb702e327885187d88ab1b3cbb3a7ab205898df726e8596": {
"Name": "nginxA",
"EndpointID": "631fea0b6c656f99a310828578ef2d50a6a710e9e8a9918b1904f8b19a0ea6c0",
"MacAddress": "02:42:c0:a8:c8:02",
"IPv4Address": "192.168.200.2/24",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
参考リンク
Docker version: Running 2 sites/containers at same time · Issue #1393 · geerlingguy/drupal-vm · GitHub
Macのログインフックを利用して自動でlo0のipアドレスを割り振る – joppot
<p style='padding: 5px;'>