コンテナイメージの実体を見てみる

# これは何？

コンテナイメージの実体を見てみる話です。
概念と使い方はわかるけど、「**コンテナイメージっていったい何者なんだろう**」と思っていたので中を見てみました。
認識違いがあったら教えて頂けると非常に嬉しいです。



# 環境とか

* Docker version 18.09.1, build 4c52b90



# 題材として適当なコンテナイメージを用意する

```
$ docker pull node/8.15.0-alpine
```

```
$ docker images
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
node                     8.15.0-alpine       5c0c5c94503f        4 weeks ago         66.3MB
```



# コンテナイメージをtarにする

```
$ docker save node:8.15.0-alpine > node.tar
```

```
$ ls -ltr
-rwxrwxrwx 1 xxx xxx 69734400 Jan 26 14:37 node.tar
```


# tarを解凍してみる

```
$ tar -xvf node.tar
5491dce778832e33c284cd8185100e76d6daa18f8cbc32458c706776894127fc/
5491dce778832e33c284cd8185100e76d6daa18f8cbc32458c706776894127fc/VERSION
5491dce778832e33c284cd8185100e76d6daa18f8cbc32458c706776894127fc/json
5491dce778832e33c284cd8185100e76d6daa18f8cbc32458c706776894127fc/layer.tar
5c0c5c94503f7310f50e73e8bcaa584eab5e5256ac34b9745589b64bb8fa09bb.json
84f3cb797bec406c8818fd5b1deaad92efd6776bb451c974175373102cfe72ac/
84f3cb797bec406c8818fd5b1deaad92efd6776bb451c974175373102cfe72ac/VERSION
84f3cb797bec406c8818fd5b1deaad92efd6776bb451c974175373102cfe72ac/json
84f3cb797bec406c8818fd5b1deaad92efd6776bb451c974175373102cfe72ac/layer.tar
eaabbaf2e7fe2603c758103f3f9397a82ea9c1f39e421735753afef370323a1f/
eaabbaf2e7fe2603c758103f3f9397a82ea9c1f39e421735753afef370323a1f/VERSION
eaabbaf2e7fe2603c758103f3f9397a82ea9c1f39e421735753afef370323a1f/json
eaabbaf2e7fe2603c758103f3f9397a82ea9c1f39e421735753afef370323a1f/layer.tar
manifest.json
repositories
```

これがコンテナイメージの実体のようです。
上で``docker images``した時に表示されたイメージIDと同じものがありますね。
種類を分けるとこんな感じでしょうか。

* **buildされたコンテナイメージの情報**
    * 5c0c5c94503f7310f50e73e8bcaa584eab5e5256ac34b9745589b64bb8fa09bb.json
* **レイヤーの情報**
    * 5491dce778832e33c284cd8185100e76d6daa18f8cbc32458c706776894127fc
    * 84f3cb797bec406c8818fd5b1deaad92efd6776bb451c974175373102cfe72ac
    * eaabbaf2e7fe2603c758103f3f9397a82ea9c1f39e421735753afef370323a1f
* **メタ情報**
    * manifest.json
    * repositories



# buildされたコンテナイメージの情報

めちゃくちゃ長いです。たくさん情報持ってるんですね。
レイヤー名とかも確認できますね。

