RevComm Tech Blog

コミュニケーションを再発明し 人が人を想う社会を創る

docker run hello-world を掘り下げてみた

この記事は、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 イメージ内設定ファイルを眺めていくと一致する箇所がありました。試行錯誤したところ、以下のように書き換えると動きました。

  1. sha256sum layer.tar の結果で、46331d(省略).json の "diff_ids" を更新する
  2. 46331d(省略).json のファイル名を、自分自身の sha256 の値に変更する
  3. 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 の他にもさまざまな場面で知らない技術に触れ、そして業務で挑戦する機会が数多くあります。

あなたのご参加をお待ちしております!

hrmos.co