ベンチャー流サーバ構築のススメ(同期ツール編)
ダイエット中で炭水化物の量が気になる尾藤正人です。
前回のエントリベンチャー流サーバ構築のススメ(ソフトウェア編)では、主にOS周りのことについて書きました。複数台のサーバを管理するのに重要なのは極力構成を同じにすることです。そうすることで管理コストが大幅に下がります。
以前Klabさんのサーバ管理者向け無精のすすめ ~ちょっと便利なツールの紹介~というエントリで同期ツールの紹介がありましたが、ウノウでも同じような感じの独自ツールを作って同期をとっています。今回はこの同期ツールの紹介をしたいと思います。
僕が shell scripter ということもあってスクリプトは全て sh で。zsh の特殊な記法が使いたかったので zsh で書いています。
凡例
全てのコマンドは最後に対象とするホスト名を指定します。all というは特殊な指定で全てのサーバに対してリクエストが送られます。
共通関数
全てのスクリプトから読み込まれる共通関数です。
function absdirname()
{
local dir=`dirname $1`
if [ "x$dir" = "x." ]; then
dir=$PWD
elif ! echo $dir|grep '^/' > /dev/null; then
dir=$PWD/$dir
fi
cd $dir
echo $PWD
}
function absfilename()
{
local dir=`absdirname $1`
if [ "x$dir" = "x/" ]; then
dir=""
fi
echo $dir/`basename $1`
}
function get_servers()
{
local servers i=1 max=12
while [ $i -le $max ]; do
servers=(${servers[@]} "s$i");
i=`expr $i + 1`
done
echo ${servers[@]};
}
dir_diff.sh directory host
ディレクトリ以下にあるファイルの diff を表示します。zsh の "<()" という記法を使ってます。これを使うと標準出力をファイルのように扱えて直接 diff に渡せるので便利です。
#!/bin/zsh
source `dirname $0`/sh_functions
PROGRAM_NAME=`basename $0`
function usage()
{
echo Usage: $PROGRAM_NAME directory host
exit $1
}
function _dir_diff()
{
if [ -z "$1" -o -z "$2" ]; then
echo "Lack of arguments" 1>&2
return 1
fi
local source=`absfilename $1`
diff -u <(ssh root@$2 find $source|sort) <(sudo find $source|sort)|grep '^\(+\|-\)'
}
# check arguments
if [ -z "$1" -o -z "$2" ]; then
usage 1
fi
if [ "x$2" != "xall" ]; then
_dir_diff $1 $2
exit $?
fi
for host in `get_servers`; do
echo ++++++++++++++++++++++++++++++++++++++
echo + diff from $host
echo ++++++++++++++++++++++++++++++++++++++
_dir_diff $1 $host
echo
done
rpm_diff.sh host
rpm パッケージの diff を表示します。サーバ間でパッケージの過不足がないかどうか確認するのに使います。
#!/bin/zsh
source `dirname $0`/sh_functions
PROGRAM_NAME=`basename $0`
function usage()
{
echo Usage: $PROGRAM_NAME host
exit $1
}
function _rpm_diff()
{
if [ -z "$1" ]; then
echo "You must specify a host name" 1>&2
return 1
fi
diff -u <(ssh root@$1 rpm -qa|sort) <(rpm -qa|sort)|grep '^\(+\|-\)'
}
# check arguments
if [ -z "$1" ]; then
usage 1
fi
if [ "x$1" != "xall" ]; then
_rpm_diff $1
exit $?
fi
for host in `get_servers`; do
echo ++++++++++++++++++++++++++++++++++++++
echo + diff from $host
echo ++++++++++++++++++++++++++++++++++++++
_rpm_diff $host
echo
done
xdiff.sh source host
ファイルの diff をとります。設定ファイルがちゃんと同期されてるかどうか確認したりするのに便利です。
#!/bin/zsh
source `dirname $0`/sh_functions
PROGRAM_NAME=`basename $0`
function usage()
{
echo Usage: $PROGRAM_NAME source host
exit $1
}
# check arguments
if [ -z "$1" -o -z "$2" ]; then
usage 1
fi
if [ "x$2" != "xall" ]; then
_xdiff $1 $2
exit $?
fi
for host in `get_servers`; do
echo ++++++++++++++++++++++++++++++++++++++
echo + diff from $host
echo ++++++++++++++++++++++++++++++++++++++
_xdiff $1 $host
echo
done
xexec.sh command [options ...] host
host で指定されたサーバでコマンドを実行します。ライブラリが更新されたときに一気に全サーバで ldconfig 実行したりとか、他にもいろいろ使えます。
#!/bin/zsh
source `dirname $0`/sh_functions
PROGRAM_NAME=`basename $0`
function usage()
{
echo "Usage: $PROGRAM_NAME command [options ...] host"
exit $1
}
function _xexec()
{
if [ -z "$1" -o -z "$2" ]; then
echo "Lack of arguments" 1>&2
return 1
fi
ssh root@$2 $1
}
# check arguments
if [ -z "$1" -o -z "$2" ]; then
usage 1
fi
host=
command=
while [ -n "$1" ]; do
command="$command $host"
host=$1
shift
done
if [ "x$host" != "xall" ]; then
_xexec $command $host
exit $?
fi
for host in `get_servers`; do
echo ++++++++++++++++++++++++++++++++++++++
echo + execute $command on $host
echo ++++++++++++++++++++++++++++++++++++++
_xexec $command $host
if [ $? -ne 0 ]; then
echo "Failed to execute on $host" 1>&2
exit $?
fi
echo
done
xrsync.sh source host
その名の通り rsync します。ファイルを一斉にシンクロするのに便利です。
#!/bin/zsh
source `dirname $0`/sh_functions
PROGRAM_NAME=`basename $0`
function usage()
{
echo Usage: $PROGRAM_NAME source host
exit $1
}
function _xrsync()
{
if [ -z "$1" -o -z "$2" ]; then
echo "Lack of arguments" 1>&2
return 1
fi
local opts source host
while [ -n "$1" ]; do
opts="$opts $source"
source=$host
host=$1
shift
done
local source=`absfilename $source`
local destination=`dirname $source`
local opts="-auvz -e ssh $opts"
eval sudo rsync $opts $source root@$host:$destination
}
# check arguments
if [ -z "$1" -o -z "$2" ]; then
usage 1
fi
host=
arguments=
while [ -n "$1" ]; do
arguments=(${arguments[@]} $host)
host=$1
shift
done
if [ "x$host" != "xall" ]; then
_xrsync ${arguments[@]} $host
exit $?
fi
for host in `get_servers`; do
echo ++++++++++++++++++++++++++++++++++++++
echo + rsync to $host
echo ++++++++++++++++++++++++++++++++++++++
_xrsync ${arguments[@]} $host
if [ $? -ne 0 ]; then
echo "Failed to execute on $host" 1>&2
exit $?
fi
echo
done
xscp.sh source [source ...] host
こっちは scp 版。ちょっとしたファイルを同期したときに便利。
#!/bin/zsh
source `dirname $0`/sh_functions
PROGRAM_NAME=`basename $0`
function usage()
{
echo "Usage: $PROGRAM_NAME source [source ...] host"
exit $1
}
function _xscp()
{
if [ -z "$1" -o -z "$2" ]; then
echo "Lack of arguments" 1>&2
return 1
fi
local sources host
while [ -n "$1" ]; do
sources=(${sources[@]} $host)
host=$1
shift
done
local opts="-pr"
local source destination
for source in ${sources[@]}; do
source=`absfilename $source`
destination=`dirname $source`
eval sudo scp $opts $source root@$host:$destination
done
}
# check arguments
if [ -z "$1" -o -z "$2" ]; then
usage 1
fi
host=
sources=
while [ -n "$1" ]; do
sources=(${sources[@]} $host)
host=$1
shift
done
if [ "x$host" != "xall" ]; then
_xscp ${sources[@]} $host
exit $?
fi
for host in `get_servers`; do
echo ++++++++++++++++++++++++++++++++++++++
echo + scp to $host
echo ++++++++++++++++++++++++++++++++++++++
_xscp ${sources[@]} $host
if [ $? -ne 0 ]; then
echo "Failed to execute on $host" 1>&2
exit $?
fi
echo
done
まとめ
サーバ間の同期を取るためにウノウで導入しているツール群を紹介しました。こういう細かいツールを最初に作っておくと、作業が効率よく進み非常に楽になります。ぜひ、みなさんの環境でも導入してみてはいかがでしょうか。
何回か続けてきたベンチャー流サーバ構築のススメですが、おかげさまでみなさんから多くのトラックバック、コメント、ブックマークをいただきました。今回で大体サーバ構築に関するトピックは網羅したかと思います。何かありましたら、トラックバックなりコメントなりをいただければ幸いです。

