Slide 1

Slide 1 text

ちょっと Overlayfs の実装、 読んでみました

Slide 2

Slide 2 text

自己紹介 名前: @akachochin 近況:お仕事は、組み込みソフト技術者です。 一時期カーネルを離れていましたが、 NetBSD/Linux カーネル の世界にまた戻ってきました。 趣味:趣味は名のとおり、赤提灯めぐり。場末の飲み屋、さい こーです。これだけで 20 分程度語れるけど、今は語りません ( 笑 )

Slide 3

Slide 3 text

いきさつ 前回の「コンテナ型仮想化の情報交換会@東京」懇親会で「ち ょっと喋ってみようかなあ」と勢いで言ったのが運のつき。 しっかり覚えられてました ( 笑 )

Slide 4

Slide 4 text

今日お話すること 腐ってもカーネルでご飯食べてる身。何か、カーネルのソース 読むか。 じゃあ、 Overlayfs 読むか。 Union Filesystem の実装見たことないし。

Slide 5

Slide 5 text

使用上の注意です。 ● 読んだソースは、 linux_4.4.0-21.37 (Ubuntu16.04) です。 自宅で主に使っているのが Ubuntu16.04 で、 ftrace した結果 と突き合わせるといろいろはかどるからです。 ● プレゼンの時間的な制約などから、細かいことは説明しきれま せん。なので、厳密さよりもわかりやすさを優先したり、話を 端折ったりしている箇所が結構あります。ご了承のほどを。

Slide 6

Slide 6 text

Overlayfs って何? Overlayfs って何?

Slide 7

Slide 7 text

Overlayfs って何? この章を書くにあたり、「 LXC で学ぶコンテナ入門 -軽量仮 想化環境を実現する技術第 18 回  Linux カーネルのコンテナ 機能 [7]overlayfs 」という記事を大いに参考としました。 記事を書かれた加藤さん、大変感謝です。

Slide 8

Slide 8 text

Overlayfs って何? Overlayfs は Union filesystem の一種です。 Union filesystem は複数のファイルシステムをひとつの場所で マウントし、仮想的にまとめてあたかもひとつのファイルシス テムであるかのように見せる技術です。 Linux 3.18 のときに Linux カーネルにマージされました。 また、 Docker では 1.4.0 で overlayfs に対応しました。

Slide 9

Slide 9 text

Overlayfs って何? 図にすると、こんな感じ。 ※https://docs.docker.com/engine/userguide/storagedriver/overlayfs-driver/ より図 を引用 複数のファイルシステムのレイヤを重ねあわせてマージし たものを見せている雰囲気がつかめたでしょうか?

Slide 10

Slide 10 text

Overlayfs って何? とはいっても、イメージしづらいので、 Docker のコンテナを 例に説明します。 ・ Docker の Image Layers は ReadOnly なファイルシステム 層を参照しています。 ・ Docker がコンテナを作ると き、 Image の上に Writeable な層 ( コンテナ層 ) が追加さ れます。 ※https://docs.docker.com/engine/userguide/storagedriver/imagesandcontainers/ より図を引用

Slide 11

Slide 11 text

Overlayfs って何? コンテナ層を使って、 Docker はひとつのイメージを共有しつ つ、それぞれ異なる複数のコンテナを作れるのです。 ※https://docs.docker.com/engine/userguide/storagedriver/imagesandcontainers/  より図を引用

Slide 12

Slide 12 text

Overlayfs って何? Overlayfs に戻ります。 Overlayfs 視点では、各レイヤなどの 名称は以下のとおりとなります。 ※https://docs.docker.com/engine/userguide/storagedriver/overlayfs-driver/ より図 を引用 Docker での呼び名 overlayfs での呼び名 下の Read Only な層 Image Layer lowerdir 上の writable な層 Container Layer upperdir マージした結果 Container mount merged(Overlayfs から 見たときに見えるもの )

Slide 13

Slide 13 text

Overlayfs って何? マージされたファイルシステムを Overlayfs 経由で見ると・・・ ● ファイルシステム間で重複しないものはそのまま見える ● ファイルが重複→より上位のファイルシステム内のファイルが見える ● ディレクトリが重複→内容がマージされたディレクトリが見える lower upper A overlay a B b C C d d A a C d B b upper と lower の ディレクトリを マージ

Slide 14

Slide 14 text

