この記事は、RevComm Advent Calender 8日目の記事です。
はじめに
こんにちは。PBX チームの山崎です。
RevComm では毎週 Tech Talk と題して社内勉強会が実施されています。
その中で Docker の hello-world というイメージの存在を知り、早速使ってみました。
$ docker run --rm hello-world Hello from Docker! This message shows that your installation appears to be working correctly. (snip)
Hello World が表示されました。これ自体は特に面白みはありません。
ところが思いのほかイメージサイズが小さく、これはちょっと気になります。
$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu latest 3c2df5585507 4 weeks ago 69.2MB hello-world latest 46331d942d63 8 months ago 9.14kB
ということで調べてみました。
中身を見てみよう
普段使うイメージは Linux のユーザーランドがほぼそのまま動いているので、まずはシェルに入ってみます。
$ docker run -it --rm hello-world /bin/sh docker: Error response from daemon: failed to create shim: OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "/bin/sh": stat /bin/sh: no such file or directory: unknown.
失敗しましたね。
このサイズなら当然 sh は入らないので失敗するのは妥当です。
しかし中身が分からないのは困ったなとマニュアルを眺めていると、docker image save というコマンドがありました。このコマンドはイメージを tar ball にできるとあるので早速やってみます。
$ mkdir docker-work $ cd docker-work/ $ docker image save hello-world | tar xvf - $ tree --noreport . ├── 42e000e434c92e9a1ddac6cccd9219b2043d53ddf81e9abbb285dc58edea4f79 │ ├── VERSION │ ├── json │ └── layer.tar ├── 46331d942d6350436f64e614d75725f6de3bb5c63e266e236e04389820a234c4.json ├── manifest.json └── repositories
取り出せました。manifest.json が何やらメタデータっぽい雰囲気を出しているので確認してみます。
[ { "Config": "46331d942d6350436f64e614d75725f6de3bb5c63e266e236e04389820a234c4.json", "RepoTags": [ "hello-world:latest" ], "Layers": [ "42e000e434c92e9a1ddac6cccd9219b2043d53ddf81e9abbb285dc58edea4f79/layer.tar" ] } ]
名前から、layer.tar がファイルシステムのレイヤーかなと想像できます。解凍してみましょう。
$ cd 42e000e434c92e9a1ddac6cccd9219b2043d53ddf81e9abbb285dc58edea4f79/
$ tar xvf layer.tar
x hello
いかにもなファイルが出てきました。ではこれを実行してみると...
$ docker run -–rm -it -v $(pwd):/work -w /work ubuntu root@6cbf4ec43930:/work# ./hello Hello from Docker! This message shows that your installation appears to be working correctly. (snip)
Hello World が表示されましたね。 となると、この hello ファイルが何であるか気になります。調べてみましょう。
root@6cbf4ec43930:/work# apt update && apt install -y file binutils less root@6cbf4ec43930:/work# file hello hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, stripped root@6cbf4ec43930:/work# objdump -D hello | less
static link なので、きっと libc もリンクして単一バイナリで動くようにしているのかなと思いきや、その割にはコードが小さいですね。逆アセンブルしてみるかと objdump を眺めてみても strip されているのでなかなかつらいものがあります。
ここまで調べたら答えを見てもいいでしょうと言い訳しながら答えを開いてみます。
https://github.com/docker-library/hello-world/blob/master/hello.c
なるほど。システムコールを直接実行していますね。
すなわち、hello-world イメージでは単体で動作するバイナリを起動しているということがわかりました。
中身を入れ替えてみよう
中身を理解できたら、今度は書き換えてみたくなりますよね。ということで違うメッセージを表示してみましょう。
ビルド環境を整えるのが面倒なので、元の hello ファイルを書き換えることにします。
root@6cbf4ec43930:/work# apt install bsdmainutils root@6cbf4ec43930:/work# hexdump -c hello | less (snip) 0000c20 300 003 _ 326 \n H e l l o f r o m 0000c30 D o c k e r ! \n T h i s m e s 0000c40 s a g e s h o w s t h a t (snip)
0x0c30 = 3120 バイト以降を書き換えればよさそうですね。やってみましょう。
root@6cbf4ec43930:/work# echo -n 'RevComm' | dd of=hello bs=1 seek=3120 count=7 conv=notrunc root@6cbf4ec43930:/work# ./hello Hello from RevComm (snip)
メッセージが変わりました。後片づけをして、Docker ホストに戻ります。
root@6cbf4ec43930:/work# rm layer.tar root@6cbf4ec43930:/work# tar -cf layer.tar hello root@6cbf4ec43930:/work# rm hello
ここまでで、以下のようなディレクトリ構成になっています。
$ tree --noreport
.
├── 42e000e434c92e9a1ddac6cccd9219b2043d53ddf81e9abbb285dc58edea4f79
│ ├── VERSION
│ ├── json
│ └── layer.tar
├── 46331d942d6350436f64e614d75725f6de3bb5c63e266e236e04389820a234c4.json
├── manifest.json
└── repositories
変更したファイルを docker image load で取り込みます。 しかしながら、このままではうまくいきませんでした
$ docker image rm hello-world $ tar -c . | docker image load efb53921da33: Loading layer [==================================================>] 10.75kB/10.75kB invalid diffID for layer 0: expected "sha256:efb53921da3394806160641b72a2cbd34ca1a9a8345ac670a85a04ad3d0e3507", got "sha256:695cd43dbd538aae8230019f942d69bc53390dd7710063aae6b58cbb469414e1"
sha256 ハッシュが異なると言っています。おそらく layer.tar を書き換えたので、そのハッシュ値を各所で更新する必要がありそうです。 layer.tar の sha256 の値と、元の hello-world イメージ内設定ファイルを眺めていくと一致する箇所がありました。試行錯誤したところ、以下のように書き換えると動きました。
- sha256sum layer.tar の結果で、46331d(省略).json の "diff_ids" を更新する
- 46331d(省略).json のファイル名を、自分自身の sha256 の値に変更する
- manifest.json の "Config" を、2.で変更したファイル名にする
でもってワクワクしながら実行してみると...
$ tar -c . | docker image load 695cd43dbd53: Loading layer [==================================================>] 10.75kB/10.75kB Loaded image: hello-world:latest $ docker run --rm hello-world Hello from RevComm (snip)
動きました!
まとめ
最初はなんだ Hello World かと思っていましたが、探ってみると Docker の仕組みに対する理解が深まりました。Hello World といえど奥が深いですね。
おわりに
RevComm にはさまざまなバックグラウンドを持ったエンジニアが集まっています。Tech Talk の他にもさまざまな場面で知らない技術に触れ、そして業務で挑戦する機会が数多くあります。
あなたのご参加をお待ちしております!