Dockerコンテナとイメージの仕組みを視覚化してみた

  • このエントリーをはてなブックマークに追加

      2021/11/29

pasted0この記事は、Docker 102レベルを意図して書かれている。Dockerが何か分からない、または仮想マシンや構成管理ツールと比べてどうなのか分からないという方には、この記事は現時点ではやや高度すぎるかもしれない。

この記事が、dockerコマンドラインの習得に悪戦苦闘している人、中でも特に、コンテナとイメージの違いを正確に知っている人の助けとなれば良いと思っている。さらに具体的に言えば、この記事では、普通のコンテナと起動中のコンテナを見分けていくつもりだ。

pasted image 12

私はこれを、下層の細かい部分、つまりユニオンファイルシステムに注意しながら書いている。私はdocker技術の経験が比較的浅く、dockerコマンドラインを習得するのが難しいと感じていたため、私自身、このプロセスに数週間にわたって取り組んだ。

脱線:私が思うに、短期間で学び、かつツールを正しく使えているという自信をつける最良の方法は、技術が内側でどう動くのかを理解することだ。技術というものは大抵、ハラハラする大げさな広告とともにリリースされるが、これが適切な使用パターンを真に理解するのを難しくさせる。さらに具体的に言うと、技術は大抵、抽象化モデルを作り、この抽象化モデルが新しい専門用語と隠喩を作りあげてしまう。これらは、最初は便利なのかもしれないが、最終的には知識の習得を困難にする。

この良い例がGit だ。私はツリー、ブロブ、コミット、タグ、ツリーのようなものを含むGitの基礎モデルを理解するまで、Gitを本格的に使えなかった。これについては過去の投稿に書いたが、Gitの内側が分からない人は、ツールを真にマスターすることはできないといまだに確信している。

イメージの定義

最初に紹介する図は、imageについてで、下のような2つの異なる図で示されている。これは、読み取り専用レイヤスタックの「union view」と定義される。

pasted image 2

図の左に、読み取りレイヤの束があるのがわかる。これらのレイヤは、内部実装の詳細のみで、ホストのファイルシステム内にある起動中コンテナ外部からアクセスできる。重要なのは、これらは読み取り専用(または変更不可)だが下のレイヤになされた変化(デルタ)を捉える。各レイヤが一つの親を持つが、その親自体、親その他を持っていることがある。最上位レイヤは、結合中(union-ing)のファイルシステム(私のdocker実装ではAUFS)から読み込むことができ、全変更を読み取り専用ファイルシステムとして、単一の統合的ビューで表示する。これは右の「union view」を参照してほしい。

これらのレイヤの輝かしい姿を見たいなら、ホストファイルシステムの別の場所を探してみるといいかもしれない。これらのレイヤは起動中のコンテナの中から直接見ることはできない。私のdockerホストシステムでは、aufsと呼ばれるサブディレクトリの/var/lib/dockerで見ることができる。# sudo tree -L 1 /var/lib/docker/
/var/lib/docker/
├── aufs
├── containers
├── graph
├── init
├── linkgraph.db
├── repositories-aufs
├── tmp
├── trust
└── volumes7 directories, 2 files

コンテナの定義

コンテナは、最上位が読み書きレイヤであるレイヤスタックの「union view」として定義される。

pasted image 3

上の図に示した通り、最上レイヤが読み書きレイヤであることを除いて、イメージとほぼ同じことだと分かるだろう。何人かの読者は現段階で、この定義では、このコンテナが起動中かどうかについて何も触れられていないことに気づくかもしれないが、これはわざとそうしている。まさに、この発見こそが、これまで私が抱えていた多くの混乱を解消してくれたのだ。

要点: コンテナは、(読み取り専用レイヤ自体の)イメージ上の読み書きレイヤとしてのみ定義される。これは起動中である必要がない。

だから、起動中のコンテナについて議論したいなら、起動中のコンテナの定義をする必要がある。

起動中コンテナの定義

