当我们需要维护的服务器有几百台上千台时,服务器的批量和自动化维护脚本就显得异常关键。本文讨论实现批量化管理的几个关键技术。
一、使用key 登陆并建立互信,以secureCRT 为例
1.使用公钥登陆的sshd 设置
vi /etc/ssh/sshd_config
修改:
PermitRootLogin yes 为 PermitRootLogin no
PasswordAuthentication yes 为PasswordAuthentication no
2.将secureCRT 生成的公钥上传的需要互信服务器上,使用 ssh-keygen -i -f Identity.pub >> /home/wang/.ssh/authorized_keys 导入公 钥。
勾选 secureCRT 全局选项-->ssh2-->启用openssh 代理程序转发(这个很关键),wang 用户使用私钥登陆,则使用wang 用户的相同公钥上的服务器间就建立互信关系。
例: 可以直接使用 ssh 登陆到192.168.20.2下,而不需要输入密码等
也可以使用scp /home/wang/test.txt 而直接传递文件。
ssh -i /home/lixin/.ssh/authorized_keys $a <<EOF
/usr/local/nginx/conf/vhosts/cms_vhosts/add.sh $1
EOF
第二种linux 多机互信的方法:
在服务器A 和B 上使用wang 用户执行执行:ssh-keygen -t rsa
将B 服务器上生成的 公钥id_rsa.pub复制到A服务器wang用户下的.ssh 相同目录,cat id_rsa.pub(B) >>/home/wang/.ssh/authorized_keys (A)则就可以从服务器B上使用
ssh ) 而登陆的服务器A,而无需要密码等
su - wang -c "ssh -i /home/wang/.ssh/id_rsa "
二、 使用sudo 使普通用户特定的管理员命令
以下以wang 用户为例
visudo
加入:wang ALL=(ALL) NOPASSWD: /bin/kill, /usr/local/nginx/sbin/nginx, /usr/sbin/userdel
则wang 这个普通用户就可以在不需要密码的情况下使用这些命令了
例: /usr/bin/sudo /usr/sbin/userdel test
三、什么是expect?
Expect是一个用来实现自动交互功能的软件套件。使用它系统管理员的可以创建脚本用来实现对命令或程序提供输入,而这些命令和程序是期望从终端(terminal)得到输入,一般来说这些输入都需要手工输入进行的。Expect则可以根据程序的提示模拟标准输入提供给程序需要的输入来实现交互程序执行。甚至可以实现实现简单的BBS聊天机器人。
Expect工作原理
从最简单的层次来说,Expect的工作方式象一个通用化的Chat脚本工具。Chat脚本最早用于UUCP网络内,以用来实现计算机之间需要建立连接时进行特定的登录会话的自动化。
Chat脚本由一系列expect-send对组成:expect等待输出中输出特定的字符,通常是一个提示符,然后发送特定的响应。
文用一个最短的例子说明脚本的原理。
脚本代码如下:
##############################################
#!/usr/bin/expect
set timeout 30
spawn ssh -l username 192.168.1.1
expect "password:"
send "ispass\r"
interact
##############################################
1. [#!/usr/bin/expect]
这一行告诉操作系统脚本里的代码使用那一个shell来执行。这里的expect其实和linux下的bash、windows下的cmd是一类东西。
注意:这一行需要在脚本的第一行。
2. [set timeout 30]
基本上认识英文的都知道这是设置超时时间的,现在你只要记住他的计时单位是:秒
3. [spawn ssh -l username 192.168.1.1]
spawn是进入expect环境后才可以执行的expect内部命令,如果没有装expect或者直接在默认的SHELL下执行是找不到spawn命令的。所以不要用 “which spawn“之类的命令去找spawn命令。好比windows里的dir就是一个内部命令,这个命令由shell自带,你无法找到一个dir.com 或 dir.exe 的可执行文件。
它主要的功能是给ssh运行进程加个壳,用来传递交互指令。
4. [expect "password:"]
这里的expect也是expect的一个内部命令,有点晕吧,expect的shell命令和内部命令是一样的,但不是一个功能,习惯就好了。这个命令的意思是判断上次输出结果里是否包含“password:”的字符串,如果有则立即返回,否则就等待一段时间后返回,这里等待时长就是前面设置的30秒
5. [send "ispass\r"]
这里就是执行交互动作,与手工输入密码的动作等效。
温馨提示: 命令字符串结尾别忘记加上 “\r”,如果出现异常等待的状态可以核查一下。
6. [interact]
执行完成后保持交互状态,把控制权交给控制台,这个时候就可以手工操作了。如果没有这一句登录完成后会退出,而不是留在远程终端上。如果你只是登录过去执行
#!/usr/bin/expect #注意安装的路径,不确定 whereis expect 一下
# Change a login shell to bash
set user [lindex $argv 0]
spawn bash $user
expect "]:"
send "/bin/bash "
expect eof
exit
例如下面的Chat脚本实现等待标准输出出现Login:字符串,然后发送somebody作为用户名;然后等待Password:提示符,并发出响应sillyme。
引用:Login: somebody Password: sillyme
例子:
下面是一个能用来实现自动执行该命令的Expect脚本:
#!/usr/bin/expect# Change a login shell to tcsh
set user [lindex $argv 0]
spawn chsh $user
expect "]:"
send "/bin/tcsh "
expect eof
exit
这个简单的脚本可以解释很多Expect程序的特性。
和其他脚本一样首行指定用来执行该脚本的命令程序,这里是/usr/bin/expect。
程序第一行用来获得脚本的执行参数(其保存在数组$argv中,从0号开始是参数),并将其保存到变量user中。
第二个参数使用Expect的spawn命令来启动脚本和命令的会话,这里启动的是chsh命令,实际上命令是以衍生子进程的方式来运行的。
随后的expect和send命令用来实现交互过程。脚本首先等待输出中出现]:字符串,一旦在输出中出现chsh输出到的特征字符串(一般特征字符串往往是等待输入的最后的提示符的特征信息)。对于其他不匹配的信息则会完全忽略。当脚本得到特征字符串时,expect将发送/bin/tcsh和一个回车符给chsh命令。
最后脚本等待命令退出(chsh结束),一旦接收到标识子进程已经结束的eof字符,expect脚本也就退出结束。 例: 通过expect 工具进行批量管理,expect工具很强大,可以实现交互式管理,比如如果你想改密码,输入passwd命令后,系统会提示你输入New Password: ,如果使用普通脚本的话,那你是没办法进行交互式的。但是expect就可以做到检测系统的返回值并且根据返回的提示来自动交互,如下例:
#!/usr/bin/expect -fset ipaddress [lindex $argv 0] #设置命令行参数
set passwd [lindex $argv 1] #参数1 为password
set ipaddress [lindex $argv 0] #参数 0 为IP 地址
set timeout 1000
spawn ssh root@$ipaddress
expect {
"yes/no" { send "yes\r";exp_continue }
"Password:" { send "$passwd\r" } #自动输入密码
}
expect "hknp"
send "/etc/init.d/heartbeat stop \r" #停止一个程序
expect "hknp"
send "exit\r" #退出系统
expect eof
exit
以上脚本通过命令: expect ha-switch.exp 192.168.193.133 ‘123DDFD’执行 ,其中123DDFD 就是133这台机子的root密码,如果你的一百台机子都是一样的密码,你就可以写个简单的批量脚本来登录所有的机子并停止一个程序,如下:
#!/bin/bash
for i in $(seq 100 200);
do
IP = "192.168.193.$i"
expect ha-switch.exp $IP '123DDFD'
done
这样此脚本就会调用ha-switch.exp脚本并登录到192.168.193.100-200的机器上分别执行"/etc/init.d/heartbeat stop 命令了。
详细用法请参考: http://www.chinaunix.net/jh/24/594417.html 四、后续输入作为子命令的 EOF
Shell中通常将EOF与 << 结合使用,表示后续的输入作为子命令或子Shell的输入,直到遇到EOF为止,再返回到主调Shell。
例:
su - bak_user -c "ssh -p2188 -i /home/bak_user/.ssh/id_rsa <<EOF
mkdir -p $remotedir
EOF"
这里再简要回顾一下<<的用法。当Shell看到<<的时候,它就会知道下一个词是一个分界
符。在该分界符以后的内容都被当作输入,直到shell又看到该分界符(位于单独的一行)。这个
分界符可以是你所定义的任何字符串。
常用文件重定向:
command > filename 把标准输出重定向到一个新文件中
command >> filename 把标准输出重定向到一个文件中(追加)
command 1 > filename 把标准输出重定向到一个文件中
command > filename 2 >&1 把标准输出和标准错误一起重定向到一个文件中
command 2 >filename 把标准错误重定向到一个文件中
command 2 >> filename 把标准错误重定向到一个文件中(追加)
command >> filename 2 >&1 把标准输出和标准错误一起重定向到一个文件中(追加)
command < filename > filename2 command命令以filename文件作为标准输入,
以filename2文件作为标准输出
command < filename command命令以filename文件作为标准输入
command << delimiter 从标准输出中读入,直至遇到delimiter分界符
command <&m 把文件描述符m作为标准输出
command >&m 把标准输出重定向到文件描述符m中
command <&- 关闭标准输入
注:<<delimiter 作为分界符绝对与 <<filename不混,因为后者根本不存在!!!
使用python 建立具有交互功能的批量化脚本
局域网内有一百多台电脑,全部都是linux操作系统,所有电脑配置相同,系统完全相同(包括用户名和密码),ip地址是自动分配的。现在有个任务 是在这些电脑上执行某些命令,者说进行某些操作,比如安装某些软件,拷贝某些文件,批量关机等。如果一台一台得手工去操作,费时又费力,如果要进行多个操 作就更麻烦啦。
或许你会想到网络同传,网络同传是什么?就是在一台电脑上把电脑装好,配置好,然后利用某些软件,如“联想网络同传”把系统原样拷贝过去,在装系统 时很有用,只要在一台电脑上装好,同传以后所有的电脑都装好操作系统了,很方便。同传要求所有电脑硬件完全相同,在联想的电脑上装的系统传到方正电脑上肯 定会出问题的。传系统也是很费时间的,根据硬盘大小,如果30G硬盘,100多台电脑大约要传2个多小时,反正比一台一台地安装快!但是如果系统都传完 了,发现忘了装一个软件,或者还需要做些小修改,再同传一次可以,但是太慢,传两次半天时间就没了。这时候我们可以利用ssh去控制每台电脑去执行某些命 令。
先让我们回忆一下ssh远程登录的过程:首先执行命令 ssh username@192.168.1.x ,第一次登录的时候系统会提示我们是否要继续连接,我们要输入“yes”,然后等一段时间后系统提示我们输入密码,正确地输入密码之后我们就能登录到远程 计算机,然后我们就能执行命令了。我们注意到这里面有两次人机交互,一次是输入‘yes’,另一次是输入密码。就是因为有两次交互我们不能简单的用某些命 令去完成我们的任务。我们可以考虑把人机交互变成自动交互,python的pexpect模块可以帮我们实现自动交互。下面这段代码是用pexpect实 现自动交互登录并执行命令的函数:
点击(此处)折叠或打开
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
-
- import pexpect
-
- def ssh_cmd(ip, passwd, cmd):
- ret = -1
- ssh = pexpect.spawn('ssh root@%s "%s"' % (ip, cmd))
- try:
- i = ssh.expect(['password:', 'continue connecting (yes/no)?'], timeout=5)
- if i == 0 :
- ssh.sendline(passwd)
- elif i == 1:
- ssh.sendline('yes\n')
- ssh.expect('password: ')
- ssh.sendline(passwd)
- ssh.sendline(cmd)
- r = ssh.read()
- print r
- ret = 0
- except pexpect.EOF:
- print "EOF"
- ssh.close()
- ret = -1
- except pexpect.TIMEOUT:
- print "TIMEOUT"
- ssh.close()
- ret = -2
- return ret
利用pexpect模块我们可以做很多事情,由于他提供了自动交互功能,因此我们可以实现ftp,telnet,ssh,scp等的自动登录,还是比较实用的。根据上面的代码相信读者已经知道怎么实现了(python就是那么简单!)。
用上面的代码去完成任务还是比较费时间的,因为程序要等待自动交互出现,另外ubuntu用ssh连接就是比较慢,要进行一系列的验证,这样才体现 出ssh的安全。我们要提高效率,在最短的时间内完成。后来我发现了python里面的paramiko模块,用这个实现ssh登录更加简单。看下面的代 码:
- #-*- coding: utf-8 -*-
- #!/usr/bin/python
- import paramiko
- import threading
- def ssh2(ip,username,passwd,cmd):
- try:
- ssh = paramiko.SSHClient()
- ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
- ssh.connect(ip,22,username,passwd,timeout=5)
- for m in cmd:
- stdin, stdout, stderr = ssh.exec_command(m)
- # stdin.write("Y") #简单交互,输入 ‘Y’
- out = stdout.readlines()
- #屏幕输出
- for o in out:
- print o,
- print '%s\tOK\n'%(ip)
- ssh.close()
- except :
- print '%s\tError\n'%(ip)
- if __name__=='__main__':
- cmd = ['cal','echo hello!']#你要执行的命令列表
- username = "" #用户名
- passwd = "" #密码
- threads = [] #多线程
- print "Begin......"
- for i in range(1,254):
- ip = '192.168.1.'+str(i)
- a=threading.Thread(target=ssh2,args=(ip,username,passwd,cmd))
- a.start()
上面的程序还是有些技巧的:
1.利用多线程,同时发出登录请求,同时去连接电脑,这样速度快很多,我试了一下,如果不用多线程,直接一个一个挨着执行的话,大约5~10秒钟才 能对一台电脑操作完,具体时间要根据命令的来决定,如果是软件安装或者卸载时间要更长一些。这样下来怎么也要一二十分钟,用多线程后就快多了,所有的命令 执行完用了不到2分钟!
2.最好用root用户登录,因为安装或者卸载软件的时候如果用普通用户又会提示输入密码,这样又多了一次交互,处理起来就比较麻烦!安装软件时 apt-get install xxx 最好加上“-y”参数,因为有时安装或删除软件时提示是否继续安装或卸载,这又是一次自动交互!加上那个参数后就没有人机交互了。
3. 循环时循环所有ip,因为计算机的ip是路由器自动分配的,保险起见,最好全部都执行,保证没有遗漏的主机
4.远端执行命令时如果有交互,可以这样用 stdin.write("Y")来完成交互,“Y”就是输入“Y”。
5.把所有的命令放到一个列表里面,遍历列表可以依次执行列表里面的命令
6.为了更好的进行控制,最好在电脑上提前把root用户打开,装好ssh服务器并让其开机自动执行。