Overlayfs って何? lower から upper へコピーして・・ ・ (copy up) lower upper A overlay B A B B Write Write upper 側へ 書き込み overlayfs 経由でファイルへの書き込みを行うと・・・ ● upper 側→ upper 側のファイルが書き換わる ● lower 側→ lower 側から upper 側にファイルがコピーされ、 upper 側が書き換 わる

Slide 15

Slide 15 text

Overlayfs って何? overlayfs 経由でファイルの削除を行うと・・・ ● upper 側→ upper 側のファイルが削除される ● lower 側→ upper 側に「削除」を示すファイルを生成、 lower 側ファイルを「覆 う」。 lower upper A overlay B A B 削除 削除 「削除」を示すファイルが作成さ れ、 lower のファイルが「見えなく なる」。 (whiteout)

Slide 16

Slide 16 text

Overlayfs って何? ここまで見たことをまとめます ● overlayfs は、 lowerdir と upperdir をひとつのマウントポイ ントでマウントし、その中で仮想的にマージして見せます。 ● 下に Read Only なファイルシステム層 (lowerdir) を積み重ね ます。 ● lowerdir の上に書き込み可能なファイルシステム層 (upperdir) をひとつ置きます。 ● ファイルシステムへの変更は upperdir で吸収します。

Slide 17

Slide 17 text

Overlayfs を使う準備

Slide 18

Slide 18 text

Overlayfs を使う準備 Overlayfs をマウントする際、以下のようなコマンドを実行し ます。 mount -t overlayfs -o \ lowerdir=lower,upperdir=upper,workdir=work \ overlayfs mountpointdir 少し複雑ですので、ひとつずつ見ていきましょう ( 「 \ 」は行変更を示します。念のため。 )

Slide 19

Slide 19 text

Overlayfs を使う準備 -t overlayfs これは、マウントしたいファイルシステムが Overlayfs であ ることを指定しています。

Slide 20

Slide 20 text

Overlayfs の使い方 -o \ lowerdir=lower,upperdir=upper,workdir=work オプションをカンマ区切りで指定します。指定するオプショ ンは以下のとおりです。 オプション 概要 lowerdir lowerdir として重ねあわせるファイルシステムイメージ ( ディレクトリ ) のパスを指定。複数指定可能。 upperdir upperdir として重ねあわせるファイルシステムイメージ ( ディレクトリ ) のパスを指定。書き込み可能なローカ ルのファイルシステムの必要がある。 workdir overlayfs が使う作業ディレクトリ。 upperdir と同じフ ァイルシステムの名前空間内に存在する必要あり。 upperdir を指定する際は、セットで指定。

Slide 21

Slide 21 text

Overlayfs を使う準備 overlayfs mountpointdir マウントする際はストレージデバイスを指定することが多い のですが、 overlayfs を使用するときに指定する「デバイ ス」は仮想的な「デバイス」という意味合いで overlayfs を 指定します。 最後に仮想的にファイルシステム群をマージした結果を見せ るためのマウントポイントを指定します。

Slide 22

Slide 22 text

Overlayfs の動き

Slide 23

Slide 23 text

Overlayfs の動き 今回は以下のディレクトリ構成で動きを見ます。 lowerdir upperdir upfile updir workdir overlay lowfile lowdir 注:今回は実験を簡単にするた めに、すべてのディレクトリ ・ファイルは同一の名前空間 内の ext4 内にあります。

Slide 24

Slide 24 text

Overlayfs の動き - ls( ディレクトリを読む ) - overlayfs のマウントをしたあとで、 ls コマンドを使 い、 overlay の中を見ます。 すると、 upperdir と lowerdir がマージされたものが見えま す。 lowerdir upperdir upfile updir overlay lowfile lowdir upfile updir lowfile lowdir

Slide 25

Slide 25 text

Overlayfs の動き - ls( ディレクトリを読む ) - 2つのファイルシステムが「重ね合わせられ」ひとつのファ イルシステムに見えることがわかります。 では、これをどうやって実現しているのでしょうか。 ここで、カーネルのソースを読んでみたいと思います。

Slide 26

Slide 26 text

Overlayfs の動き - ls( ディレクトリを読む ) - ls を行う際、ディレクトリの中身を読むために libc の readdir() が呼ばれます。 glibc の場合、 readdir() を経由して、 getdents というシス テムコールが呼ばれます。 getdents は最終的に iterate_dir() という関数を呼び出しま す。この関数は 2 つの引数を受け取ります。

Slide 27

Slide 27 text

