docker volume createで作成したボリュームをローカルにバックアップしたい。
しかし、Mac や Win10 には docker volume inspect で表示される Mountpoint に該当ボリュームがないのです。
Docker Engine のバージョンによっても挙動が違うらしく、簡単にバックアップ&復元できないかと「mac docker volume バックアップ」で Qiita 記事をググっても記事の内容がバラバラで困ったので、自分のググラビリティとして。
TL; DR (今北産業)
実行中のコンテナの、特定のファイルをローカルにコピーしたいだけなら、docker cp コマンドを使うのが楽です。
docker cp <コンテナ名 or コンテナID>:<コンテナ内のパス> <出力先のパス>
$ docker cp \
lancache-dns-1:/opt/cache-domains/cache_domains.json \
./cache_domains.json
Successfully copied 4.61kB to /root/lancache/cache_domains.json
逆に、複数ディレクトリやボリュームをバックアップしたいだけなら、Alpine Linux などの軽量コンテナに、バックアップ元とバックアップ先の2つのボリュームを一旦マウントし tar などでアーカイブするのが楽です。
docker run でバックアップする
以下は、docker volume create data-hoge で作成し、利用されていたボリュームを、ローカルの ./backup にバックアップしたい場合のコマンドです。
Alpine Linux の軽量イメージからコンテナを作成し、各々をマウントしてアーカイブしています。
- バックアップしたいボリューム(
-v data-hoge:/data) - バックアップ先のディレクトリ(
-v "$(pwd)/backup":/backup)
docker run --rm \
-v data-hoge:/data \
-v "$(pwd)/backup":/backup \
alpine \
tar cvf /backup/backup.tar /data
復元は、上記の逆の手順でアーカイブとボリュームをマウントして、ボリューム内に解凍します。
docker compose run でバックアップする(バックアップ・リストア用 docker-compose.yaml)
ボリュームをバックアップする専用のディレクトリを作成し、バックアップ & リストア用の YAML ファイルを作成しておくと便利です。
特に、docker system prune などで、イメージ・コンテナ・ボリュームなどを定期的に prune prune する場合に重宝します。
# バックアップの実行(カレントの ./backup/backup.tar.gz に sample-data ボリュームがアーカイブされる)
docker compose run --rm backup
# リストア先のボリュームを作成
docker volume create sample-data
# リストアの実行(カレントにある ./backup/backup.tar.gz をリストア先に展開する)
docker compose run --rm restore
# 下記 sample-data を適宜バックアップしたいボリューム名に変更する
version: "3.9"
volumes:
sample-data:
external: true
services:
backup:
image: alpine
volumes:
- sample-data:/fromA
- ./backup:/toB
working_dir: /fromA
entrypoint: [ "tar", "czvf", "/toB/backup.tar.gz", "."]
restore:
image: alpine
volumes:
- ./backup/backup.tar.gz:/fromA/backup.tar.gz
- sample-data:/toB
working_dir: /fromA
entrypoint: [ "tar", "xzvf", "/fromA/backup.tar.gz", "-C", "/toB"]
TS; DR (データの永続化を完全に理解した気になるコマケーこと)
「データ消えちゃうんだよね」「永続化っ Σ👋 バシッ」
基本的に Docker のコンテナが削除されると、コンテナ内のデータは消えます。当然ですが。
そこで、コンテナ内で作成・更新されたデータを残すことを「データの永続化」と言います。
Docker コンテナのデータ永続化には、いくつか種類があります。
- Dockerfile と同じ階層のディレクトリをマウントして、永続化したいデータをそこに置く
- Docker ボリュームを作成&マウントして、永続化したいデータをそこに置く
上記1の「ローカル・ディレクトリをマウントする」のが簡単であり、最も一般的です。
しかし、「複数の異なるコンテナで同じフォルダを共有したい」場合に問題が発生します。例えば、複数コンテナで同じディレクトリをマウントし、ログや生成されたデータを保存したい場合などです。
この上記1の問題は、コンテナごとに Dockerfile を分けていた場合に顕著に現れます。
各々のディレクトリが異なるため、共有したいディレクトリの階層が異なることになり、マウントできません。
$ tree
.
├── docker-compose.yml <-- ./data と ./sample1/data はコンテナと
│ 同じ階層以下にあるのでマウントできる。
├── data <---------------- sample1, sample2 のコンテナからは階層
│ │ が異なるため相対パスではマウントできない。
│ ├── common1.json
│ ├── data2.json
│ └── data3.json
├── sample1
│ ├── data <------------ sample1 のコンテナはマウントできる。
│ │ │ sample2 のコンテナからは相対パスではマウ
│ │ │ ントできない。
│ │ ├── common1.json
│ │ ├── data2.json
│ │ └── data3.json
│ └── Dockerfile
└── sample2 <------------- (´・ω・`)ショボーン
└── Dockerfile
マウントできないと言いましたが、絶対パスで指定すればマウントはできます。しかし、パスは環境によって変わるものなので相対パスで指定したいところですが、できないのが問題です。
実は Windows や macOS の場合、絶対パスでも Docker Desktop の設定で親ディレクトリのパスを通していないとマウントできません。[Preferences] -> [Resources] -> [File sharing] に登録されているディレクトリ以下のパスのみが Docker から参照・マウントできます。
Docker をふいんきで触っているものだから、セキュリティが心配になり、「なんで /Users ディレクトリがあるの」と、気持ち悪いので親ディレクトリの登録を削除しちゃったのを失念していた、といった意外に見落としがちな設定箇所だったりします。
これは Windows や macOS の Docker(Docker Desktop)の場合のセキュリティ上の制限です。具体的には Dockerfile のある階層以下のディレクトリしか相対パスでマウントできないように制限されているからです。
つまり、./data は使えても ../data は使えないと言うことです。
そのような場合、3 つの方法があります。
- あきらめて絶対パスでマウントする
- 上階層に
docker-composeを置いてマウントする - Docker ボリュームを作成&マウントする
この、最後の 3 目の方法が本記事の本題です。
$ docker volume create コマンドで仮想ボリュームを作成し、それを各々のコンテナにマウントする方法です。
特に、この方法だとホスト側のディスク・フォーマットを気にしなくてもいいというメリットがあります。
問題は、良くも悪くも「仮想ボリューム」であることです。
なぜなら、この仮想ボリューム内のデータをローカルにバックアップするのが Windows や macOS の場合、いささか面倒だからです。詳しくは下記「Mac や Windows の Docker ボリュームは特殊」参照。
Docker ボリュームのバックアップ
まずは、具体的な手順から。
いささか冗長的ですが、「完全に理解した」気分になれるように丁寧に説明したいと思います。
$ docker volume ls
DRIVER VOLUME NAME
local data-hoge
docker の ls コマンド(docker volume ls)を使うと、作成済みの仮想ボリュームが一覧で確認できます。上記の場合、data-hoge がすでに作成済みとなります。
$ docker volume ls
DRIVER VOLUME NAME
$ docker volume create data-hoge
data-hoge
$ docker volume ls
DRIVER VOLUME NAME
local data-hoge
任意の仮想ボリュームを作成したい場合は create コマンド(docker volume create)を使います。上記は data-hoge を新たに作成しています。
それでは、とあるコンテナに data-hoge がマウントされて、中にデータが保存されていると仮定します。この中身をローカルの ~/my_backup/backup にバックアップしたいと思います。
$ # バックアップの作業ディレクトリの作成&移動
$ cd mkdir ~/my_backup && cd "$_"
$ # バックアップ開始(/data を /backup にアーカイブする)
$ docker run --rm \
-v data-hoge:/data \
-v $(pwd)/backup:/backup \
alpine \
tar cvf /backup/backup.tar /data
tar: removing leading '/' from member names
data/
data/hello.txt
...
上記は、作業ディレクトリ ~/my_backup に移動後、まっさらな Alpine linux のコンテナを立ち上げて 2 つのボリュームをマウントしています。
具体的には data-hoge の仮想ボリュームをコンテナの /data に、ローカルの ./backup ディレクトリをボリュームとしてコンテナの /backup にマウントしています。
あとは tar コマンドでコンテナの /backup/backup.tar に /data ディレクトリの中身をアーカイブ(固めて 1 つに)しています。
これにより、ローカルの ~/my_backup/backup/ ディレクトリに backup.tar が作成されます。
バックアップからリカバリしたい場合は、アーカイブする変わりに -xvf でアーカイブを展開します。(圧縮→解凍みたいなものです)
$ # 作業ディレクトリに移動
$ cd ~/my_backup
$ # 作成されたバックアップの確認
$ ls
backup
$ # ディレクトリ構造の確認(要treeコマンド。brew install tree でインストール可能)
$ tree
.
└── backup
└── backup.tar
1 directory, 1 file
$ # アーカイブの解凍
$ tar -xvf ./backup/backup.tar
x data/
x data/hello.txt
...
$ # 解凍ファイルの確認
$ ls
backup data
$ # ディレクトリ構造の確認
$ tree
.
├── backup
│ └── backup.tar
└── data
├── ...
└── hello.txt
Mac や Windows の Docker ボリュームは特殊
docker volume create --name data-hoge とボリュームを作成した場合、inspect コマンドでボリューム情報を表示すると以下のようになります。
$ docker volume inspect data-hoge
[
{
"CreatedAt": "2019-12-09T04:28:16Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/data-hoge/_data",
"Name": "data-hoge",
"Options": {},
"Scope": "local"
}
]
しかし、上記の Mountpoint のパスにデータがあるかと思いきや、ありません。
$ ls /var/lib/docker/volumes/data-hoge/_data
ls: /var/lib/docker/volumes/data-hoge/_data: No such file or directory
$ # そもそも /var/lib/docker がない。docker いっちゃった?
$ ls /var/lib/
postfix
> docker volume ls
DRIVER VOLUME NAME
local data-hoge
> docker volume inspect sample
[
{
"CreatedAt": "2020-03-19T14:09:56Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/data-hoge/_data",
"Name": "sample",
"Options": {},
"Scope": "local"
}
]
PS > ls /var/lib/docker/volumes/data-hoge/_data
ls : パス 'C:\var\lib\docker\volumes\data-hoge\_data
' が存在しないため検出できません。
発生場所 行:1 文字:1
+ ls /var/lib/docker/volumes/data-hoge/_data
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\var\lib\docker\volumes\data-hoge\_data:String) [Get-ChildItem], ItemNot
FoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
PS > # やっぱり /var/lib/ は存在しない。docker いっちゃってる。
PS > ls /var/lib/
ls : パス 'C:\var\lib\
' が存在しないため検出できません。
発生場所 行:1 文字:1
+ ls /var/lib/
+ ~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\var\lib\:String) [Get-ChildItem], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemComman
これは Mac や Windows10 の Docker Desktop の場合、Docker は VM(仮想マシン) 上で動いているためです。
つまり docker volumes create で作成されたボリュームも仮想マシンのイメージの中に作成されるということなので、ローカルには直接作成されません。
以下は macOS の場合ですが、作成された仮想マシンのイメージは以下のディレクトリに設置されます。(Docker v19.03.5 現在)
~/Library/Containers/com.docker.docker/Data/vms/0/
問題は、このディレクトリから該当するイメージと、そこからデータを引き出す方法がわかりづらいこと。
$ tree ~/Library/Containers/com.docker.docker/Data/vms/0/
/Users/admin/Library/Containers/com.docker.docker/Data/vms/0/
├── 00000002.000005f4
├── 00000002.00001000
├── 00000002.00001001
├── 00000002.00001002
├── 00000002.0000f3a4
├── 00000002.0000f3a5
├── 00000003.000005f5
├── 00000003.00000948
├── Docker.raw
├── config.iso
├── connect
├── data
├── guest.000005f5 -> 00000003.000005f5
├── guest.00000948 -> 00000003.00000948
├── hyperkit.json
├── hyperkit.pid
├── lifecycle-server.sock
├── log
├── nic1.uuid
└── tty -> /dev/ttys000
2 directories, 18 files
screen と tty で仮想マシンに接続する方法もあるようなのですが、確認ができるというだけでローカルにデータを保存するには煩雑な作業が必要そうです。
$ # 仮想マシンの tty に接続
$ screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty
cd
docker-desktop:~# # 仮想マシン上で Mountpoint を確認
docker-desktop:~# ls /var/lib/docker/volumes/data-qithub/_data
hello.txt ...
docker-desktop:~# # Ctrl+a -> Crtl+k で exit
Really kill this window [y/n] y
[screen is terminating]
これらのファイルを下手に触って、うっかりイメージを壊したりしそうで危険な臭いがします。また、docker volume archive <ボリューム名> のようなコマンドもないようです。(´・ω・`)
$ docker volume --help
Usage: docker volume COMMAND
Manage volumes
Commands:
create Create a volume
inspect Display detailed information on one or more volumes
ls List volumes
prune Remove all unused local volumes
rm Remove one or more volumes
Run 'docker volume COMMAND --help' for more information on a command.
そこで、確実で安全なバックアップ方法がないか探したところ、公式に書いてありました。ダミーのコンテナに、ボリュームとローカルのディレクトリをマウントして tar アーカイブする方法です。(訳は、筆者訳)
For example, create a new container named
dbstore:
【訳】 例えば、まず "dbstrore" というコンテナを新規に作成します。
$ docker run -v /dbdata --name dbstore ubuntu /bin/bash
Then in the next command, we:
- Launch a new container and mount the volume from the
dbstorecontainer- Mount a local host directory as
/backup- Pass a command that tars the contents of the
dbdatavolume to abackup.tarfile inside our/backupdirectory.【訳】 次のコマンドで、以下を行いましょう。
- 別の新しいコンテナを起動し、先の
dbstoreコンテナからボリュームをマウントする。- ローカルのディレクトリを
/backupとしてマウントする。tarコマンドで、dbdataボリュームの内容をアーカイブして、/backupディレクトリ内のbackup.tarファイルに格納する。
既存のコンテナのボリュームを新規コンテナにマウントしてアーカイブする方法$ docker run --rm \ --volumes-from dbstore \ -v "$(pwd)":/backup \ ubuntu tar cvf /backup/backup.tar /dbdata(Backup, restore, or migrate data volumes @ docs.docker.com より)
確かにシンプルで力強い方法です。なにより直感的(覚えやすい)ですな。
ただ、ubuntu コンテナは 78MB 程度とは言え、筆者は alpine の軽量コンテナ(6 MB)が個人的に好みです。
バックアップとリストア
公式にある上記の方法だと ubuntu コンテナであるだけでなく処理後に不要になったダミー・コンテナが残ってしまいます。ボリュームが生きている(docker volume ls で表示される)のであれば、以下のようにバックアップするのが簡単だと思います。
$ # バックアップしたいボリューム名がdbstoreの場合
$ docker run --rm \
-v dbstore:/dbstore \
-v "$(pwd)/backup":/backup \
alpine \
tar cvf /backup/backup.tar /dbstore
ちなみに、リカバリーは逆の手順で、ボリューム内に tar ファイルを解凍します。
$ # リカバリー用の空のボリューム作成 (dbstore2)
$ docker volume create --name dbstore2
$ # リカバリー(Ubuntu イメージにリカバリする場合)
$ docker run --rm \
-v dbstore2:/dbdata \
-v "$(pwd)/backup.tar":/backup/backup.tar \
ubuntu \
bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1"
なお、上記の $(pwd) の箇所ですが、macOS や Windows はスペースの入ったパスが多いため、"$(pwd)" とダブルクォートで囲うクセを付けておくと良いと思います。
参考文献
- Docker Volume (特に volumeタイプ) のわかりづらいところを説明してみる @ Qiita
- Docker for MacのDisk Imageの場所が変わった @ Qiita
- dockerのデータボリュームとそのバックアップ方法 @ Qiita
- Docker for Macのvolumesの場所 @ Qiita
- データ・ボリュームのバックアップ・修復・移行 | コンテナでデータを管理する @ Docker 1.9 beta 日本語ドキュメント