{{tag>linux}} # rsync を使ってバックアップシステムを作る 要件はこんな感じで。 + ディスクをたくさん持っているファイルサーバーにバックアップサーバーの役割を持たせる + rsync over ssh で接続する + 方向はバックアップサーバーから rsync で接続して、バックアップ対象のファイルを pull してくる ## バックアップサーバー側の作り込み ### ssh 接続用の鍵ペアの作成 この鍵はパスなしで作ります。 ``` # ssh-keygen -t ed25519 Enter file in which to save the key (/root/.ssh/id_ed25519): /path/to/id_ed25519 Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /path/to/id_ed25519. Your public key has been saved in /path/to/id_ed25519.pub. : : ``` ### shell スクリプトを書く。 こんな感じですね……shell スクリプトはあまり得意じゃないので美しくないかもしれないですが、とりあえずは動きますw #!/bin/bash ################################################################################ # backup_remote.sh # create @17.04.17 ################################################################################ . /path/to/utils.sh ## # settings # LIST_DIR='/path/to/backup.d' LIST_EXT='.list' DEST_DIR='/path/to/dest' LOG_DIR='/var/log/rsync' SSH_USER='collector' SSH_PRIVKEY='/path/to/id_ed25519' RSYNC_CMD='/usr/bin/rsync' RSYNC_DEFOPTS='--delete --exclude lost+found' ## # check variables. # if [ ! -f "${RSYNC_CMD}" ] || [ ! -x "${RSYNC_CMD}" ]; then error "'${RSYNC_CMD} is not executable." exit 1 fi if [ ! -d "${LIST_DIR}" ] || [ ! -x "${LIST_DIR}" ]; then error "Cannot access to '${LIST_DIR}'." exit 1 fi if [ ! -f "${SSH_PRIVKEY}" ] || [ ! -r "${SSH_PRIVKEY}" ]; then error "Cannot access to '${SSH_PRIVKEY}'." exit 1 fi if [ ! -d "${DEST_DIR}" ] || [ ! -w ${DEST_DIR} ]; then error "Cannot access to '${DEST_DIR}'." exit 1 fi if [ ! -d "${LOG_DIR}" ] || [ ! -w ${LOG_DIR} ]; then error "Cannot access to '${LOG_DIR}'." exit 1 fi ## # check argument # if [ -z "$1" ]; then error "usage: ${0} " exit 1 fi HOST=${1} if [ "${HOST}" = 'local' ]; then error "Not acceptable hostname '${HOST}'." exit 1 fi FILE="${LIST_DIR}/${HOST}${LIST_EXT}" if [ ! -f "${FILE}" ] && [ ! -r "${FILE}" ]; then error "Cannot access to '${FILE}'." exit 1 fi ## # start main. # LOG_FILE="${LOG_DIR}/${HOST}.$(date +%Y-%m-%d-%H.%M.%S).log" RET=0 info "Backup start." info "Rsync log is ${LOG_FILE}" while read DIR OPTS; do # skip blank line. if [[ "${DIR}" =~ ^\s*$ ]]; then continue fi # skip comment line. if [[ "${DIR}" =~ ^\s*# ]]; then continue fi if [[ "${DIR}" =~ [^/]$ ]]; then warn "Directory path should ends with '/'." DIR="${DIR}/" fi SRC="${HOST}:${DIR}" DST="${DEST_DIR}/${HOST}${DIR}" # create directory tree /bin/mkdir -p ${DST} if [ $? -ne 0 ]; then error "Cannot create directory: '${DST}'." RET=2 continue fi info "Start sync from '${SRC}' to '${DST}'." ${RSYNC_CMD} -avvr \ --rsh "ssh -l ${SSH_USER} -i ${SSH_PRIVKEY} -o strictHostKeyChecking=no" \ --rsync-path="sudo rsync" \ ${RSYNC_DEFOPTS} ${OPTS} \ ${SRC} ${DST} \ >> ${LOG_FILE} 2>&1 if [ $? -ne 0 ]; then error "Failed to rsync from '${SRC}' to '${DST}': $!" RET=3 else info "Succeeded to rsync from '${SRC}' to '${DST}'." fi done < ${FILE} info "Backup finished." exit ${RET} utils.sh は、`info` やら `warn` やらの function を定義しているだけなので割愛。 に `local` を使えないようにしているのは、もう1つこのバックアップサーバー内でローカル rsync する shell スクリプトがありまして、そのリストファイルが 'local.list' で作ってあるからです。ちょっと書式が違うので……思いっきり俺環のはなしです。 で、この shell スクリプトはこんな風に使います。 ``` # ./backup.sh ``` `${LIST_DIR}` に設定したディレクトリには .list というテキストをこんな感じで置いておきます。 ``` /var/log/nginx/ --exclude=error_log --bwlimit=8192 /var/log/portage/ /var/log/hogefuga/ --bwlimit=8192 ``` `${IFS}` で区切って1番目にはバックアップを取得するディレクトリを。2番目以降は rsync のオプションです。指定できるのはディレクトリのみで、ファイルを指定することはできません。 バックアップサーバー側はこれで準備okですね。 ## バックアップ対象となるサーバー側の作り込み ここまでやる必要はないですね……っていう程度には作り込みます。 ### バックアップ専用ユーザーの作成 ssh で接続してきて、極力 rsync 以外のことはできないようなユーザーを作成します。 ``` useradd --create-home --uid 1999 --groups users --shell /bin/rbash --comment rsyncBackupCollector collector ``` `--uid` を指定しているのは、複数あるバックアップ対象のサーバーの全てで同じ uid を使うようにするためです。こうしておけば、以降の記事で作る /home/collector/ 以下の環境を tar で固めて展開できたりするので、多少ですが省力化になります。 この時点で `--shell` で rbash を指定しているので、これだけでもできることはかなり制限されているのですが……趣味とノリと勢いでもっともっとこのユーザー collector でできることを絞っていきます。 #### コマンドサーチパスの制限 collector ユーザー専用の bin ディレクトリを作成します。 ``` # mkdir /home/collector/bin ``` 作成した専用 bin に、collector ユーザーが使用できるコマンドのシンボリックリンクを作成します。 ``` # ln -sf /usr/bin/sudo /home/collector/bin/sudo # ln -sf /usr/bin/rsync /home/collector/bin/rsync # ls -l /home/collector/bin total 0 lrwxrwxrwx 1 root root 14 Apr 17 12:03 rsync -> /usr/bin/rsync lrwxrwxrwx 1 root root 13 Apr 17 12:03 sudo -> /usr/bin/sudo ``` /home/collector/.bashrc を編集して、コマンドサーチパスを作成した専用 bin ディレクトリに限定します。 ``` PATH=/home/collector/bin ``` ついでに .bashrc と .bash\_profile を collector 自身で変更できないように owner を変更してしまいます。 ``` # chown root:root /home/collector/.bashrc /home/collector/.bash_profile ``` ちなみに rbash では、環境変数 `${PATH}` はリードオンリーになっていて変更できません。 ``` # ssh -i /path/to/id_ed25519 collector@server 'PATH=/usr/bin' rbash: PATH: readonly variable ``` また `cd` は禁止されている上に、コマンドに `/` を含めることも許されないため、フルパスでコマンドを打つこともできません。 ``` # ssh -i /path/to/id_ed25519 collector@server 'cd /bin;ls' rbash: line 0: cd: restricted rbash: ls: command not found # ssh -i /path/to/id_ed25519 collector@server '/bin/ls' rbash: /bin/ls: restricted: cannot specify `/' in command names ``` #### ssh 接続の制限 /home/collector/.ssh/authorized_keys に、バックアップサーバーから接続するための公開鍵を追加して、色々な制限を加えます。 ``` from="192.168.ip.addr",environment="PATH=/home/collector/bin",no-agent-forwarding,no-port-forwarding,no-user-rc,no-X11-forwarding,no-pty ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKu83AydT6QgO7x/4VnuMSpTaaHvr6V8CZpsYXQQg7l+ collector ``` このへんの設定は `man sshd` に書いてありますね。`from=` で接続元を制限しています。 また `environment=`((sshd\_config で `PermitUserEnvironment yes` を設定しておく必要があります。))でコマンドサーチパスを限定しています。これは、通常の ssh ログインの場合は .bashrc の `PATH=/home/collector/bin` の設定が効くのですが、`ssh collector@host 'echo $PATH'` のように直接コマンド実行をする場合はログインシェルを取らないので .bashrc 等が適用されない(/etc/env.d/あたりで決定するデフォルトが適用される)ためです。 あと `no-` と付くものはとりあえず全部設定しておきます。 ちなみにこの時点で1度 no-pty を外して、ssh でログインしてみるのも良いかもしれません。結構絶望的になにもできない環境になっていますが……やっぱり完璧ではなくどこかに穴があるんでしょうねw ### sudoers の設定 collector ユーザーの権限では触れないファイル等もあるので、rsync は sudo コマンドを経由して root 権限で実行させます。そのために sudoers にこんな設定を入れます。 ``` collector ALL=(root) NOPASSWD:/home/collector/bin/rsync ``` ### 接続確認 ここまでできたら、バックアップサーバー側から秘密鍵を使って ssh 経由で sudo rsync が起動できるかどうかを確認してみます。 ``` # ssh -i /path/to/id_ed25519 collector@server 'sudo rsync --version' rsync version 3.1.2 protocol version 31 Copyright (C) 1996-2015 by Andrew Tridgell, Wayne Davison, and others. Web site: http://rsync.samba.org/ Capabilities: 64-bit files, 64-bit inums, 64-bit timestamps, 64-bit long ints, socketpairs, hardlinks, symlinks, IPv6, batchfiles, inplace, append, ACLs, xattrs, iconv, symtimes, prealloc rsync comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. See the GNU General Public Licence for details. ``` こんな感じで rsync のバージョン情報が表示されれば、バックアップ対象となるサーバーの設定は完了です。 ## 完成 というわけで、バックアップサーバー側で `/path/to/backup.sh ` を好きなだけ並べた shell スクリプトを作って、そいつをキックする backup.service やら backup.timer やらを作って完成です。うちの環境では、リモート rsync で引っ張ってきたログをさらにバックアップサーバー上で別のディスクに rsync するローカルバックアップ用の shell スクリプトと、さらにリモートとローカルのバックアップを順次実行、リターンコードを見て `logger` コマンドで syslog に投げるような上位 shell スクリプトを作って、そいつを timer 起動しています。 ……ムダに手間かけて作りこんじゃいました。