docker の --platform の「すげー」かったり「すげーくなかったり」する話と buildx の「悩ましい」話

はじめのほぼ一歩の次に取り組むネタじゃないんだろうけれども。

「過渡期」という一言で片付けられる問題、だったらいいね、と思う。昨日から一日かけてやってみたことから、現状についてそう思った。


まず、Docker が「解決したい問題」について、改めて考える。大きな柱がいくつかあるが、その一つが「どんなホスト環境であろうと誰もが同じソフトウェアを使えて、同じ結果を得ることが出来る」であることには異論ないだろう。そうでしょ?

これは「Docker が OS の垣根を超えてくれる」ということがまず一つ。ホストが Windows でも Mac OS でも、BSD Unix でも、一枚 linux エミュレーションの層を挟み込むことによって「どこでも linux で動くものを動かせる」ということになる。こちらについての恩恵については、「はじめの 1.3 歩」で存分に語ったと思っている。

もう一つが「Docker がプラットフォームの垣根を超えてくれる」という、もう一つのさらに広い根底の部分(狭義だと「platform = machine architecture + OS」)。たとえばワタシの PC はいわゆる「architecture = amd64」機なのだが、「arm32v7 機向けとして作られたものを動かせる」。いや、「動かせる場合は動かせる」。そして、「amd64」機で「arm32v7 環境を(作れる場合は)作れる」。このクロスプラットフォーム性についてが、なんともまぁ「惜しい、ほんとうに惜しい」ことになってる。十分に今でもすげーのだが、というか昔を知ってるほど感動するよ、でも「まだといえばまだ」なのだ、なんとも歯痒い。


「amd64」と「arm32v7」をまたぐことが「どこででも」とはいかないのが実情。たぶん qemu が関係してるんではなかと思うんだけれど、少なくとも本日時点での GitHub workflow では、standard_init_linux.go:nnn: exec user process caused “exec format error” が起こる。

そもそもがプラットフォーム境界を易々と超えてしまうことは凄まじいことだ、ということは、昔を知っているほど思う。ので、出来ないことに驚きはしないんだけれど、今の問題は「出来てしまう環境がしっかり存在している」ことである。だから出来ない環境が悪目立ちしてしまう。


クロスプラットフォームに関し、「クロスビルド」と「クロスランタイム」とでも言うんかね、日本語で言えば「作れることと動かせること」、この二つは別物だし、今でも分けて考えたほうがいい。近い将来その垣根がなくなるであろうとしても、現状は。

「クロスコンパイル」というアプローチに皆が注目していなかった頃の話から始める。時は 1990 年代。これはいわゆる「UNIX ワークステーション文化の終焉期」と「Windows を中心とした PC 文化の爆発的普及」という時期にあたっている。研究機関や大学だけの特権に近かった「インターネット」が一般に普及したのもこの時期である。

この、2000年になる前の時代は、まずは「その環境専用のバイナリがその環境で動けばいい」(Windows など)か、「同じソースコードがまったく異なる環境でコンパイル出来て、それが動けばいい」(Unix 系)という二項対立だったといって良い。後者は「移植性」と言う言い方をして重要視されていた。つまり amd64 機だろうと s390x 機だろうと「同じ C ソースコードをその環境でビルド出来て、動かせる」。これが、およそ 1995 年くらいまでの傾向。

その後、少しずつ「クロスコンパイル」への需要が高まってくる。当時実際によく必要だったものとしては、x86/x86_64/amd64/i386 の行き来で、これは例えばあなたが x86 マシンを使っていたとして、そのマシン環境で「i386 用のバイナリを作(れ)る」ということ。これによって開発者自身が実機を持っていなくても、多様な環境向けにソフトウェアのバイナリパッケージを配布出来る、素敵だ、となっていったのが、1995 年頃以降から今まで。linux の爆発的普及がこれへの需要を高めたと言ってよい。linux ディストリビュータにとってこれは非常に重要なことだ。ただしこれには「x86マシンで、作った i386 バイナリを動かせる」話は含まれない。これは実際にその実機を用意して、そこに持っていって動かす必要がある(あった)。

