Dockerイメージの内容を確認してみます。
Dockerイメージの中身
Dockerイメージをダウンロードしてtar形式で保存します。
$docker image pull ubuntu:latest $mkdir docker-image $cd docker-image $docker save ubuntu:latest --output ubuntu.tar
展開して中身を確認すると、jsonファイルやいくつかのディレクトリで構成されていることが分かります。
$tar -xf ubuntu.tar $tree . ├── 26b77e58432b01665d7e876248c9056fa58bf4a7ab82576a024f5cf3dac146d6.json ├── 59e53182e47ea02b7d77268a13d8262c19efb4d3dd1eb6cba3af51116b9d0260 │ ├── VERSION │ ├── json │ └── layer.tar ├── 75ce788460b10c20b25c56456cb658024953645afc1d838abacae3ec3f94cd7b │ ├── VERSION │ ├── json │ └── layer.tar ├── 7c7a59dd75533bba2f4d2c48a08a3e6b5de89ee96f1416981936ff7a9369e8ac │ ├── VERSION │ ├── json │ └── layer.tar ├── manifest.json ├── repositories └── ubuntu.tar
manifest.json
manifest.jsonは、Docker imageの構成情報を上位で管理するファイルです。
こちらを確認することで、Docker imageの全体像をつかむことができます。
$jq . manifest.json [ { "Config": "26b77e58432b01665d7e876248c9056fa58bf4a7ab82576a024f5cf3dac146d6.json", "RepoTags": [ "ubuntu:latest" ], "Layers": [ "59e53182e47ea02b7d77268a13d8262c19efb4d3dd1eb6cba3af51116b9d0260/layer.tar", "75ce788460b10c20b25c56456cb658024953645afc1d838abacae3ec3f94cd7b/layer.tar", "7c7a59dd75533bba2f4d2c48a08a3e6b5de89ee96f1416981936ff7a9369e8ac/layer.tar" ] } ]
Config
"Config"(26b77e58432b01665d7e876248c9056fa58bf4a7ab82576a024f5cf3dac146d6.json)には、主にイメージビルド時の環境情報が書かれています。
Dockerfile経由でイメージをビルドしたのであれば見覚えのある設定があるかもしれません。
$jq . 26b77e58432b01665d7e876248c9056fa58bf4a7ab82576a024f5cf3dac146d6.json { "architecture": "amd64", "config": { "Hostname": "", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Cmd": [ "/bin/bash" ], "Image": "sha256:0543ca860eea0b5b793ae01f44ffc8d126f2d9dbd5092bb395091d292af8464b", "Volumes": null, "WorkingDir": "", "Entrypoint": null, ...
Layers
"Layers"では、コンテナのイメージレイヤがアーカイブ形式で管理されています。
また、イメージレイヤのtarファイルを展開するとOS(ubuntu)関連のファイルが含まれていることが確認できます。
$ cd 59e53182e47ea02b7d77268a13d8262c19efb4d3dd1eb6cba3af51116b9d0260 $ tar -xf layer.tar $ ls VERSION boot etc json lib lib64 media opt root sbin sys usr bin dev home layer.tar lib32 libx32 mnt proc run srv tmp var
コンテナは、Configの実行時情報やイメージレイヤから作成されたファイルシステムをもとに実行されます。
コンテナランタイム
コンテナはコンテナランタイムと呼ばれるソフトウェアが実行しています。
コンテナランタイムには高レベルコンテナランタイムと低レベルコンテナランタイムがあります。
高レベルコンテナランタイム(containerd)はKubernetesとDockerと直接やり取りするランタイムです。基本的には、コンテナを論理単位で管理する機能がメインになっています。開発工程でいえば設計レベルのような粒度です。一方、低レベルコンテナランタイム(runc)は、システムコール発行によってOSの機能に直接アクセスしながらコンテナを動作させます。高レベルコンテナランタイムと低レベルコンテナランタイムの違いとしては、OSへの依存度があります。高レベルコンテナランタイム(contianerd)は、特定のOSに依存しないため、理論上LinuxでもWindowsでも動作します。一方、低レベルコンテナランタイム(runc)は、Linux Kenelの機能に強く依存しているためWindowsでは動きません。WIndowsではhcsがLinuxのruncにあたります。概念レベルの仕様は同じですが、iptablesの箇所がWindows Firewallで構成されていたりと、実装は全く異なります。
いずれにせよ、実際にコンテナを実行するのは低レベルコンテナランタイムになります。 そのため、Dockerイメージのコンフィグやファイルバンドルがリレー形式で、高レベルコンテナランタイムや低レベルランタイムに受け渡されていきます。特に、低レベルコンテナランタイム関連の仕様は、Open Container Initiativeという団体が策定していて、コンテナイメージはOCIイメージ(image-spec)として定義されています。DockerイメージはOCIイメージと高い互換性があるため、構成に大きな違いはありません。
ちなみに、Docker、contianerd、runcついでに言うとKubernetesはGo言語で書かれています。コンテナ関連の研究をしたり、コンテナエンジンの開発をする場合は、Go言語の知識が必要になります。逆に、コンテナを使うだけ(インタフェースレベルの理解)であれば、特に意識する必要はありません。セキュリティの視点で見ても、コンテナエンジンレイヤの脆弱性は短期間で修正されることが多いため、そこまでクリティカルではありません。ただし、脆弱性の公開やインシデントに即応するにはこれらの知識が必要になるかもしれません。
Windowsコンテナランタイムは、ブラックボックス実装のため、理解にはリバースエンジニアリングの知識が必要になります。実際、NTTの方ですらWinDBGで処理を追うのを断念したという記事を見かけたので、闇は深そうです。少なくとも私のようなポンコツが挑めば廃人確定でしょう。
OCIやruncについてはまた今度書きたいと思います。