Dockerがディスク容量を節約するための仕組み
March 2, 2020
Dockerってなんでディスクを食い尽くさないんだろ?
Dockerってなんでディスクを食い尽くさないか疑問に思いません? DockerHubとかをのぞいてイメージのサイズを見ると512MBとかあったりして、それをコンテナとしていくつも起動してとやってるとあっという間にディスクがパンパンになりそうな気がします。 が、実際はそんなことはあまりないです。
Dockerが如何にしてディスク容量をおさえているかのかが気になったので調べてみました。
Dockerのイメージとコンテナ、そしてレイヤーについて
Dockerにおけるイメージとコンテナはなんでしょうか? ここではディスクがどのように使われるかを意識して、レイヤーの考えと共にイメージとコンテナを説明してみます。
イメージについて
図を用意しました。Dockerfileとイメージの関係性です。
このDockerfileをbuildするとしましょう docker buildを行うとDockerfileの一行一行が読み込み専用のレイヤーとして作成されどんどん積み重なっていきます
FROM ubuntu
のレイヤーCopy
のレイヤーRUN
のレイヤー
といった形です。 そしてこれを束ねたものがイメージになります。
コンテナについて
コンテナはイメージの上に薄い書き込み可能なレイヤーを付与して束ねたものです。 コンテナ内でのディスクへの書き込みなどはこの書き込みレイヤーに対して行われ、R/Oのイメージのレイヤー群に書き込みが行われることはありません。
なぜこのような仕組みになっているか?
冒頭で述べた、ある1つのイメージから複数のコンテナを動かして、1つのコンテナ毎にイメージが持つファイル全てをコピーすることを想像してください。 すごいボリュームになりますよね R/Oで共有できる部分をまとめれば容量を節約できるのが想像できますよね
さらにレイヤーという概念を取り入れていることで、コンテナとイメージ間だけでなく、イメージとイメージの間でもディスクの節約ができます。 つまりイメージ間でもレイヤーが共通の部分は共有されます
# Dockerfile-1
FROM ubuntu:18.04
RUN apt-get update && apt-get install -y git
RUN curl https://google.co.jp
# Dockerfile-2
FROM ubuntu:18.04
RUN apt-get update && apt-get install -y git
RUN curl https://yahoo.co.jp
上記のように同じレイヤー(aptでgitを取るとこまで同じ)は共有されるわけです。
Docker Storage Driverについて
前述のイメージ、コンテナという概念を実現するための仕組みがDocker Storage Driver
になります。
実装は複数ありますが、公式で推奨されているoverlay2についてもう少し掘り下げてみます。
他の実装については以下のリンクを参照 https://docs.docker.com/storage/storagedriver/select-storage-driver/
overlay2とは?
FileSystemの1つです。
その中でもUnion File System
と呼ばれる、複数の異なるファイルシステムのファイルやディレクトリ同士を透過的に重ねる (マージする) ことができるFile Systemの実装の1つになります。
overlay2では複数のディレクトリ/ファイルを重ね合わせて1つのディレクトリツリーのように見せることができます。
overlay2では3つのレイヤーを用いることで、これを実現しています。
overlay2の3つのレイヤーについて
Lower
: 読み込み専用のレイヤーUpper
: 書き込み可能なレイヤーOverlay
: LowerとUpperをマージしたレイヤー
ユーザーはOverlayレイヤーに対して操作を行います。この時ユーザーはLowerやUpperについて意識することなくデータを扱えます。
ただこれだけだと、少しわかりづらいと思うので、実際にファイルを参照/書き込み/削除した場合にどのように動作するかを確認してみます。
実際に動かして動作を確認してみる
実機を使って、Overlayの挙動を確認していきます.
ここではoverlay2でなく、overlayで確認しています。ここで説明するLower, Upper, Overlayというレイヤーの概念については違いはありません、overlay2はoverlayのアップデートバージョンで、性能が大幅に向上しています。また大きな違いはoverlay2では複数のLowerレイヤを重ね合わせれるようになりました。
OverlayFSの用意
# Upper, Lower, Overlay用のディレクトリを作成します
mkdir lower upper overlay work
# テスト用に各ディレクトリにファイルを配置しておきます
echo "Lower" > lower/onlyLower
echo "Lower" > lower/both
echo "Upper" > upper/both
echo "Upper" > upper/onlyUpper
# ディレクトリをOverlayFSとしてマウントしてあげます。
mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work overlay overlay
# overlayディレクトリを覗くと、レイヤーがマージされている様子がわかります
ls -lF overlay/
#=> total 12
#=> -rw-r--r-- 1 root root 6 Mar 14 07:24 both
#=> -rw-r--r-- 1 root root 6 Mar 14 07:24 onlyLower
#=> -rw-r--r-- 1 root root 6 Mar 14 07:24 onlyUpper
ファイルの参照
overlayでファイルを参照するとき、UpperレイヤーからLowerレイヤーから順に読んでいきます。 もしUpperレイヤーに該当のファイルが存在すれば、そのファイルを返します。 Upperレイヤーに存在しなければ、Lowerレイヤーから読み込みます。
cat overlay/onlyUpper
#=> Upper
cat overlay/both
#=> Upper
cat overlay/onlyLower
#=> Lower
ファイルの書き込み
Upperに存在するファイルに対しての書き込みは通常の書き込み処理をUpperレイヤーのファイルに対して行います。 Upperに存在しないファイルに対しての書き込みは少し特殊な挙動になります。 overlayはまずLowerから対象のファイルをUpperにCopyし、新しくUpperにコピーされたファイルに対して書き込みを行います。これによりLowerのR/Oが保たれます。
# それぞれのファイルに書き込みを行います
echo "Add" >> overlay/onlyUpper
echo "Add" >> overlay/both
echo "Add" >> overlay/onlyLower
# 正常に書き込みされています
cat overlay/onlyUpper
#=> Upper
#=> Add
cat overlay/both
#=> Upper
#=> Add
cat overlay/onlyLower
#=> Lower
#=> Add
# upperレイヤーに新しくファイルが作成されていることを確認します
ls -lF upper/
#=> total 12
#=> -rw-r--r-- 1 root root 10 Mar 14 07:25 both
#=> -rw-r--r-- 1 root root 10 Mar 14 07:25 onlyLower
#=> -rw-r--r-- 1 root root 10 Mar 14 07:25 onlyUpper
ファイルの削除
ファイルの削除も少し面白い挙動になります。 ファイルの削除はLowerに削除対象のファイルが存在するかどうかで挙動が変わります。 Upperに削除対象ファイルが存在する場合は、通常の削除処理を行いUpper内の該当ファイルを削除します。
Lowerにファイルが存在する場合は、Upperにwhiteoutファイルと呼ばれる、削除対象と同名のファイルを作成し、Lowerのファイルを削除はしません。
なぜこのような挙動になるのでしょうか? まず上記画像の真ん中の例(Upper/Lower両方にあるファイルを削除する場合)を考えてみましょう. Upperのファイルだけを削除してLowerに残してしまうと、overlayの挙動としては、Lowerからファイルが読み込めてしまいます。そのため、WhiteoutというファイルをUpperに置いてあげることで、Lowerからファイルを読み込まないことを実現しています。
さらに画像の上の例(Lowerにのみ存在する場合)を考えます ここでLowerのファイルを削除するR/Oが保たれませんし、Lowerはここだけでなく、別のoverlayとマージされることもあります。(前述のDockerのイメージとイメージの間でレイヤーが共有される話です) そのため、ここでファイルを削除することは他の参照元に対しても影響を与える可能性があるため、同様にwhiteoutファイルを作成する挙動になっています。
# Upperにのみ存在するファイルを削除する
rm overlay/onlyUpper
# 普通に削除処理
ls -lF overlay/
#=> total 8
#=> -rw-r--r-- 1 root root 10 Mar 14 07:25 both
#=> -rw-r--r-- 1 root root 10 Mar 14 07:25 onlyLower
ls -lF upper/
#=> total 8
#=> -rw-r--r-- 1 root root 10 Mar 14 07:25 both
#=> -rw-r--r-- 1 root root 10 Mar 14 07:25 onlyLower
# UpperとLower両方に存在するファイルの削除
rm overlay/both
# overlayレイヤーからは消えているように見える
ls -lF overlay/
#=> total 4
#=> -rw-r--r-- 1 root root 10 Mar 14 07:25 onlyLower
# Upperレイヤーには同名のwhiteoutファイルが作成される
ls -lF upper/
#=> total 4
#=> c--------- 1 root root 0, 0 Mar 14 07:30 both
#=> -rw-r--r-- 1 root root 10 Mar 14 07:25 onlyLower
# Lowerにはファイルが残っている
ls -lF lower/
#=> total 8
#=> -rw-r--r-- 1 root root 6 Mar 14 07:24 both
#=> -rw-r--r-- 1 root root 6 Mar 14 07:24 onlyLower
# 書き込み処理が行われる前のデータが残っている
cat lower/both
#=> Lower
この仕組みを用いて、Docker Storage Driverがイメージ/コンテナという概念を実現しているのです。
まとめ
Dockerのイメージ、コンテナ、レイヤーの概念を確認して イメージをR/O, コンテナをイメージ+R/Wのレイヤーの組み合わせにすることでディスク容量を節約していることを確認しました。 また、これの実現方法の1つである、OverlayFSで実際にR/Oレイヤ(Lower)とR/Wレイヤ(Upper)の組み合わせ(Overlay)の動作を確認しました。
参考
- https://www.slideshare.net/akachochin/overlayfsa-brief-report-of-overlayfs-source-code-reading
- https://docs.docker.com/storage/storagedriver/overlayfs-driver/
- https://blog.tiqwab.com/2017/02/18/docker-technology.html
- https://qiita.com/fireowl11/items/d1cf03aff691f9c0eff9
- https://qiita.com/awakia/items/af9b46c322905cdce1d7
- http://gihyo.jp/admin/serial/01/linux_containers/0018?page=3
- https://docs.docker.com/storage/storagedriver/
- http://docs.docker.jp/engine/userguide/storagedriver/imagesandcontainers.html