「x86マシンで、作った i386 バイナリを動かせる」という目標も含め、いわゆる「環境の垣根を超える」ための「仮想化」を我々一般人が利用出来るようになりはじめたのは2000年代。マイクロソフトの Virtual PC も、確か 2006 年よりは前に誕生してたと思う。一瞬記憶違いかと思ったが、概ね間違った記憶ではなさそう(2004年には使えてたことがわかる)。正確なことを言えば、「レトロ環境のエミュレータ」はゲーム機のものを中心に 1990 年代から盛んではあったものの、その時点でフレッシュでアクティブな OS のエミュレーションは、やはり2000年代に入ってからだったと思う。そして ZEN、KVM、QEMU あたりが「本格的なビジネス用途」でさえも頻繁に使われるようになったのは 2010 年代。「クラウド」という言葉の裏には仮想化がある。ただし、ここでも基本的には「OS の境界を超える」ことが主目標で、「x86マシンで、作った i386 バイナリを動かせる」はまだ夢物語だったと言ってよい(出来ないことの方が多かった)。

そして今に至る。びっくりするよ。Docker Desktop for Windows を導入出来た Windows 11 on AMD64 機にて、まずは以下の Dockerfile をビルド出来ることを確認せよ:

Dockerfile
1 FROM arm32v7/buildpack-deps:22.10
2 
3 RUN apt-get -q update && apt-get -yq --no-install-recommends upgrade
4 RUN apt-get -yq --no-install-recommends install less

ビルドと中身確認:

 1 [me@host: mywork]$ docker build -f ./Dockerfile --platform=linux/arm/v7 -t mytestcntn .
 2 [+] Building 1823.7s (7/7) FINISHED
 3  => [internal] load build definition from Dockerfile                               1.2s
 4  => => transferring dockerfile: 188B                                               0.0s
 5  => [internal] load .dockerignore                                                  0.9s
 6  => => transferring context: 2B                                                    0.0s
 7  => [internal] load metadata for docker.io/arm32v7/buildpack-deps:22.10           19.7s
 8  => [auth] arm32v7/buildpack-deps:pull token for registry-1.docker.io              0.0s
 9  => [1/3] FROM docker.io/arm32v7/buildpack-deps:22.10@sha256:0473d9fa4d9...269  1418.5s
10  => => resolve docker.io/arm32v7/buildpack-deps:22.10@sha256:0473d9fa4d9...269     0.3s
11     ...
12  => => extracting sha256:4e63839d7ac5bea9a287ece8da8807fc2777c0ab6f22e91...      173.5s
13  => => extracting sha256:e0d33c06fec115a97cc09b74dba2ffb431b6ae57d7be0f2...      493.8s
14  => [2/3] RUN apt-get -q update && apt-get -yq --no-install-recommends upgrade   266.7s
15     ...
16 [me@host: mywork]$ docker inspect mytestcntn | grep -i arch -B2 -A2
17             "Labels": null
18         },
19         "Architecture": "arm",
20         "Variant": "v7",
21         "Os": "linux",

ここまでが「クロスビルド」ね、「Docker Desktop for Windows を導入出来た Windows 11 on AMD64 機で linux on ARM/v7 環境のブツを作れる」。そして、本日時点での GitHub workflow で出来ないこと。この「ビルド出来ない」の中身は実は「実行出来ない」ことが原因なことは、…、まぁ貼ったリンク先を読んだ人ならわかるだろうけど、要するにホスト環境にある「Dockerfile に書かれた手順を実行するエミュレーション」の層がうまくプラットフォームの違いを扱えなくて失敗するということ。workflow の定義でいうと「run-on: ubuntu-latest」で amd64 以外を(うまく)扱えないということ。エラーログから、qemu のレイヤーの問題みたい。(ほかのプロジェクトをみるに、たぶん「前までは出来てた」んだと思う。何の問題もなく出来てたっぽい記述になってるので。でも今日の今は出来ない。)

で、これを「ワタシの Windows 11 on AMD64 で動かせるのか」が、まぁビックリするほど何の支障もなく動く:

 1 [me@host: mywork]$ docker run -it --rm mytestcntn bash
 2 WARNING: The requested image's platform (linux/arm/v7) does not match the detected host platform (linux/amd64) and no specific platform was requested
 3 root@45a83a277252:/# ls
 4 bin  boot  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
 5 root@45a83a277252:/# exit
 6 [me@host: mywork]$
 7 [me@host: mywork]$ docker run -it --rm --platform=linux/arm/v7 mytestcntn bash
 8 root@ddf1b87301fc:/# cd /tmp
 9 root@ddf1b87301fc:/tmp# cat > psizeof.c
