wiki:LinuxBackupScript

Linuxでバックアップするスクリプトの例

最近はバックアップがちょっとテーマです。 過去に遡れるファイルシステム「NILFS」をいじったり、 「cryptsetup」と「LUKS」でディスクの暗号化をやってみたり。 バックアップ方法やソフトの比較もしました。

個人的な結論として、現在のバックアップ方法は、「cryptsetup」と「LUKS」で暗号化したパーティションの上に「NILFS」ファイルシステムを載せて、「rsync」で転送するという方法に落ち着いています。 このページでは、rsyncでバックアップするために使用している簡単なスクリプトと設定をお見せします。 単に「cronrsyncすればいいじゃない?」というわけにはいかないのです。

cronでrsyncすれば?

一番単純には、cronの設定にrsyncコマンドを書けばいいような気がします。たとえば「/ec/cron.d/backup」にこんな感じ。

0 3 * * * root rsync -a / backupserver:/backup/server1

これで、深夜3時に自動的にrsyncコマンドが動きます。 動かすユーザは「root」にしました。 スーパーユーザ以外だと、いろいろと読めないファイルが有ってバックアップになりません。

問題があります。 cronで動かすので、手動でパスワードを入力してやることはできません。 かといって、昔のように「rsh」で「.rhosts」でIPアドレスだけで相手を信用できるような時代ではありません。 最低でも、SSHで公開鍵を使って認証することは必要でしょう。 でも、その秘密鍵にパスフレーズを付けないで運用するのは、ちょっと危険すぎます。 さあ、どうしましょう。

バックアップ先が一般ユーザの権限なら?

バックアップ元をスーパーユーザで動かすのは仕方ないとしても、バックアップ先はスーパーユーザでなくてもいいかも?  そうすれば、秘密鍵をパスフレーズで守らなくても、リスクは限定されるからいいかもしれません。

いや、残念ながら、rsyncを使っている限り、そういうわけにはいかないのです。 ファイルのオーナやグループ、パーミッション、デバイスファイルなどもそのままバックアップしておきたいのです。 rsyncは、これら情報をそのままバックアップ先のファイルシステムに反映することしかできません。 さらに、これを反映するためには、バックアップ先のプロセスはスーパーユーザとして動作することが必要なのです。

もしも、rsyncがこれらの情報を別のファイルとして保存してくれるならば、これは解決してしまうのですが。 ちょっと残念です。まあ、rsyncはバックアップのために作られた物では無いから仕方ありません。

SSHで動かすコマンドを制限できるはず

SSHのサーバ側(sshd)の機能で、動作を許可するコマンドを制限することができます。いいアイデアかもしれません。 各ユーザの「authorized_keys」ファイルに「command=」というオプションを指定すれば、使用された鍵ごとに、強制的にコマンドを実行することができます。

command="cat /etc/motd" ssh-rsa AAAAB3NzaC1yc2EAAAA...

こうすれば、たとえ「ssh backupserver rm -rf /」なんてやろうとも、「/etc/motd」の内容が表示されるだけです。 rsyncのトランスポートとしてSSHを使う場合、相手のマシンでは「rsync --server …」というようなコマンドが実行されます。 このコマンドを「command=」オプションに指定すればいいんじゃない?というのがこのアイデアです。

command="rsync --server -vlHogDtprxze.iL --delete . /backup/server1" ssh-rsa AAAAB3NzaC1yc2EAAAA...

この話はよくネットで見かけます。 この人とか この人とか この人とか、たくさん見かけます。

でも、でも、これじゃ困るんです。

先ほど、「command=オプションで強制的にコマンドを実行する」と書きました。 「実行するコマンドを制限する」ではないのです。 つまり、ある鍵を使用したら、必ずそのコマンドが実行されてしまいます。 rsync以外のことはできません。「df」でディスクの残量を見たりしたいじゃないですか。 そういうことが全くできません。

