こんにちは。Legalscapeソフトウェアエンジニアの小林です。
この記事はLegalscape アドベントカレンダー 2021の12/10のエントリです。
皆さん、Docker使ってますか?便利ですよね(テンプレ)
そんな便利なDockerですが、個人的にイマイチポイントだなと思っているのがport 周りの扱いです。
例えば、Mysqlのcontainerを立てた時に3306ポートをhostの3306ポートにmappingするのが一般的かと思います。
version: "3.3"
services:
mysql:
image: mysql:8.0
ports:
- 3306:3306
volumes:
- ./.data/mysql:/var/lib/mysql
environment:
- TZ=Asia/Tokyo
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
こうすることで、hostの3306を通じてMysqlに接続できますね。便利です。1
...
いや、本当に便利ですか?
Dockerが普及した今となっては、複数projectで別のdocker-composeを利用することが多いと思います。その時、各projectにMysqlコンテナが存在したらどうでしょうか?もちろん Bind for 0.0.0.0:3306 failed: port is already allocated.
となりますね。
よく使う回避策としてはhost側portを別のportにmappingすることでしょうか。2
# 前
ports:
- 13306:3306
# 略
この13306のところをprojectごとに変えれば Bind for 0.0.0.0:3306 failed: port is already allocated.
は発生しなくなりますね。めでたしめでたし!
...
本当に?
個人的には、この手法は以下の点でイマイチだと考えています。
-
各projectでport番号を覚えてないといけない
「このprojectのMysqlのportは何番だっけ?」
-
他のprojectで使用したport番号を回避しないといけない
「とりあえず53306とかにmappingするか。・・・被ってるやんけ!」
この問題を回避するためにはどうすればよいでしょうか?
解決策1: (若干イケてない)
先にイケてないポイントを書きますと、GUIユーザーが泣きを見ます。僕は基本的にCUIを使う人間なのでこの方法で8割程度満足できます。
具体的な方法ですが、mappingに使用するhost側のportをランダムにします。
# 前
ports:
- 3306
# 略
port publish時にhost側のportを指定しなければ、hostのランダムなportにmappingしてくれます。
どのportにmappingされたかは、docker ps
や docker inspect
、 docker compose port
を使用すればわかります。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
353e24477126 mysql:8.0 "docker-entrypoint.s…" 10 minutes ago Up 10 minutes 33060/tcp, 0.0.0.0:49583->3306/tcp, :::49583->3306/tcp clever_jemison
[
{
"NetworkSettings": {
"Ports": {
"3306/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "49583"
},
{
"HostIp": "::",
"HostPort": "49583"
}
]
"33060/tcp": null
},
...
},
...
}
]
0.0.0.0:49583
Makefileでこのportを使ってMysql cliを実行するtargetを定義します。
mysql-cli:
mysql --port $(shell docker-compose port mysql 3306 | cut -d: -f2) --host 127.0.0.1 --user root -p
上述したとおり、この方法ではコマンドを駆使してランダムに振り当てられたportを取得しているので、GUIを用いてMysqlに繋いでいる人はとても困ります。
解決策2: (問題点をほぼ完全に解決できるが、現時点でlinuxでしか動かない)
そもそもやりたいことは、containerのportにアクセスしたいだけなので、それをport mapping無しで実現できれば解決します。
そう、contianerのprivateIPを使用すればよいのです。
docker inspectを使えば、containerのprivate IPがわかります。
[
{
"NetworkSettings": {
"Networks": {
"app_default": {
"IPAddress": "172.24.3.2",
...
}
}
...
},
...
}
]
```shell
mysql -h 172.24.3.2 --port 3306 -u root -p
これであればportが重複するなどという問題はそもそも起こりません。IPを調べる手間を除けば完璧に思えます。
ただ、IPを調べるのが面倒くさいです。面倒臭すぎます。
というわけでこれを解決するアプリケーションを作りました。
Dockerコンテナとして起動しておくと、他のコンテナが起動する度にhostの /etc/hosts
を書き変えて、コンテナにアクセスするためのドメインを用意してくれます。
例えば、
version: "3.3"
services:
mysql:
image: mysql:8.0
ports:
- 3306
volumes:
- ./.data/mysql:/var/lib/mysql
environment:
- TZ=Asia/Tokyo
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
networks:
default:
name: projectX
このようなcomposeファイルでMysqlコンテナを起動すると mysql.projectX.docker.internal
というドメインを割り当ててくれるので、
mysql -h mysql.projectX.docker.internal --port 3306 -u root -p
という感じでMysqlに接続できます。どのprojectのMysqlに繋いでいるかも分かりやすいですね。完璧です。
唯一の問題点はMacで動作しないことです(windowsでの動作可否は未確認) 。/etc/private/hosts
を書き換える時に権限エラーが出てしまいます。
バイナリをDockerコンテナとしてではなくネイティブバイナリとしてsudo付きで実行すればこの問題は解決すると思われますが、PC起動時に docker-hosts
の起動が必要になってしまい若干手間なので、なんとかDockerコンテナとして実行できないか模索中です。
解決策をご存じの方がいらっしゃいましたら、是非ご教授ください。
(これに似たようなことDocker本体でやってくれないかなー)
終わりに
Legalscapeではエンジニアの採用を積極的に行っています。是非 採用ページ も見ていってください!
Legalscapeの事業内容や技術スタックなどは、アドベントカレンダーの他エントリをご覧ください。