Dockerfileを理解して書きたくてレイヤ構造を拝んできた忘備録

# はじめに
Docker入門した時に、コンテナ？イメージ？レイヤ？とごっちゃになったので勉強したことを忘備録としてまとめる

# ゴール
- コンテナの構造と仕組みを理解する
- イメージの実体を見てみる

# 参考サイト
とっても参考にさせていただきました。ありがとうございます。

https://qiita.com/okmtz/items/f8231c83134a6363647b


# Dockerコンテナのレイヤ構造について

## 前提知識
- `Dockerfile`はテキスト形式のファイルで、イメージを作りあげるために実行するコマンドライン命令を記載する。
- `docker build`を実行すると、`Dockerfile`に記載したコマンドライン命令を順次実行し、ビルド結果となるイメージが作成される。
- コンテナはイメージの実行可能なインスタンス（実行環境）


## コンテナはレイヤの集まり
<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728837/55588203-7d3f-5a37-8b69-8d3fe024bb2d.png" >

#### ■イメージレイヤ
イメージを構成する**読み込み専用**のレイヤで、1つ以上存在する

#### ■コンテナレイヤ
コンテナ上の**読み書き可能なレイヤ**で、コンテナ内に1つ存在する
実行中のコンテナ内でファイルの変更を行った際は、イメージレイヤではなくコンテナ起動時に追加されるコンテナレイヤに記録される


:::note info
💡 レイヤ構造を実現するために、Dockerは`Unionファイルシステム(UnionFS)`を導入している。`UnionFS`は、複数にファイルシステム上のディレクトリやファイルを**レイヤ**として重ね合わせ、それらを仮想的に1つのファイルシステムとして扱う技術のことで、非常に軽量で高速。
:::


## 読み込み専用のレイヤ
Dockerイメージにあたる。 

- イメージ構築(build)の時に、`Dockerfile`に記載された命令(`RUN`と `COPY`と `ADD`)の結果がレイヤとして追加されていく。 

- `Dockerfile`の**命令ごとに１つのレイヤが作成される**ため、命令を1行ずつ記載せずに`&&`で結合すればレイヤの数が減らすことができる。

- レイヤに含まれる情報
    - ベースのイメージは？
    - インストールしているものは？
    - 環境変数や設定は？
     など


### 図で表すと・・・・

```dockerfile:Dockerfile
FROM ubuntu:20.04
RUN apt-get update
COPY ./hello.sh /hello.sh
CMD [ "/hello.sh" ]
```

- FROM :ubuntu:20.04の Docker イメージからレイヤを作成
- RUN  :aptgetをアップデート
- COPY :ローカルファイルを（コンテナのレイヤに）追加
- CMD  :コンテナ起動後にコンテナ内でhello.shを実行

<img width=700 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728837/7e95eb9e-a0ea-3740-6406-2c2cb87b9b8c.png" >



## 読み書き可能のレイヤ

読み書き可能レイヤにはコンテナで実行したルートファイルシステムへの変更差分のみが格納される。
つまり、**コンテナに対して行った追加・修正・削除したデータが保存**される。 

- 読み込み専用レイヤに含まれるファイルに変更を加える場合は、変更対象のファイルのみが読み書き可能レイヤにコピーされ変更が加えられる。

- コンテナが削除されると、その書き込みレイヤも削除される。

- 1つのイメージからコンテナを複数実行することができるがイメージレイヤは**読み込み専用**としてコンテナ間で共有される。
  - 他のコンテナにより書き換えられることはなく、安全に共有できる。
  - コンテナ個々に専用の読み書き可能レイヤがあり、コンテナで行った操作はこのレイヤに書き込まれる。コンテナ間で共有はされない。

これらの仕組みによって、**それぞれのコンテナは他のコンテナによって意図せず書き換えられない**ようになっているため、効率良くリソースを使うことができる。

### 図で表すと・・・・

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728837/4fc6e535-4ddb-2765-88c8-8d7ef926ffd5.png)

