4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Legalscape アドベントカレンダー 2021Advent Calendar 2021

Day 10

dockerのportアクセスをより良くしたい

Last updated at Posted at 2021-12-10

こんにちは。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. は発生しなくなりますね。めでたしめでたし!

...

本当に?
個人的には、この手法は以下の点でイマイチだと考えています。

  1. 各projectでport番号を覚えてないといけない

    「このprojectのMysqlのportは何番だっけ?」

  2. 他のprojectで使用したport番号を回避しないといけない

    「とりあえず53306とかにmappingするか。・・・被ってるやんけ!」

この問題を回避するためにはどうすればよいでしょうか?

解決策1: (若干イケてない)

先にイケてないポイントを書きますと、GUIユーザーが泣きを見ます。僕は基本的にCUIを使う人間なのでこの方法で8割程度満足できます。
具体的な方法ですが、mappingに使用するhost側のportをランダムにします。

# 前
    ports:
      - 3306
# 略

port publish時にhost側のportを指定しなければ、hostのランダムなportにmappingしてくれます。
どのportにmappingされたかは、docker psdocker inspectdocker compose port を使用すればわかります。

docker ps
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
docker inspect
[
    {   
        "NetworkSettings": {
            "Ports": {
                "3306/tcp": [
                    {   
                        "HostIp": "0.0.0.0",
                        "HostPort": "49583"
                    },
                    {   
                        "HostIp": "::",
                        "HostPort": "49583"
                    }   
                ]
                "33060/tcp": null
            },
            ...
        },
        ...
    }   
]
docker-compose port mysql 3306
0.0.0.0:49583

Makefileでこのportを使ってMysql cliを実行するtargetを定義します。

Makefile
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がわかります。

docker inspect
[
    {   
        "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-hosts

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の事業内容や技術スタックなどは、アドベントカレンダーの他エントリをご覧ください。

  1. 余談ですが、docker-composeはコンテナ起動の設定ファイルと認識しているので、1コンテナしか立てない場合も積極的に利用しています。

  2. Mysql固有の回避策として、host側のclientを使わず、docker execでcontaier側のclientを使う方法(これであればそもそもport mappingが不要)が有りますが、portを使用するアプリケーション全般に使用できる方法ではないので、今回はこれについては考えません。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?