rsyncとシェルスクリプトを使った自動バックアップ環境の構築手順
背景
レンタルサーバ上のwordpressのファイルや、ローカルにあるファイル共有サーバ内のファイルを定期的に自動でバックアップしたいと思ったことがある人は多いと思います。私も常々そう思っていましたが、まあまだデータが消えるようなことは起きないだろうと根拠なく考え、結局やらずじまいでした。ただ、最近大事なデータが危うく無くなる事態に遭遇し、やはりバックアップは大事だと実感しました。そこで指定したファイルのバックアップを指定時刻に自動で作成する環境をようやく構築したので、その手順をメモしておきます。
やりたいこと
以下の図のように、ローカルにあるバックアップ保存用の1台のバックアップサーバ(図中のBackup server)が、同じローカルにあるファイル共有サーバ(図中のFile sharing server)とインターネットを介した先にあるリモートサーバ(図中のRemote server、レンタルサーバなど)のファイルをバックアップサーバに接続された外付けHDDに毎日午前3時に保存させるようにします。
具体的には、バックアップサーバがrsyncというLinuxコマンドによってバックアップを作成します。また、rsyncによるバックアップ作成を定期的に実行するために、Linuxのcronという仕組みを利用します。
環境の構築とは言っていますが、単純にrsyncを実行するシェルスクリプトをcronで定期実行するだけです。
rsyncについて
rsyncはscpと似ています。私自身rsyncとscpの違いについてよくわかっていませんでしたが、大事な違いとしては、rsyncは差分転送できるという点です。一方でscpは差分転送はできないです。rsyncとscpの違いについては以下のサイトに詳しく書いてあり、大変参考になりました。ありがとうございました。
rsyncとscpについて調べてみた | Pandora Pocket scpとrsyncの所作の違い。気をつけるところ | kenjiskywalker's diary scpとrsyncの違い | sasata299's blog
環境
以下の環境で構築しました。なお、今回の方法ではリモートサーバ(今回はレンタルサーバ)はssh接続が可能であることを前提としています。
- バックアップサーバ:Ubuntu Server 13.04 64bit
- ファイル共有サーバ:Windows7 Ultimate 64bit
- レンタルサーバ:さくらインターネット
- バックアップ用外付けHDD:WD Elemetns Portable 1.0TB(こちらにレビュー書いてます。おすすめです。)
ファイル共有サーバのバックアップを作成する
事前準備
事前準備として以下が必要になります。
- バックアップサーバにバックアップ用外付けHDD、ファイル共有サーバの共有フォルダのマウント先ディレクトリを作成
- 外付けHDDをバックアップサーバにマウント
- 共有フォルダをバックアップサーバにマウント
外付けHDD、共有フォルダのマウント用ディレクトリを作成
これは以下のコマンドでOKです。以下では外付けHDDのマウント先として「/media/external」、共有フォルダのマウント先として「/mnt/smbfs」というディレクトリを作成しています。
$ sudo mkdir /media/external
$ sudo mkdir /mnt/smbfs
ちなみに、マウントポイントとして「/media」、「/mnt」のどちらを使うかは、厳密なルールはないようです。たぶん。個人的な使い分けとしては、USB、外付けHDDなどのリムーバルディスクは「/media」にマウントし、「/mnt」は共有フォルダとか用にしてました。一応以下のURLに「/media」、「/mnt」の目的が載っているので興味のある方は参照ください。
Filesystem Hierarchy Standard
ファイル共有サーバのフォルダをバックアップサーバにマウント
ファイル共有サーバの共有フォルダをマウントするために、「cifs-utils」をインストールする必要があります。
$ sudo apt-get install cifs-utils
「cifs-utils」をインストールしたら、あとは以下のコマンドで共有フォルダをマウントすることができます。
$ sudo mount -t cifs 共有フォルダのパス マウント先 -o username=ユーザ名,password=パスワード
よって、例えば以下のような場合は、
- ファイル共有サーバのIPアドレス(ホスト名も可):192.168.1.10
- 共有フォルダのパス://192.168.1.10/Users/user/share
- マウント先:/mnt/smbfs (上記で作成したマウント先ディレクトリのパス)
- 共有フォルダのユーザ名:user
- 共有フォルダのユーザ名に対するパスワード:pass
$ sudo mount -t cifs //192.168.1.10/Users/user/share /mnt/smbfs/ -o username=user,password=pass
ただし、毎回上記のコマンドを実行してマウントするのは手間なので、Ubuntu Serverの起動時に自動でマウントするようにします。そのためには、「/etc/fstab」を編集して起動時にマウントしたい共有フォルダの情報を追記します。
$ sudo vi /etc/fstab
追記内容は以下のとおりです。最後の1行が追記した部分でそれ以外は私の環境でのデフォルトで記述されていた内容です。
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
#
/dev/mapper/server--vg-root / ext4 errors=remount-ro 0 1
# /boot was on /dev/sda1 during installation
UUID=92222-0abcs-3eee-3333kkkkkk /boot ext2 defaults 0 2
/dev/mapper/server--vg-swap_1 none swap sw 0 0
//192.168.1.10/Users/admin/share /mnt/smbfs cifs username=user,password=pass 0 0
追記した部分は、以下のように共有フォルダのパス、マウント先、ユーザ名、パスワードです。
//192.168.1.10/Users/admin/share /mnt/smbfs cifs username=user,password=pass 0 0
なお、最後にある2つの0は、それぞれdump(バックアップ)オプション、fsck(ファイルシステムのチェック)オプションを意味しており、0で無効、1ならば有効です。fstabの書式とオプションについては以下のサイトが参考になりました。
fstabの設定 | UNIXLife /etc/fstabに記述されている数字の意味 | すぱいだー日記
fstabに追記し終えたら、試しに以下のmountコマンドでマウントし直してみます。「-a」はfstabに記載されたファイルシステムすべてをマウントするためのオプションです。
$ sudo mount -a
実行結果に何も表示されなければ正常にマウントされたはずです。試しにマウントディレクトリに移動して中身を確認してみるといいです。
ちなみに、もし以下のようにmemory allocate エラーが出る場合は、以下のサイトを参考にWindows7のレジストリの値を変更することで直る場合があるようです。ただし、レジストリをいじることになるので慎重に自己責任でお願いします。
mount error 12 = Cannot allocate memory
Refer to the mount.cifs(8) manual page (e.g.man mount.cifs)
Windowsの共有フォルダをLinuxでマウントして使用する方法
rsyncを使ったシェルスクリプト
事前準備ができたら次にrsyncコマンドを含むシェルスクリプトを作成します。今回作成したシェルスクリプトを以下に載せます。以下のシェルスクリプトの使用は自己責任でお願い致します。また、シェルスクリプトの中に全角スペースが入ると動作しないので気をつけてください。
#!/bin/bash
MAXNUM=3 # バックアップの最大保存日数
MAILADD=hogehoge@hoge.com # ログ内容の送信先メールアドレス
# バックアップ元ディレクトリのパス(共有フォルダのマウントディレクトリのパス)
SOURCEDIR=/mnt/smbfs
# バックアップ作成先ディレクトリのパス(外付けHDDのマウントディレクトリのパス)
DESTDIR=/media/external
TMPLOG=/var/log/rsync_tmp.log # ログファイル
# ログファイルの存在チェック。存在しなければ作成
if [ ! -e $TMPLOG ]; then
echo "$TMPLOG NOT found." >> $TMPLOG
touch $TMPLOG # touchコマンドを利用してログ用の空ファイルを作成
echo -e "$TMPLOG was created.\n" >>$TMPLOG
# 「-e」はエスケープコード(\nなど)を使用可能にするためのオプション
fi
echo "`date` backup start" >> $TMPLOG
# 最新バックアップフォルダ名を取得
LASTBACKUP=`ls -t $DESTDIR | grep backup | head -1`
# 最古バックアップフォルダ名を取得
OLDESTBACKUP=`ls -tr $DESTDIR | grep backup | head -1`
# 新しく作成するバックアップフォルダ名に日付を入れる
NEWBACKUP=`date +%Y%m%d`backup
# LASTBACKUPが空文字列であるかを確認
# 「-n」は、Non-zeroであるかどうかを確認するためのもの。Non-zeroならばTRUEとなる。
# LASTBACKUPが存在する場合
if [ -n "$LASTBACKUP" ]; then
# すでに最新のバックアップ(スクリプト実行時の日付を含むバックアップ)が作成済の場合
if [ "$LASTBACKUP" = "$NEWBACKUP" ]; then
echo "Last backup:$LASTBACKUP is the same as $NEWBACKUP" >> $TMPLOG
cat $TMPLOG | mail -s "rsync log from $HOSTNAME" $MAILADD
rm -rf $TMPLOG
exit 1
# 最新のバックアップが末作成の場合
else
mkdir $DESTDIR/$NEWBACKUP # 最新バックアップの格納用ディレクトリを作成
echo "$DESTDIR/$NEWBACKUP was created" >> $TMPLOG
# rsyncのコマンド用のオプションを指定。バックアップディレクトリが存在するので、そのディレクトリを--link-destで指定
OPTION="-av --link-dest=../$LASTBACKUP"
fi
# LASTBACKUPが存在しない場合
else
echo "No LASTBACKUP" >> $TMPLOG
mkdir $DESTDIR/$NEWBACKUP # 今日の日付入りのバックアップディレクトリを作成
echo -e "$DESTDIR/$NEWBACKUP was created \n" >> $TMPLOG
# rsyncのコマンド用のオプションを指定。バックアップディレクトリが1つもないので、--link-destオプションは使用しない。
OPTION="-av"
fi
# 実行するrsyncコマンドをログに残す
echo -e "command is rsync $OPTION $SOURCEDIR $DESTDIR/$NEWBACKUP \n" >> $TMPLOG
# rsyncを実行
rsync $OPTION $SOURCEDIR $DESTDIR/$NEWBACKUP >> $TMPLOG 2>&1
code=$?
# $codeが0でない場合の処理(正常終了でない場合)
if [ $code -ne 0 ]; then
echo -e "Error\n" >> $TMPLOG
cat $TMPLOG | mail -s "rsync Error $code from $HOSTNAME" $MAILADD
rm -rf $TMPLOG
exit 1
fi
echo -e "`date` backup end \n" >> $TMPLOG
### 最古のバックアップフォルダを削除するための処理 ###
# バックアップフォルダの数を確認
backnum=`ls -t $DESTDIR | grep backup | wc -l`
#backupフォルダがMAXNUM以上ある場合は最も古いフォルダを削除
if [ $backnum -gt $MAXNUM ]; then
rm -rf $DESTDIR/$OLDESTBACKUP
echo "$OLDESTBACKUP was deleted." >> $TMPLOG
else
echo "No need to delete $OLDESTBACKUP." >> $TMPLOG
fi
cat $TMPLOG | mail -s "rsync log from $HOSTNAME" $MAILADD
rm -f $TMPLOG
なお、上記のシェルスクリプトでは、rsyncでできない部分のうち、以下の点をその他のLinuxコマンドで実現しています。
- 作成してから指定日数以上経過したバックアップは削除し、指定した日数分のバックアップだけを残す。
- バックアップ作成の結果をログとして残し、さらに指定したメールルアドレスにログ内容を送信する
7,10行目
ここではそれぞれバックアップを作成する元のディレクトリとバックアップ作成先のディレクトリを指定しています。なお、バックアップ作成元は上記で作成した共有フォルダのマウントディレクトリ、バックアップ作成先には同じく上記で作成した外付けHDDのマウントディレクトリを指定しています。
25、28行目
この行では、3つのコマンドをパイプでつなげて$DESTDIRにある最新のバックアップフォルダの名前を取得してそれを変数LASTBACKUPに保存しています。
具体的には、「ls -t $DESTDIR」で$DESTDIRにあるファイル、ディレクトリの一覧をタイムスタンプの新しい順番で取得し、次に「grep backup」によってその中から文字列「backup」を含むものを抽出、そして最後に「head -1」よって抽出してリストしたもののうち一番上にあるもの、すなわちタイムスタンプが最新のディレクトリ名を取得しています。
そして28行目は逆に$DESTDIRにある最古のバックアップフォルダの名前を取得してそれを変数OLDESTBACKUPに保存しています。「ls -tr」のrがタイムスタンプの古い順での表示を指示するオプションです。
31行目
ここでは、「20140101backup」といったようにシェルスクリプト実行日の年月日に文字列「backup」が結合された文字列を変数NEWBACKUPに保存しています。このNEWBACKUPが新しく作成バックアップディレクトリの名前になります。
なお、「date+%Y%m%d
」で日付の表示形式を指定しており、これらをバッククオートで囲むことでコマンドとして認識し実行させています。
37行目
条件式の中の「-n」はNon-zeroであるかどうかを判定するためのものです。これでLASTBACKUPに格納された名前と同じ名前を持つディレクトリが存在するかを判定しています。
41行目
mailコマンドを使って$TMPLOGの内容をメールで送信しています。
cat $TMPLOG | mail -s "rsync log from $HOSTNAME" $MAILADD
上記の記述によって、catコマンドで$TMPLOGの内容を表示、そしてその表示結果をパイプでmailコマンドに渡しています。このようにすることで、$TMPLOGの内容を本文とするメールを送信できます。なお、mailコマンドに続く「-s "rsync log from $HOSTNAME"」は件名です。「-s」が件名を指定するオプションです。$HOSTNAMEはubuntuのホスト名を表す環境変数です。そして最後にある$MAILADDが送信先のメールアドレスになります。 mailコマンドを使ってメールをgmailに送信するための手順をこちらにまとめたので、必要な方は参照下さい。
50,58行目
rsyncのオプションを指定しています。「-av」のうちのaオプションは、元ファイルのパーミッションやグループなどを保持したままコピーするためのもので、オプションとして「-rlptgoD」を付加したのと同じ意味です。vオプションは、コピーしたファイル名を表示するためのものです。
なお、LASTBACKUPが存在する場合は、「-av」に加えて差分バックアップを指示するために「- -link-dest」をオプションとして付加し、続けて差分を取る対象となるディレクトリ、すなわちLASTBACKUPを指定しています。LASTBACKUPが存在しない場合は、「- -link-dest」を付加せずにオプションとして「-av」だけ指定します。
つまり、ここでのLASTBACKUPの存在可否によるオプション指定によって、rsyncコマンドが最終的に以下のように分かれます。
- LASTBACKUPが存在する場合
- LASTBACKUPが存在しない場合
$ rsync -av --link-dest=../$LASTBACKUP $SOURCEDIR $DESTDIR/$NEWBACKUP >> $TMPLOG 2>&1
$ rsync -av $SOURCEDIR $DESTDIR/$NEWBACKUP >> $TMPLOG 2>&1
なお、「>> $TMPLOG」を指定することでrsyncによってコピーされるファイル名などのログが、$TMPLOGに追記されていきます。そのため、場合によっては $TMPLOGで指定したログファイルのサイズがかなり大きくなります。このシェルスクリプトでは、最終的に$TMPLOGの内容をメール送信しその後削除してしまいますが(92、93行目)、もしログを削除せず残す場合は、ログローテーションの仕組みを取り入る必要があります。もしくは、追記ではなく最新のログ内容だけ残せば十分な場合は、単純にリダイレクトを「>> $TMPLOG」から「> $TMPLOG」に変更してログを残すようにすればいいかと思います。
また、「--link-dest」オプションを使って外付けHDDに差分バックアップを作成する場合、差分バックアップの作成先となる外付けHDDのフォーマットがext3などである必要があります。詳しくはこのページの最後にメモしました。
67,70行目
変数codeに格納される「$?」は特殊変数というもので、この特殊変数が呼ばれた直前に評価された判定結果を参照できるそうです。すなわち、ここでは、rsyncコマンドの評価結果が変数codeに格納されます。そしてその値が0(正常終了)でなかった場合の処理がエラー対策処理として次の行に記述しています。特殊変数などシェルスクリプトに関して参考にしたサイトを以下にメモしておきます。
シェルスクリプトにおける条件式や演算子について参考にしたサイト 演算比較 | シェルスクリプト入門 $?などの特殊変数について参考にしたサイト bash変数と特殊変数 | webzoit.net
72行目
rsyncの実行結果が正常終了でなかった場合の処理です。この場合はLOGの内容とcodeの内容をメールで$MAILADDに送信しています。「mail」コマンドによるメールの送信については、こちらにまとめましたので必要な方は参考にして頂ければと思います。
82行目
25、28行目と同じ要領で$DESTDIRにあるバックアップフォルダの数を取得しています。「wc -l」が行数を数えるコマンドで、これによって文字列「backup」を含むフォルダ数を取得しています。そして後はMAXNUM以上あった場合に一番古いバックアップフォルダを削除しています。
あとは作成したシェルスクリプトには用途に合わせて適切な実行権限を与えておきます。今回は以下のようにしました。
$ sudo chmod 755 rsync.sh
シェルスクリプトを自動実行するためにcronに登録
上記で実行権限を与えたシェルスクリプトをcronに登録して指定した時刻に自動実行させます。cronについては、以下のサイトにわかりやすく解説してあるので省略します。大変参考になりました。ありがとうございました。
crontabの使い方 | omnioo lab. records cron力をつけよう!全てのcrontab入門者に贈る9個のテクニック | DQNEO起業日記
rsyncを含むシェルスクリプトがcronに設定した時間に自動で実行できれば、これでバックアップを自動作成する環境の構築の完了です。
リモートサーバのバックアップを作成する
リモートサーバのバックアップを作成する場合もファイル共有サーバの場合とほぼ同じです。ただ、シェルスクリプトにいくつか追加する情報と修正する部分があるのでその部分のみをメモします。
リモートサーバにssh接続するための情報を追記
リモートサーバにssh接続するために必要なユーザ名、パスワード、ホスト名をシェルスクリプトの「#!/bin/bash」の下あたりに追記します。また、公開鍵認証をして接続する場合は、鍵の指定もします(KEY=/home/ubuntu/.ssh/hogehoge.keyの部分が該当)。ssh接続する場合のポート番号はリモートサーバ、レンタルサーバによって異なるので、適宜変更してください。
#!/bin/bash
USER=username #ユーザ名
PASS=password #パスワード
HOST=hogehoge.jp #ホスト名
PORT=22
KEY=/home/ubuntu/.ssh/hogehoge.key
expectコマンドでパスワード入力を自動化してssh接続
まずexpectコマンドを使用するために以下でインストールしておきます。
$ sudo apt-get install expect
上記のシェルスクリプトでは、rsyncコマンドの実行部分が以下のようになっていました。
# rsyncを実行
rsync $OPTION $SOURCEDIR $DESTDIR/$NEWBACKUP >> $TMPLOG 2>&1
これを以下のように置き換えます。なお、以下はパスフレーズありの公開鍵認証でssh接続する場合です。
# rsyncを実行
expect -c "
set timeout 10
spawn rsync -e \"ssh -i $KEY -p $PORT\" $OPTION $USER@$HOST:$SOURCEDIR $DESTDIR/$NEWBACKUP
expect \"Are you sure you want to continue connecting (yes/no)?\" {
send \"yes\n\"
expect \"Enter passphrase for key '$KEY':\"
send \"$PASS\n\"
} \"Enter passphrase for key '$KEY':\" {
send \"$PASS\n\"
}
interact
"
上記で行っているのは、expectコマンドを使用したssh接続に必要なパスワード入力の自動化です。簡単に説明すると、上記の「Are you sure you want to continue connecting (yes/no)?」のようにコマンドを実行した時に予期される質問をexpectの後に記述し、質問に対する答えをsendのあとに記述しておくことで自動入力します。これによって、yes/noの入力やパスワード入力を自動化することができます。詳しくは以下のサイトが参考になりました。ありがとうございました。
対話型のコマンドを自動化できるexpectコマンド | A day in the boys life
また、rsyncコマンドの部分は「-e」オプションによって、ssh接続で必要な設定を行っています。例えば以下の場合は、
rsync -e \"ssh -i $KEY -p $PORT\" $OPTION $USER@$HOST:$SOURCEDIR $DESTDIR/$NEWBACKUP
「-i」オプションで鍵ファイルを指定し、「-p」オプションでポート番号を指定しています。sshのオプションについては以下のサイトで確認できます。
SSH (1) | OpenSSH日本語マニュアルページ
ちなみに、パスフレーズありの公開鍵ではなくパスワード認証でのssh接続を自動化したい場合は、以下のようにします。
# rsyncを実行
expect -c "
set timeout 10
spawn rsync -e \"ssh -p $PORT\" -$OPTION $USER@$HOST:$SOURCEDIR $DESTDIR/$NEWBACKUP
expect \"Are you sure you want to continue connecting (yes/no)?\" {
send \"yes\n\"
expect \"$USER@$HOST's password:\"
send \"$PASS\n\"
} \"$USER@$HOST's password:\" {
send \"$PASS\n\"
}
interact
"
expectに続く質問内容とそれに対応する答えをパスワード認証の場合に合わせた形にし、またrsyncコマンドの「-e」の後にはポート番号を指定する「-p $PORT」のみを記述しています。
なお、当然ながらexpectの後に続く質問内容や、sendの後に続く答えが正確でなければ、expectコマンドは動作しないです。また、今回はすぐにできたexpectコマンドを使ってしまいましたが、expectコマンドは日本語入力ができないなどの問題点もあるようです。よって、ssh接続の他の方法としてsshpassコマンドを使う方法なども考えたほうがいいかもしれません。ここらへんについては以下が勉強になりました。ありがとうございました。
sshにパスワードで自動ログインするならexpectよりもsshpassを使おう | PCと遊ぶ日々の記録
rsyncで遭遇したエラー
rsync: link operation not permitted(1) が出る
rysncのログを確認すると大量にこのエラーが出ていました。これは、今回のようにバックアップ作成先である外付けHDDのフォーマット形式がハードリンクに対応してないに関わらず、「- -link-dest」オプションを使用してハードリンクを作成しようとしていたことが原因のようです。すなわち、FATなどWindowsのフォーマットのHDDだとハードリンクを作成できずにこのエラーが出るようです。よって、このエラーを出さずに差分でバックアップを作成したい場合は、HDDのフォーマットをext3などにする必要があります。
Operation not permitted (link) | Back In Time (英語)
まとめ
Linuxのコマンドやシェルスクリプトは奥が深く勉強不足だと改めて感じました。正直、なんとか調べながら自分が望む動作をさせるようにすることができましたが、色々な観点から見て無駄や間違っている部分、不十分な点があるのだと思います。なので。もし何かおかしい点や改善点があったらぜひとも教えて頂ければと思います。よろしくお願いします。
関連記事
- 公開日:2022/09/11 更新日:2022/09/11
Google Compute EngineのインスタンスにSSH接続できなくなった時の対処方法
Google Compute EngineのインスタンスにSSH接続できていたのにSSHの設定ミスや原因不明でSSH接続できなくなった時の対処方法をメモします。自分のPCからだけでなく、Google Cloud Platform上での管理画面からもSSHできない場合にも有効です。
- 公開日:2022/09/08 更新日:2022/09/08
Google Compute EngineのインスタンスにSSH接続する
Google Compute Engineのインスタンスにssh接続するための手順をまとめます。
- 公開日:2018/02/10 更新日:2018/02/10
CentOSでrootのときだけ日本語が文字化けする時の原因と対処方法
さくらVPSでCentOSを使いはじめたところ、一般ユーザで使用している時はvimなどのエディタで編集していても日本語が問題なく表示されていましたが、root権限で同様に編集のためにファイルを開いたら日本語が文字化けしてしまいました。以降ではこの原因と対処方法についてメモします。
- 公開日:2017/12/09 更新日:2017/12/09
サーバ上のディレクトリをローカルにマウントするsshfsの使い方
Linuxでは、サーバ側のディレクトリを自身のローカルPCにマウントして、ローカル上のディレクトリして使用することができるsshfsというコマンドが用意されています。ここではこのsshfsの使い方をメモします。
- 公開日:2017/08/27 更新日:2017/08/27
MacでubuntuのインストールUSBを作成する方法と手順
MacでubuntuのインストールUSBを作成する手順をメモします。ここで載せる手順はターミナルでのコマンドによる方法になります。