```5c0c5c94503f7310f50e73e8bcaa584eab5e5256ac34b9745589b64bb8fa09bb.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",
            "NODE_VERSION=8.15.0",
            "YARN_VERSION=1.12.3"
        ],
        "Cmd":["node"],
        "ArgsEscaped":true,
        "Image":"sha256:e615719cc465120b3f6a8a76ad4a39464a3d65be67f99c0bd90b4a847a00d651",
        "Volumes":null,
        "WorkingDir":"",
        "Entrypoint":null,
        "OnBuild":null,
        "Labels":null
    },
    "container":"d29ae8e85f8f1b00dbc79c2160e202969205c1429dcc31553b18585f27248ff7",
    "container_config":{
        "Hostname":"d29ae8e85f8f",
        "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",
            "NODE_VERSION=8.15.0",
            "YARN_VERSION=1.12.3"
        ],
        "Cmd":[
            "/bin/sh",
            "-c",
            "#(nop) ",
            "CMD [\"node\"]"
        ],
        "ArgsEscaped":true,
        "Image":"sha256:e615719cc465120b3f6a8a76ad4a39464a3d65be67f99c0bd90b4a847a00d651",
        "Volumes":null,
        "WorkingDir":"",
        "Entrypoint":null,
        "OnBuild":null,
        "Labels":{}
    },
    "created":"2018-12-27T01:40:51.334534029Z",
    "docker_version":"18.06.1-ce",
    "history":[
        {
            "created":"2018-12-21T00:21:29.97055571Z",
            "created_by":"/bin/sh -c #(nop) ADD file:2ff00caea4e83dfade726ca47e3c795a1e9acb8ac24e392785c474ecf9a621f2 in / "
        },
        {
            "created":"2018-12-21T00:21:30.122610396Z",
            "created_by":"/bin/sh -c #(nop)  CMD [\"/bin/sh\"]",
            "empty_layer":true
        },
        {
            "created":"2018-12-27T01:22:27.078806811Z",
            "created_by":"/bin/sh -c #(nop)  ENV NODE_VERSION=8.15.0",
            "empty_layer":true
        },
        {
            "created":"2018-12-27T01:40:46.64036283Z",
            "created_by":"/bin/sh -c addgroup -g 1000 node     \u0026\u0026 adduser -u 1000 -G node -s /bin/sh -D node     \u0026\u0026 apk add --no-cache         libstdc++     \u0026\u0026 apk add --no-cache --virtual .build-deps         binutils-gold         curl         g++
     gcc         gnupg         libgcc         linux-headers         make         python   \u0026\u0026 for key in     94AE36675C464D64BAFA68DD7434390BDBE9B9C5     FD3A5288F042B6850C66B31F09FE44734EB7990E     71DCFD284A79C3B38668286BC97EC7A07EDE3FC1     DD8F2338BAE7501E3DD5AC78C273792F7D83545D     C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8     B9AE9905FFD7803F25714661B63B535A4C206CA9     77984A986EBC2AA786BC0F66B01FBB92821C587A     8FCCA13FEF1D0C2E91008E09770F7A9A5AE15600     4ED778F539E3634C779C87C6D7062848A1AB005C     A48C2BEE680E841632CD4E44F07496B3EB3C1762     B9E2F5981AA6E0CD28160D9FF13993A75599653C   ; do     gpg --batch --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys \"$key\" ||     gpg --batch --keyserver hkp://ipv4.pool.sks-keyservers.net --recv-keys \"$key\" ||     gpg --batch --keyserver hkp://pgp.mit.edu:80 --recv-keys \"$key\" ;   done     \u0026\u0026 curl -fsSLO --compressed \"https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION.tar.xz\"     \u0026\u0026 curl -fsSLO --compressed \"https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc\"     \u0026\u0026 gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc     \u0026\u0026 grep \" node-v$NODE_VERSION.tar.xz\\$\" SHASUMS256.txt | sha256sum -c -     \u0026\u0026 tar -xf \"node-v$NODE_VERSION.tar.xz\"     \u0026\u0026 cd \"node-v$NODE_VERSION\"     \u0026\u0026 ./configure     \u0026\u0026 make -j$(getconf _NPROCESSORS_ONLN)     \u0026\u0026 make install     \u0026\u0026 apk del .build-deps     \u0026\u0026 cd ..     \u0026\u0026 rm -Rf \"node-v$NODE_VERSION\"     \u0026\u0026 rm \"node-v$NODE_VERSION.tar.xz\" SHASUMS256.txt.asc SHASUMS256.txt"
        },
        {
            "created":"2018-12-27T01:40:46.898832094Z",
            "created_by":"/bin/sh -c #(nop)  ENV YARN_VERSION=1.12.3",
            "empty_layer":true
        },
        {
            "created":"2018-12-27T01:40:51.183517333Z",
            "created_by":"/bin/sh -c apk add --no-cache --virtual .build-deps-yarn curl gnupg tar   \u0026\u0026 for key in     6A010C5166006599AA17F08146C2130DFD2497F5   ; do     gpg --batch --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys \"$key\" ||     gpg --batch --keyserver hkp://ipv4.pool.sks-keyservers.net --recv-keys \"$key\" ||     gpg --batch --keyserver hkp://pgp.mit.edu:80 --recv-keys \"$key\" ;   done   \u0026\u0026 curl -fsSLO --compressed \"https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz\"   \u0026\u0026 curl -fsSLO --compressed \"https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz.asc\"   \u0026\u0026 gpg --batch --verify yarn-v$YARN_VERSION.tar.gz.asc yarn-v$YARN_VERSION.tar.gz   \u0026\u0026 mkdir -p /opt   \u0026\u0026 tar -xzf yarn-v$YARN_VERSION.tar.gz -C /opt/   \u0026\u0026 ln -s /opt/yarn-v$YARN_VERSION/bin/yarn /usr/local/bin/yarn
 \u0026\u0026 ln -s /opt/yarn-v$YARN_VERSION/bin/yarnpkg /usr/local/bin/yarnpkg   \u0026\u0026 rm yarn-v$YARN_VERSION.tar.gz.asc yarn-v$YARN_VERSION.tar.gz   \u0026\u0026 apk del .build-deps-yarn"
        },
        {
            "created":"2018-12-27T01:40:51.334534029Z",
            "created_by":"/bin/sh -c #(nop)  CMD [\"node\"]",  
            "empty_layer":true
        }
    ],
    "os":"linux",
    "rootfs":{
        "type":"layers",
        "diff_ids":[
            "sha256:7bff100f35cb359a368537bb07829b055fe8e0b1cb01085a3a628ae9c187c7b8",
            "sha256:734b6a5256135b58db8ecef74e164d2682722f2b208467bf484392ee87509645",
            "sha256:1f3213370fe13af42ae5e55610b7e64d7c995edf4f95ebdfa4671456812c38dd"
        ]
    }
}
```



