如何通过 java 实现在多台 Linux 服务器间自动执行各种命令或查询日志
SSHxcute 是一个现存的基于 Java 的远程执行类库,允许工程师利用 Java 代码通过 ssh 远程执行 Linux/Unix 系统上的命令或者脚本,这种方式不管是针对软件测试还是系统部署,都简化了自动化测试与系统部署的步骤。但是但是经过测试,现有的情况只是支持对单个服务器的连接。本文介绍如何实现多服务器之间的切换并且根据配置文件来自动链接并读取日志或执行命令等操作。
0 评论:
包 向华, 软件工程师, IBM
2014 年 12 月 18 日
在 IBM Bluemix 云平台上开发并部署您的下一个应用。
简介
系统的爆炸性增长让应用的分布式变的越来越普遍。虽然现有的分布式系统的应用,给予应用本身很大的可扩展性。但是相对来说,维护性就可能到其影响,让维护人员的工作量加倍和工作效率降低。其中,最常见的麻烦之一便是错误处理问题的查找。当用户需要根据错误日志来诊断问题出现的时间和相对位置的时候,多个服务器之间的相互切换和命令的重复键入是没有办法被避免的,用户需要重复各种简单而且单调的工作模式,在不同的机器当中重复实现。
现有的使用环境
对于软件开发测试人员来说,大型的应用意味着对远程主机依赖会达到更高的层次。从应用环境的部署到命令的远程执行,都需要依赖各种客户端工具。对于开发人员,当每次执行的命令很多时,一般是把其写入到一个脚本里面,通过客户端工具进行远程登录后执行。但是每次的执行都只是对一个特定的服务器,但是现有的分布式系统让工作能够均衡的负载到各个服务器上面,这也加大在相同工作内容上服务器上面进行部署的工作,而且当脚本部署到各个服务器上面时,当其中的一个脚本内容修改时,无法达到所有脚本的一致性,这也会导致很多的平衡性的问题。
图 1. 原始使用 SSH 工作流程
开发人员更加喜欢可智能化的集成开发环境,比如说,sshxcute 是一个现存的基于 Java 的远程执行类库,开发人员可以通过运行一个 Java 类来部署环境,远程执行命令或者脚本。他是一个框架,允许工程师利用 Java 代码通过 ssh 远程执行 Linux/Unix 系统上的命令或者脚本,这种方式不管是针对软件测试还是系统部署,都简化了自动化测试与系统部署的步骤。
SSHXcute 的设置宗旨在于:
最小的系统需求
易用性
内置命令
/
脚本任务执行功能易扩展
图 2. 使用 SSHxcute 工作流程图
但是不论是客户端还是先有的 Java 执行类库,都有一个缺陷,那就是无法实现相同任务分布式服务器的自动切换。
缺陷分析
测试人员在当下,如果发现问题后一般会执行如下步骤以获得错误日志,只有获得更准确的错误日志开发人员才能更快速的分析问题根本解决问题。
测试人员发现界面或后台运行程序出现错误
打开 Putty,连接 1 号应用 app 服务器,打开相关日志文档查找错误日志
连接 1 号数据库服务器,打开相关日志文档查找错误日志
连接 1 号后台任务管理服务器,打开相关日志文档查找错误日志
下载各种配置文件,已查询错误
重复步骤 1-5,遍历所有关联服务器(Job,App,DB)查找日志
图 3. 测试人员手动检查服务器步骤图-1
图 4. 测试人员手动检查服务器步骤图-2
实现方式
SSHXcute 提供的 API 包含有关闭以及切换的操作,但是经过测试,现有的情况只是支持对单个服务器的连接。无论是调用关闭还是切换接口,都无法影响现有的连接。
只有对现有的源代码进行改进,才能够自动实现多服务器之间的切换并且更够根据配置文件来自动实现。
如何实现自动遍历所有服务器
在开发测试人员的日常分析中,经常要通过遍历大量不同的服务器以找到真正需要的日志,因此实现自动遍历所有服务器,能够大大缩短连接转换服务器的时间,提高效率。
实现代码如下:
清单 1:实现自动遍历所有服务器
//读取服务器磁盘空间信息命令,读取使用率大于 90%的
String cmd = "df -h | grep -b -E \\(9[1-9]\\%\\)\\|\\(100\\%\\)";
JSch sshSingleton = new JSch();
//从配置文件中加载用户名和密码
Properties userProp = new Properties();
userProp.load(new FileReader("user.properties"));
String userName = userProp.getProperty("username");
String password = userProp.getProperty("password");
//从配置文件中加载服务器信息
Properties serversProp = new Properties();
serversProp.load(new FileReader("servers.properties"));
for (Map.Entry<Object, Object> serverProp : serversProp.entrySet()) {
String name = (String) serverProp.getKey();
String server = (String) serverProp.getValue();
System.out.println("Start working on: " + name);
Session session = sshSingleton.getSession(userName, server);
session.setPassword(password);
Properties config = new Properties();
//设置 SSH 连接时不进行公钥确认
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
//打开命令执行管道
ChannelExec channel = (ChannelExec) session.openChannel("exec");
BufferedReader in = new BufferedReader(new InputStreamReader(
channel.getInputStream()));
channel.setCommand(cmd);
channel.connect();
//读取命令输出信息
String msg;
while ((msg = in.readLine()) != null) {
System.out.println(msg);
}
channel.disconnect();
session.disconnect();
}
user.properties
用来存放用户名和密码:
username=admin
password=xxxxxxxx
servers.propesties
用来存放 servers 的 IP 信息,例如:
#alias name = host name/IP
server1=192.168.0.11
server2=192.168.0.12
server3=192.168.0.13
server4=192.168.0.14
server5=192.168.0.15
如何动态读取服务器相关日志
在多台 job servers 中,由于配置了负载均衡,因此 job 就会被分发到不同的 server 上,所以对应 job 的日志可能会出现在不同的 server 上。通常我们需要根据界面上的 ID 从一个日志文件先找出相关的 JobId,然后再从其他日志文件中再找出具体的错误日志信息。
实现代码如下:
清单 2. 动态读取服务器日志
//日志 1 中特殊字符信息
String uniqeStr = "RunID: 123";
//日志 1 文件位置
String serverlogFilePath = "/opt/server/log/server.log";
//日志 2 中特殊字符信息
String jobIdExpr = "id=[0-9]+";
//日志 2 文件位置
String joblogFilePath = "/opt/server/log/job.log";
JSch sshSingleton = new JSch();
Properties userProp = new Properties();
userProp.load(new FileReader("user.properties"));
String userName = userProp.getProperty("username");
String password = userProp.getProperty("password");
Properties serversProp = new Properties();
serversProp.load(new FileReader("servers.properties"));
Pattern p = Pattern.compile(jobIdExpr);
for (Map.Entry<Object, Object> serverProp : serversProp.entrySet()) {
String name = (String) serverProp.getKey();
String server = (String) serverProp.getValue();
Session session = sshSingleton.getSession(userName, server);
session.setPassword(password);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
//打开执行管道
ChannelExec channel = (ChannelExec) session.openChannel("exec");
BufferedReader in = new BufferedReader(new InputStreamReader(
channel.getInputStream()));
//设置命令,从日志 1 中找出关键字符信息
channel.setCommand("cat " + serverlogFilePath + " |grep -w "
+ uniqeStr);
channel.connect();
String msg;
String jobId = null;
while ((msg = in.readLine()) != null) {
Matcher m = p.matcher(msg);
if (m.find()) {
jobId = m.group();
break;
}
}
//关闭第一个执行管道
channel.disconnect();
if (jobId != null) {
//日志 1 中发现关键字符信息后,在同一服务器中日志文件 2 中继续查找
System.out.println("found log in jobServer: " + name);
//另外再打开一个新的执行管道
channel = (ChannelExec) session.openChannel("exec");
in = new BufferedReader(new InputStreamReader(
channel.getInputStream()));
String cmd = "cat " + joblogFilePath + " |grep -A 10 " + jobId;
channel.setCommand(cmd);
channel.connect();
//输出需要查找的日志信息
while ((msg = in.readLine()) != null) {
System.out.println(msg);
}
channel.disconnect();
}
session.disconnect();
if (jobId != null) {
//已经在这台服务器中找到日志,不需要继续去其他服务器中查找了
break;
}
}
如何实现自动上传下载文件
在测试过程中,我们经常需要上传更新一些配置文件或者下载服务器上的一些日志文件,下面我们就以一台服务器做为上传下载的示例。
实现代码如下:
清单 3. 自动上传下载文件
String serverFile = "/opt/log/1.log";
String localFolder = "C:/tmp";
String localFile = "C:/tmp/user.xml";
String serverFolder = "/tmp/";
JSch sshSingleton = new JSch();
Properties userProp = new Properties();
userProp.load(new FileReader("user.properties"));
String userName = userProp.getProperty("username");
String password = userProp.getProperty("password");
String server = "192.168.0.1";
//设置端口
int port = 22;
Session session = sshSingleton.getSession(userName, server, port);
session.setPassword(password);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
//打开 ftp 管道
Channel channel = session.openChannel("sftp");
channel.connect();
ChannelSftp c = (ChannelSftp) channel;
//转到根目录,便于使用绝对路径来进行文件传输
c.cd("/");
//从服务器上下载日志文件 1.log 到本地目录 C:/tmp
c.get(serverFile, localFolder);
//上传配置文件 user.xml 到服务器上,如果服务器上已经存在该文件,则覆盖它
c.put(localFile, serverFolder, ChannelSftp.OVERWRITE);
session.disconnect();
展望
我们现有的改进方式不过是重新修改 SSHXcute 的代码让其能够支持多个服务器之间的自动切换,还没有对其完全自动化,我们期望的前景是能够配置一个让开发人员完全不需要进行任何代码开发的第三方服务器来存储并且代理实现所有的配置命令,并且给用户一个可以配置的界面,让用户对其进行新的命令和执行方式的配置 (例如是否可以进行自动化的时间配置)。
图 5. 使用 jsch 搭建 web 版应用流程图
总结
实现自动化遍历服务器,动态读取错误日志能够大大缩短开发测试人员分析问题的时间,提高解决问题的效率。但遇到复杂的错误时,单从错误日志并不能完全地查出根本所在,因此我们需要不断地扩展其他功能以应对各种复杂的情况。我们将会在后续的文章中对功能进一步深入细化。