rsyncとシェルスクリプトを使った自動バックアップ環境の構築手順

公開日:2014/05/25 更新日:2014/05/25
rsyncとシェルスクリプトを使った自動バックアップ環境の構築手順のサムネイル

背景

レンタルサーバ上のwordpressのファイルや、ローカルにあるファイル共有サーバ内のファイルを定期的に自動でバックアップしたいと思ったことがある人は多いと思います。私も常々そう思っていましたが、まあまだデータが消えるようなことは起きないだろうと根拠なく考え、結局やらずじまいでした。ただ、最近大事なデータが危うく無くなる事態に遭遇し、やはりバックアップは大事だと実感しました。そこで指定したファイルのバックアップを指定時刻に自動で作成する環境をようやく構築したので、その手順をメモしておきます。

やりたいこと

以下の図のように、ローカルにあるバックアップ保存用の1台のバックアップサーバ(図中のBackup server)が、同じローカルにあるファイル共有サーバ(図中のFile sharing server)とインターネットを介した先にあるリモートサーバ(図中のRemote server、レンタルサーバなど)のファイルをバックアップサーバに接続された外付けHDDに毎日午前3時に保存させるようにします。

rsync_image.png 具体的には、バックアップサーバが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をバックアップサーバにマウント
  • 共有フォルダをバックアップサーバにマウント
まずこれらの事前準備についてメモします。ただし、Ubuntu Desktop 14.04で確認したところ、外付けHDDは「/media/user/」(userはUbuntu Desktopのユーザ名)に自動でマウントされていたので、すでにマウントされている場合は以下の外付け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
以下のコマンドによってWindowsの共有フォルダをマウントすることができます。
$ 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が存在する場合
  • $ rsync -av --link-dest=../$LASTBACKUP $SOURCEDIR $DESTDIR/$NEWBACKUP >> $TMPLOG 2>&1
    
  • LASTBACKUPが存在しない場合
  • $ 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で遭遇したエラー

rysncのログを確認すると大量にこのエラーが出ていました。これは、今回のようにバックアップ作成先である外付けHDDのフォーマット形式がハードリンクに対応してないに関わらず、「- -link-dest」オプションを使用してハードリンクを作成しようとしていたことが原因のようです。すなわち、FATなどWindowsのフォーマットのHDDだとハードリンクを作成できずにこのエラーが出るようです。よって、このエラーを出さずに差分でバックアップを作成したい場合は、HDDのフォーマットをext3などにする必要があります。

Operation not permitted (link) | Back In Time (英語)

まとめ

Linuxのコマンドやシェルスクリプトは奥が深く勉強不足だと改めて感じました。正直、なんとか調べながら自分が望む動作をさせるようにすることができましたが、色々な観点から見て無駄や間違っている部分、不十分な点があるのだと思います。なので。もし何かおかしい点や改善点があったらぜひとも教えて頂ければと思います。よろしくお願いします。

関連記事

開発アプリ

nanolog.app

毎日の小さな出来事をなんでも記録して、ログとして残すためのライフログアプリです。