Django関連ファイルのバックアップ(Dropbox)

Python

概要

CentOS7でDjangoを動かすシリーズの一つです。

Djangoプロジェクトを使ってWebサイトを公開したら、バックアップについても考える必要があります。何かあったときに、アップロードしたファイルやデータベースの内容が消えてしまうのは避けなければなりません。

今回はメディアファイルやデータベースの内容をDropboxへ、Pythonプログラム上から保存していきます。各種データではなくサーバー環境全体をバックアップしたい場合は、Mondo Rescue等を使ってください。

データのバックアップですが、次の3つは最低限バックアップする必要があります。

  1. Djangoプロジェクトのソースコード

  2. メディアファイル

  3. データベースの内容

Djangoプロジェクトですが、これはGit・Githubなどを使って開発していれば問題はありません。それでバックアップされているようなものです。静的なファイルも、Djangoプロジェクトのソースコード内にあるので大丈夫ですね。

なので、アップロードされたメディアファイルと、データベースの中身をバックアップすることになります。

Dropboxの設定

保存先として、Dropboxを今回利用していきます。まず、Dropboxのアカウントは作成しておきましょう。

DropboxのDevelopperページへ行きます。「Create apps」を押しましょう。
Dropboxの設定1

ここはちょっと環境によってかわりますが、「Dropbox API」にチェックし、「Full Dropbox」を選択し、「Name your app」に適当な名前を入れます。おわったら、「Create aPP」ボタンを押します。
Dropboxの設定2

この画面に移動するので、「Generated access token」下の「Generate」ボタンを押します。表示されるトークンを今後使います
Dropboxの設定3

あとは。dropboxのapiを利用するため、pythonライブラリをインストールしておきます。

pip install dropbox

データベースのバックアップ

ファイル名は何でもいいのですが、サーバーにbackup_db.py のようなファイルを作り、次のようにしておきます。MariaDB(MySQL)を利用している場合の例です。

import datetime
import subprocess
import dropbox

dbx = dropbox.Dropbox('さきほどGenerateしたトークン')
now = datetime.datetime.now()
db_file_name = f'{now.hour}-{now.minute}-{now.second}.db'
dropbox_path = f'/mydjango/{now.year}/{now.month}/{now.day}/{db_file_name}'
subprocess.run(f'mysqldump -u ユーザー名 データベース名 -pパスワード > {db_file_name}', shell=True)

print(now, 'backup start')
dbx.files_upload(open(db_file_name, 'rb').read(), dropbox_path,  mode=dropbox.files.WriteMode('overwrite'))
print(datetime.datetime.now(), 'backup end')

Dropboxへのファイルアップロードは、dbx.files_upload(ファイルデータ, Dropboxのパス)のように行います。

Dropboxのパス部分ですが、今回の例ならば結果的に /mydjango/2019/7/21/12-31-15.db といった感じの文字列になります。 アップロードされた様子

ファイルデータ部分は、subprocess.run()mysqldumpコマンドを行い、その結果できた12-31-15.dbといったデータベースのdumpファイルを指定しています。やっていることはシンプルです。

SQLiteならば、次のような感じになります。こちらはdb.sqlite3をそのままアップロードするだけ。

import datetime
import subprocess
import dropbox

dbx = dropbox.Dropbox('さきほどGenerateしたトークン')
now = datetime.datetime.now()
db_file_name = f'{now.hour}-{now.minute}-{now.second}.sqlite3'
dropbox_path = f'/mydjango/{now.year}/{now.month}/{now.day}/{db_file_name}'

print(now, 'backup start')
dbx.files_upload(open('/path/to/db.sqlite3', 'rb').read(), dropbox_path,  mode=dropbox.files.WriteMode('overwrite'))
print(datetime.datetime.now(), 'backup end')

メディアファイルのバックアップ

メディアファイルを、/var/www/html/media に置いている場合の例です。

サーバーにbackup_media.pyのようなファイルを作り、次のようにしておきます。

import datetime
from pathlib import Path
import dropbox

dbx = dropbox.Dropbox('さきほどGenerateしたトークン')
now = datetime.datetime.now()
dropbox_path = f'/mydjango/{now.year}/{now.month}/{now.day}'