10 #include <stdio.h>
11 
12 int main()
13 {
14     printf("sizeof(long)=%d\n", sizeof(long));
15     printf("sizeof(double)=%d\n", sizeof(double));
16     printf("sizeof(size_t)=%d\n", sizeof(size_t));
17     return 0;
18 }
19 root@ddf1b87301fc:/tmp# gcc psizeof.c -opsizeof
20 root@ddf1b87301fc:/tmp# ls -l
21 total 12
22 -rwxr-xr-x 1 root root 7808 Jun  8 15:38 psizeof
23 -rw-r--r-- 1 root root  198 Jun  8 15:32 psizeof.c
24 root@ddf1b87301fc:/tmp# ./psizeof
25 sizeof(long)=4
26 sizeof(double)=8
27 sizeof(size_t)=4
28 root@ddf1b87301fc:/tmp# exit

繰り返すが、これを動かしている環境は「Windows 11 on AMD64」。そして、動かしているものは「linux ubuntu on ARM/v7」(32bit)。

これで 32bit/64bit の差異、エンディアンの差異などについての検証を、机上ではなく実際に動かして確認することが出来る、てことで、これは OSS 開発者なんかにとっては、ほんとうに嬉しいことだろう、知らんけど。


さらに話を進めると、明るい未来と現時点でのダークサイドが顕わになってくる。buildx の話だ。

buildx についてのワタシのおぼろげな把握は:

  1. 元々は「Morby BuildKit」という拡張。
  2. これが本体に取り込まれつつあり、Docker Desktop では組み込まれていて、そうでない場合も後からプラグイン出来る。
  3. それが buildx。
  4. そしてやろうと思えば「build==buildx」にも出来る。

設計からしてこういう置き換えが可能なものらしいんだけれど、これこそが「本日時点での悩み」となる…、のだが、話は順に。

まず「なんで buildx したくなった?」なのだが、これである。具体的には、まずは以下が「出来ない」:

一気に色んなプラットフォームに対応したいぜ、のつもり
1 [me@host: wk]$ docker build --platform=linux/amd64,linux/ppc64le -t hhsprings/hoge .

これが出来ないからと言って、正直「そりゃそうだ」と今なら思うわけだ、ここまで長々言ってきた通り、そもそも「クロスビルド出来ている」だけでも十分に尊くて高等なことなのだから。けれども buildx ではそれが出来るという:

一気に色んなプラットフォームに対応したいぜ、のつもり version 2
1 [me@host: wk]$ docker buildx build --platform=linux/amd64,linux/ppc64le -t hhsprings/hoge .

これはまぁ出来るは出来るんだけれど、これの前に下ごしらえが必要。例えば:

一気に色んなプラットフォームに対応したいぜ、のつもり version 3
1 [me@host: wk]$ docker buildx create --name=mybuilder --use
2 [me@host: wk]$ docker buildx build --platform=linux/amd64,linux/ppc64le -t hhsprings/hoge .

そして「出来るは出来るんだけれど」という前置きがさらに増えてきてイライラが募ってくる。これね、このままだと「おれの作ったコンテナはどこーっっ!!」と絶叫することになる。この例で作られるコンテナは「どこからも使えない」。docker images だので見える実体にならないの。

これには-o」(--output)指定が必須、ということ。だけれども、これこそが問題の根幹。これ、「type=docker」がちょうど buildx でない build と等価なのだけれど、これが multi-arch 非対応なのだ。

理想像について https://docs.docker.com/desktop/multi-arch/ が書いていて:

Leverage multi-CPU architecture support

Docker images can support multiple architectures, which means that a single image may contain variants for different architectures, and sometimes for different operating systems, such as Windows.

When running an image with multi-architecture support, docker automatically selects the image variant that matches your OS and architecture.

Most of the Docker Official Images on Docker Hub provide a variety of architectures. For example, the busybox image supports amd64, arm32v5, arm32v6, arm32v7, arm64v8, i386, ppc64le, and s390x. When running this image on an x86_64 / amd64 machine, the amd64 variant is pulled and run.

Multi-arch support on Docker Desktop
Docker Desktop provides binfmt_misc multi-architecture support, which means you can run containers for different Linux architectures such as arm, mips, ppc64le, and even s390x.

This does not require any special configuration in the container itself as it uses qemu-static from the Docker for Mac VM. Because of this, you can run an ARM container, like the arm32v7 or ppc64le variants of the busybox image.

うまくいっている部分についてはこの通りだと理解はしたんだけれど、まさに「過渡期」を絶賛進行中なのはやってみるとわかるわけだ。GitHub workflow の問題については binfmt_misc 相当の何かが今欠落しちゃってるということかと思う。あるいはそれを出来るようになる設定が出来たりする? 少なくとも、何もしないなら、先の「exec format error」エラーで、目的を遂行出来ないわけだ。そしてこのことと先ほどの「-o type=docker は multi-arch 非対応」のあわせ技で非常に悩ましいことになるのである。

