谷本 心 in せろ部屋

はてなダイアリーから引っ越してきました

MacのDockerに別マシンからアクセスする。

Dockerでk8sやらミドルウェアやらを色々動かしているとマシンのリソースをそこそこ消費するので、NAS代わりにしているIntel Mac miniをDockerのサーバにできないかなと思って試行錯誤しました。

結論

まず、DockerのホストにしたいMac側で次のコマンドを打つ。

docker run \
    -it \
    --rm \
    --name=api-forwarder \
    -p 2375:2375 \
    -v /var/run/docker.sock:/var/run/docker.sock \
    alpine sh -c 'apk add --no-cache socat && socat TCP-LISTEN:2375,reuseaddr,fork UNIX-CONNECT:/var/run/docker.sock'

そしてクライアントにしたいマシンで export DOCKER_HOST=tcp://(ホストのIP):2375 コマンドを打つ。.zshrc にでも書いておけば良い。

色々やった上でこの結論に至ったのですが、その経緯も書いておきます。

Macにsocatを入れると挙動が怪しかった

やりたいことでググるとすぐにこのエントリーが出てきました。 hawksnowlog.blogspot.com socatを使って2375ポートへのアクセスを /var/run/docker.sock のソケットに転送するようです。

まずMacにsocatをインストールして(brew install socat)次のコマンドを打ちます。

socat -d TCP-LISTEN:2375,range=127.0.0.1/32,reuseaddr,fork UNIX:/var/run/docker.sock

そしてクライアント側で export DOCKER_HOST=tcp://(ホストのIP):2375 をすれば、docker ps などのコマンドでホスト側のdockerにアクセスできるようになりました。

ただ動きはするものの、コマンドを打つたびにホスト側のMac(Dockerデーモンとsocatを動かしている方)にエラーが出ていました。

2023/01/22 00:51:18 socat[83752] E write(6, 0x7f7c29009000, 5): Broken pipe
2023/01/22 00:51:19 socat[83753] E write(6, 0x7f7c2a009000, 5): Broken pipe
2023/01/22 00:51:28 socat[83756] E write(6, 0x7f7c2a809000, 5): Broken pipe
2023/01/22 00:54:05 socat[83892] E write(6, 0x7f7c2a809000, 5): Broken pipe
2023/01/22 00:54:06 socat[83893] E write(6, 0x7f7c29009000, 5): Broken pipe
2023/01/22 00:54:15 socat[83894] E write(6, 0x7f7c2a809000, 5): Broken pipe

何これ。socatコマンドに慣れ親しんでないので原因がよく分かりません。どうあれ気持ち悪いので、別の方法を模索します。

Macでは daemon.js のhostsを書いても効果がない

令和5年においては、困りごとはChatGPT先生に聞いてみるというスタイルが主流です*1

ChatGPT先生との会話

どちらの回答も微妙でしたが、デーモンの設定に tcp://0.0.0.0:2375 を入れれば良いことが分かりました。なんだ、そんな簡単な方法があるなら早く言ってくれよ。

Docker DesktopのPreferencesにあるDocker Daemonというメニューでdaemon.jsonを編集できます。デフォルトはこんな感じ。

{
  "builder": {
    "gc": {
      "defaultKeepStorage": "20GB",
      "enabled": true
    }
  },
  "experimental": false,
  "features": {
    "buildkit": true
  }
}

ここにhostsを追加しました。

{
  "builder": {
    "gc": {
      "defaultKeepStorage": "20GB",
      "enabled": true
    }
  },
  "experimental": false,
  "features": {
    "buildkit": true
  },
  "hosts": [
    "tcp://0.0.0.0:2375",
    "unix:///var/run/docker.sock"
  ]
}

これで再起動してみたのですが・・・netstatしてみても、2375ポートがLISTENされている様子がありません。

ちなみにDocker Desktopの設定を有効にして再起動した時点で tcp://0.0.0.0:2375tcp://127.0.0.1:2375 に書き換えられてしまうので、そこも対応しなきゃなと思っていたのですが、どうあれ2375ポートがLISTENされないことには話が始まりません。なぜMacだとこの設定は無視されるのか、引き続き調べてみます。

MacのDockerはVM内で動いている

mac ignore docker hosts daemon.json あたりでググってみると、そのままの質問が見つかりました。 github.com そこに回答もありました。

I suspect the issue in that case is that while the daemon may be listening on those IP-addresses and ports, it's doing so within the VM that it's running in. Those ports are not mapped / forwarded to the macOS host, so won't be accessible there.

英語だとよく分からないので日本語に訳すと「デーモンはたぶんそのアドレスとポートでLISTENしてるけど、VMの中で動いていて、そのポートがmacOSホストにマッピングされてないから効果がないんじゃね?」ということです。日本語にしてもよく分かりません。

あ、そういえばMacのDockerはVMの中で動いているのでした。

LinuxのDocker構成
OS XのDocker構成
(出典:Mac OS X — Docker-docs-ja 1.13.RC ドキュメント

たしかにこの構成だと、Dockerデーモンの設定をいじって2375ポートで待ち受けてもLinux VMが2375ポートで待ち受けるだけになり、Macの2375ポートでは待ち受けされませんね。

Linuxでsocatを使う

上のフォーラムの回答の続きに、DockerでAlpine Linuxを起動してsocatを動かす方法が紹介されていました。

docker run \
    -it \
    --rm \
    --name=api-forwarder \
    -p 2375:2375 \
    -v /var/run/docker.sock:/var/run/docker.sock \
    alpine sh -c 'apk add --no-cache socat && socat TCP-LISTEN:2375,reuseaddr,fork UNIX-CONNECT:/var/run/docker.sock'

Mac/var/run/docker.sock をDockerで起動したAlpine Linux/var/run/docker.sock にマウントして、socatを使って2375ポートをDockerのソケットに流すようにして、Alpine Linuxの2375ポートとMacの2375ポートをマッピングするという形です。

Alpine Linux上のsocatを経由する形
これだとMacにsocatを入れた時のようなエラーメッセージも出ないようですし、Macにsocatを入れなくて済むので、この方法を使うことにしました。

所感

そんなわけで、無事にMacをDockerのサーバとして利用できるようにしたので、メインマシンのリソースを消費しなくて済むようになりました。またM1/M2のMacamd64イメージを実行せざるを得ない時にも(Rosetta2対応により高速化されたとはいえ)Intel Mac miniで実行できますね。

ただこんな一手間を掛けてMacをDockerサーバにするよりも、メモリをたくさん積んだLinuxサーバを1台用意しても良いかも知れませんね。最近vscodeのRemote ContainerやJetBrainsのRemote Developmentのように開発環境をリモートに置くことも流行りつつあるようですから、またちょっと考えたいと思います。

*1:諸説ある