Docker のイメージ/ファイルシステムの形式 (1)

# はじめに
Docker のイメージやファイルシステムの形式について調べてみました。

Docker のイメージやファイルシステムの形式と言ったとき、次の３つのいずれかを指すと思います。

1. Docker Hub(または Private Registry) からダウンロードされる Docker イメージの形式
2. ダウンロードした Docker イメージをホスト上に保存する際の形式。または、docker build コマンドでビルドしたイメージの形式。
3. コンテナのファイルシステムの形式

今回は 1、つまり docker pull した時に Docker Hub から何がどう落ちてくるのかをまとめてみます。
![DockerImages.png](https://qiita-image-store.s3.amazonaws.com/0/79316/2c2d4cbc-af19-27c0-5c58-d0915bc3b4e5.png)

**調査環境**

* CentOS 7.1 (カーネル 4.2.1)
* Docker 1.8.2 (ストレージドライバは overlay)

# Docker イメージを構成するもの

Docker イメージは次の2種類のファイルから構成されます。

* Manifest ファイル  
  イメージのメタデータを持つ JSON ファイル。
* レイヤーファイル  
  各レイヤーのディレクトリツリーを含んだ tar.gz ファイル。
  １つのレイヤーに対して、１つの tar.gz が存在する。

例えば、１つの Docker イメージは以下のようなファイル群で表現されます。
![DockerImageFiles.png](https://qiita-image-store.s3.amazonaws.com/0/79316/31d80563-81d5-f233-04ce-cdff165eddd7.png)

## Manifest ファイル
Manifest ファイルは、Docker イメージに関する次のような情報を含む JSON ファイルです。
（Manifest のフォーマットに関しては[ここを参照](https://github.com/docker/docker/issues/8093)してください）。

* イメージ名 (例： centos)
* イメージのタグ名 (例： latest)
* イメージを構成するレイヤーのリスト
* 各レイヤーの ID
* 署名



```docker pull <イメージ名>``` コマンドを実行すると、Docker Engine はまず Manifest ファイルをダウンロードします。 Manifest には、イメージを構成するレイヤーファイルが書かれた fsLayers フィールドがあります。

レイヤーファイルの実体は Docker Hub の Blob という領域に置かれており、Docker Engine は fsLayers を参照し、Blob からレイヤーファイルをダウンロードしていきます。

以下の Manifest の例では、5つのレイヤーファイルからイメージが構成されていることになります。

```json:Manifestファイルの例
{
   "schemaVersion": 1,
   "name": "library/centos",
   "tag": "latest",
   "architecture": "amd64",
   "fsLayers": [
      { "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" },
      { "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" },
      { "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" },
      { "blobSum": "sha256:c3bf6062f354b9af9db4481f24f488da418727673ea76c5162b864e1eea29a4e" },
      { "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" }
   ],
   "history": [
      { "v1Compatibility": "{\"id\":\"0f73ae75014f435e...\",\"parent\":\"f37e6a610a37349d...\", (中略...)" },
      { "v1Compatibility": "{\"id\":\"f37e6a610a37349d...\",\"parent\":\"f9a8cbc8dd13fb5b...\", (中略...)" },
      { "v1Compatibility": "{\"id\":\"f9a8cbc8dd13fb5b...\",\"parent\":\"f6f39725d938c8ad...\", (中略...)" },
      { "v1Compatibility": "{\"id\":\"f6f39725d938c8ad...\",\"parent\":\"47d44cb6f252ea4f...\", (中略...)" },
      { "v1Compatibility": "{\"id\":\"47d44cb6f252ea4f...\", …" }
   ],
   "signatures": [ "中略..." ]
}
```


なお、レイヤー間の親子関係は、Manifest の history に書かれています。
また、Manifest とレイヤーファイルの URL は以下のようになります(Docker Registry HTTP API V2 の場合)。

* Manifest ファイル  
  ```https://registry-1.docker.io/v2/library/<イメージ名>/manifests/<タグ名>```  
  (例) https://registry-1.docker.io/v2/library/centos/manifests/latest

* レイヤーファイル  
  ```https://registry-1.docker.io/v2/<イメージ名>/blobs/<tarsum>```  
  (例) https://registry-1.docker.io/v2/library/centos/blobs/sha256:c3bf6062f354b9af9db4481f24f488da418727673ea76c5162b864e1eea29a4e 


## レイヤーファイル

Docker Engine はダウンロードしたレイヤーファイルを /var/lib/docker/tmp に一時的に置きます。
今回試した centos:latest のイメージは 5つのレイヤーから構成されているため、以下のように 5つのレイヤーファイルがダウンロードされています。

```text
[root@centos7 tmp]# pwd
/var/lib/docker/tmp
[root@centos7 tmp]# ls -al
total 61460
drwx------. 2 root root     4096 Oct  3 06:32 .
drwx------. 9 root root     4096 Oct  3 06:32 ..
-rw-------. 1 root root       32 Oct  3 06:32 GetImageBlob068882538
-rw-------. 1 root root       32 Oct  3 06:32 GetImageBlob561232172
-rw-------. 1 root root       32 Oct  3 06:32 GetImageBlob568501953
-rw-------. 1 root root 62906573 Oct  3 06:32 GetImageBlob621683063
-rw-------. 1 root root       32 Oct  3 06:32 GetImageBlob747002488
```

(補足) これらのファイルは通常はすぐに削除されるため、削除されないように Docker のソースを修正して調査しています。

GetImageBlobXXX ファイルの実体は tar.gz です。中身は何かというと、ディレクトリツリーが含まれています。GetImageBlob621683063~~という0バイトのファイルが複数含まれてますが理由は分かりません。

![LayerFile.png](https://qiita-image-store.s3.amazonaws.com/0/79316/fdb2aba1-9ab2-18ad-42bc-cd665dd5b40a.png)

Docker Engine は、このように /var/lib/docker/tmp にレイヤーファイルをダウンロードした後は、ストレージドライバ(AUFS, devicemapper, overlay など)を使いイメージをホストに保存していきます。

# 最後に
今回は、docker pull する際に、何がどうダウンロードされるかを調べてみました。あくまで Docker Hub からダウンロードされる際の形式なので、まだ Docker Engine のストレージドライバからは独立した段階です。

次回は、ダウンロードした Docker イメージが、Linux ホスト上でどのような形式で保存されるかをまとめてみます。

# 参考サイト

* [Docker Registry HTTP API V2](http://docs.docker.com/registry/spec/api/)