linux服务器集群重复批量操作脚本实现
http://blog.csdn.net/flyinmind/article/details/8074863
在服务器集群的维护中,经常会遇到同样的操作重复执行很多遍的情况,“登录服务器->做操作->退出”,继续下一个服务器。简单枯燥、容易出错、并且毫无成就感。
我在做push产品的过程中,见到有同事在这个简单重复的工作中,经常犯一些低级错误,心灰意冷。所以我花了一点时间将能自动化的过程全部自动化,操作人员只需做两件事:
1、记录所有服务器的IP、SSH端口、用户名、密码、登录提示符、主路径,记为:xx.srv文件;
每行一个服务器,以逗号分隔,比如:"192.168.9.1:22,opush,opush,>,/home/push/8080"
2、记录每个服务器上要做的重复操作,记为:yy.cmd文件。
每行一个命令,[]括起的部分表示在本地执行的,<>括起的是最开始执行一次,{}括起的部分表示在所有服务器都操作完之后,最后在本地执行一次的命令,比如:
[scp {USER}@{HOST}:/home/opush/logs/* ./rec]
ps aux|grep {USER}|grep catalina|grep startup|awk '{print \\\$2}' | xargs kill -9
cd /home/{USER}
rm -rf ./logs/*
./bin/startup.sh
{tar cfz logs.tar.gz ./rec/*}
上面例子中,先拷贝tomcat的日志到本地的rec目录,然后登陆到服务器上关闭tomcat、删除日志、启动tomcat,所有服务器都做一遍之后,退回到本机打包压缩日志文件。
上面例子中出现了{USER}、{HOST},这个类似于“宏”,在执行时会被替换为相应值,看当前在集群中的哪个服务器上执行,比如在192.168.9.1上执行,用户名是opush,则这里就会被替换成他们,还有:{PORT}、{PASSWORD}、{PATH}、{NO}几个宏,可以在命令中使用。
还有一点需要注意,<>,[],{}只能括住一行,不能多行,如果有多个命令,比如多行,并且每行一对括号。
最后一点,这两个文件如果行首是"#",则表示注释。
本工具的目录结构如下:
/--
|--conf/
|--exec
做好后,将这个两个文件放到conf目录下,xx.srv文件可以只需一份,在不同的操作中重复使用;每一类操作记录一个yy.cmd文件(比如上面的操作可以命名为getlogs_reset.cmd),在以后操作时只需运行:
./exec xx.srv yy.cmd
OK,所有操作都自动完成。
脚本的基本原理是使用awk读取配置文件,使用expect脚本来完成自动的交互,所以在你的跳板机上(只需跳板机安装就可以了)必须要有awk、expect;这个脚本用的是bash,所以也需要它,如果没有,可能要对exec做一点改动,比如适应ksh、csh等。如果你用的是suse,安装包中已自带了expect,使用yast安装就可以了,其他的linux也应该可以安装。
下面是脚本的源码,供参考,如果你做了什么改进,或发现了问题,欢迎发给我,一起来改进它。本来想起一个开源项目,因为这个太小了,也没有必要做的太通用,所以就放在这里,供大家参考吧。希望能将你从重复枯草的键盘运动中解放出来:)
#!/bin/bash
translateServers() {
awk '
BEGIN {
FS=","
serverNum = 0
}
function trim(str) {
gsub(/^\s*/, "", str)
gsub(/\s*$/, "", str)
return str
}
{
prompt = ">"
port = 22
host = ""
user = ""
password = ""
path = ""
line = trim($0)
pos = index(line, "#")
if (pos != 1) {
if (NF >= 3) {
server = $1
user = $2
password = $3
pos = index(server, ":")
if ( pos > 1 ) {
split(server, arr, ":")
host = arr[1]
port = arr[2]
} else {
host = server
}
if (NF > 3) {
prompt = $4
}
if (NF > 4) {
path = $5
}
print "host_" NR "=\"" host "\""
print "port_" NR "=" port
print "user_" NR "=\"" user "\""
print "password_" NR "=\"" password "\""
print "prompt_" NR "=\"" prompt "\""
print "path_" NR "=\"" path "\""
serverNum = serverNum + 1
}
}
}
END {
print "SERVER_NUM=" serverNum
}
' $1
}
translateCommands() {
awk '
BEGIN {
commandNum = 0
remote_commands = ""
foreType = 0
FS=","
}
function trim(str) {
gsub(/^\s*/, "", str);
gsub(/\s*$/, "", str);
return str
}
function print_remote() {
if (remote_commands != "" && foreType == 0) {
print "cmd_type" commandNum "=0"
print "cmd_line" commandNum "=\"" remote_commands "\""
}
remote_commands = ""
}
function print_local(line, end, type) {
print_remote()
commandNum = commandNum + 1
len = length(line)
last = substr(line, len, 1)
if (last == end) {
command = substr(line, 2, len - 2)
} else {
command = substr(line, 2)
}
print "cmd_type" commandNum "=" type
print "cmd_line" commandNum "=\"" command "\""
}
{
type = 0
gsub(/\"/, "\\\\\\\"")
#gsub(/\$/, "\\\\\\\$")
line = trim($0)
header = substr(line, 1, 1)
if (header != "#" && length(line) > 1) {
if (header == "[") {
type = 1
print_local(line, "]", type)
} else if (header == "{") {
type = 2
print_local(line, "}", type)
} else if (header == "<") {
type = 3
print_local(line, ">", type)
} else {
if (remote_commands == "") {
commandNum = commandNum + 1
}
type = 0
remote_commands = remote_commands"\\r"line
}
foreType = type
}
}
END {
print_remote()
print "COMMAND_NUM=" commandNum
}
' $1
}
executeRemote() {
HOST="$1"
PORT="$2"
USER="$3"
PASSWORDD="$4"
PROMPT="$5"
CMDS="$6"
remote_commands="
puts \"login server, wait ${PROMPT}...\\n\";
spawn ssh -p ${PORT} ${USER}@${HOST};
set timeout 15;
set doItAgain 1;
while { \${doItAgain} } {
expect {
\"*continue connecting*\" {
send \"yes\\r\";
}
\"*assword*\" {
send \"${PASSWORD}\\r\";
}
\"*${USER}*${PROMPT}\" {
puts \"login ${HOST} successfully : )\";
set doItAgain 0;
}
\"*#\" {
puts \"login ${HOST} successfully : )\";
set doItAgain 0;
}
timeout break;
}
}
if { \$doItAgain == 0 } {
set CMDS [split \"${CMDS}\" \"\\r\"];
foreach CMD \${CMDS} {
send \"\${CMD}\\r\";
expect \"*${USER}*${PROMPT}\";
}
send \"exit\\r\";
expect eof;
} else {
puts \"fail to login\";
}
"
expect -c "$remote_commands"
}
scpFile() {
SCPCMD=$1
PASSWORD=$2
scp_commands="
puts \"spawn ${SCPCMD}\\n\";
spawn ${SCPCMD};
set doItAgain 1;
while { \$doItAgain } {
expect {
\"*continue connecting*\" {
send \"yes\\r\";
}
\"*assword:*\" {
send \"${PASSWORD}\\r\";
}
eof {
set doItAgain 0;
}
}
}
"
expect -c "$scp_commands"
}
runCommand() {
N=$1
HOST=$(getCfgItem "host_${N}")
PORT=$(getCfgItem "port_${N}")
USER=$(getCfgItem "user_${N}")
PASSWORD=$(getCfgItem "password_${N}")
PMPT=$(getCfgItem "prompt_${N}")
MAINPATH=$(getCfgItem "path_${N}")
for((k = 1; k <= COMMAND_NUM; k++)); do
TYPE=$(getCfgItem "cmd_type${k}")
CMD=$(getCfgItem "cmd_line${k}")
CMD=${CMD//\{HOST\}/$HOST}
CMD=${CMD//\{PORT\}/$PORT}
CMD=${CMD//\{USER\}/$USER}
CMD=${CMD//\{PATH\}/$MAINPATH}
CMD=${CMD//\{PASSWORD\}/$PASSWORD}
CMD=${CMD//\{NO\}/$N}
if [ $TYPE = 1 ]; then
echo "execute \"${CMD}\""
if [[ $CMD =~ "^scp.*$" ]]; then
scpFile "${CMD}" "${PASSWORD}"
else
eval "${CMD}"
fi
elif [ $TYPE = 0 ]; then
executeRemote "$HOST" "$PORT" "$USER" "$PASSWORD" "$PMPT" "$CMD"
fi
done
}
## only local command, and run at the end
runCommandOnce() {
EXPECTED_TYPE=$1
for((i = 1; i <= COMMAND_NUM; i++)); do
TYPE=$(getCfgItem "cmd_type${i}")
if [ "$TYPE" -eq "$EXPECTED_TYPE" ]; then
echo "execute \"${CMD}\""
CMD=$(getCfgItem "cmd_line${i}")
eval "${CMD}"
fi
done
}
if [ $# -lt 1 ]; then
echo "Usage: exec server_list_file command_list_file"
exit
fi
server_file="./conf/$1"
command_file="./conf/$2"
temp_file="./$2.cmd"
dos2unix ${server_file}
dos2unix ${command_file}
translateServers ${server_file} > ${temp_file}
translateCommands ${command_file} >> ${temp_file}
echo -e "getCfgItem() {\nif [ -n \\$\${1} ]; then\n eval echo \\$\${1}\nelse\n echo \"\"\nfi\n}" >> ${temp_file}
source ${temp_file}
runCommandOnce 3
echo "Start to execute command on all ${SERVER_NUM} servers"
for(( i = 1; i <= SERVER_NUM; i++)); do
runCommand $i
done
runCommandOnce 2
echo "Execute commands end"
rm ${temp_file}