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
dbstore
container- Mount a local host directory as
/backup
- Pass a command that tars the contents of the
dbdata
volume to abackup.tar
file inside our/backup
directory.【訳】 次のコマンドで、以下を行いましょう。
- 別の新しいコンテナを起動し、先の
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 日本語ドキュメント