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}

linux服务器集群重复批量操作脚本实现的更多相关文章

  1. Linux服务器集群系统(一)--转

    引用地址:http://www.linuxvirtualserver.org/zh/lvs1.html LVS项目介绍 章文嵩 (wensong@linux-vs.org)2002 年 3 月 本文介 ...

  2. Linux服务器集群系统(一)(转)

    add by zhj:虽然是2002年的文章,但读来还是收益良多.在 章文嵩:谈LVS及阿里开源背后的精彩故事 中LVS发起人及主要贡献者谈了LVS的开发过程及阿里开源的一些故事 原文:http:// ...

  3. 【原创】Linux服务器集群通过SSH无密码登录

    SSH 无密码授权访问slave集群机器 1. 安装SSH,所有集群机器,都要安装SSH环境介绍:  Master : CNT06BIG01 192.168.3.61 SLAVE 1: CNT06BI ...

  4. Linux服务器集群系统(LVS)

    from:http://www.linuxvirtualserver.org/zh/lvs1.html#5 本文介绍了Linux服务器集群系统--LVS(Linux Virtual Server)项目 ...

  5. 浅析Linux服务器集群系统技术

    浅析Linux服务器集群系统技术 目录 前言 常用的服务器集群 集群系统的优势 LVS集群的通用体系结构 为什么使用层次的体系结构 为什么是共享存储 可伸缩Web服务 前言 总结两篇技术文章,努力学习 ...

  6. Gravitational Teleport 开源的通过ssh && kubernetes api 管理linux 服务器集群的网关

    Gravitational Teleport 是一个开源的通过ssh && kubernetes api 管理linux 服务器集群的网关 支持以下功能: 基于证书的身份认证 ssh ...

  7. Linux服务器集群系统(一)

    Reference: http://www.linuxvirtualserver.org/zh/lvs1.html LVS项目介绍 章文嵩 (wensong@linux-vs.org)2002 年 3 ...

  8. 官方文档-Linux服务器集群系统(一)

    转载-Linux服务器集群系统(一) LVS项目介绍 章文嵩 (wensong@linux-vs.org)2002 年 3 月 本文介绍了Linux服务器集群系统--LVS(Linux Virtual ...

  9. 转载-lvs官方文档-Linux服务器集群系统(二)

    Linux服务器集群系统(二) LVS集群的体系结构 章文嵩 (wensong@linux-vs.org) 2002 年 4 月 本文主要介绍了LVS集群的体系结构.先给出LVS集群的通用体系结构,并 ...

随机推荐

  1. Injection of autowired dependencies failed; autowire 自动注入失败,测试类已初始化过了Spring容器。

    1 严重: StandardWrapper.Throwable org.springframework.beans.factory.BeanCreationException: Error creat ...

  2. js取对象的属性值循环

    var data = {name: "liuyang", job: "web", age: "27"} Object.keys(data). ...

  3. hunnu11550:欧拉函数

    Problem description   一个数x的欧拉函数Φ(x)定义为全部小于x的正整数中与x互质的数的数目,如小于5且和5互质的数有1.2.3.4,一共4个,故Φ(5)=4. 对于随意正整数x ...

  4. [D3] Drawing path in D3

    Here we have a force layout with three nodes. In the example, we will link three nodes with line and ...

  5. WCF REST (一)

    最近工作中学习使用了WCF REST,REST 有很多好处 高效 简约  面向资源  而客户端调用 也变得非常简单.REST 入门的资料等 大家可以去网上找 这里主要分享下遇到的问题以及解决~ 一.环 ...

  6. js课程 4-12 js中正则表达式如何使用

    js课程 4-12 js中正则表达式如何使用 一.总结 一句话总结: 1.js正则表达式手册取哪里找? w3cschool或者菜鸟教程->找到js正则表达式->完整的RegExp参考手册这 ...

  7. POJ 3159 Candies 还是差分约束(栈的SPFA)

    http://poj.org/problem?id=3159 题目大意: n个小朋友分糖果,你要满足他们的要求(a b x 意思为b不能超过a x个糖果)并且编号1和n的糖果差距要最大. 思路: 嗯, ...

  8. 8.5 Android灯光系统_源码分析_通知灯

    参考文章(应用程序举例)how to use the LED with Android phonehttp://androidblogger.blogspot.jp/2009/09/tutorial- ...

  9. 【习题 5-14 UVA - 1598】Exchange

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 各组数据之间有空行! 且最后一行后面没有空行! 然后就是用set来模拟就好. 删除的时候,不着急删除. 因为并不用时刻输出集合大小. ...

  10. mysql 存相同内容:utb8mb4 会比 utf8 占用更多的内存吗,utf8mb4 浪费内存吗?utf8 utf8mb4 区别

    原文:mysql 存相同内容:utb8mb4 会比 utf8 占用更多的内存吗,utf8mb4 浪费内存吗?utf8 utf8mb4 区别 参考:http://www.fengyunxiao.cn u ...