さらに、rsyncのサーバ側(バックアップ先)の引数はちゃんとドキュメント化されていません。 クライアント側(バックアップ元)でのrsyncに与えた引数によって、サーバ側の引数が変わるのです。 上記のこの人たちは皆、「rsyncを実行してみて、その最中にpsで調べて」なんて言っています。 アンドキュメンテッドな物を、クリティカルなシステムに使うのは、ちょいと自分が許せません。 なによりも、クライアント側のrsyncの引数を変えたら、動かなくなってしまうのです。 また、rsyncのバージョンが変わったら動かなくなってしまうかもしれません。

解決

専用のシェルを書くことにしました。 以前に書いた、シェルを書く話じゃないですよ。 rsyncのサーバ側の適切なコマンド行と、ついでにdfコマンドだけを実行できる、コマンドシェルを書きます。 なーんて言っても、「C言語でインタプリタを書いて…」なんてたいそうな話ではないですよ。

sh -c "echo tako"

というように「-c」オプションで指定されたコマンドを実行するプログラムを作ればいいのです。 SSHはサーバ側で、指定されたコマンドをこんなふうに実行するのです。 この程度なら、シェルスクリプトで書きましょう。 シェルスクリプトでシェルを書く、って話です。 ややこしい。

こうやって作ったシェルを、「/etc/passwd」に指定します。 もちろん、「root」ユーザのシェルとして指定してしまったら、まずいことになってしまいます。 スーパーユーザ権限を持つ別のユーザ、たとえば「backup」を作って、そのシェルとして指定します。

backup:x:0:0:Backup Superuser:/root/backup:/root/backup/shell

こんな感じ。この例では、「/root/backup/shell」が、シェルスクリプトで書いたシェルです。 あとは、このユーザのauthorized_keysファイルに、バックアップ元のマシンのrootユーザの公開鍵を書きます。

これで目的は達成されるのですが、もうひとつ要求を付け加えました。 複数のバックアップ元からのバックアップを、1台のバックアップ先で受け入れたいのです。 一番簡単には、「backup2」とかいうユーザを増やして、「shell2」なんていうシェルを作ればできちゃいます。 でも、これじゃエレガントじゃない。 一つのユーザ、一つのシェルで、複数のバックアップ元に対応したい。

authorized_keysファイルでは、「command=」オプションの他に「environment=」というオプションも有ります。 これを使って環境変数を変えれば、シェルの中で動作を変えることができるかもしれない。 …と淡い期待を抱きましたが、この機能はセキュリティを低下させるリスクがあるため、通常はオフにされています。 「/etc/ssh/ssd_config」の設定を変えればこの機能をオンにすることができますが、これではリスクが全ユーザに波及してしまいます。

接続元のIPアドレスからホスト名を得れば…とも考えますが、バックアップ元が動的IPアドレスの場合には困ってしまいます。

だいぶ悩んで発見した方法は、「command=」オプションに「server1」とか「server2」というようなバックアップ元のマシン名を指定することです。 いや、「server1はコマンド名じゃないでしょ?」と思いますよね。 だいじょうぶ。僕らはシェルを作ったのですから。 そのシェルで「server1」なんて文字列をコマンドだと思って処理すればいいだけのことです。 本来実行を要求されたコマンドは、「SSH_ORIGINAL_COMMAND」という環境変数で得られます。

これで解決できますね。

設定例

バックアップ元

/etc/cron.d/backup
0 3 * * * root /bin/sh /root/backup/job
/root/backup/job
#!/bin/sh
ME=server1
DST=backupserver

