データはコンテナより長く生きなければならない
一言でいうと
コンテナの書き込みレイヤーは、コンテナとともに死にます。長く生きるべきデータは、ボリュームやバインドマウントでコンテナの外に置く必要があります。
なぜ必要なのか
コンテナを再デプロイしたらアップロードしたファイルがすべて消えた、という報告は、今でもよくあります。原因はバグではなく、設計です。書き込みレイヤーはコンテナオブジェクトに付いており、コンテナを削除すると、そのレイヤーも一緒に消えます。
ここにパフォーマンスの問題が重なります。下のレイヤーのファイルを変更するには、上のレイヤーへまるごとコピーする必要があり(copy-up)、データベースファイルのような大きなファイルには非常に不利です。大量の書き込みは必ずボリュームに移しなさいという言葉が、そのため決まり文句になりました。
どう動くのか
方式は3つあり、性格が違います。
| 項目 | バインドマウント | 名前付きボリューム | tmpfs |
|---|---|---|---|
| 場所 | ホストのパスを直接指定 | ランタイムが管理 | メモリ |
| 永続性 | ホストに永続 | ボリュームに永続 | 終了時に消滅 |
| 移植性 | 低い(パスに依存) | 高い | 高い |
| バックアップ | 自分で管理 | 専用コマンド | 不可 |
| 主な用途 | 設定ファイル、開発中のソース | DBデータ、アップロード | 一時ファイル、シークレット |
構文も2つあります。短い形式の-v source:target:optsと、明示的な--mount type=...,source=...,target=...,readonlyで、後者はオプション名が見えるため、レビューで誤解が少なくなります。
読み取り専用マウントは、思った以上に重要です。設定ファイルを:roで接続すると、コンテナが、うっかりでも侵害でも、設定を変更できません。アプリケーションが書き込みを試みるとRead-only file systemエラーが出て、このエラーはそのまま「このコンテナが、書き込んではいけない場所に書き込もうとしている」というサインです。
現場での姿
バックアップのパターンは、決まり文句のように固まっています。ボリュームとバックアップ用ディレクトリを同時にマウントした一時コンテナを起動して、アーカイブを作ります。
docker run --rm -v dk-data:/source:ro -v /root/backup:/backup alpine:3.20 tar czf /backup/dk-data.tgz -C /source .
-Cでソースディレクトリの中に入り、相対パスで格納することが核心です。こうしておくと、復元するときにパスがずれません。
rootless環境では、所有権の問題がよく起きます。ホストとコンテナのUIDマッピングが違うためで、このときはファイルをchmod 777で開放する代わりに、マッピングを理解して合わせるほうが正しいです。権限を開放すると、そのコンテナだけでなく、同じホストの他のプロセスにも開放することになるからです。
ボリュームとバインドマウントを選ぶ基準
名前は似ていますが、性質が違います。
| 名前付きボリューム | バインドマウント | |
|---|---|---|
| 管理主体 | Docker | 人 |
| 場所 | /var/lib/docker/volumes/… |
ホストの任意のパス |
| バックアップ | docker run --rm -v vol:/d …で取り出す |
ホストのファイルのまま |
| 権限 | イメージのUIDに従って初期化 | ホストの権限のまま |
| 開発の利便性 | ソースの変更が見えない | すぐに反映 |
権限の行が、実務の落とし穴です。バインドマウントは、ホストのファイルの所有者・モードをそのまま持ってくるため、コンテナがnonroot(UID 65532)で動くのにホストのファイルがroot:root 0644だと、書き込めません。逆に名前付きボリュームは、最初に作るとき、コンテナ内の対象パスの権限をコピーして初期化するため、たいていそのまま動作します。
最初に一度だけコピーされる
名前付きボリュームの初期化は、空のときの1回だけです。
빈 볼륨을 /app/config 에 붙이면
→ 이미지의 /app/config 내용이 볼륨으로 복사된다
이미 내용이 있는 볼륨을 붙이면
→ 이미지의 내용은 가려진다. 복사하지 않는다
このコードブロックの韓国語の文は、空のボリュームを/app/configにマウントするとイメージの内容がボリュームへコピーされ、すでに内容のあるボリュームをマウントするとイメージの内容は隠れてコピーされない、という意味です。
そのため、イメージを新しく作ってデフォルトの設定ファイルを変更しても、古いボリュームを使うコンテナは、古いファイルを見続けます。「イメージを直したのに反映されない」のよくある原因です。設定はボリュームに置かず、ConfigMapや環境変数で注入するほうがよいです。
Kubernetesでの対応
| Docker | Kubernetes | 使う場所 |
|---|---|---|
| 名前付きボリューム | PVC | DBデータ |
| バインドマウント | hostPath | ノードのファイルへのアクセス(できる限り避ける) |
| tmpfs | emptyDir: {medium: Memory} |
機密情報・一時データ |
| — | emptyDir: {} |
コンテナ間の共有(Podの寿命) |
emptyDirはPodが削除されると消えます。ノードの再起動でも消えます。ところが、Podの再起動(コンテナだけが死ぬ場合)では残っているため、テストするとき勘違いしやすくなります。
権限の問題は、fsGroupで解決します。
spec:
securityContext:
fsGroup: 2000 # 볼륨의 그룹 소유자를 2000 으로 바꿔 준다
runAsUser: 1000
runAsNonRoot: true
fsGroupは、ボリューム内のすべてのファイルの所有権を再帰的に変更するため、ファイルが非常に多いとPodの起動が遅くなります。その場合は、fsGroupChangePolicy: OnRootMismatchで、ルートディレクトリだけを確認させます。
次のラボですること
ボリュームを作って使い、コンテナを削除してもデータが残ることを確認し、読み取り専用マウントのエラーメッセージを自分で出してみたうえで、バックアップと復元を往復します。