Dockerコンテナ内にあるファイルのバックアップ方法

## 記事の概要

Dockerコンテナ内にあるデータのライフサイクル（寿命）は、コンテナのライフサイクルと共にあります。
通常、Dockerボリュームなどを使い、**データのライフサイクル**は**コンテナのライフサイクルと分離**します。

ただし、Dockerボリューム内に蓄積されたデータも**何らかの障害で失われる可能性**や**過去の状態に戻したい場合**があります。
Dockerボリュームのライフサイクルとは別に、バックアップを取得して**第3のライフサイクル**として管理しておくと安心です。

この記事では、**OS**のディレクトリ上へ**Dockerボリューム内にあるファイル**をバックアップする方法について説明します。
また**Dropbox API**を使って、取得したバックアップファイルをDropboxへ保管する方法についても解説します。

## 前提条件

本手順の検証は以下の環境で行いました。

OS：CentOS7
Docker：ver19.03.13

また、Dropbox APIを使用するため、Dropboxアカウントが必要になります。

## Dockerボリューム内にあるファイルをバックアップする

以下の`docker run`コマンドを実行することで、Dockerボリューム内にあるファイルをOS上のディレクトリにバックアップできます。

```bash
docker run \
--rm \
--volumes-from [バックアップ対象のコンテナ名] \
-v [バックアップ先となるOSのファイルパス]:/backup \
busybox cp [コンテナ内のバックアップ対象のファイルパス] /backup/[バックアップファイル名]
```

