もろず blog

もろちゃんがITに関しての様々なトピックを解説します

イケてるエンジニアになろうシリーズ 〜Dockerガチ入門編〜

f:id:chanmoro999:20180801134631p:plain

しばらく記事を書いていなかったので久しぶりのポストです

さて、僕は1年半くらい前から Docker を使い初めて、今では何かアプリケーションを開発するときは積極的に Docker を使っています
Dockerfile で環境構築の手順がそのまま残るし、環境差異を気にしなくて良くなるし、動く状態のプログラムを環境ごと Docker イメージとして扱えるのが最高だと思っています

今回は一度はちゃんと調べておきたいと思っていた Docker が利用しているファイルシステムの仕組みについて書きたいと思います


この記事では
1. Docker のファイルシステムに対するざっくりの理解
2. Docker が利用するファイルシステム
3. ソースコードを見てみる
4. まとめ

について書きます

Docker 入門てきなタイトルですが、Get started みたいなことではなく Docker の使い方とかは一切出てきませんのでその点ご了承ください・・・


1. Docker のファイルシステムに対するざっくりの理解

さて、Docker イメージやファイルシステムについて説明される時は、以下の図のように複数のレイヤーを重ねている図をよく見かけます

f:id:chanmoro999:20180801122211p:plain

初めて Docker を使う人にとってはパッと見で分かりやすいのかもしれませんが、実際に Docker をたくさん使ってきた感覚を持って改めてこの図を見て身ると、Docker のファイルシステムの動作はこの図だけだとなかなかイメージできないよなーとふと思いました


そこで、Docker のファイルシステムの全体についてだいたいこんなイメージを持っておけば問題ないでしょうというのをざっくり1枚の図に詰め込んでみました

f:id:chanmoro999:20180801120956p:plain

いろいろ詰め込みすぎてカオスになってしまいましたが、ポイントを列挙するとこんな感じです

  • Dockerfile に書かれる1つのコマンドに対応して1つのレイヤーが作成される
  • イメージ内のファイルは読み取り専用として扱われ変更されることはない
  • コンテナの実行時にはコンテナ専用の書き込み可能レイヤーがマウントされる
  • コンテナ内のファイルを参照する場合
    • 複数のレイヤーに同じファイルが存在する場合は最上位レイヤーのものを利用
  • コンテナ内のファイルが変更される場合
    • 元のファイルが書き込み可能レイヤーにコピーされ変更
  • コンテナ内のファイルが削除される場合
    • 書き込み可能レイヤーに特殊ファイルが作成されてアクセスできないよう制御

これら機能は実際のところどうやって実現されているんだろう?というのが気になってきますね
これらは Docker が利用しているファイルシステムの仕組みによって実現されています

ガチ入門感があってワクワクしてきましたね


記事の先頭に戻る

2. Docker が利用するファイルシステム

さて、それでは Docker が利用するファイルシステムについて詳細を見ていきましょう

前述の図では、各レイヤー毎に部分的なディレクトリ構造が保持されていて、Dockerイメージ全体としては各レイヤーが持つディレクトリ構造の和として利用されていると説明しました
この動作は UnionFileSystem という仕組みのファイルシステムによって実現されています

UnionFileSystem は同じマウントポイントに複数のディスクをマウントし、それぞれに含まれるディレクトリ構造の和として扱う仕組みを提供するファイルシステムです
言葉だと説明が難しいので図に書いてみるとこんな感じです

f:id:chanmoro999:20180801123600p:plain


複数のディスクに同じディレクトリやファイルがに存在する場合には後からマウントしたディスクのものが優先されます

UnionFileSystem でのファイルの作成、更新、削除については、

  • 新規に作成する場合は最上位の書き込み可能なディスクに作成
  • 読み取り専用の領域に対して更新する場合は、書き込み時にコピーを作成して更新
  • 読み取り専用の領域に対して削除する場合は、whiteout ファイルという特殊なファイルを作成して動作上見えなくする

という動作をします

この仕組みが理解できると Docker イメージ、コンテナ内のファイルシステムの動作は UnionFileSystem の動作の通りだなーというのがよくわかりますね