def upload_all_files(path):
    if path.is_dir():
        for p in path.iterdir():
            upload_all_files(p)
    elif path.is_file():
        dbx.files_upload(path.read_bytes(), dropbox_path+ str(path), mode=dropbox.files.WriteMode('overwrite'))


print(now, 'backup start')
upload_all_files(Path('/var/www/html/media'))
print(datetime.datetime.now(), 'backup end')

こちらもやっていることは単純で、/var/www/html/media以下の全てのファイルを走査し、個別にdbx.files_upload()としてアップロードしているだけです。

バックアップの定期実行

あとは、上で書いたバックアップ処理を定期的に呼び出すだけです。定期的な処理はcronを使うのが一般的です。サーバーで crontab -e とコマンドを実行し、次のように入力しておきましょう。

10 0 * * * /usr/local/bin/python3.7 /home/narito/narito.ninja/tools/backup_db.py >>/tmp/backup_db.log 2>&1
20 0 * * * /usr/local/bin/python3.7 /home/narito/narito.ninja/tools/backup_media.py >>/tmp/backup_media.log 2>&1

毎日0時10分にDBのバックアップを、0時20分にメディアファイルのバックアップを行います。

補足ですが、今回書いたスクリプトはcron専用という訳ではありません。好きなタイミングで python backup_db.py のようにして実行することもできます。例えば、DjangoプロジェクトをFabric3を使ってデプロイではDjangoプロジェクトのデプロイを紹介していますが、デプロイ処理のはじめにでも、念のためDBのバックアップをとっておく、といったこともできるでしょう。

今回紹介した処理は、いわゆるフルバックアップです。フルバックアップでは時間や容量的に厳しくなってきたら、差分、増分バックアップを考えましょう。

よく出るエラー

よく出るエラーも幾つか紹介していきます。

同じ名前・パス

全く同じパスへのアップロードに注意しましょう。例えば、次のように同じパスへアップロードすると

file_name = 'hello.txt'
src = open(file_name, 'rb').read()
dropbox_path = f'/test/{file_name}'

# 1度アップロードする
dbx.files_upload(src, dropbox_path)

# もう一度、同じパスにアップロードする
dbx.files_upload(src, dropbox_path)

エラーになります。WriteError('conflict',とありますね。

dropbox.exceptions.ApiError: ApiError('fa8bf946e694677022086924b980f4e8', UploadError('path', UploadWriteFailed(reason=WriteError('conflict', WriteConflictError('file', None)), upload_session_id='AAAAAAAAj4_pVexbOqYjcg')))

同じパスでアップロードする必要がある場合、例えばファイルの更新や上書きがしたいならば、mode引数で指定します。上で紹介したコードには、つけていました。

dbx.files_upload(src, dropbox_path, mode=dropbox.files.WriteMode('overwrite'))

少し厄介なのが、次のように大文字・小文字のファイル名が混在している場合です。これも上のエラーが出ます。同じファイルと見なされているようです。

a.png
a.PNG

先に、こういったファイルをリネームしておくのが確実です。

こういった混在はそんなに沢山ないはずだし、重要なファイルでないなら、私は上書きモードでそのままやることも多いです。メディアファイルの一個や二個ですし。おすし。

アップロードできない名前

次のようなファイルがある場合も注意です。これらのファイル名は、Dropboxのファイル名として使えません。

  • desktop.ini
  • thumbs.db
  • .ds_store
  • icon\r
  • .dropbox
  • .dropbox.attr

WriteError('disallowed_name'といったエラーになってしまいます。上のようなファイルは削除しておくか、どうしてもという場合は、違う名前にしてアップロードしましょう。

dropbox.exceptions.ApiError: ApiError('8ca703e2add0dddea245642cdddadc87', UploadError('path', UploadWriteFailed(reason=WriteError('disallowed_name', None), upload_session_id='AAAAAAAAkS3f-32BQ41_pw')))

Relation Posts

CentOS7でDjangoを動かすシリーズ

CentOS7、Nginx、Gunicornの環境でDjangoを動作させる上での、個人的なまとめ記事です。

シリーズ・まとめ CentOS7

Comment

記事にコメントする

まだコメントはありません。