いいだろうか。「-o type=image,push=false」は「multi-arch 対応」しているものの、「push していないものは、その内容を利用出来ない(run出来ない)」。「-o type=image,push=true」は「multi-arch 対応」しているし、その内容を使えるものの、「push するのだから、その時点で(デフォルトでは Docker Hub に向いているので)全世界に向けての公開を意味する」。つまり「公開前にローカルにお試し」出来ない。「全世界に向けての公開を意味」させたくなければ、レポジトリをプライベートにする(無料では一つしか作れない)か、オレオレレジストリを立てて使うかしかない。

「全世界に向けての公開を意味」を許容できたとしても、GitHub workflow で実行出来ないとなれば、非力なネットワークの非力な PC での実行で頑張るしかない。Dockerfile 内記述に従った各 RUN などの個々はせいぜい倍未満の性能比だが、問題はネットワークで、おそらく1000倍くらいの差がある。GitHub workflow で数秒で終わる pull や push は、Pocket Wi-Fi なワタシのネットワークでは、これが10分だの30分だの一時間だのかかる。タイムアウトなども頻発するし。

話はこれだけでは終わらない。最初の例:

Dockerfile
1 FROM arm32v7/buildpack-deps:22.10
2 
3 RUN apt-get -q update && apt-get -yq --no-install-recommends upgrade
4 RUN apt-get -yq --no-install-recommends install less

これは実に「理想像ではない」。なぜなら「arm32v7/」というのが「マルチプラットフォーム」のためのソースコードとして相応しくない記述だからだ。単一イメージで複数の architecture 版を内蔵出来る、というのが設計らしいので、こうやって Dockerfile にプラットフォームを書くようではダメなのだ。ビルド実行時の指定だけに依存して欲しいのだ。けど、ワタシはこのやりかたしかまだ見つけられてない(FROM で引き継ぐ場合の話だよ、しかも FROM する相手次第と思う)。ARG で置き換えられるようにはしたけれど、そもそも「--platform=linux/arm64/v8」という指定と「arm64v8」との対応関係を自力で管理しなければならないのが非常に腑に落ちない。

そんなこんなでワタシのものについてはこの状況である


もうひとつ。「buildx を build そのものとして使う」話。

察しがいい人はもうわかるかもしれない。これはワタシは絶対にお勧めしない。今は。

-o」(--output)指定が必須、の件である。つまり、「素の build では拒絶されるオプション -o 指定が不可欠」なので、とても混乱する。そんな数文字のタイピングを節約する意味はない。素直に「buildx」のまま使ったほうが、ワタシは精神衛生上良いと思ってる。


そんなわけだ。

どんなわけだ?

基本的に「docker の --platform はすげー」であることは間違いないんだけれど、これもね、特に「マルチプラットフォーム対応」まで考える場合には、まだ「こういうトラブルに対しても自力で問題解決するのが苦でない人」以外にはなかなか説明するのに臆する、てとこかなと思う。正直まさに「隔世の感がある」という言葉がこれほどしっくりすることはない、てほどに「感激」出来るものなんだけどねぇ、ワタシにとっては…。なかなか悩ましいところだ。

こうしたことが「安定」するのはいつの日か、は読めない。ただ、「情報」は少しずつ整っていくんだろうから、まぁ気長に待てる人は待てばいいのだろうけどね…、情報さえ整ってれば措置もそれほど難儀じゃなくなるのだから。

なお、冒頭に書いた通り、まだワタシはたった一日足掻いただけでこれを書いた。ので、もしかしたら大事なことを見逃してる可能性はある。特に GitHub workflow に関して。皆出来てるようなので、ワタシだけ出来ないんだとするなら何か統合出来るオプションがあるんだろう。そうでないならまさに「今時点での問題」。どちらなのかはわからない。とにかく「たった一日足掻いただけでこれを書いた」ことは念頭にこれを読んで欲しい。…てか最後に書くことじゃないか…。


すぐさま(8:15)追記:
GitHub workflow については拡張が提供されてた:

ゆえにワタシのものはこうなった

もちろんこれが解決したからと言って buildx の悩ましさが消えるわけではない。まぁこれが一番大きかったのは確かだけれども。


2022-06-22追記:
なんとなく「オレオレレジストリを立てて使うかしかない」の件、敷居が高いと思い込んでたんだけれど、「なんだよ、docker のことは docker で解決すればよくね?」と今更:

「もちろんこれが解決したからと言って buildx の悩ましさが消えるわけではない」はそのままだけど、「全世界公開しないと run 出来ない」問題はこれで解決出来るので、ずいぶん良くなったとは思うよこれで。



Related Posts