当BlogはVPS上のWordpressで運用しており、自動バックアップ機能を以下の記事の手順で実現しています。「さくらVPSのWordPress環境バックアップ自動化」
今回、新たにバックアップコンテンツのリストア作業を自動化しましたのでその手順を公開します。
目次
1.自動リストア実現の動機
今まで、バックアップしたBlogコンテンツを自宅サーバ環境にリストアする作業は手動で実施していました。参考「WordPress手動バックアップとDocker環境へのリストア」
その為Blogを更新するとVPSのBlogとローカルバックアップ環境のBlogとでコンテンツに差異が発生し、手動でリストア作業を実施するまでバックアップ環境が最新化されないのが課題でした。Blogを更新したら自動的にバックアップ環境へも反映されるとストレスフリーで楽になりそうです。そこでこの自動リストアに取り組むことにしました。
2.前提
バックアップファイルはVPS上のWordpressより取得し自宅サーバ上のSamba公開ディレクトリに保存してあるものとします。リストア先は、Centos7.3で構築したDockerホストマシン上で動作しているdocker-composeで構築したWordpress環境(当記事参考)とします。
3.リストア作業の準備
自動リストア処理で必要となるツールやコンテナ環境を以下のように準備します。
(1)sambaマウントツール
Centosでsamba共有ディレクトリをマウントするには「cifs-utils」が必要です。yumでインストールします。
1 |
# yum -y install cifs-utils |
(2)ZIP暗号化解除
ZIPファイルを暗号化解除するにはUNZIPパッケージが必要です。yumでインストールします。
1 |
# yum -y install unzip |
(3)mysqlコンテナへマウントフォルダ追加
Docker-composeで構築したWordpres環境のmysqlコンテナに新たなマウントディレクトリを追加します。理由は、リストア用SQLとそれを実行するshellスクリプトを格納しmysqlコンテナから実行するためです。通常は、以下のコマンドでホストからmysqlコンテナに接続しmysqlコマンドを実行できるのですが、WordpressDBをバックアップしたSQLファイルは容量が大きくこの方法ではエラーになってしまいます。
ホストからmysqlコンテナに接続しmysqlコマンドを実行するコマンド
1 |
# docker exec -it phpsql_db_1 mysql -u user_hoge -pXXXXXXXXX wp_dbwordpress -e"$(cat BlogURL_UPDATE.sql)" |
docker-compose.ymlに追加したマウント設定
1 2 3 4 5 6 7 8 9 10 11 12 |
db: image: mysql volumes: - /etc/localtime:/etc/localtime:ro + - ./data/sqltmp:/home volumes_from: - sqldata logging: driver: syslog environment: MYSQL_ROOT_PASSWORD: "password" TZ: "Asia/Tokyo" |
5行目を追加。「data/sqltmp」ディレクトリをmysqlコンテナの「/home」にマウントしています。尚、Docker-composeでのWordpress環境構築及びコンテナ連携については以下の当Blog内記事を参照ください。
Docker-Composeでnginx,php,mysqlの3コンテナを連携する
4.リストア処理手順と概要図
(1)リストア処理手順概要
バックアップファイルは自宅ファイルサーバのsamba共有ディレクトリに存在するのでDockerホストサーバからこのディレクトリをマウントします。マウント後に暗号化ZIP圧縮したコンテンツファイルと同じく暗号化ZIP圧縮したSQLファイルをコピーします。これを一旦ローカルに展開した後、対象とするDockerコンテナのマウントディレクトリにコピーします。SQLファイルをmysqlコンテナ上で実行します。実行するのはBlogDBよりバックアップしたSQLファイルとVPS向けURLをローカル向けURLに書き換えるSQLです。
(2)リストア全体概要図
上記手順のイメージ図がこちらになります。
大まかなイメージはつかめますが詳細は良くわからないですね。
後述するシェルスクリプトで詳細を説明します。
5.リストア手順詳細(ホスト用スクリプト)
こちらではDockerホストサーバで実行するシェルスクリプトを提示します。
FileRestore.sh (2017/7/25 重複記述の6行を削除し行番号を訂正しました)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
PATH=/usr/local/sbin:/usr/bin:/bin ##LOG ENVIRONMENT LOGDIR=/var/log LOG=$LOGDIR/restore_wp_`date +%w`.log ## FOR MYSQL BACKUP CPFL1_TGT=BACKUP_blogdb_`date +%w`.zip CPFL1_PRV=BACKUP_blogdb.zip CPFL1_TMP=BACKUP_blogdb_tmp.zip ## EXECSQL FILES UNZIPFL1=BACKUP_blogdb_`date +%w`.sql CPSQLFL=BACKUP_blogdb.sql UPDTSQL=BlogURL_UPDATE.sql ## FOR wp-content CPFL2_TGT=BACKUP_blog_`date +%w`.zip CPFL2_PRV=BACKUP_blog.zip CPFL2_TMP=BACKUP_blog_tmp.zip ## RESTORE DIR UNZIPDIR=wp-content RESTDIR=/home/docker/test/phpsql/data/www/blog ## SMB DIR TGTDIR=//192.168.1.8/pub1/01COMBlog MOUNTDIR=/mnt/smbhsv ## SMB US/PASS MNTUSR=hoge MNTPASS=hogehogepass ## ZIPPASS ZIPPASS=XXXXXXXXXXX ## RESTORE TEMP DIR RESTORE=/restore ## Docker mount dir SQLDIR=/home/docker/test/phpsql/data/sqltmp ## CONTAINER CONTAINER=phpsql_db_1 ## PROCESS START rm -f $LOG echo `date +%Y%m%d%H%M%S` wpcontents restore start >>$LOG ##MKDIR AND MOUNT if [ ! -e $MOUNTDIR ]; then ##rm -rf $MOUNTDIR >>$LOG mkdir $MOUNTDIR >>$LOG fi ##MOUNT mount -t cifs -o user=$MNTUSR,password=$MNTPASS $TGTDIR $MOUNTDIR >>$LOG 2>&1 echo `date +%Y%m%d%H%M%S` MOUNT $TGTDIR complete >>$LOG ##CHECK FLTIMESTAMP FL1_TGT=$MOUNTDIR/$CPFL1_TGT FL1_PRV=$RESTORE/$CPFL1_PRV FL2_TGT=$MOUNTDIR/$CPFL2_TGT FL2_PRV=$RESTORE/$CPFL2_PRV FLG_GO1=0 FLG_GO2=0 if [ -e $FL1_PRV ]; then if [ $FL1_TGT -nt $FL1_PRV ]; then echo "TGTFILE WAS RENEWED" >>$LOG FLG_GO1=1 else echo "TGTFILE WAS NOT RENEWED" >>$LOG fi else FLG_GO1=1 fi if [ -e $FL2_PRV ]; then if [ $FL2_TGT -nt $FL2_PRV ]; then echo "TGTFILE WAS RENEWED" >>$LOG FLG_GO2=1 else echo "TGTFILE WAS NOT RENEWED" >>$LOG fi else FLG_GO2=1 fi ## バックアップファイルが更新された場合に実行 if [ $FLG_GO1 -eq 1 -a $FLG_GO2 -eq 1 ]; then cp -p $FL1_TGT $RESTORE/$CPFL1_TMP >>$LOG 2>&1 echo `date +%Y%m%d%H%M%S` COPY $FL1_TGT TO $RESTORE/$CPFL1_TMP complete >>$LOG cp -p $FL2_TGT $RESTORE/$CPFL2_TMP >>$LOG 2>&1 echo `date +%Y%m%d%H%M%S` COPY $FL2_TGT TO $RESTORE/$CPFL2_TMP complete >>$LOG ## ZIP展開1 unzip -P $ZIPPASS $RESTORE/$CPFL1_TMP -d $RESTORE >/dev/null ## ZIP展開2 unzip -P $ZIPPASS $RESTORE/$CPFL2_TMP -d $RESTORE >/dev/null ## wpcontents copy & permission change cp -rfp $RESTORE/$UNZIPDIR $RESTDIR chown -R nginx_docker:nginx_docker $RESTDIR/$UNZIPDIR ## SQLFILE COPY TO docker mountdir cp -fp $RESTORE/$UNZIPFL1 $SQLDIR/$CPSQLFL cp -fp /root/$UPDTSQL $SQLDIR cp -fp /root/sqlexec.sh $SQLDIR chmod +x $SQLDIR/sqlexec.sh ## docker command sql ## docker exec -it $CONTAINER sh /home/sqlexec.sh >>$LOG docker exec -i $CONTAINER sh /home/sqlexec.sh >>$LOG 2>&1 ## FILE DELETE & RENAME rm -rf $RESTORE/$UNZIPDIR rm -f $RESTORE/$UNZIPFL1 mv $RESTORE/$CPFL1_TMP $RESTORE/$CPFL1_PRV mv $RESTORE/$CPFL2_TMP $RESTORE/$CPFL2_PRV fi umount $MOUNTDIR echo `date +%Y%m%d%H%M%S` wpcontents restore end >>$LOG |
(1)環境準備
1-40行でマウント先ディレクトリやユーザーとパスワード、ファイル名等を定義しています。
(2)LOGファイル出力
42行目で1週間前の過去ログを削除し43行目でリストアトレースログを出力しています。
(3)sambaディレクトリマウント
46-49行でマウントディレクトリが存在ない場合ディレクトリを作成しています。
52行目でsamba共有ディレクトリをマウントしています。
このsamba共有ディレクトリにVPSからSCPでコピーしてきたバックアップファイルがあります。
(4)ファイルタイムスタンプ比較
55-80行目でバックアップDBファイルとバックアップコンテンツファイルのそれぞれでファイル作成日付が前回リストアで使用したファイルと比較して新しくなっているか確認し新しい場合のみリストアを実施しています。(古いファイルをリストアして上書きしないための考慮)
(5)リストア処理本体
83行目のIF文でバックアッファイルが新しくなっている場合にリストア処理を行います。
84,86行目でバックアップファイルを中間ファイルにコピーします。
90,93行目で暗号化ZIPファイルを/restoreディレクトリに展開しています。
96行目で展開したWordpressコンテンツフォルダ一式をDockerマウントディレクトリに上書きコピーしています。
97行目でコピーしたディレクトリのオーナーをコンテナとホスト間の共通ユーザー「nginx_docker」に変更しています。(当記事参照)
99行目でUNZIPしたSQLファイルをmysqlコンテナがマウントしているディレクトリにコピーします。
100行目でDB内のURLを書き換えるSQLをroot直下からマウントディレクトリにコピーしています。
101行目でmysqlコンテナ内で実行するshellスクリプトをmysqlコンテナがマウントしているディレクトリにコピーしています。
102行目でコピーしたシェルスクリプトに実行権限を付与しています。
105行目でmysqlコンテナに接続し、上記sqlexec.shのシェルスクリプトを実行しています。
これでDBが最新になります。
(6)後処理
108,109行目でunzip展開したファイルを削除します。
110,111行目でコピーした中間ファイル名を正式ファイル名にリネームしています。
114行目でmauntディレクトリをアンマウントして終了です。
6.リストア手順詳細(mysqlコンテナ用スクリプト)
こちらではDockerホストサーバから呼び出され、mysqlコンテナ内でSQLファイルを実行するシェルスクリプトを提示します。
sqlexec.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 |
PATH=/usr/local/sbin:/usr/bin:/bin SQL1=/home/BACKUP_blogdb.sql SQL2=/home/BlogURL_UPDATE.sql LOG=/var/log/restoresql_`date +%w`.log ##DB US/PASS DBUSR=hoge DBPASS=password ##TGT DB DATABASE=blogdb echo `date +%Y%m%d%H%M%S` restore SQL start >$LOG mysql -u $DBUSR -p$DBPASS $DATABASE < $SQL1 >>$LOG 2>&1 mysql -u $DBUSR -p$DBPASS $DATABASE < $SQL2 >>$LOG 2>&1 echo `date +%Y%m%d%H%M%S` restore SQL ended >>$LOG |
(1)環境準備
1-7行目で実行するSQLファイルのパスと実行ユーザ・パスワードを定義しています。
(2)sql実行
11-12行目で定義したSQLファイルを実行しblogdbを更新しています。11行目がVPSのWordpressDBよりバックアップしたSQLファイル。12行目がVPS向けURLをローカル向けURLに書き換えるSQLです。
7.Cronへの登録
上記スクリプトがDockerホストサーバで定期的に実行されるよう/etc/cron.d/restore
ファイルを用意し以下のように設定します。
1 2 |
# vi /etc/cron.d/restore 30 05 * * * root /root/FileRestore.sh |
毎朝5時30分にBLOGのリストア処理が実行されます。
8.成功までの顛末
(1)Cron失敗
実は、当初Cronからの自動リストアはうまく動作していませんでした。ターミナルからの実行では問題無く動作するのですが。
(2)調査結果
調査したところDockerに接続してシェルスクリプトを実行する以下の行がうまく動作していませんでした。
1 |
docker exec -it $CONTAINER sh /home/sqlexec.sh >>$LOG |
コンテナで実行するsqlexec.shに調査ログを仕込んで実行結果を見てみるとログが出力されていません!Cronからシェルスクリプトを起動すると動作しないようです。
(3)試行錯誤
これらをヒントにGoogleで調査してみましたがどこにも適切な回答はありません。そこで基本に戻りDocker公式日本語マニュアルを精査してみました。
exec
1 2 3 4 5 6 7 8 9 10 11 |
使い方: docker exec [オプション] コンテナ コマンド [引数...] 実行中のコンテナでコマンドを実行 -d, --detach=false デタッチド・モード: コマンドをバックグラウンドで実行 --detach-keys デタッチド・コンテナに特定のエスケープ・キー・シーケンスを設定 --help=false 使い方の表示 -i, --interactive=false アタッチしていなくても STDIN をオープンにし続ける --privileged=false コマンドに拡張 Linux ケーパビリティの追加 -t, --tty=false 疑似ターミナル (pseudo-TTY) の割り当て -u, --user= ユーザ名か UID (書式: <名前|uid>[:<グループ|gid>]) |
最初に試したのは 「-u root」オプションの追加。しかし状況は変化なし。Cronではroot指定で実行しているので当然であります。
次に試したのは「-t」オプションの削除。Cronで実行時に疑似ターミナルは割り当てできないのではとの推論の元。結果は予想通りでした。以下が変更後のコマンド行です。
1 |
docker exec -i $CONTAINER sh /home/sqlexec.sh >>$LOG 2>&1 |
合わせて「 2>&1」のリダイレクトで標準エラー出力もログに出力するようにしています。
(4)結論
Cronから実行するShellScript内でDockerコンテナに接続し、コンテナ内のShellスクリプトを実行する場合は、execコマンドのオプションに-tを付加しない。
通常は「Dockerコンテナに接続しコンテナ内のShellスクリプトを実行する」の前提はターミナルから実行するなのでこの-tオプションがついていても問題はありません。
検索結果を流用する際には、訳も分らずコピペするのではなくマニュアルを参照してオプションの意味や条件を良く理解したうえで使用するのがよろしいようです。自分への教訓です。