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
どちらの回答も微妙でしたが、デーモンの設定に 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:2375
が tcp://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の中で動いているのでした。
たしかにこの構成だと、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ポートをマッピングするという形です。
所感
そんなわけで、無事にMacをDockerのサーバとして利用できるようにしたので、メインマシンのリソースを消費しなくて済むようになりました。またM1/M2のMacでamd64イメージを実行せざるを得ない時にも(Rosetta2対応により高速化されたとはいえ)Intel Mac miniで実行できますね。
ただこんな一手間を掛けてMacをDockerサーバにするよりも、メモリをたくさん積んだLinuxサーバを1台用意しても良いかも知れませんね。最近vscodeのRemote ContainerやJetBrainsのRemote Developmentのように開発環境をリモートに置くことも流行りつつあるようですから、またちょっと考えたいと思います。
*1:諸説ある