rsyncで世代管理(1)

【概要】
rsyncで世代バックアップを取得するスクリプトです。
シェルスクリプト(bash)で、
CRONに登録することを想定しています。
手動実行も可能です。
※スクリプトを使用する場合は、充分にテストしてからご利用ください

バックアップ元からバックアップ先にrsyncします。
バックアップ元または、バックアップ先をリモート(SSH接続)とすることもできます。
※バックアップ先と、バックアップ元の両方をリモートにすることはできません。
バックアップ先で世代管理を行います。

世代管理処理の流れとしては下記になります。(2022年4月に処理を更新しました)
◆3世代の例
 1回目:バックアップ先に世代1ディレクトリを作成してrsync
 2回目:バックアップ先に世代2ディレクトリを作成してrsync
 3回目:バックアップ先に世代3ディレクトリを作成してrsync
 4回目:バックアップ先の世代1ディレクトリを新規の世代3にリネームしてrsync
 ※リネームはrsyncの負荷を減らすことを目的にしています。
  リネームするため、一時的に1世代減るのでご注意ください。

バックアップ先をリモートにする場合は、スクリプトの関数をSSHコマンド経由で渡して実行します。
(typeset -f)

【動作確認した環境】
Centos7.9

【前提条件】
・SSHを使用する場合はSSH公開鍵認証でバックアップ元にパスワードなしで接続可能なこと
(下記の【その他】参照)
・SSHを使用する場合は接続元と接続先の両方にrsyncがインストールされていること
(下記の【その他】参照)

【rsyncgen.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_FULL=<数字>

################################################################################

# variable initialization
RC=0
DIR_DST_SSH_FLG=0
DIR_SRC_SSH=""
DIR_DST_SSH=""
YYYYMMDD_hhmmss=$(date '+%Y%m%d_%H%M%S')

# backup generation check
echo "${BKUP_GEN_FULL}" | grep -q "^[0-9]\+$"
RC=$?
if [ "${RC}" != "0" ] || [ ${BKUP_GEN_FULL} -le 0 ] ; then
  /usr/bin/logger "ERROR: rsync script NUM_SRC variable error: ${BKUP_GEN_FULL}"
  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_FULL=0
  NUM_DEL_FULL=0
  FNC_RC=0
  
  FNC_DIR_DST=$1
  FNC_BKUP_GEN_FULL=$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

  ls -1 ${FNC_DIR_DST} | grep "^BKUP_GEN_FULL_" > /dev/null 2>&1
  FNC_RC=$?
  if [ "${FNC_RC}" = "0" ] ; then
    cd ${FNC_DIR_DST}
    NUM_DST_FULL=$(ls -1d ${FNC_DIR_DST}/BKUP_GEN_FULL_* | sort -r | wc -l)

    if [ ${NUM_DST_FULL} -gt 0 ] ; then
      # delete over 1 more generation
      if [ ${NUM_DST_FULL} -gt ${FNC_BKUP_GEN_FULL} ] ; then
        NUM_DEL_FULL=$(expr ${NUM_DST_FULL} - ${FNC_BKUP_GEN_FULL})
        ls -1d ${FNC_DIR_DST}/BKUP_GEN_FULL_* | sort -r | tail -n ${NUM_DEL_FULL} | xargs rm -rf
        FNC_RC=$?
        if [ "${FNC_RC}" != "0" ] ; then
          /usr/bin/logger "ERROR: rsync script rm command return code: ${FNC_RC}"
          exit 1
        fi
      fi
      
      # rename over oldest generation(reduce rsync diff)
      if [ ${NUM_DST_FULL} -ge ${FNC_BKUP_GEN_FULL} ] ; then
        NUM_DEL_FULL=$(expr ${NUM_DST_FULL} - ${FNC_BKUP_GEN_FULL})
        LAST_GEN_FULL=$(ls -1d ${FNC_DIR_DST}/BKUP_GEN_FULL_* | sort -r | tail -1)
        mv ${LAST_GEN_FULL} ${FNC_DIR_DST}/BKUP_GEN_FULL_${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_FULL_${FNC_YYYYMMDD_hhmmss} ] ; then
          mkdir -p ${FNC_DIR_DST}/BKUP_GEN_FULL_${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_FULL_${FNC_YYYYMMDD_hhmmss} ] ; then
      mkdir -p ${FNC_DIR_DST}/BKUP_GEN_FULL_${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_FULL} ${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_FULL} ${YYYYMMDD_hhmmss}
  RC=$?
  if [ "${RC}" != "0" ] ; then
    /usr/bin/logger "ERROR: rsync script function dst_gen_mng: local run"
    exit 1
  fi
