【概要】
rsyncで世代バックアップを取得するスクリプトです。
シェルスクリプト(bash)で、
CRONに登録することを想定しています。
手動実行も可能です。
※スクリプトを使用する場合は、充分にテストしてからご利用ください
バックアップ元からバックアップ先にrsyncします。
バックアップ元または、バックアップ先をリモート(SSH接続)とすることもできます。(2022年4月に処理を更新しました)
※バックアップ先と、バックアップ元の両方をリモートにすることはできません。
バックアップ先で世代管理を行います。
(1)との違いは、rsync処理を前回世代との差分(--link-dest
)にしている所です。
差分の無いものはハードリンクになります。ハードリンクはファイルの実体に対して複数設定可能です。すべてのハードリンクが無くなるとファイルの実体が削除となります。リンク数は「ls -l」コマンドで確認可能です。ハードリンクはinode番号が同じになります。inode番号については「ls -li」コマンドで確認可能です。
(例1)「-rw-r--r--」の右の「3」がハードリンク数
# ls -l aaa.txt
-rw-r--r-- 3 root root 4 4月 16 10:24 aaa.txt
(例2)一番左の数字がinode番号
# ls -li aaa.txt
286782 3 root root 4 4月 16 10:24 aaa.txt
処理の流れとしては下記になります。
◆3世代の例
1回目:バックアップ先に世代1ディレクトリを作成してrsync
2回目:バックアップ先に世代2ディレクトリを作成して、世代1とバックアップ元の差分をrsync
3回目:バックアップ先に世代3ディレクトリを作成して、世代2とバックアップ元の差分をrsync
4回目:世代1を新規の世代3にリネームして、前回の世代とバックアップ元の差分をrsync
【動作確認した環境】
Centos7.9
【前提条件】
・SSHを使用する場合はSSH公開鍵認証で接続先にパスワードなしで接続可能なこと
※(1)を参照ください
・SSHを使用する場合は接続元と接続先の両方にrsyncがインストールされていること
【rsyncgen2.sh】
#!/bin/bash
### Set rsync directory and generation #########################################
# Caution: source and destination cannot both be remote.
## rsync source directory
# / is not required at the end of the directory
# (exsample: DIR_SRC=/root/testsrcdir)
# (example: DIR_SRC_USER="testuser", if not use : DIR_SRC_USER="")
# (example: DIR_SRC_IP="192.168.1.10" , if not use : DIR_SRC_IP="")
DIR_SRC=<バックアップ元ディレクトリフルパス>
DIR_SRC_USER=""
DIR_SRC_IP=""
## rsync destination directory
# / is not required at the end of the directory
# (exsample: DIR_SRC=/root/testdstdir)
# (example: DIR_DST_USER="testuser", if not use : DIR_DST_USER="")
# (example: DIR_DST_IP="192.168.1.10" , if not use : DIR_DST_IP="")
DIR_DST=<バックアップ先ディレクトリフルパス>
DIR_DST_USER=""
DIR_DST_IP=""
## backup generation
# (exsample: BKUP_GEN_FULL=3)
BKUP_GEN=<数字>
################################################################################
# variable initialization
RC=0
DIR_DST_SSH_FLG=0
DIR_SRC_SSH=""
DIR_DST_SSH=""
YYYYMMDD_hhmmss=$(date '+%Y%m%d_%H%M%S')
DIR_DST_CNT=0
AGO_GEN=""
FULL_BKUP_FLG=0
# backup generation check
echo "${BKUP_GEN}" | grep -q "^[0-9]\+$"
RC=$?
if [ "${RC}" != "0" ] || [ ${BKUP_GEN} -le 0 ] ; then
/usr/bin/logger "ERROR: rsync script NUM_SRC variable error: ${BKUP_GEN}"
exit 1
fi
# rsync directory check
if [ -z ${DIR_SRC} ] || [ "${DIR_SRC}" = "/" ] ; then
/usr/bin/logger "ERROR: rsync script DIR_SRC variable error: ${DIR_SRC}"
exit 1
fi
if [ -n "${DIR_SRC_USER}" ] && [ -n "${DIR_SRC_IP}" ] ; then
DIR_SRC_SSH="${DIR_SRC_USER}@${DIR_SRC_IP}:${DIR_SRC}"
else
DIR_SRC_SSH="${DIR_SRC}"
fi
if [ -n "${DIR_DST_USER}" ] && [ -n "${DIR_DST_IP}" ] ; then
DIR_DST_SSH="${DIR_DST_USER}@${DIR_DST_IP}:${DIR_DST}"
DIR_DST_SSH_FLG=1
else
DIR_DST_SSH="${DIR_DST}"
fi
# DIR_DST generation management
function dst_gen_mng () {
# variable initialization
NUM_DST=0
NUM_DEL=0
FNC_RC=0
FNC_DIR_DST=$1
FNC_BKUP_GEN=$2
FNC_YYYYMMDD_hhmmss=$3
# rsync directory check
if [ -z ${FNC_DIR_DST} ] || [ "${FNC_DIR_DST}" = "/" ] ; then
/usr/bin/logger "ERROR: rsync script FNC_DIR_DST variable error: ${FNC_DIR_DST}"
exit 1
fi
# generation management
ls -1 ${FNC_DIR_DST} | grep "^BKUP_GEN_" > /dev/null 2>&1
FNC_RC=$?
if [ "${FNC_RC}" = "0" ] ; then
cd ${FNC_DIR_DST}
NUM_DST=$(ls -1d ${FNC_DIR_DST}/BKUP_GEN_* | sort -r | wc -l)
if [ ${NUM_DST} -gt 0 ] ; then
# delete over 1 more generation
if [ ${NUM_DST} -gt ${FNC_BKUP_GEN} ] ; then
NUM_DEL=$(expr ${NUM_DST} - ${FNC_BKUP_GEN})
ls -1d ${FNC_DIR_DST}/BKUP_GEN_* | sort -r | tail -n ${NUM_DEL} | xargs rm -rf
FNC_RC=$?
if [ "${FNC_RC}" != "0" ] ; then
/usr/bin/logger "ERROR: rsync scriptrm command return code: ${FNC_RC}"
exit 1
fi
fi
# rename over oldest generation(reduce rsync diff)
if [ ${NUM_DST} -ge ${FNC_BKUP_GEN} ] ; then
NUM_DEL=$(expr ${NUM_DST} - ${FNC_BKUP_GEN})
LAST_GEN=$(ls -1d ${FNC_DIR_DST}/BKUP_GEN_* | sort -r | tail -1)
mv ${LAST_GEN} ${FNC_DIR_DST}/BKUP_GEN_${FNC_YYYYMMDD_hhmmss}
FNC_RC=$?
if [ "${FNC_RC}" != "0" ] ; then
/usr/bin/logger "ERROR: rsync script mv command return code: ${FNC_RC}"
exit 1
fi
else
# mkdir backup direcotry
if [ ! -d ${FNC_DIR_DST}/BKUP_GEN_${FNC_YYYYMMDD_hhmmss} ] ; then
mkdir -p ${FNC_DIR_DST}/BKUP_GEN_${FNC_YYYYMMDD_hhmmss}
FNC_RC=$?
if [ "${FNC_RC}" != "0" ] ; then
/usr/bin/logger "ERROR: rsync script mkdir: ${FNC_DIR_DST}"
exit 1
fi
fi
fi
fi
else
# mkdir backup direcotry
if [ ! -d ${FNC_DIR_DST}/BKUP_GEN_${FNC_YYYYMMDD_hhmmss} ] ; then
mkdir -p ${FNC_DIR_DST}/BKUP_GEN_${FNC_YYYYMMDD_hhmmss}
FNC_RC=$?
if [ "${FNC_RC}" != "0" ] ; then
/usr/bin/logger "ERROR: rsync script mkdir: ${FNC_DIR_DST}"
exit 1
fi
fi
fi
}
# run funciton dst_gen_mng
if [ "${DIR_DST_SSH_FLG}" = "1" ] ; then
ssh ${DIR_DST_USER}@${DIR_DST_IP} "$(typeset -f dst_gen_mng); dst_gen_mng ${DIR_DST} ${BKUP_GEN} ${YYYYMMDD_hhmmss}"
RC=$?
if [ "${RC}" != "0" ] ; then
/usr/bin/logger "ERROR: rsync script function dst_gen_mng: ssh run"
exit 1
fi
else
dst_gen_mng ${DIR_DST} ${BKUP_GEN} ${YYYYMMDD_hhmmss}
RC=$?
if [ "${RC}" != "0" ] ; then
/usr/bin/logger "ERROR: rsync script function dst_gen_mng: local run"
exit 1
fi
fi
# chcek need full rsync
if [ "${DIR_DST_SSH_FLG}" = "1" ] ; then
DIR_DST_CNT=$(ssh ${DIR_DST_USER}@${DIR_DST_IP} "ls -1d ${DIR_DST}/BKUP_GEN_* | wc -l")
if [ ${DIR_DST_CNT} -ge 2 ] ; then
# get one time ago directory (remote)
AGO_GEN=$(ssh ${DIR_DST_USER}@${DIR_DST_IP} "ls -1d ${DIR_DST}/BKUP_GEN_* | sort -r | head -2 | tail -1")
FULL_BKUP_FLG=0
else
FULL_BKUP_FLG=1
fi
else
DIR_DST_CNT=$(ls -1d ${DIR_DST}/BKUP_GEN_* | wc -l)
if [ ${DIR_DST_CNT} -ge 2 ] ; then
# get one time ago directory (local)
AGO_GEN=$(ls -1d ${DIR_DST}/BKUP_GEN_* | sort -r | head -2 | tail -1)
FULL_BKUP_FLG=0
else
FULL_BKUP_FLG=1
fi
fi
if [ "${FULL_BKUP_FLG}" = "0" ] ; then
# run rsync diff from one time ago
/usr/bin/rsync -ar --delete --link-dest=${AGO_GEN} ${DIR_SRC_SSH} ${DIR_DST_SSH}/BKUP_GEN_${YYYYMMDD_hhmmss}
RC=$?
# return code check and logging
if [ "${RC}" = "0" ] ; then
/usr/bin/logger "INFO: rsync script diff success ${DIR_SRC_SSH} ${DIR_DST_SSH}"
else
/usr/bin/logger "ERROR: rsync script diff ${DIR_SRC_SSH} ${DIR_DST_SSH} return code: ${RC}"
exit 1
fi
else
# run rsync full (first time only)
/usr/bin/rsync -ar --delete ${DIR_SRC_SSH} ${DIR_DST_SSH}/BKUP_GEN_${YYYYMMDD_hhmmss}
RC=$?
# return code check and logging
if [ "${RC}" = "0" ] ; then
/usr/bin/logger "INFO: rsync script success ${DIR_SRC_SSH} ${DIR_DST_SSH}"
else
/usr/bin/logger "ERROR: rsync script ${DIR_SRC_SSH} ${DIR_DST_SSH} return code: ${RC}"
exit 1
fi
fi
exit 0
【使用方法】
①viでシェルスクリプト作成
viエディタでファイルを新規作成して、上記「rsyncgen2.sh」の内容を貼り付けます。
$ vi rsyncgen2.sh
下記を環境にあわせて編集し、保存します。
SSH経由の場合は、「DIR_SRC_USER、DIR_SRC_IP」または「DIR_DST_USER、DIR_DST_IP」も編集してください。※両方ともリモートにすることはできません
・DIR_SRC=<バックアップ元ディレクトリ>
・DIR_DST=<バックアップ先ディレクトリ>
・BKUP_GEN=<数字>
②パーミッションを変更
$ chmod 700 rsyncgen2.sh
③スクリプトを実行して動作確認
詳細な動作確認をする場合
「/usr/bin/bash -x rsyncgen.sh」で実行できます。
スクリプト実行前に、スクリプトを動作確認用に編集すると安全です。
rsyncはオプションが多数ありますので、必要に応じてスクリプト処理を変更ください。
「/usr/bin/rsync -ar --delete ${SRC} ${DST}
」
→「/usr/bin/rsync -arhvn --delete ${SRC} ${DST}
」など。
(-nはDRY RUNで実際には実行せず動作確認できます)
また、負荷に応じて「nice」コマンドや「ionice」コマンドの利用もご検討ください。
通常実行は下記です。
$ ./rsyncgen2.sh
④CRON登録
「crontab -l」で確認、「crontab -e」で編集します。編集時の操作方法はviのエディタと同じです。
「<分> <時> <日> <月> <曜日> <シェルスクリプトのフルパス> > /dev/null 2>&1」
例)
45 22 * * * /root/backup/rsyncgen2.sh > /dev/null 2>&1
【注意点】
※rsyncは初回のみフルバックアップを使用、それ以外は差分バックアップを使用しています
※バックアップはバックアップ先ディレクトリ配下の「BKUP_GEN_YYYYMMDD_hhmmss」(YYYYMMDD_hhmmssは日時)に格納されます
※loggerコマンドで/var/log/messageにログを出力しています