# レイヤーの情報

レイヤーと思われるtarの中身を見てみます。

* 5491dce778832e33c284cd8185100e76d6daa18f8cbc32458c706776894127fc/layer.tar
* 84f3cb797bec406c8818fd5b1deaad92efd6776bb451c974175373102cfe72ac/layer.tar
* eaabbaf2e7fe2603c758103f3f9397a82ea9c1f39e421735753afef370323a1f/layer.tar



### 5491dce778832e33c284cd8185100e76d6daa18f8cbc32458c706776894127fc/layer.tar

```
$ tar -xvf 5491dce778832e33c284cd8185100e76d6daa18f8cbc32458c706776894127fc/layer.tar
bin/
bin/arch
bin/ash
bin/base64
bin/bbconfig
～～～
etc/alpine-release
etc/apk/
etc/apk/arch
etc/apk/keys/
etc/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub
～～～
var/run
var/spool/
var/spool/cron/
var/spool/cron/crontabs
var/tmp/
```

果てしないものが展開されましたので中略しています。
途中で``alpine``という文言もチラホラ見えたので、これはOS？

### eaabbaf2e7fe2603c758103f3f9397a82ea9c1f39e421735753afef370323a1f/layer.tar

```
$ tar -xvf eaabbaf2e7fe2603c758103f3f9397a82ea9c1f39e421735753afef370323a1f/layer.tar
etc/
etc/apk/
etc/apk/protected_paths.d/
etc/apk/world
etc/group
～～～
usr/local/lib/node_modules/npm/node_modules/stringify-package/index.js
usr/local/lib/node_modules/npm/node_modules/stringify-package/package.json
usr/local/lib/node_modules/npm/node_modules/strip-ansi/
usr/local/lib/node_modules/npm/node_modules/strip-ansi/index.js
usr/local/lib/node_modules/npm/node_modules/strip-ansi/license
～～～
usr/sbin/
usr/share/
var/
var/cache/
var/cache/misc/
```

これも果てしないので中略。
ただ、思いっきり``node_modules``を展開していたのでわかりやすかったです。nodeのレイヤーですね。



# メタ情報

``manifest.json``と``repositories``には、メタ情報が入ってるようです。

```manifest.json
[
    {
        "Config":"5c0c5c94503f7310f50e73e8bcaa584eab5e5256ac34b9745589b64bb8fa09bb.json",
        "RepoTags":["node:8.15.0-alpine"],
        "Layers":[
            "5491dce778832e33c284cd8185100e76d6daa18f8cbc32458c706776894127fc/layer.tar",
            "eaabbaf2e7fe2603c758103f3f9397a82ea9c1f39e421735753afef370323a1f/layer.tar",
            "84f3cb797bec406c8818fd5b1deaad92efd6776bb451c974175373102cfe72ac/layer.tar"
        ]
    }
]
```

```repositories.
{
    "node":{
        "8.15.0-alpine":"84f3cb797bec406c8818fd5b1deaad92efd6776bb451c974175373102cfe72ac"
    }
}
```

# 実際のDockerfileとの対応

開発者の皆様がDockerfileを上げてくれているので、``node:8.15.0-alpine``のDockerfileを見てみます。

* [nodejs/docker-node - docker-node/8/alpine/Dockerfile]
(https://github.com/nodejs/docker-node/blob/86b9618674b01fc5549f83696a90d5bc21f38af0/8/alpine/Dockerfile)



このDockerfileは以下の6つの命令から成ります。

1. FROM
1. ENV
1. RUN 
1. ENV
1. RUN
1. CMD

**Dockerfileをbuildする際にレイヤーを新たに作り出す命令というのは``RUN``、``ADD``、``COPY``の3つだけ**ですから、tarを解凍して確認できた3つのレイヤーは

* FROM(alpineのベースイメージのレイヤー)
* RUN
* RUN

と対応していると思われます。(たぶん)



# まとめ

* コンテナイメージをtarにすれば人間が中身を確認できる
* 内容は大きく3種類
    * コンテナイメージ情報json
    * 各レイヤーのリソースや設定情報もろもろ
    * メタ情報json
* レイヤーの中には、当たり前だが各プロダクトを構成するファイルがちゃんと詰め込まれている

# 感想

知らなくてもコンテナ使う上では困らないと思いますが、なんだかよくわからないまま使うのはイヤなので見てみました。
レイヤー情報などわかりやすく格納されていて、かなり納得できました。
同時に、あらためてコンテナおよびDockerの便利さを再確認できました。