fi


# run rsync
/usr/bin/rsync -ar --delete ${DIR_SRC_SSH} ${DIR_DST_SSH}/BKUP_GEN_FULL_${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 rsync failed. ${DIR_SRC_SSH} ${DIR_DST_SSH} return code: ${RC}"
  exit 1
fi


exit 0

【使用方法】
①viでシェルスクリプト作成
 viエディタでファイルを新規作成して、上記「rsyncgen.sh」の内容を貼り付けます。
 $ vi rsyncgen.sh
 下記を環境にあわせて編集し、保存します。SSH経由の場合は、「DIR_SRC_USER、DIR_SRC_IP」または「DIR_DST_USER、DIR_DST_IP」も編集してください。※両方ともリモートにすることはできません
 ・DIR_SRC=<バックアップ元ディレクトリ>
 ・DIR_DST=<バックアップ先ディレクトリ>
 ・BKUP_GEN=<数字>
②パーミッションを変更
 $ chmod 700 rsyncgen.sh
③スクリプトを実行して動作確認
 詳細な動作確認をする場合
 「/usr/bin/bash -x rsyncgen.sh」で実行できます。
 スクリプト実行前に、スクリプトを動作確認用に編集すると安全です。
 rsyncはオプションが多数ありますので、必要に応じてスクリプト処理を変更ください。
  「/usr/bin/rsync -a --delete ${SRC} ${DST}
 →「/usr/bin/rsync -ahvn --delete ${SRC} ${DST}」など。
 (-nはDRY RUNで実際には実行せず動作確認できます)
 通常実行は下記です。
 $ ./rsyncgen.sh
負荷に応じて、「nice」コマンドや「ionice」コマンドの利用もご検討ください。
④CRON登録
 「crontab -l」で確認、「crontab -e」で編集します。編集時の操作方法はviのエディタと同じです。
 「<分> <時> <日> <月> <曜日> <シェルスクリプトのフルパス> > /dev/null 2>&1」
 例)
 45 22 * * * /root/backup/rsyncgen.sh > /dev/null 2>&1

【注意点】
※rsyncはフルバックアップを使用しています。
※バックアップはバックアップ先ディレクトリ配下の「BKUP_GEN_FULL_YYYYMMDD_hhmmss」(YYYYMMDD_hhmmssは日時)に格納されます
※loggerコマンドで/var/log/messageにログを出力しています

【その他】
◆rsyncのインストール
 Centosに新規インストールの場合、rootユーザにて「yum install rsync」コマンドで可能です。

◆SSH公開鍵接続の設定例
 ・接続元
  ①接続用ユーザでログインまたは「su <ユーザ名>」コマンドでスイッチ
  ②ユーザのホームディレクトリへ移動
   $ cd
  ③鍵確認
   (「id_rsa」、「id_rsa.pub」ファイルが無いこと。ある場合は⑤へ)
   (「.ssh」ディレクトリがない場合はコマンド「mkdir .ssh」→「chmod 700 .ssh」)
   $ ls -l .ssh
  ④鍵作成
   (確認事項は格納ディレクトリとパスワード。全部Enterキー押下でもOKです)
   $ ssh-keygen -t rsa
  ⑤公開鍵id_rsa.pubの内容を表示
   (メモ帳等にコピペして記録)
   $ cat .ssh/id_rsa.pub
  ⑥鍵ファイルのパーミッションを変更
   $ chmod 600 .ssh/id_rsa
   $ chmod 600 .ssh/id_rsa.pub
  
 ・接続先
  ①接続許可ユーザでログインまたはsuでスイッチ
  ②ユーザのホームディレクトリへ移動
   $ cd
  ③鍵確認
   (「.ssh」ディレクトリがない場合はコマンド「mkdir .ssh」→「chmod 700 .ssh」)
   $ ls -l .ssh
  ④鍵接続許可ファイル作成
   (「authorized_keys」ファイルに公開鍵id_rsa.pubの内容を記載。既存で内容がある場合は追記)
   $ vi .ssh/authorized_keys
  ⑤鍵接続許可ファイルのパーミッションを変更
   $ chmod 600 .ssh/authorized_keys
  
 ・接続元
  ①接続用ユーザでログインまたは「su <ユーザ名>」コマンドでスイッチ
  ②SSH接続
   (初回接続時はknown_hostsファイルに登録するフィンガープリントを確認されるので
   「yes」を入力してEnterキー)
   $ ssh <接続先ユーザ>@<接続先IPアドレス>