**[--rm](https://docs.docker.jp/engine/reference/run.html#rm)**：
　このオプションはコンテナの終了時に、コンテナが使用しているファイルシステムを削除します。
　バックアップ用のコンテナなので、用が済んだら後片付けを行います。

**--volumes-from [バックアップ対象のコンテナ名]**：
　このオプションでは**バックアップ対象のコンテナにマウントされているDockerボリューム**を、**バックアップ用のコンテナ**（ここではbusybox）にマウントします。
　例えば、**app**コンテナの**/var/www/app/data**にDockerボリュームをマウントしている場合、**busybox**コンテナの**/var/www/app/data**にDockerボリュームがマウントされます。

**-v [バックアップ先となるOSのファイルパス]:/backup**：
　このオプションでは**バックアップ先となるOSのファイルパス**を、**バックアップ用のコンテナの/backup**（ここではbusybox）にマウントします。
　例えば、OSのファイルパスとして**/home/hogehoge/app/data**を指定すると、**/home/hogehoge/app/data**が**busybox**コンテナの**/backup**にマウントされます。

**busybox cp [コンテナ内のバックアップ対象のファイルパス] /backup/[バックアップファイル名]**：
　このコマンドは、バックアップしたいファイルを、OS上のディレクトリにコピーするために実行します。
　例えば、コンテナ内のファイルパスとして**/var/www/app/data/db.sqlite3**を指定し、バックアップファイル名として**db.sqlite3**を指定すれば、**/backup/db.sqlite3**（/backupにはOS上のディレクトリがマウントされている）へ**/var/www/app/data/db.sqlite3**がコピーされます。

## DropboxへAPIを使ってファイルをアップロードする

Dropboxは無料でも使用可能なクラウドストレージです。
また、同一のファイル名は30日間だけ遡って、復元することもできます（つまり30日バージョン管理として使えます）。

バージョン履歴は**ストレージ容量にカウントされないため、容量節約にもなります。**

> バージョン履歴はストレージ容量にカウントされますか？
> いいえ、バージョン履歴はストレージ容量には含まれません。

[バージョン履歴の概要](https://help.dropbox.com/ja-jp/files-folders/restore-delete/version-history-overview)

ここでは上記の特徴から、ファイル名を同一のものにして、バックアップのたびに上書きするようにします。

### Dropbox App consoleへログイン

以下のURLへアクセスします。
https://www.dropbox.com/login?cont=https%3A%2F%2Fwww.dropbox.com%2Fdevelopers%2Fapps%3F_tk%3Dpilot_lp%26_ad%3Dtopbar4%26_camp%3Dmyapps

### appを作成

画面右上にある`Create app`を選択します。

![dropbox_create_app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302006/6c63acd8-1f7f-a84f-4f48-6c16e8988edb.png)

* `1. Choose an API`：`Scoped access`
* `2. Choose the type of access you need`：`App folder`（このappのためのフォルダを作り、そこだけにアクセスさせます。）
* `3. Name your app`：好きなappの名前を入力します。

`Create app`ボタンを選択して作成します。

### appのパーミッションを設定

作成されたappを選択後、`Permissions`タブを開きます。

![dropbox_permissions.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302006/0bf802b6-7d66-ffdd-eee7-b44c4c302d01.png)

アップロードに必要な`files.content.write`にチェックを入れます。
ここでは、`files.content.read`にもチェックを入れていますが、不要であれば、入れなくて大丈夫です。

画面下部にある`Submit`ボタンを押します。

### アクセストークンの発行

`Settings`タブに戻り、アクセストークンを作成します。

![dropbox_access_token.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302006/fb5d2fb7-3294-9422-7ff3-e84310b5f1b2.png)

`Access token expiration`を`Short-lived`にすると短い間だけ有効になります。
`No expiration`は無期限です。

どちらかを選択した後で、`Generated access token`の`Generate`ボタンを選択します。
以下のような文字列が生成されるため、コピーして保管します（**ページ遷移すると消えるため**）。

```
sl.A9KVBrdIBMTHy1tyM6vUi3KMKHrh_LFJhX-ygxkgzprNyF6q19hdPksEjgzQ_7YSMcBzha7xruUug1RjvbAlzuinAyAfeguvOqj1j5HlLPR0MAzntJTgsS_JZnhogehogehoge
```

### Dropbox API Explorerでアップロード検証

以下のURLに接続してDropbox API Explorerを開きます。
https://dropbox.github.io/dropbox-api-v2-explorer/#files_upload

![dropbox_API_Explorer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302006/f1cc3f1e-d887-d16e-dcb3-cfa460b10f5a.png)

`File to upload`にテスト用のファイルを選択します。
`path`は`/test.txt`のように`/`に`ファイル名（アップロード先のファイル名）`を結合したものです。
`mode(optional)`は`overwrite`を選択します。

`Submit Call`ボタンを押して正常にアップロードされることを確認します。
実際にDropboxの画面を開くと、`アプリ`フォルダが作成されていて、前の手順で作成したapp名のフォルダが作られています。

```
アプリ
　= app名（pathで指定するときのルートフォルダ、つまり/）
　　= hogehoge.txt
```

### curlで実行できるようにする。

Dropbox API Explorer内で`Show Code`ボタンを押すと、実際に実行するコードを参照できます。

以下は例です。

```bash
curl -X POST https://content.dropboxapi.com/2/files/upload \
 --header 'Authorization: Bearer ZTZ-m7zw48MAAAAAAAAAAZ5Ps8hDrpTPI37W3Ocfgc1LmB3Hu0hogehogehoge' \
 --header 'Content-Type: application/octet-stream' \
 --header 'Dropbox-API-Arg: {"path": "/test.txt", "mode":{".tag":"overwrite"}}' \
 --data-binary @'hogehoge.txt'
```

自分の環境で`Show Code`を実行した後で、下2行を変更します。

```bash
 --header 'Dropbox-API-Arg: {"path": "Dropboxのファイルパス（ファイル名を入れる）", "mode":{".tag":"overwrite"}}' \
 --data-binary @'Dropboxへアップロードするクライアントのファイルパス'
```

例えば、Dropboxの`アプリ - test（testはapp名）`配下に`OS上の/home/hoge/hogehoge.txt`を`dropbox_hogehoge.txt`という名前でアップロードするには、

```bash
 --header 'Dropbox-API-Arg: {"path": "/dropbox_hogehoge.txt", "mode":{".tag":"overwrite"}}' \
 --data-binary @'/home/hoge/hogehoge.txt'
```

とします。

実行できる形になったら、OS上から実行して正常にアップロードされるかを確認します。

## cronを使って定期実行させる

このままでも手動で実行は可能ですが、cronを使って毎日実行する例を記載します。

### 非rootユーザー

dockerコマンドを実行できるユーザー（dockerグループに所属）で実行しています（rootではない）。

`crontab -e`で編集画面へ移動し、以下を書き込みます。

```bash
SHELL=/bin/bash

00 18 * * * docker run --rm --volumes-from container -v /home/hogehoge/backup/data:/backup busybox cp /backup_file_in_container/fugafuga /backup/backup_`date +\%Y\%m\%d`
00 19 * * * upload_to_dropbox.sh
00 20 * * * rm /home/hogehoge/backup/data/backup_`date --date '7 days ago' +\%Y\%m\%d`
```

18時に実行しているのは、コンテナ内にあるファイルをバックアップするコマンドです。
ここでは日付をファイル名に付けています。

19時はDropboxへファイルをアップロードするスクリプトを実行します。
先程のcurlコマンドをスクリプトファイルに入れただけです。

```bash:upload_to_dropbox.sh
file_name="/home/hogehoge/backup/data/backup_`date +%Y%m%d`"

curl -X POST https://content.dropboxapi.com/2/files/upload \
 --header 'Authorization: Bearer ZTZ-m7zw48MAAAAAAAAAAZ5Ps8hDrpTPI37W3Ocfgc1LmB3Hu0hogehogehoge' \
 --header 'Content-Type: application/octet-stream' \
 --header 'Dropbox-API-Arg: {"path": "/test.txt", "mode":{".tag":"overwrite"}}' \
 --data-binary @${file_name}
```

20時に、OS上のバックアップファイルを削除します。
念の為、OS上にも7日分のバックアップファイルを保持しておきたいので、7日前のものを削除します。

### rootユーザー

人にもよりますが、私が運用している環境はコンテナからバックアップしたファイルの所有者がrootになっています。
このままだと、root以外のユーザーでファイルを消せないので、所有者を変更します。

rootユーザーで`crontab -e`を実行し、以下を書き込みます。

```bash
30 18 * * * chown hogehoge:hogehoge -R /home/hogehoge/backup/data/
```

## 最後にDropboxへのアップロードが正常に行われたかどうかの通知をSlackで受け取ります。

後日執筆予定。

## 参考

[Backup a container](https://docs.docker.com/storage/volumes/#backup-a-container)
Dockerコンテナのバックアップ方法について記載あり

[Dropbox API v2](https://www.dropbox.com/developers/documentation/http/documentation)
APIドキュメント

APIを利用する場合は上記ドキュメントと合わせてExploreも使用すると楽になる。
[Dropbox API Explorer](https://dropbox.github.io/dropbox-api-v2-explorer/)