[※図はこちらのサイトを参考して書かせていただきました](https://qiita.com/okmtz/items/f8231c83134a6363647b)

<br>
<br>

### お互いの環境が影響し合わないことを試してみる
1つのイメージから２つコンテナを起動し、お互いの環境が影響し合わないことを確認する。

1. Dockerイメージを作成
   [公式チュートリアル](https://docs.docker.com/get-started/02_our_app/#get-the-app) の以下の章を実施しました。
    - [get-the-app](https://docs.docker.com/get-started/02_our_app/#get-the-app)
    - [build-the-apps-container-image](https://docs.docker.com/get-started/02_our_app/#build-the-apps-container-image)

    ```dockerfile:Dockerfile
    FROM node:12-alpine
    RUN apk add --no-cache python2 g++ make
    WORKDIR /app
    COPY . .
    RUN yarn install --production
    CMD ["node", "src/index.js"]
    EXPOSE 3000
    ```

1. Dockerイメージの確認
    ```docker
    $ docker image ls
    REPOSITORY       TAG              IMAGE ID       CREATED         SIZE
    testimg          latest           9c693a57b0ee   4 minutes ago   430MB
    ```

1. 同じイメージから2つコンテナを立ち上げる

    ```docker
    # コンテナ名「testcontainer1」
    $ docker run -dp 3000:3000 --name testcontainer1 -it testimg
    dfac31bb54ee7450dc9a1f6742e40422700595a351f2580995e2dd7d9d84c2e1
    
    # コンテナ名「testcontainer2」
    $ docker run -dp 3001:3000 --name testcontainer2 -it testimg
    791ababe73ae298415dd8e57e91693ead5f0763052ce4cbc0cc3506632db88cc

    # コンテナの起動を確認
    $ docker ps
    CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                    NAMES
    791ababe73ae   testimg   "docker-entrypoint.s…"   42 seconds ago   Up 41 seconds   0.0.0.0:3001->3000/tcp   testcontainer2
    dfac31bb54ee   testimg   "docker-entrypoint.s…"   54 seconds ago   Up 53 seconds   0.0.0.0:3000->3000/tcp   testcontainer1
    ```
    ※   ２つ同時に起動できるようにポートを変えておく。
    <br>

1. 確認
   各コンテナに配置したアプリケーションをブラウザで開いてそれぞれデータを作ってみると、コンテナ同士のデータが共有されていないことが確認できる。
    - コンテナ「testcontainer1」http://localhost:3000/ 
        <img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728837/d8abbdbb-c5e5-bd56-e08f-577d85b40745.png">

    - コンテナ「testcontainer2」http://localhost:3001/　
        <img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2728837/5079fae0-5571-e91a-e541-6291649c3f1c.png">




# レイヤ構造を拝んでみる

### まずはイメージ作成

```dockerfile:Dockerfile
FROM ubuntu:20.04
RUN apt-get update
COPY ./hello.sh /hello.sh
CMD [ "/hello.sh" ]
```

`Dockerfile`を作成してビルドする
```docker
docker build -t test-image .
```

### 作成したイメージを出力する

```
docker save test-image | tar -xC ./export/
```

- [docker save [image]](https://docs.docker.com/engine/reference/commandline/save/) でイメージを出力する
- イメージ「test-image」をtar形式で出力している


```bash:exportフォルダに出力されたファイル
|--export
|  |--955c4c5ed5f543d022e7e5e4af1a9a2fab12f26991ee42fb5c468aa8678dc426
|  |  |--VERSION
|  |  |--json
|  |  |--layer.tar
|  |
|  |--a992d083f1f0bb0a74aee6b683535b30de1e1e9ccfd876e0c1dedc920ddaa84a
|  |  |--VERSION
|  |  |--json
|  |  |--layer.tar
|  |
|  |--74008e61fb84e526d304357806ebec7ad43aed15ea763c7b98a93df28bc0588f
|  |  |--VERSION
|  |  |--json
|  |  |--layer.tar
|  |
|  |--8ff95465ba803d01aac73488eb1f044a91d8dcb6e38a71d1ec102d6a2b044313.json
|  |
|  |--manifest.json
|  |--repositories
```

- `layer.tar` : イメージを構成するレイヤ群
- `manifest.json`, `repositories` : イメージの構成などに関する情報
- `8ff954……..313.json` : 実行コマンドや環境変数など、実行環境を再現するため情報
    
### 各ファイルの中身を確認 

#### `layer.tar` 


```bash
# FROMで作成されたレイヤ（ubuntuを構成するフォルダやファイル）
$ tar --list -f 955c4c5ed5f543d022e7e5e4af1a9a2fab12f26991ee42fb5c468aa8678dc426/layer.tar 
bin
boot/
dev/
etc/
etc/.pwd.lock
etc/adduser.conf
〜略〜
var/run
var/spool/
var/spool/mail
var/tmp/

# RUNで作成されたレイヤ（「apt-get update」で変更した分のフォルダやファイル）
$ tar --list -f a992d083f1f0bb0a74aee6b683535b30de1e1e9ccfd876e0c1dedc920ddaa84a/layer.tar 
tmp/
var/
var/lib/
var/lib/apt/
var/lib/apt/lists/
〜略〜
var/lib/apt/lists/ports.ubuntu.com_ubuntu-ports_dists_focal_main_binary-arm64_Packages.lz4
var/lib/apt/lists/ports.ubuntu.com_ubuntu-ports_dists_focal_multiverse_binary-arm64_Packages.lz4

# COPYで作成されたレイヤ（ローカルからコピーしたファイル）
$ tar --list -f 74008e61fb84e526d304357806ebec7ad43aed15ea763c7b98a93df28bc0588f/layer.tar 
hello.sh
```

#### `manifest.json`
イメージ情報の概要が記載されていた。

```bash
$ cat manifest.json
[
	{
		"Config": "8ff95465ba803d01aac73488eb1f044a91d8dcb6e38a71d1ec102d6a2b044313.json",
		"RepoTags": [
			"test-image:latest"
		],
		"Layers": [
			"955c4c5ed5f543d022e7e5e4af1a9a2fab12f26991ee42fb5c468aa8678dc426/layer.tar",
			"a992d083f1f0bb0a74aee6b683535b30de1e1e9ccfd876e0c1dedc920ddaa84a/layer.tar",
			"74008e61fb84e526d304357806ebec7ad43aed15ea763c7b98a93df28bc0588f/layer.tar"
		]
	}
]
```

#### `8ff954……..313.json`
イメージ情報詳細や変更履歴が記載されていた。
```json
{
	"architecture": "arm64",
	"config": {
		"Env": [
			"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
		],
		"Cmd": [
			"/hello.sh"
		],
		"ArgsEscaped": true,
		"OnBuild": null
	},
	"created": "2022-06-21T04:49:35.639404252Z",
	"history": [
		{
			"created": "2022-06-07T01:25:15.695379365Z",
			"created_by": "/bin/sh -c #(nop) ADD file:8bb0809a8ac8e978274cf731cff7529372088d22c5b0233a28f01ef414aefbca in / "
		},
		{
			"created": "2022-06-07T01:25:16.433758958Z",
			"created_by": "/bin/sh -c #(nop)  CMD [\"bash\"]",
			"empty_layer": true
		},
		{
			"created": "2022-06-21T04:49:35.619560543Z",
			"created_by": "RUN /bin/sh -c apt-get update # buildkit",
			"comment": "buildkit.dockerfile.v0"
		},
		{
			"created": "2022-06-21T04:49:35.639404252Z",
			"created_by": "COPY ./hello.sh /hello.sh # buildkit",
			"comment": "buildkit.dockerfile.v0"
		},
		{
			"created": "2022-06-21T04:49:35.639404252Z",
			"created_by": "CMD [\"/hello.sh\"]",
			"comment": "buildkit.dockerfile.v0",
			"empty_layer": true
		}
	],
	"os": "linux",
	"rootfs": {
		"type": "layers",
		"diff_ids": [
			"sha256:9fa12d818e9150724ca19fb518320d6cd48d987908062905a670c1a32e4a03a9",
			"sha256:88a7de6f4747c2993263f43ebd5ae1bcc7d2f07cf1ddb4d9c19a4884ce578f2b",
			"sha256:86cb9b63e55597f3168589be583702f4c9e08b6b0d0639c78094a54b992a68d1"
		]
	},
	"variant": "v8"
}
```

#### repositories
最新版のレイヤ情報かな。`Dockerfile`を変更して`docker build`したら変わるはず。

```json
{"test-image":{"latest":"74008e61fb84e526d304357806ebec7ad43aed15ea763c7b98a93df28bc0588f"}}
```


# おわりに
Dockerイメージについて自分の中でスッキリしたことにより、なんとなく書いていた`Dockerfile`も意識して書けるようになりました。