起動中のコンテナは、読み取り・書き込みできる「union view」で、内部は分離したプロセス・スペースとプロセスである。下の図は、プロセス・スペースに囲まれた読み書きコンテナだ。

pasted image 4

これは、cgroupやnamespaceなどのカーネルレベルの技術によってもたらされるファイルシステム上の分離行為で、これがdockerをこのような有望な技術にしている。このプロセス・スペース内のプロセスは、「union view」ファイル内でファイルの変更、消去、作成をすることができ、この「union view」ファイルは読み書きレイヤに取り込まれる。これを以下の図で表した。

pasted image 5

これを見るには、次のコマンドを実行して欲しい:docker run ubuntu touch happiness.txt.
すると、もう起動中のコンテナがなくても、ホストシステムの読み書きレイヤにある新規ファイルを見ることができる(注:コンテナ内ではなく、ホストシステム上でこれを実行すること)。# find / -name happiness.txt
/var/lib/docker/aufs/diff/860a7b…889/happiness.txt

イメージレイヤの定義

いよいよ、仕上げるためにイメージレイヤを定義しなければならない。以下の図は、イメージレイヤを表している。これを見ると、レイヤが単なるファイルシステムの変更ではないことに気づく。

pasted image 6

メタデータは、レイヤについての追加情報である。このレイヤは、実行時間と実装時間に関する情報だけでなく、レイヤの親についての階層的情報もdockerに取り込ませる。このメタデータは、読み取り、読み書き両方のレイヤに含まれている。

pasted image 7

また、前述したように、各レイヤはIdを使った親レイヤへのポインタを含んでいる(ここでの親レイヤは下記の通り)。レイヤが親レイヤを指していない場合、スタックの一番下にある。

メタデータの場所:
今時点では(docker開発者が実装を変更することができないであろうことは百も承知だが)、イメージ(読み取り専用)レイヤ用のメタデータは、特定のレイヤ/var/lib/docker/grap内の、「json」と呼ばれるファイルの中で見つけることができる:
id/var/lib/docker/graph/e809f156dc985…/json
「e809f156dc985..」のところは省略されたレイヤのidだ。コンテナのメタデータはたくさんのファイルに分かれるようだが、大体は/var/lib/docker/containers/(は読み書きレイヤのid)内で見つかる。このディレクトリは、コンテナを外部に公開するために必要とされる実行時のメタデータをたくさん含んでいる: networking、 naming、logsなど。

全部まとめてやってみる

それでは、これらの視覚的な例えと実装の詳細に照らし合わせて、コマンドを見てみよう。

docker create

SnapCrab_NoName_2015-12-11_18-44-15_No-00
「docker create」コマンドは、イメージidに基づき、スタックの最上位に読み書きレイヤを追加する。このコマンドは、コンテナを起動しない。
pasted image 10

docker start

SnapCrab_NoName_2015-12-7_20-48-30_No-00
「docker start」のコマンドは、コンテナレイヤのユニオンビューの周りにプロセス・スペースを作成する。コンテナ一つにつき、プロセス・スペースも一つだけとされている。

docker run

SnapCrab_NoName_2015-12-11_18-45-3_No-00
皆(自分も含め)が聞く最初の質問の一つは、「『docker start』 と 『docker run』の違いは何?」だ。読者からすると、この違いを細やかに説明することこそが、この記事の全体的な真意なのではないのかと言うかもしれない。
pasted image 12
ご覧のとおり、docker runコマンドはイメージから始め、コンテナを作成し、コンテナを起動する(コンテナを起動中にする)。これは非常に便利で、 2つのコマンドの詳細を隠蔽する。

脱線: 前述した、Gitシステムを理解することとの類似点に続いて、私は「docker run」コマンドが「git pull」に似ていると考えている。「git pull」(「git fetch」と「git merge」の組み合わせ)のように、「docker run」はそれ自体に意味や権限を持つ2つの基本的なコマンドの組み合わせだ。

そういう意味では、これは確かに便利だが誤解を生みやすそうである。

