简易自动化部署服务器集群
前提
目前公司使用多个服务器对外提供服务。其中只有一台服务器有外网带宽,有几台内网业务服务器。这带有两个问题:
- 怎么管理内网的服务器
- 怎么自动化部署服务器,减少人工参与的工作量和失误
针对这两个问题,我们使用SSH/SCP和PEXPECT来解决。
SSH/SCP
我们使用SSH通过有外网的服务器建立起本地和没有外网的服务器的隧道,之后所有的操作都可以通过这个隧道来进行操作。
首先进行隧道的创建。
ssh -L [bind_address:]tunnelport:host:hostport <SSH hostname>
- bind_address 指定绑定的IP地址,默认情况会绑定在本地的回环地址(即127.0.0.1),如果空值或者为*会绑定本地所有的IP地址,如果希望绑定的端口仅供本机使用,可以指定为localhost。
- tunnelport 指定本地绑定的端口
- host 指定目标地址的IP,如果目标主机和ssh server是同一台主机时该参数指定为localhost
- host_port 指定目标端口。当我们要使用SCP/SSH时都为22
- SSH hostname指有外网带宽的服务器
然后就是通过隧道使用SSH登陆无外网带宽的服务器
ssh -p tunnelport x@127.0.0.1
这里使用-p参数,把ssh使用的端口为之前绑定的隧道端口tunnelport。
使用SCP进行文件的传输操作。
scp -P tunnelport src_file x@127.0.0.1:dst_file
这里使用-P参数,把SCP使用的端口设置为之前绑定的隧道端口tunnelport。
- src_file本地文件
- dst_file需要拷贝到的文件路径或文件名
Pexpect
Pexpect 是一个用来启动子程序并对其进行自动控制的 Python 模块。 Pexpect 可以用来和像 ssh、ftp、passwd、telnet 等命令行程序进行自动交互,方便在工作中实现与命令行交互的自动化。
在做实验的过程主要使用了spawn、sendline和expect三个函数来实现我们的要求。
-
spawn
class spawn: def __init__(self,command,args=[],timeout=30,maxread=2000,searchwindowsize=None, logfile=None, cwd=None, env=None) spawn是Pexpect模块主要的类,用以实现启动子程序,它有丰富的方法与子程序交互从而实现用户对子程序的控制。它主要使用 pty.fork() 生成子进程,并调用 exec() 系列函数执行 command 参数的内容。
-
expect
expect(self, pattern, timeout=-1, searchwindowsize=None)
在参数中: pattern 可以是正则表达式, pexpect.EOF , pexpect.TIMEOUT ,或者由这些元素组成的列表。需要注意的是,当 pattern 的类型是一个列表时,且子程序输出结果中不止一个被匹配成功,则匹配返回的结果是缓冲区中最先出现的那个元素,或者是列表中最左边的元素。使用 timeout 可以指定等待结果的超时时间 ,该时间以秒为单位。当超过预订时间时, expect 匹配到pexpect.TIMEOUT。
- sendline
这些方法用来向子程序发送命令,会额外在后面多加个回车来模拟操作。与之相关的还有send和sendcontrol两个函数
实验例程
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pexpect
import os
import time
#cmd需要向命令行输入的命令
#passeword host的密码
def ssh_login (cmd, password):
ssh_newkey = 'Are you sure you want to continue connecting'
new_ssh = pexpect.spawn(cmd)
i = new_ssh.expect([pexpect.TIMEOUT, ssh_newkey, 'password: ']) #登陆的时候会有两种状态
if i == 0: #如果是超时
print 'ERROR!'
print 'SSH could not login. Here is what SSH said:'
print new_ssh.before, new_ssh.after
return None
if i == 1: #如果需要输入信息登陆
new_ssh.sendline ('yes')
i = new_ssh.expect([pexpect.TIMEOUT, 'password: '])
if i == 0:
print 'ERROR!'
print 'SSH could not login. Here is what SSH said:'
print new_ssh.before, new_ssh.after
return None
new_ssh.sendline(password)
return new_ssh
#登出
def ssh_logout(newpexpect):
newpexpect.close()
#等待终端出输入符号
def ssh_wait_prompts(ssh):
ssh.expect([pexpect.EOF, '[$#>]'])
#由于scp需要在没有建立链接的时候需要验证,所以封装一下
def scp_run(cmd, password):
new_scp = ssh_login(cmd, password)
ssh_wait_prompts(new_scp)
ssh_logout(new_scp)
def main ():
#创建隧道,这个需要一直保持,直到不用这个隧道时才能释放
server1_tunnel = ssh_login ("ssh -L 8082:192.168.132.144:22 x@192.168.132.141","x")
slb = ssh_login('ssh x@192.168.132.144', 'x') #正常登陆服务器
server1 = ssh_login('ssh -p 8082 x@127.0.0.1', 'x') #通过隧道登陆服务器
scp_run('scp -P 8082 /home/x/test.py x@127.0.0.1:/home/x/tesdt.py', 'x') #通过隧道传送文件
ssh_wait_prompts(server1)
#通过ssh运行一个脚本,删除文件
server1.sendline('/home/x/del.sh')
#测试连通性
ssh_wait_prompts(server1)
server1.sendline('ls')
ssh_wait_prompts(server1)
print server1.before
ssh_logout(server1)
ssh_logout(slb)
ssh_logout(server1_tunnel)
if __name__ == '__main__':
try:
main()
except Exception, e:
print 'fail', str(e)
traceback.print_exc()
os._exit(1)