Dockerのコードを読む -イメージ生成編-

ついに24日!!!!
皆様いかがお過ごしでしょうか?
今年も残すところ7日という事実に耐えられずお布団で震えています。

## はじめに
続きそうなタイトルですが、続編を投稿するかは謎です。
その名の通り、Dockerのコードを読んだり準備したりする話です。

## 事前準備
イメージの構造がわからない状態で読み始めると辛くなるので
先に[OCI image-spec](https://github.com/opencontainers/image-spec)を読むことをオススメします。
なぜOCIかというと、Dockerのドキュメントより仕様としてしっかりまとまっているのと、
Dockerを参考にしているのもあって大枠は一致しているからです。

> そこらへんをまとめたスライド
> https://speakerdeck.com/ladicle/container-image-architecture

## どこから読むか？
とりあえず `GTAG`生成した後に`ls` して雰囲気をつかみます。

```bash
drwxr-xr-x   15 ladicle  staff       510 12 24 08:05 api
drwxr-xr-x   17 ladicle  staff       578 12 24 08:05 builder
drwxr-xr-x   10 ladicle  staff       340 12 24 08:05 cli
drwxr-xr-x    6 ladicle  staff       204 12 24 08:05 cliconfig
drwxr-xr-x  187 ladicle  staff      6358 12 24 08:05 client
drwxr-xr-x    4 ladicle  staff       136 10  5 19:41 cmd
drwxr-xr-x   25 ladicle  staff       850 12 24 08:05 container
drwxr-xr-x   43 ladicle  staff      1462 12 24 08:05 contrib
drwxr-xr-x  153 ladicle  staff      5202 12 24 08:05 daemon
drwxr-xr-x   19 ladicle  staff       646 12 24 08:05 distribution
drwxr-xr-x    4 ladicle  staff       136 12 24 08:05 dockerversion
drwxr-xr-x    8 ladicle  staff       272 12 24 08:05 docs
drwxr-xr-x    8 ladicle  staff       272 12 24 08:05 experimental
drwxr-xr-x   14 ladicle  staff       476 12 24 08:05 hack
drwxr-xr-x   12 ladicle  staff       408 12 24 08:05 image
drwxr-xr-x  141 ladicle  staff      4794 12 24 08:05 integration-cli
drwxr-xr-x   19 ladicle  staff       646 12 24 08:05 layer
drwxr-xr-x   28 ladicle  staff       952 12 24 08:05 libcontainerd
drwxr-xr-x   63 ladicle  staff      2142 12 24 08:05 man
drwxr-xr-x    3 ladicle  staff       102 10  5 19:41 migrate
drwxr-xr-x    8 ladicle  staff       272 12 24 08:05 oci
drwxr-xr-x   18 ladicle  staff       612 12 24 08:05 opts
drwxr-xr-x   58 ladicle  staff      1972 12 24 08:05 pkg
drwxr-xr-x   11 ladicle  staff       374 12 24 08:05 plugin
-rw-r--r--    1 ladicle  staff      3704 12 24 08:05 poule.yml
drwxr-xr-x    4 ladicle  staff       136 10  5 19:41 profiles
drwxr-xr-x   17 ladicle  staff       578 12 24 08:05 project
drwxr-xr-x    6 ladicle  staff       204 12 24 08:05 reference
drwxr-xr-x   19 ladicle  staff       646 12 24 08:05 registry
drwxr-xr-x    4 ladicle  staff       136 12 24 08:05 restartmanager
drwxr-xr-x   16 ladicle  staff       544 12 24 08:05 runconfig
drwxr-xr-x   23 ladicle  staff       782 12 24 08:05 volume
```

`image`, `layer`あたりがそれっぽいですね。
imageディレクトリの中の`image.go`を開くとimage構造体が定義されています。
しかし、定義から逆に追っていくのは大変そうです。

この構造体が呼ばれそうなシーンをパッと考えてみると、
したの4つが思いつきました。一番興味がある&全体の流れがわかりそうな`1`を追っていくことにします。

1. `docker build`が呼ばれてからイメージが作成されるまで
2. `docker save`が呼ばれてからtarが生成されるまで
3. `docker expose`が呼ばれてからtarが生成されるまで
1. `docker run`を実行してからイメージが展開されるまで

## コマンド実行部分を探せ

`docker build`が呼ばれてからイメージが作成されるまでを追うことにしたので、それっぽい所を探してみます。
先ほど`ls`した時に`cli`ディレクトリがあったので中を見てみます。

```
(%-U-)< ls cli/command/image/build.go
-rw-r--r--  1 ladicle  staff  16064 12 24 08:05 cli/command/image/build.go # これっぽい
```

中を見ると、`コマンドを実行した時に実行されるよ`というコメントが。
ここから順に追っていくと流れがわかりそうです。
https://github.com/docker/docker/blob/d1dfc1a5ef95dc5621a07915f9786199442043c7/cli/command/image/build.go#L65

## メモを取る
記念すべきトリガが発見されたのでメモを残しておきます。
普段emacsを使っているので`org-capture`を使い
dockerのコードリーディング用ファイルにメモと現在のカーソル位置を記憶しておきます。

![image](https://qiita-image-store.s3.amazonaws.com/0/26683/cb377318-2a2c-a166-13b3-2aca926d430a.png)

## Docker daemonのbuild処理部分までたどる

トリガさえ見つかればあとは`GTAG`や`ag`を使って順に辿っていくだけです。

1. クライアント側のビルド用ファイルのパッケージ化
   https://github.com/docker/docker/blob/d1dfc1a5ef95dc5621a07915f9786199442043c7/cli/command/image/build.go#L137
    1. クライアントがコマンドのOptionを解析&ビルド対象のディレクトリをdockerignoreのファイルを除いてtarへ圧縮
    2. RemoteAPIのbuild Imageを叩いてビルドオプションとtarファイルをPOSTする
2. APIserverがリクエストを受け付け
   https://github.com/docker/docker/blob/d1dfc1a5ef95dc5621a07915f9786199442043c7/api/server/router/build/build_routes.go#L142
    4. RemoteAPIのリクエストをAPIserverが受け取る(初期設定だとunix socket domain経由)
    5. 実際のイメージ生成を行う`BuildFromContext`が呼ばれる

## ビルド処理をざっくり把握する

やっと実際のイメージ生成部分までたどり着きました! 本丸の関数はこれです。
https://github.com/docker/docker/blob/d1dfc1a5ef95dc5621a07915f9786199442043c7/builder/dockerfile/builder.go#L219

気になる所はあとで下っていくとしてまずはざっくり見ていきます。
1. L226: DockerfileをパースしてASTを生成する
2. L231: Repository名とタグ名をサニタイズする
3. L236: `LABEL`を別途パースする(複数のLABELが定義されていたとしても、レイヤーを複数に分けず1レイヤーにまとめるため)
4. L251: ASTの簡単なエラーチェックを行う
5. L256: ASTからイメージ(レイヤー)を順次作成していく
    5.1. 親イメージで作業用コンテナを起動
    5.2. コマンドを実行
    5.3. イメージとして保存
    5.4. 作業用コンテナを削除
6. L282: 指定されたARGが全て使われたかチェックし、使われていない場合はエラーを返す
7. L293: イメージが何も生成されなかった場合もエラーとして返す
7. L297: `squash`オプションをチェックしてレイヤーをまとめている(よく使われていた[docker-squash](https://github.com/jwilder/docker-squash)を最近本家が対応した)
8. L309: ReferenceStoreにタグを追加し、作成したImageIDと紐付ける
9. L315: 成功メッセージを流してイメージを返す

## Dockerfileのパースを追う

パース部分のコードを詳しく見ていきます。メイン部分はここ。
https://github.com/docker/docker/blob/d1dfc1a5ef95dc5621a07915f9786199442043c7/builder/dockerfile/parser/parser.go

パース時にはDockerfileの各コマンドをNodeに分割する処理がされています。
ルートNodeのChildrenの数はイメージのレイヤ数と一致します。
※ squashオプションがfalseの時

![image](https://qiita-image-store.s3.amazonaws.com/0/26683/40d4e01c-2444-5202-d1e6-edfcfcacf331.png)明日

処理を追っていくと、
1. rootのNodeを作成
2. コマンドごとに内容をパース
    2.1. 空行やコメント行はskip
    2.2. コマンドパース用のディスパッチャに渡す
    2.3. ディスパッチされた関数は該当コマンド(ADD, COPYなど)ごとのパース結果をNodeとして返す(ADDなどで複数のファイルが指定されている場合は、ファイルごとにノードが作成され、NodeのNextで順次参照できるようにする)
3. 戻ってきた値をrootのNondeのChildrenスライス末尾に追加

### build実行時の出力と対応付ける

dockerのbuildコマンドを実行すると下のような出力が出ると思います。
コード読んでいる時に迷子になりかけたら、この出力と対応付けると何処にいるのか分かりやすくなります。

```bash
# Terminalで実行
$ docker build -t test ./ 

# RemoteAPIでPOST(ここまでclient側)
Sending build context to Docker daemon 557.1 kB
Sending build context to Docker daemon 1.114 MB
Sending build context to Docker daemon 1.425 MB

# 親イメージの取得
Step 1 : FROM python:3.5.2-alpine
 ---> a047e3d0ae2b

# 各コマンドの実行開始
Step 2 : RUN apk add --no-cache git
# 親イメージの中間コンテナを立ち上げ
 ---> Running in 7785e4cfb01a
# RWな最上位レイヤにコマンド内容を実行し差分を保存 
 ---> f413fb825b75
# 通常は中間コンテナ削除
Removing intermediate container 7785e4cfb01a 

Step 3 : WORKDIR /root
 ---> Running in 9ade91b50d0d
 ---> 3d023174b126
Removing intermediate container 9ade91b50d0d
Step 4 : COPY hoge/* test/
 ---> 2b3b569195a9
Removing intermediate container 33b83c3c513e
Step 5 : EXPOSE 9090
 ---> Running in 063eafd08fb9
 ---> 56164a4dcbd9
Removing intermediate container 063eafd08fb9

# レポジトリ作成完了メッセージを表示
Successfully built 56164a4dcbd9
```

## おわりに
今回はDockerと自分なりのコードの読み方を書きました。

コード書く方は、ペアプロとかレビューとかである程度わかるのですが、
読む方は個人作業すぎて他の人がどうやっているのか見えてこないので気になります。
オススメの方法とかあれば教えていただけると嬉しいです。