また、UnionFileSystem にはいろいろな種類のの実装があり、Docker ではどの実装を利用するかを StorageDriver で選択できるようになっています
docs.docker.com

Dockerが登場した当初からしばらくは AUFS が使われていたようですが、現在の公式ドキュメントでは AUFS よりもパフォーマンスが高いという理由で OverlayFS の利用が推奨されているようでした
docs.docker.com


UnionFileSystem は当然ながら Docker やコンテナだけのためのものではなく、身近な例だと LiveCD などで外部ストレージから OS を起動する際にも利用されているようです
その他 UnionFileSystem についての詳細はこちらの記事が非常に分かりやすかったのでぜひ一度読んでみてください
UnionMountとUnion-type Filesystem(1) - O'Reilly Japan Community Blog


記事の先頭に戻る

3. ソースコードを見てみる

さて、UnionFileSystem の仕組みによって Docker のファイルシステムが構成されていることが分かりましたが、本当にそうなのかコードを見てみたくなってきましたね

Docker のソースコードはこちらの2つのリポジトリにありました
https://github.com/docker/engine
https://github.com/moby/moby

Docker は2017年に Docker のコア部分を OSS にした mobyプロジェクトというのを発足しており、Docker の実装は moby のコードをベースにされているようです
Github 上でも docker/engine は moby/moby を fork して作られているのが分かりますね
実際に中身を見ていくと確かに実装の大部分は moby のコードを利用しているようでした


Docker では1つのレイヤー毎に対応したディレクトリが作成され、そのディレクトリを UnionFileSystem の仕組みによってマウントしているようです
この辺りのコードに新しいレイヤー(=新しいディレクトリ)を作る処理が書かれています
https://github.com/docker/engine/blob/master/daemon/graphdriver/overlay/overlay.go#L280
https://github.com/docker/engine/blob/master/layer/layer_store.go#L479

またこのテストコードでは2つのレイヤーをマウントした時の挙動をテストをしています
https://github.com/docker/engine/blob/master/layer/mount_test.go#L14

前述の UnionFileSystem の挙動と同様の動作を確認するテストが書かれています

この他にも Docker イメージをビルドする際の `ADD`、`RUN` に対応する処理のコードはここから追っていくことができます
https://github.com/docker/engine/blob/master/builder/dockerfile/dispatchers.go#L90
https://github.com/docker/engine/blob/master/builder/dockerfile/dispatchers.go#L341


関連するコードが大量にありすぎてうまくまとめられなかったので、コードのご紹介はこの程度で・・・
いろいろな部分が抽象化されているので、ここに書いてる!というのは示しにくいですね

大枠のイメージとしてはこれまでに掲載した図の通りですが、ソースコードを見ることで実際にその通りに実現されているということがよく分かりました


記事の先頭に戻る

4. まとめ

さて、今回は Docker ガチ入門ということで Docker が利用しているファイルシステムについて詳細を見ていきました
今回調べた内容が頭に入っていると、Docker ガチ入門の第一歩を踏み出せたような気がしてきますね

プロセス分離の仕組みや同一ホスト内でのセキュリティというのも奥が深そうだったので、またテンションが上がってきたら調べて続きを書きたいと思います


実用上では Docker を単体で使うという場面はあまり無く、大抵は Kubernetes とかのオーケストレーションツールを利用することになると思います
個人的には、これらを使うことでアプリケーションスタックが丸ごと定義できて、非常に簡単に環境を再現して動かせるというところにとても魅力を感じてます
サーバーサイドエンジニアがインフラも含めてプログラミングすることがとても簡単になり、実際の業務でも一気にできることの幅が広がった感じがしています


今回色々と調べてみて、今までの認識は "Docker が便利だ!" という程度で認識が固定されていてしまったんですけど、技術的には昔から存在していたものを開発者のデプロイ・デリバリーの視点でより簡単に使いやすくしたものが Docker なんだなーということがよく分かりました

コンテナの分野を一気に盛り上げた Docker は非常に素晴らしいソフトウェアだと思いますが、その他のコンテナ技術にも特色がありそれぞれ進化しているようなので、DockerやKubernetesに限らず今後も様々な製品を積極的に使っていこうとおもいます

皆さんも快適なコンテナライフを!


記事の先頭に戻る