ついに24日!!!!
皆様いかがお過ごしでしょうか?
今年も残すところ7日という事実に耐えられずお布団で震えています。
はじめに
続きそうなタイトルですが、続編を投稿するかは謎です。
その名の通り、Dockerのコードを読んだり準備したりする話です。
事前準備
イメージの構造がわからない状態で読み始めると辛くなるので
先にOCI image-specを読むことをオススメします。
なぜOCIかというと、Dockerのドキュメントより仕様としてしっかりまとまっているのと、
Dockerを参考にしているのもあって大枠は一致しているからです。
そこらへんをまとめたスライド
https://speakerdeck.com/ladicle/container-image-architecture
どこから読むか?
とりあえず GTAG
生成した後にls
して雰囲気をつかみます。
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
を追っていくことにします。
-
docker build
が呼ばれてからイメージが作成されるまで -
docker save
が呼ばれてからtarが生成されるまで -
docker expose
が呼ばれてからtarが生成されるまで -
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のコードリーディング用ファイルにメモと現在のカーソル位置を記憶しておきます。
Docker daemonのbuild処理部分までたどる
トリガさえ見つかればあとはGTAG
やag
を使って順に辿っていくだけです。
- クライアント側のビルド用ファイルのパッケージ化
https://github.com/docker/docker/blob/d1dfc1a5ef95dc5621a07915f9786199442043c7/cli/command/image/build.go#L137- クライアントがコマンドのOptionを解析&ビルド対象のディレクトリをdockerignoreのファイルを除いてtarへ圧縮
- RemoteAPIのbuild Imageを叩いてビルドオプションとtarファイルをPOSTする
- 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
気になる所はあとで下っていくとしてまずはざっくり見ていきます。
- L226: DockerfileをパースしてASTを生成する
- L231: Repository名とタグ名をサニタイズする
- L236:
LABEL
を別途パースする(複数のLABELが定義されていたとしても、レイヤーを複数に分けず1レイヤーにまとめるため) - L251: ASTの簡単なエラーチェックを行う
- L256: ASTからイメージ(レイヤー)を順次作成していく
5.1. 親イメージで作業用コンテナを起動
5.2. コマンドを実行
5.3. イメージとして保存
5.4. 作業用コンテナを削除 - L282: 指定されたARGが全て使われたかチェックし、使われていない場合はエラーを返す
- L293: イメージが何も生成されなかった場合もエラーとして返す
- L297:
squash
オプションをチェックしてレイヤーをまとめている(よく使われていたdocker-squashを最近本家が対応した) - L309: ReferenceStoreにタグを追加し、作成したImageIDと紐付ける
- L315: 成功メッセージを流してイメージを返す
Dockerfileのパースを追う
パース部分のコードを詳しく見ていきます。メイン部分はここ。
https://github.com/docker/docker/blob/d1dfc1a5ef95dc5621a07915f9786199442043c7/builder/dockerfile/parser/parser.go
パース時にはDockerfileの各コマンドをNodeに分割する処理がされています。
ルートNodeのChildrenの数はイメージのレイヤ数と一致します。
※ squashオプションがfalseの時
処理を追っていくと、
- rootのNodeを作成
- コマンドごとに内容をパース
2.1. 空行やコメント行はskip
2.2. コマンドパース用のディスパッチャに渡す
2.3. ディスパッチされた関数は該当コマンド(ADD, COPYなど)ごとのパース結果をNodeとして返す(ADDなどで複数のファイルが指定されている場合は、ファイルごとにノードが作成され、NodeのNextで順次参照できるようにする) - 戻ってきた値をrootのNondeのChildrenスライス末尾に追加
build実行時の出力と対応付ける
dockerのbuildコマンドを実行すると下のような出力が出ると思います。
コード読んでいる時に迷子になりかけたら、この出力と対応付けると何処にいるのか分かりやすくなります。
# 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と自分なりのコードの読み方を書きました。
コード書く方は、ペアプロとかレビューとかである程度わかるのですが、
読む方は個人作業すぎて他の人がどうやっているのか見えてこないので気になります。
オススメの方法とかあれば教えていただけると嬉しいです。