entrykitがM1 mac(Apple Silicone)で実行できないときの対処法

現状

M1 macでDockerのApple M1 Tech preview 7 *1 を動かしています。

golang:1.15 imageがたまたま手元で動いていたのでこれで試します。

entrykitはv0.4.0のバイナリを配布していますが、これを実行しようとすると以下のようにgoレベルで落ちてしまい実行できません。

$ docker run --rm -it golang:1.15 bash
root@aa08b9d02add:/go# wget https://github.com/progrium/entrykit/releases/download/v0.4.0/entrykit_0.4.0_Linux_x86_64.tgz
root@aa08b9d02add:/go# tar -xvzf entrykit_0.4.0_Linux_x86_64.tgz

root@aa08b9d02add:/go# ./entrykit
runtime: failed to create new OS thread (have 2 already; errno=22)
fatal error: newosproc

runtime stack:
runtime.throw(0x84a820, 0x9)
    /usr/local/go/src/runtime/panic.go:527 +0x90
runtime.newosproc(0xc820026000, 0xc820035fc0)
    /usr/local/go/src/runtime/os1_linux.go:150 +0x1ab
runtime.newm(0x8e5980, 0x0)
    /usr/local/go/src/runtime/proc1.go:1105 +0x130
runtime.main.func1()
    /usr/local/go/src/runtime/proc.go:48 +0x2c
runtime.systemstack(0xa2b6e0)
    /usr/local/go/src/runtime/asm_amd64.s:262 +0x79
runtime.mstart()
    /usr/local/go/src/runtime/proc1.go:674

goroutine 1 [running]:
runtime.systemstack_switch()
    /usr/local/go/src/runtime/asm_amd64.s:216 fp=0xc820020770 sp=0xc820020768
runtime.main()
    /usr/local/go/src/runtime/proc.go:49 +0x62 fp=0xc8200207c0 sp=0xc820020770
runtime.goexit()
    /usr/local/go/src/runtime/asm_amd64.s:1696 +0x1 fp=0xc8200207c8 sp=0xc8200207c0

あまりパソコンに詳しくないのですが、 downloadしたものはx86_64(amd64)環境のbuildと思われるので、aarch64(arm64)環境では動かないのかなと推測します。

また、entrykitは長らくreleaseされておらず、作者も興味を失っている気がする(憶測)。

界隈で一瞬流行ったので、なんとなく使われているということが多いのではないでしょうか。

考えうる解決法

解決方法はいくつか考えられます。

1. entrykitを捨てる

そもそもprehookだけの利用の場合、entrykitは必要ありません。

prehookコマンドの正体は、

$ prehook ① -- ②

となっている場合、

  1. ①を外部コマンドとして実行する(複数可)。その後②でexecする。
  2. --以降がなく①だけの場合は①でexecする。

ただこれだけです。

よって

Dockerfile

ENTRYPOINT[ \
  "prehook", "ruby -v", "--", \
  "prehook", "bundle install", "--" ]

Dockerfile

ENTRYPOINT[ "./entrypoint.rb" ]

entrypoint.rb

#! /usr/bin/env ruby

system("ruby -v")
system("bundle install")

exec *ARGV

と別ファイルに切り出せます。

もちろんrubyじゃなくshellでもいいです。

Multi stage build

これだけなら話は早いですが、entrykitを使うからには、おそらくprehook以外のentrykitコマンドと組み合わせたいという使い方が多いと思います。

そんなときの対処方法としてMulti stage buildがおすすめです。

FROM golang:1.15 AS entrykit
RUN go get -v -ldflags "-s -w" github.com/progrium/entrykit/cmd

とすると、Docker環境内でdownloadとbuildが始まり、/go/bin/cmdという実行ファイルが生成されます。これがentrykitの実体です。 cmdじゃなくentrykitという名前でbuildする方法がいまいち分からなかったので誰か教えて下さい……。-oはgo getでは使えないらしい。

ともかくこれで、M1環境でしかもgolang version 1.15でbuildしたentrykitのbinaryが手に入ります。簡単。

次にentrykitを使う場所で、entrykitの実体だけCOPYしてきます。

Dockerfile

COPY --from=entrykit /go/bin/cmd entrykit
RUN entrykit --symlink

これで各種コマンドが生成されます。symlinkはCOPYできないのでCOPY後に--symlinkするようにしましょう。 また、使うコマンドが1つだけなら、

COPY --from=entrykit /go/bin/cmd render

だけでもいいです。お好みで。

Cross buildしてbinaryをプロジェクトリポジトリに置いとく

手元でCross buildして複数環境用にbuildしておき、生成されたbinaryが使われるようにuname -mとかで切り替える。。。

面倒そうな上にMulti stage build以上のメリットはなさそうです。

結論

  • entrykit本当にいるのかどうかよく考えよう。
  • Multi stage build便利。

課題

Multi stage buildの場合、entrykitのファイル配置がおかしいのか、go getでversion指定する方法がよく分からない……。go歴0秒なのでご教授いただけると幸いです。

逆に言うとversion指定しなくてもいいのでメンテナンスレス!

感想

バイナリ配布型のプロダクトの場合、fat gemなどと同じく継続的なreleaseが必要でメンテナ負担が大きいのではないかなと推測します。(作ったこと無いのでなんとも言えないけど)

その点、スクリプト型言語なら再buildなどが必要ないので、 ミニマムに使えるけどメンテナンス負担が大きいバイナリ配布と、 導入は大げさだけどメンテナンス負担が小さいスクリプト配布と使い分けがありそうですね。

とはいえ今回のように都度buildすりゃいいじゃんと言われればまあそうなのかも。