鳩小屋

落書き帳

Dockerイメージの構成

Dockerイメージの内容を確認してみます。
f:id:FallenPigeon:20210425161556p:plain

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の実行時情報やイメージレイヤから作成されたファイルシステムをもとに実行されます。

コンテナランタイム

コンテナはコンテナランタイムと呼ばれるソフトウェアが実行しています。

コンテナランタイムには高レベルコンテナランタイムと低レベルコンテナランタイムがあります。

f:id:FallenPigeon:20210429105755g:plain
www.publickey1.jp

高レベルコンテナランタイム(containerd)はKubernetesとDockerと直接やり取りするランタイムです。基本的には、コンテナを論理単位で管理する機能がメインになっています。開発工程でいえば設計レベルのような粒度です。一方、低レベルコンテナランタイム(runc)は、システムコール発行によってOSの機能に直接アクセスしながらコンテナを動作させます。高レベルコンテナランタイムと低レベルコンテナランタイムの違いとしては、OSへの依存度があります。高レベルコンテナランタイム(contianerd)は、特定のOSに依存しないため、理論上LinuxでもWindowsでも動作します。一方、低レベルコンテナランタイム(runc)は、Linux Kenelの機能に強く依存しているためWindowsでは動きません。WIndowsではhcsがLinuxのruncにあたります。概念レベルの仕様は同じですが、iptablesの箇所がWindows Firewallで構成されていたりと、実装は全く異なります。

f:id:FallenPigeon:20210429122407p:plainf:id:FallenPigeon:20210429122410p:plain

いずれにせよ、実際にコンテナを実行するのは低レベルコンテナランタイムになります。 そのため、Dockerイメージのコンフィグやファイルバンドルがリレー形式で、高レベルコンテナランタイムや低レベルランタイムに受け渡されていきます。特に、低レベルコンテナランタイム関連の仕様は、Open Container Initiativeという団体が策定していて、コンテナイメージはOCIイメージ(image-spec)として定義されています。DockerイメージはOCIイメージと高い互換性があるため、構成に大きな違いはありません。

github.com

ちなみに、Docker、contianerd、runcついでに言うとKubernetesはGo言語で書かれています。コンテナ関連の研究をしたり、コンテナエンジンの開発をする場合は、Go言語の知識が必要になります。逆に、コンテナを使うだけ(インタフェースレベルの理解)であれば、特に意識する必要はありません。セキュリティの視点で見ても、コンテナエンジンレイヤの脆弱性は短期間で修正されることが多いため、そこまでクリティカルではありません。ただし、脆弱性の公開やインシデントに即応するにはこれらの知識が必要になるかもしれません。
Windowsコンテナランタイムは、ブラックボックス実装のため、理解にはリバースエンジニアリングの知識が必要になります。実際、NTTの方ですらWinDBGで処理を追うのを断念したという記事を見かけたので、闇は深そうです。少なくとも私のようなポンコツが挑めば廃人確定でしょう。

OCIやruncについてはまた今度書きたいと思います。