/bin/date
nice /usr/local/bin/rsync -avxzH --rsh=/usr/bin/ssh --stats --inplace --delete / backup@$DST:/backup/$ME
/usr/bin/ssh backup@$DST df
/bin/date
  • -v」や「--stats」で出力される内容、「date」や「df」の結果は、メールで届くはず。
  • -a」は、バックアップに適切な設定。
  • -x」で一つのファイルシステムに制限。
  • -z」は圧縮。
  • -H」は、ハードリンクの反映。
  • --delete」は、ファイルの削除をバックアップ先に反映するために必要。
  • --inplace」は、ファイルの中身の一部分を変更する場合に、目的のファイルに直接書き込んでしまうことを指示。 このオプションが無い場合、一時ファイルを作って完成してから置き換える。 ギガ単位の巨大なファイルをバックアップする場合は必須。
/root/.ssh/id_rsa
/root/.ssh/id_rsa.pub
rootユーザの鍵を作っておく。

バックアップ先

/backup
バックアップ先のディレクトリ。オーナはroot、モードは700。 この下にさらに、バックアップ元のマシン名のディレクトリを作っておく。
/etc/passwd
backup:x:0:0:Backup Superuser:/root/backup:/root/backup/shell
/etc/shadow
backup:*:13235:0:99999:7:::
/root/backup/.ssh/authorized_keys
command="server1",no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-rsa AAAAB3Nz……
/root/backup/shell
#!/bin/bash

# Copyright (c) 2008 by Accense Technology, Inc. All rights reserved.
# Licensed under the Creative Commons Attribution-Share Alike 3.0 license.
# <http://creativecommons.org/licenses/by-sa/3.0/>
# //sgk

# Config
RSYNC_CMD="/usr/local/bin/rsync"
MKCP_CMD="/usr/bin/mkcp"        # NILFSのコマンド。
DF_CMD="/bin/df"
BACKUP_TOPDIR="/backup"         # このディレクトリの下にサーバ名のディレクトリを作っておく。

# Startup
me="$0@`hostname`"

# Check
if [ "$#" != 2 -o "$1" != "-c" ]; then
  echo "$me: set 'command=\"source-label\"' in the 'authorized_keys' file."
  exit 1
fi
src="$2"
target="$BACKUP_TOPDIR/$src"
if [ ! -d "$target" ]; then
  echo "$me: Directory '$target' does not exist."
  exit 2
fi

if [ -z "$SSH_ORIGINAL_COMMAND" ]; then
  echo "$me: set 'command=\"source label\"' in the 'authorized_keys' file."
  exit 3
fi
set $SSH_ORIGINAL_COMMAND

# Main
case "$1" in
  rsync)
    if [ "$2" = "--server" ]; then
      shift
      # オプションだけをコピーする。
      while [ "$#" -gt 0 ]; do
        if expr -- "$1" : '[^-]' > /dev/null; then
          break
        fi
        av=(${av[*]} "$1")
        shift
      done
      nice "$RSYNC_CMD" ${av[*]} . "$target"
      if [ -x "$MKCP_CMD" ]; then
        "$MKCP_CMD" -s
      fi
    fi
    ;;

  df)
    "$DF_CMD" "$target"
    ;;

  *)
    echo "$me: $1: command not found"
    exit 4
    ;;
esac

rsyncコマンド

新しいバージョンほど、転送効率が高いようです。 バックアップ元とバックアップ先とで、バージョンが異なる場合、いずれか低い方のバージョンの方式で転送されます。  現在の最新バージョンは3.0.2のようです。 Linuxの各種ディストリビューションでは、未だに3.x系統を採用していないのが一般的なようです。 ちょっと気持ち悪いですが、自前ビルドで最新版を使うのがいいようです。

考察

実はまだちょっと、アンドキュメンテッドな事実を使っている箇所があります。まあ、しかたないでしょう。

セキュリティ上のリスクは、やっぱりゼロではありません。 特に、rootユーザの秘密鍵もバックアップによってバックアップ先にコピーされてしまうのが気になります。

バックアップ先で「fakeroot」を使うって手があるかもしれません。どうかなあ。

バックアップするときだけ暗号化パーティションのファイルシステムをマウントするようにしたいなあ。

(2008/4/13 - sgk) Licensed under the  Creative Commons Attribution-Share Alike 3.0 license.