docker ps

SnapCrab_NoName_2015-12-11_18-46-6_No-00
「doccker ps」コマンドは、システム内の起動中のコンテナを表示する。これは、非起動状態にあるコンテナを隠す、非常に重要なフィルターだ。非起動コンテナを見るには、次のコマンドを使う必要がある。

docker ps -a

SnapCrab_NoName_2015-12-11_17-29-32_No-00
「docker ps -a」コマンドについて、「a」はall(全て)の略で、このコマンドは、システム内の起動中と停止中の全コンテナを表示する。

docker images

SnapCrab_NoName_2015-12-7_20-49-30_No-00
「docker images」コマンドは、システム内の最上位イメージを表示する。事実上、イメージと読み取り専用レイヤを区別するものはない。コンテナを結合するイメージ、または取得されたイメージのみ、最上位とみなされる。各最上位の読み取り専用レイヤの下には、隠れたレイヤがたくさんあるかもしれないため、この区別は便宜上のものだ。

docker images -a

SnapCrab_NoName_2015-12-11_18-47-12_No-00
「doker images -a」はシステム内の全イメージを表示する。これはまさにシステム上の全読み取り専用レイヤを表示することと同じだ。ひとつのimage-idの下のレイヤを見たいなら、後で説明する「docker history」コマンドを使う必要がある。

docker stop

SnapCrab_NoName_2015-12-7_20-49-48_No-00
「docker stop」コマンドは、起動中のコンテナにSIGTERMを出し、そのプロセス・スペース内の全プロセスを丁寧に停止する。その結果生じるのは、普通だが非起動中のコンテナだ。

docker kill

SnapCrab_NoName_2015-12-7_20-49-48_No-00
「docker kill」コマンドは、起動中のコンテナの全プロセスに対し、非丁寧なSIGKILLコマンドを出す。これは、シェルでControl-Cと打つのと同じことだ(追記:Control-CはSIGINTを送る)。

docker pause

SnapCrab_NoName_2015-12-7_20-50-13_No-00
実行中のプロセスに実際のUNIXシグナルを送る「docker stop」や「docker kill」と違って、「docker pause」は特殊なcgroups機能を用いて実行中のプロセス・スペースを中断または一時停止させる。その論拠はここで読むことができる(https://www.kernel.org/doc/Documentation/cgroups/freezer-subsystem.txt)が、一言で言えば、Control-Z(SIGTSTP)の送信は、プロセス・スペース内のプロセスを完全に中断するには透過性に欠けるということだ。

docker rm

SnapCrab_NoName_2015-12-7_20-50-26_No-00
「docker rm」コマンドは、コンテナを定義している読み書きレイヤをホストシステムから削除する。これは、停止中のコンテナで実行されなければならない。このコマンドは、効率的にファイルを削除してくれる。

docker rmi

SnapCrab_NoName_2015-12-7_20-50-35_No-00
「docker rmi」コマンドは、イメージの「union view」を定義する読み取りレイヤを削除する。「docker pull」を出したレポジトリからは、まだイメージが見つかるかもしれないが、このコマンドはホストからイメージを削除する。「docker rmi」は最上位レイヤ(またはイメージ)にのみ使用でき、中間の読み取り専用レイヤには使うことができない(「force(強制)」の「-f」を使わない限り)。

docker commit

SnapCrab_NoName_2015-12-7_20-50-44_No-00
「docker commit」コマンドは、コンテナの最上位読み書きレイヤを取得し、読み取り専用レイヤに作り変える。これは効率的に、コンテナ(起動中でも停止中でも)を変更不可能なイメージに変えてくれる。
pasted image 23

docker build

SnapCrab_NoName_2015-12-7_20-51-8_No-00
上の図では、Dockerfileファイル内でbuildコマンドがFROM命令を開始イメージとしてどのように使うかを表しており、繰り返し1)実行(createとstart)、2)変更、3)コミットする。ループの各ステップで、新しいレイヤが作成される。「docker build」を実行することで、新規レイヤをたくさん作成することができる。