コメント
xrsync.sh "-e 'touch /tmp/hoge --'" /etc/passwd remotehost
とかすると、root権限でtouchが実行できちゃいますよー
投稿者: hirose31 | 2006年8月 7日 15:55
sudo で root権限は保護してますが、もっと安全な方法ってありますか?
投稿者: masato | 2006年8月 7日 18:07
これってどういう意味でしょうか?>「sudo で root権限は保護してます」
投稿者: hirose31 | 2006年8月 7日 20:10
-e オプションで指定したプログラムはローカルで実行されるので、ローカルのroot権限が奪取されることを危惧されているということですよね。
root権限の取得は全てsudo経由で行っており、特定ユーザ以外はrootでコマンドは実行できないようになっています。
rsync には suid ビットはたっていないので、一般ユーザが rsync 経由で root権限を取得できることは基本的にないと思います。
投稿者: masato | 2006年8月 8日 12:36
なんかちょっと誤解されているような気がします。
おっしゃる通り、rsyncの-eで指定したコマンドは、ローカルで実行され、*それはrsyncの実効ユーザ権限で実行*されます。
xrsync.shのコードでは、
eval sudo rsync ...
しているので、rsyncはroot権限でされますよね?
ということは-eのコマンドはrootで実行されますよね?
最初のコメントのように実行すると、/tmp/hogeのownerはrootになるはずです。
もっと強烈な例を挙げてみます。
$ cat /tmp/r00tsh.c
int main(int argc, char ** argv) {
system("/bin/sh");
return 0;
}
$ gcc -o /tmp/r00tsh /tmp/r00tsh.c
$ cat /tmp/setu+s
#!/bin/sh
chown root:root $1
chmod u+s $1
$ xrsync.sh "-e '/tmp/setu+s /tmp/r00tsh'" /tekitou/na/file remotehost
$ ls l /tmp/r00tsh
-r-sr-xr-x 1 root root 11356 Aug 8 15:15 /tmp/r00tsh
$ /tmp/r00tsh
sh-3.00# id
uid=2050(hirose31) gid=2050(hirose31) euid=0(root) groups=0(root),2050(hirose31)
sh-3.00# echo bad > /how_are_you
sh-3.00# cat /how_are_you
bad
rootになれるシェルのできあがりです。;p)
投稿者: hirose31 | 2006年8月 8日 15:49
sudo で root 権限が取得できるアカウントであれば、わざわざ rsync 経由で実行する必要は全然なくて、直接コマンドを実行すればいいだけの話ではないですか?
なぜ、rsync 経由での実行に言及する必要があるんでしょうか
root 権限がとれるかどうかは rsync は全然関係なくて、sudo の設定がどうなっているかが問題でしょう
そのための sudo ですよね
投稿者: masato | 2006年8月 8日 16:34
あ、もしかして、「特定ユーザ以外はrootでコマンドは実行できないように」というのは、「特定ユーザは(rsyncに限らず)全てのコマンドをroot権限で実行できる」ようにsudoの設定がされている、というこでしょうか?
だとしたら、てっきり、「特定ユーザは特定のコマンド(rsync)のみをroot権限で実行できる」ようにsudoの設定がされている、と思い込んでいたので、話が噛み合わないわけですね…
# (もちろん、特定のコマンドとはいえroot権限でのrsyncの実行を許してしまったら全然ダメですけど)
投稿者: hirose31 | 2006年8月 8日 17:17
おお、そういうことでしたか。
上記ツールは root 権限を持つ管理者が使用するコマンドなので、特定ユーザに開放したりとかはしてません。
確かに特定ユーザに rsync だけの root 権限での実行を許可した場合、hirose31 さんのおっしゃるような問題がありますね。
これは
local opts="-auvz -e ssh $opts"
を
local opts="-auvz $opts -e ssh"
にすると -e オプションが上書きされなくなって大丈夫っぽい。
投稿者: masato | 2006年8月 8日 17:54
ですです。
おっしゃる通り、もし、sudoでrsyncだけを許可する場合(これはよくないです。任意のファイルが上書きでちゃうので)、もしくは、(xrsync.shのような)内部でrsyncする特定のコマンドだけを許可する場合は、rsyncの末尾で「-e ssh」と書かないとまずいことが起こる可能性があります。
rsyncの-eは、複数ある場合は最後の-eが有効で、DESTの後にも書けるので、
eval sudo rsync $opts $source root@$host:$destination
は
eval sudo rsync $opts $source root@$host:$destination -e ssh
と書くのが確実ですね。($sourceや$desticationに-eを注入される危険性がないとはいえないので)
蛇足ですが、コピー元とコピー先のパスがいつも同じならば、rsyncの-Rオプションを使って
eval sudo rsync $opts -R $source root@$host:/ -e ssh
とすれば、ディレクトリの異なる複数のsourceのコピー (xrsync.sh /dir1/foo /dir2/bar desthost とか) もできるようになりますね。
# ($sourceを組み立てるところにもちょっと修正が必要ですが)
ところで、特定ユーザにsudoによる全コマンドのroot権限での実行を許可していると、そのユーザの権限が奪取された場合、sudoのpasswd_timeoutが切れるまでは攻撃者はroot権限で好き放題できてしまうような気がするのですがどうなんでしょうか。
投稿者: hirose31 | 2006年8月 8日 18:37
-R オプションは知りませんでした。これは便利ですね。参考になります。
passwd_timeout って sudo でパスワード打った後にしばらくパスワードを入力する必要なくなる時間のタイムアウトのことでしょうか?
経験則ですが、あれって端末毎に制御されてるみたいなので、パスワード無しでいきなり root 権限使われまくりっていうことはないかと思います。
まあ、そもそも一般ユーザであれクラックされたら再インストールしないといけませんが。
投稿者: masato | 2006年8月 8日 18:58
端末毎ではないようですよ。(Debian sargeのsudo 1.6.8p7)
投稿者: hirose31 | 2006年8月 8日 19:27
僕の手元だとちゃんと端末毎にタイムアウトの時間が制御されています。(Fedora Core 3, sudo 1.6.7p5)
/var/run/sudo/username
に端末毎のファイルが作成されていて、ファイルのタイムスタンプで最後に sudo を実行した時間を記録しているようです。
投稿者: masato | 2006年8月 8日 19:41
ながながとおつきあいいただいてありがとうございましたー m(_ _)m
ブログ、今後も楽しみにしています!
投稿者: hirose31 | 2006年8月 9日 01:18
こちらこそありがとうございました!!
投稿者: masato | 2006年8月 9日 10:06
それはともかく、参考になりました!
シェル系がものすごく弱いんで、
どこかで勉強するか何か考えないといけないです。
投稿者: o24 | 2006年8月11日 14:41
ありがとうございます。
シェルスクリプトは慣れるとかなり便利です。
ちょっとしたツールが簡単にかけるので、UNIX の管理をするならぜひ身につけておきたいスキルの一つですね。
投稿者: masato | 2006年8月11日 15:12
function get_servers() ってちゃんと動きますか?
「s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12」が返るだけのような。
投稿者: shigeno | 2006年8月15日 15:32
はい、その通りです。そういう風に意図して作ってます。
スクリプトを見ていただけると分かりますが、for と組み合わせて使っています。
投稿者: masato | 2006年8月15日 15:56
ああ、実際のホスト名が s1 なんですね。納得です。
投稿者: shigeno | 2006年8月15日 17:07
大変参考になります.
xdiff.shを実行してみたところ,_xdiffが無いと言われました.
s1# xdiff /etc/hosts s2
xdiff:17: command not found: _xdiff
どのようにすればいいのか教えていただけませんでしょうか.
投稿者: eto | 2006年10月27日 22:08