Overlayfs の動き - ls( ディレクトリを読む ) - int iterate_dir(struct file *file, struct dir_context *ctx) { /* パラメータチェックなどは割愛 */ /* ディレクトリ内の最後のエントリに到達するまで */ if (!IS_DEADDIR(inode)) { /* 前処理は割愛 */ res = file->f_op->iterate(file, ctx); /* 後処理も割愛 */ } return res; } ここで、 dir_context 構造体 (ctx) は以下 2 点が役目です。 - ディレクトリの中をたどるごとに呼び出す処理を関数ポインタで渡す - 関数ポインタ経由で渡した処理が都度の結果を格納する

Slide 28

Slide 28 text

Overlayfs の動き - ls( ディレクトリを読む ) - ここで、 file->f_op->iterate() 経由で ovl_iterate() が呼ばれます。こ れは Overlayfs 依存の関数です。 最初に、マージの必要がないケースを処理します。 static int ovl_iterate(struct file *file, struct dir_context *ctx) { /* 略 */ /* * 該当ディレクトリは重ねあわせたファイルシステム間で * 重複していなくて、マージの必要がないディレクトリである * この場合、そのディレクトリの実体をそのまま読みだす。 */ if (od->is_real) { /* 略 */ return iterate_dir(od->realfile, ctx); }

Slide 29

Slide 29 text

Overlayfs の動き - ls( ディレクトリを読む ) - 次に、 upper 側と lower 側で同名のディレクトリがあり、マージが必要な ケースを処理します。該当ディレクトリに対するマージがこれまでにされ ておらず、結果が再利用できないケースの処理です。 /* 前ページからの続き */ /* マージが必要だが、マージ結果のキャッシュがない */ if (!od->cache) { struct ovl_dir_cache *cache; /* マージを行い、その結果を「キャッシュ」とする */ cache = ovl_cache_get(dentry); /* 略 */ }

Slide 30

Slide 30 text

Overlayfs の動き - ls( ディレクトリを読む ) - 最後に、得られた結果 ( キャッシュ ) をたどり、ユーザから渡されたバッ ファにコピーします。コピー自体は ctx の関数ポインタで渡された filldir で実施します。 /* 前ページの続き */ /* 辿れるだけディレクトリの中身をたどる */ while (od->cursor != &od->cache->entries) { p = list_entry(od->cursor, struct ovl_cache_entry, l_node); /* 「消された」ことになっていない場合、 */ if (!p->is_whiteout) /* 呼び出し元から渡された ctx の中の関数ポインタを呼ぶ */ if (!dir_emit(ctx, p->name, p->len, p->ino, p->type)) break; /* 略 */

Slide 31

Slide 31 text

Overlayfs の動き - ls( ディレクトリを読む ) - 関数ポインタ (filldir) があるため、呼び出し構造がややわかりにくいで す。そこで、ここまでの話を以下の概要図でまとめました。 ユーザ側 ユーザ側 iterate_dir iterate_dir filldir filldir ovl_iterate ovl_iterate 該当レイヤの iterate 該当レイヤの iterate マージの必要が ないケースの呼び出し マージの必要が あるケース ディレクトリの中身を ユーザ側バッファに コピー ディレクトリの中身を ユーザ側バッファに コピー

Slide 32

Slide 32 text

Overlayfs の動き - upper 側 file への write - upper 側にあるファイルを Overlayfs 経由で書き換える と、 upper 側のファイルが直接書き換わります。 では、これをどうやって実現しているのでしょうか。 ここで、カーネルのソースを読んでみたいと思います。

Slide 33

Slide 33 text

Overlayfs の動き - upper 側 file への write - upper 側にあるファイルを書き込み可能で open() します。 open() からシステムコールを経由して、 vfs_open() が呼び出され、関数 ポインタ経由で ovl_d_select_inode() が呼ばれます。 int vfs_open(const struct path *path, struct file *file, const struct cred *cred) { /* 略 */ if (dentry->d_flags & DCACHE_OP_SELECT_INODE) { inode = dentry->d_op->d_select_inode(dentry, file- >f_flags); if (IS_ERR(inode)) return PTR_ERR(inode); }

Slide 34

Slide 34 text

Overlayfs の動き - upper 側 file への write - ovl_d_select_inode() の中で ovl_path_real() を呼び出します。これによ り、実体 ( ここでは upper 側のファイル ) のパス情報 (struct path) を取 得します。 struct inode *ovl_d_select_inode(... 引数省略 ...) { /* 略 */ type = ovl_path_real(dentry, &realpath); if (ovl_open_need_copy_up(... 引数省略 ...)) { /* 後述 */ } /* 略 */ /* upper 側の inode を返すことで、呼び出し元で upper 側のファイル を open() する。これで以後のアクセスは upper 側に行われる */ return d_backing_inode(realpath.dentry); }

Slide 35

Slide 35 text

Overlayfs の動き - lower 側 file への write - lower 側にあるファイルを overlayfs 経由で書き換える と、 upper 側に書き換え対象と同名のファイルが生成さ れ、 upper 側のファイルが書き換わります。 これにより、以後は upper 側の「書き換えられた」ファイル が見え、 lower 側ファイルは書き換わりません。 では、これをどうやって実現しているのでしょうか。 ここで、カーネルのソースを読んでみたいと思います。

Slide 36

Slide 36 text

Overlayfs の動き - lower 側 file への write - vfs_open() 自体の動きは、先に説明した upper 側にあるファ イルへの書き込みと同様です。 しかし、 d_select_inode 経由で呼ばれる ovl_d_select_inode の挙動が異なります。 それは、 copy_up という処理が行われることです。

Slide 37

Slide 37 text

Overlayfs の動き - lower 側 file への write - struct inode *ovl_d_select_inode(... 引数略 ...) { /* 略 */ type = ovl_path_real(dentry, &realpath); if (ovl_open_need_copy_up(... 引数略 ...)) { /* 略 */ /* ここで lower 側にあるファイルを upper 側にコピーします */ err = ovl_copy_up(dentry); /* 略 */ /* * ファイルを upper 側にコピーした後でパス情報を返し、 * 書き込み処理が upper 側ファイルに行われるようにする */ ovl_path_upper(dentry, &realpath);

Slide 38

Slide 38 text

Overlayfs の動き - lower 側 file への write - ovl_copy_up では以下の図のようなことを実施します。 わざわざ workdir を中継する理由は、変更をアトミック ( 「完全失敗で何 もない」か、「完全成功でファイルが upper 側に生成される」かのどちら か ) にしたいためと推定します。 ( 煩雑さを避けるため、詳細については 相当略しています。 ) lower upper 手順 2. workdir 内のファイル をリネームする 手順 1. 一旦 workdir にコピー workdir upper と lower は 異なるファイルシステム。 そのため、コピー失敗の リスクもある。 そこで、 workdir を中継。 そうすることで失敗時の 変更は workdir にとどまり、 upper から残骸などが 見えない。

Slide 39

Slide 39 text

Overlayfs の動き - lower 側 file の削除 - lower 側にあるファイルを overlayfs 経由で削除しても lower 側のファイルは消えません。その代わりに、「 whiteout 」と いう処理が upper 側に行われ、あたかも該当ファイルが消え たかのように見せかけます。 では、これをどうやって実現しているのでしょうか。 ここで、カーネルのソースを読んでみたいと思います。

Slide 40

Slide 40 text

Overlayfs の動き - lower 側 file の削除 - ファイルを削除する際、 unlinkat() システムコールを呼びま す。この関数は最終的に vfs_unlink() を呼び出します。 vfs_unlink() では関数ポインタ経由で ovl_unlink() を呼び出 します。 さらに ovl_unlink() では ovl_do_remove() を呼び出します。

Slide 41

Slide 41 text

Overlayfs の動き - lower 側 file の削除 - Overlayfs を経由したファイル削除には、以下の 3 パターンが あります。 lower upper A overlay B A B C 削除 削除 C 削除 パターン1 パターン 2 パターン 3 C

Slide 42

Slide 42 text

Overlayfs の動き - lower 側 file の削除 - static int ovl_do_remove(struct dentry *dentry, bool is_dir) { /* 略 ( 大切な処理も含むが、煩雑さを避けるため略 */ type = ovl_path_type(dentry); /* upper 側にだけあるファイルは、 upper 側ファイルを直接消す */ if (OVL_TYPE_PURE_UPPER(type)) { err = ovl_remove_upper(dentry, is_dir); /* lower 側にも該当ファイルがある場合、 whiteout 処理を実行 */ } else { /* 略 */ err = ovl_remove_and_whiteout(dentry, is_dir); /* 略 */ } パターン 1. upper 側を直接削除

Slide 43

Slide 43 text

Overlayfs の動き - lower 側 file の削除 - whiteout 処理とは、「 upper 側に特殊なファイルを作成し、 あたかも lower 側にあるファイルが消されたかのように見せ かける」処理です。先のパターン 2,3 の場合、必要です。 やり方は Overlayfs のバージョンによって異なります。 Overlayfs のバージョ ン 処理概要 V1 消そうとしているファイルと同名のシンボリック リンクを upper 側に作り、拡張属 性 "trusted.overlay.whiteout=y" を付与する。 v2 消そうとしているファイルと同名の charcter device file(major0:minor0) を upper 側に作成す る

Slide 44

Slide 44 text

Overlayfs の動き - lower 側 file の削除 - static int ovl_remove_and_whiteout(struct dentry *dentry, bool is_dir) { /* 略 */ if (is_dir) { /* ディレクトリを消す場合の処理。今回は略 */ } /* 略 */ /* ここで whiteout 処理を実施 */ whiteout = ovl_whiteout(workdir, dentry); /* 略 */ upper = ovl_dentry_upper(dentry);

Slide 45

Slide 45 text

Overlayfs の動き - lower 側 file の削除 - /* lower 側にのみ消去対象のファイルがある場合 ( パターン 2) */ if (!upper) { /* * upper 側にディレクトリエントリを作成する。 * "lookup" なのだが、名前とディレクトリで検索して見つ * からない場合、 inode が空のディレクトリエントリが返る。 * ( ここでは新規に whiteout のためのファイルを生成したと * 考えれば良い ) */ upper = lookup_one_len(dentry->d_name.name, upperdir, dentry->d_name.len); /* 略 */ err = ovl_do_rename(wdir, whiteout, udir, upper, 0); /* 略 */

Slide 46

Slide 46 text

Overlayfs の動き - lower 側 file の削除 - /* * 少なくとも upper 側に消去対象のファイルがある場合 * (upper 側と lower 側の双方にあるケース。パターン 3.) * 単純に upper 側のファイルを消すと、重ね合わせにより lower * 側のファイルが見えてしまい「消せていない」状態になる。 * よって、 upper 側のファイルを whiteout のファイルに置き換える */ } else { /* 略 */ err = ovl_do_rename(wdir, whiteout, udir, upper, flags); /* 略 */ } /* 略 */

Slide 47

Slide 47 text

おわりに ここまで駆け足で Overlayfs の概要と実装の概要をみてきま した。 しかし、細かいところについては時間の関係で説明できてい ません。ソースを読んだ箇所については、細かい話を近いう ちに Qiita(http://qiita.com/akachochin) にまとめたいと思 います。

Slide 48

Slide 48 text

ソースを自分で読みたい人のために いくつかアドバイスです。 1.overlayfs 内に現れる dentry 構造体がどこのレイヤの何の ファイル / ディレクトリを指しているのか意識しましょう。 2.Linux のファイルシステム層について基本的な事項を学んで みましょう。日本語で入手できる資料では、「 Linux カーネ ル解読室」第 15 〜 17 章が最も助けになると思います。 3.ftrace を使って、カーネル内関数呼び出しを追いかけまし ょう。非常にはかどります。

Slide 49

Slide 49 text

ソースを自分で読みたい人のために 4.Linux ファイルシステム層のよく使われる関数 ( 特に lookup 系 ) に慣れましょう。 そして、挙動がわからなければ、ソースを読んでみましょ う。そうすることによってファイルシステムの実装に慣れて きます。 5. データ構造をメモりながら読みましょう。今回紹介しませ んでしたが、 mount 処理 ,lookup 系処理を読むと、全体感が 捉えやすくなるでしょう。

Slide 50

Slide 50 text

参考文献・ Web ページ Web ページ ● 「 LXC で学ぶコンテナ入門 -軽量仮想化環境を実現する技術 第 18 回  Linux カーネルのコンテナ機能 [7]overlayfs 」 http://gihyo.jp/admin/serial/01/linux_containers/0018 ● 「 Understand images, containers, and storage drivers 」 https://docs.docker.com/engine/userguide/storagedriver/imagesandcontainers/ ● そのファイル、安全に更新できていますか?(アトミックなフ ァイル操作:前編) https://heartbeats.jp/hbblog/2013/10/atomic01.html

Slide 51

Slide 51 text

参考文献・ Web ページ 文献 ( 敬称略 ) ● 「 Linux カーネル 2.6 解読室」 高橋浩和 / 小田逸郎 / 山幡為佐久 著 ● 「プログラマのための Docker 教科書」 阿佐志保 著 ● 「 Understanding the Linux Kernel(3rd edition) 」 DANIEL P.BOVET & MARCO CESATI 著 ● 「 Linux Kernel Development(3rd Edition) 」 Robert Love 著

Slide 52

Slide 52 text

ご静聴ありがとうございました。