docker exec

SnapCrab_NoName_2015-12-7_20-51-38_No-00
「docker exec」コマンドは、起動中のコンテナで実行され、起動中コンテナのプロセス・スペースでプロセスを実行する。

docker inspect or

SnapCrab_NoName_2015-12-7_20-51-46_No-00
「docker inspect」コマンドは、そのコンテナやイメージの最上位レイヤに関連付けられるメタデータを取ってくる。

docker save

SnapCrab_NoName_2015-12-7_20-51-57_No-00
「docker save」コマンドは、単一のtarファイルを作成する。このファイルは別のホストシステムにインポートをする際に使うことができる。「export」コマンドと違って、全てのメタデータを含む個々のレイヤを保存する。このコマンドはイメージにのみ実行できる。

docker export

SnapCrab_NoName_2015-12-7_20-52-5_No-00
「docker export」コマンドは、「union view」中身のtarファイルを作成し、非docker用途での消費のためにファイルを均す。このコマンドはメタデータとレイヤを削除する。コンテナにおいてのみ実行される。

docker history

SnapCrab_NoName_2015-12-7_20-52-15_No-00
「docker history」コマンドは、image-idを取得し、読み取り専用レイヤ(それ自身のイメージ)を再帰的にプリントする。この読み取り専用レイヤは、入力image-idの先祖である。

結論

コンテナとイメージの視覚化を楽しんで頂けただろうか?こうした例えに関連するかは分からないが、他にも、コマンドがたくさんある(pull, search, restart, attach等)。だが、この記事を苦労して書いたことで、dockerの基本コマンドの大部分が理解しやすくなったのではないかと思っている。私がdockerを勉強したのは、ほんの2週間だったので、もし何かの点を見逃していたり、もっと良い説明ができそうな箇所があれば、コメントして欲しい。

原文:http://merrigrove.blogspot.jp/2015/10/visualizing-docker-containers-and-images.html (2015-11-20)
※元記事の筆者には直接翻訳の許可を頂いて、翻訳・公開しております。

 -プログラミング

  関連記事

未経験からGo言語エンジニアになるためには

IT業界では、人材不足が進んでいるため未経験からエンジニアを目指す方やフリーランスエンジニアを目指す

【入門編】絶対押さえておくべきScalaの特徴を徹底的に解説!

TwitterやLinkedInなど海外の有名サービスを中心にScalaが使わており、日本国内でも徐

未経験からPythonエンジニアになるためには

IT業界では、人材不足が進んでいるため未経験からエンジニアを目指す方やフリーランスエンジニアを目指す

Swift案件の動向 単価相場や案件内容について

Swift は、Worldwide Developers Conference (WWDC) 201

V8はどうやってJavaScriptコードを最適化しているのか?

僕の過去記事で、NodeJSがなぜ速いかについて話した。今日は、V8について話したいと思う。 多分、

未経験からRubyエンジニアになるためには

IT業界では、人材不足が進んでいるため未経験からエンジニアを目指す方やフリーランスエンジニアを目指す

NGINXのスレッドプールがパフォーマンスを9倍にする!

NGINXのパフォーマンスをスレッドプールで9倍にする

FAworks未公開案件やお得な情報が届く! 無料でメルマガ登録する はじめに NGINXが接続処理

もう二度と、絶対にMongoDBを使うべきじゃない理由

もう二度と、絶対にMongoDBを使うべきじゃない理由

FAworks未公開案件やお得な情報が届く! 無料でメルマガ登録する   MongoDBは

Dockerコンテナのためのテスト戦略

Dockerコンテナのためのテスト戦略

おめでとう!あなたはDockerイメージの作り方を知っていて、わかりやすいアプリケーションで複数のコ

【初心者向け】TypeScriptの特徴やJavaScriptとの違いついて解説!

TypeScriptは近年注目されている言語の1つです。本記事では、TypeScriptの特徴やJa