1、需求
之前一直是手动的巡检,然后贴图,最近服务器数量大增,有点忙不过来了。因为一直用的java,对shell脚本不是特别了解,所以这次用java写了个小项目,实现对多服务器,多任务的巡检,巡检结果有故障的会通过邮件通知。
2、功能和效果
巡检的项目主要是服务,硬盘,内存等,命令可配置,巡检结果以日期和服务器为基准输出文件,错误信息通过邮件通知管理运维人员。
3、代码
action:
package com.save.action;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Date;import java.util.HashSet;import java.util.Iterator;import java.util.List;import java.util.Map;import java.util.Set;import java.util.regex.Matcher;import java.util.regex.Pattern;import org.apache.commons.lang3.StringUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import com.alibaba.fastjson.JSON;import com.save.pojo.Cmd;import com.save.until.MailUtil;import com.save.until.PropertiesUtil;import com.save.until.SSHCommUtil;import com.save.until.WriteUntil;/** * 巡检任务 * @author zhangzhuo * */public class InspAction { final static Logger logger = LoggerFactory.getLogger(InspAction.class);/* public static void main(String[] args) { InspAction n = new InspAction(); try { n.execute(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); logger.error("dd"); } }*/ /** * 执行巡检任务 * @param args */ public void execute() throws Exception{ Listlist = this.handlerData(); Set mail = new HashSet (); for (Cmd cmd : list) { String ip = cmd.getIp(); int port = 22; String localIp = null; int localPort = 0; int timeOut = 6000; String userName = cmd.getUsername(); String password = cmd.getPassword(); String server = cmd.getServer(); String[] cmds = cmd.getCmds(); String[] result = null; logger.info(ip+"执行巡检任务开始"); try { result = SSHCommUtil.execShellCmdBySSH(ip, port, localIp, localPort, timeOut, userName, password, cmds); } catch (Exception e) { e.printStackTrace(); logger.error(ip+"巡检,服务器连接不上"); mail.add(ip+" "+"巡检,服务器连接不上"); } Date date = new Date(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd"); String dateString = formatter.format(date); //1、服务存活验证 2、硬盘占用验证 3、巡检结果写入文件 if (result != null) { for (String string : result) { if (string.contains("ps -ef|grep java")||string.contains("ps -ef|grep mongo")||string.contains("ps -ef|grep redis")) { if (!string.contains(server)) { mail.add(ip+" "+server+"服务不存在"); } } if (string.contains("df -h")) { String patt = "^[5]\\d{1}\\%|[5-9]\\d{1}\\%|\\d{3,}\\%$"; String group = null; Pattern p = Pattern.compile(patt); Matcher m = p.matcher(string); while (m.find()) { group = m.group(); } if (!StringUtils.isBlank(group)) { mail.add(ip+" "+"硬盘占用超出预警线"); } } WriteUntil.createFile("E:\\save", dateString, "\\"+ip+".txt", string); } logger.info(ip+"巡检结束"); } } //发送故障邮件通知 if (!mail.isEmpty()||mail.size()!=0) { MailUtil.getInstance().sendMail(mail); } } /** * 数据处理 * @return */ private List handlerData(){ logger.info("开始加载需要巡检的服务器数据"); Cmd cmd = null; List list = new ArrayList (); Map map = PropertiesUtil.getInstance().getAllProperty(); Iterator > it = map.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = it.next(); cmd =new Cmd(); cmd.setIp(entry.getKey()); Cmd cmd2 = JSON.parseObject(entry.getValue(), Cmd.class); String[] cmds = cmd2.getShell().split(","); cmd.setCmds(cmds); cmd.setServer(cmd2.getServer()); cmd.setUsername(cmd2.getUsername()); cmd.setPassword(cmd2.getPassword()); list.add(cmd); } logger.info("数据加载完毕"); return list; }}
pojo:
package com.save.pojo;public class Cmd { private String ip; private String username; private String password; private String shell; private String[] cmds; private String server; public String getServer() { return server; } public void setServer(String server) { this.server = server; } public String getShell() { return shell; } public void setShell(String shell) { this.shell = shell; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String[] getCmds() { return cmds; } public void setCmds(String[] cmds) { this.cmds = cmds; } }
工具类:
package com.save.until;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.InetAddress;import java.net.InetSocketAddress;import java.net.Socket;import java.net.UnknownHostException;import java.util.Properties;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import com.jcraft.jsch.JSch;import com.jcraft.jsch.JSchException;import com.jcraft.jsch.Session;import com.jcraft.jsch.SocketFactory;/** * SSH创建与服务器连接工具类 * @author 张卓 * 2017-4-21 */public class JSCHUtil { final static Logger logger = LoggerFactory.getLogger(JSCHUtil.class); private static JSch jsch = new JSch(); /** * 创建Session,并打开Session连接 * */ public static Session createSession(String dstIp, int dstPort, final String localIp, final int localPort, String userName, String password, final int timeOut) throws JSchException { //jsch.setKnownHosts("/home/foo/.ssh/known_hosts"); logger.info("开始连接:"+dstIp); // 建立一个SSH连接 Session session = jsch.getSession(userName, dstIp, dstPort); session.setPassword(password); Properties sshConfig = new Properties(); sshConfig.put("StrictHostKeyChecking", "no");//跳过主机检查 session.setConfig(sshConfig); // 此socket工厂用于创建目标主机的socket, // 并创建我们使用的这个socket字节流 session.setSocketFactory(new SocketFactory() { public OutputStream getOutputStream(Socket socket) throws IOException { return socket.getOutputStream(); } public InputStream getInputStream(Socket socket) throws IOException { return socket.getInputStream(); } public Socket createSocket(String host, int port) throws IOException, UnknownHostException { Socket socket = new Socket(); if (localIp != null) { socket.bind(new InetSocketAddress(InetAddress .getByName(localIp), localPort)); } socket.connect( new InetSocketAddress(InetAddress.getByName(host), port), timeOut); return socket; } }); session.connect(timeOut); return session; }}
package com.save.until;import java.util.Properties;import java.util.Set;import javax.mail.Authenticator;import javax.mail.Message;import javax.mail.PasswordAuthentication;import javax.mail.Session;import javax.mail.Transport;import javax.mail.internet.InternetAddress;import javax.mail.internet.MimeMessage;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import com.sun.mail.util.MailSSLSocketFactory;/** * 邮件发送 * @author zhangzhuo * */public class MailUtil { final static Logger logger = LoggerFactory.getLogger(MailUtil.class); private static MailUtil instance = new MailUtil(); private MailUtil (){} public static MailUtil getInstance() { return instance; } public void sendMail(Setmail) { String from = "XXX@qq.com";// 发件人电子邮箱 String host = "smtp.qq.com"; // 指定发送邮件的主机smtp.qq.com(QQ)|smtp.163.com(网易) Properties properties =new Properties(); properties.setProperty("mail.smtp.host", host);// 设置邮件服务器 properties.setProperty("mail.smtp.auth", "true");// 打开认证 try { //QQ邮箱需要下面这段代码,163邮箱不需要 MailSSLSocketFactory sf = new MailSSLSocketFactory(); sf.setTrustAllHosts(true); properties.put("mail.smtp.ssl.enable", "true"); properties.put("mail.smtp.ssl.socketFactory", sf); // 1.获取默认session对象 Session session = Session.getDefaultInstance(properties, new Authenticator() { public PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("XXX@qq.com", "XXX"); // 发件人邮箱账号、授权码 } }); // 2.创建邮件对象 Message message = new MimeMessage(session); message.setFrom(new InternetAddress(from)); message.addRecipient(Message.RecipientType.TO, new InternetAddress("XXX@qq.com")); message.setSubject("巡检故障通知"); StringBuffer sb = new StringBuffer(); for (String string : mail) { sb.append(" "+string+"
"); } String content = sb.toString(); message.setContent(content, "text/html;charset=UTF-8"); Transport.send(message); logger.info("故障邮件发送成功"); } catch (Exception e) { e.printStackTrace(); } }}
package com.save.until;import java.io.File;import java.io.FileOutputStream; import java.io.InputStream; import java.io.InputStreamReader;import java.io.OutputStream; import java.net.URI; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * 读取文件工具类 * @author zhangzhuo * */public class PropertiesUtil { private Properties props; private URI uri; private static PropertiesUtil ourInstance = new PropertiesUtil("/config.properties"); public static PropertiesUtil getInstance() { return ourInstance; } public PropertiesUtil(String fileName){ readProperties(fileName); } private void readProperties(String fileName) { try { props = new Properties(); InputStream fis =getClass().getResourceAsStream(fileName); InputStreamReader re=new InputStreamReader(fis,"utf-8"); props.load(re); } catch (Exception e) { e.printStackTrace(); } } /** * 获取某个属性 */ public String getProperty(String key){ return props.getProperty(key); } /** * 获取所有属性,返回一个map,不常用 * 可以试试props.putAll(t) */ public Map getAllProperty(){ Map map=new HashMap(); Enumeration enu = props.propertyNames(); while (enu.hasMoreElements()) { String key = (String) enu.nextElement(); String value = props.getProperty(key); map.put(key, value); } return map; } /** * 在控制台上打印出所有属性,调试时用。 */ public void printProperties(){ props.list(System.out); } /** * 写入properties信息 */ public void writeProperties(String key, String value) { try { OutputStream fos = new FileOutputStream(new File(uri)); props.setProperty(key, value); // 将此 Properties 表中的属性列表(键和元素对)写入输出流 props.store(fos, "『comments』Update key:" + key); } catch (Exception e) { e.printStackTrace(); } } }
package com.save.until;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import com.jcraft.jsch.Channel;import com.jcraft.jsch.JSchException;import com.jcraft.jsch.Session;/** * 执行Shell工具类 * @author zhangzhuo * */public class SSHCommUtil { final static Logger logger = LoggerFactory.getLogger(SSHCommUtil.class); /** * SHH连接Linux Shell,返回结果 */ public static String[] execShellCmdBySSH(String dstIp, int dstport, String localIp, int localPort, int timeOut, String userName, String password, String... cmds) throws Exception { Session session = null; Channel channel = null; InputStream is = null; OutputStream os = null; try { session = JSCHUtil.createSession(dstIp, dstport, localIp, localPort, userName, password, timeOut); logger.info("开始创建channel通道!"); //创建一个channel类型的通道 channel = session.openChannel("shell"); // Enable agent-forwarding. // ((ChannelShell)channel).setAgentForwarding(true); // Choose the pty-type "vt102". // ((ChannelShell)channel).setPtyType("vt102"); // Set environment variable "LANG" as "ja_JP.eucJP". // ((ChannelShell)channel).setEnv("LANG", "ja_JP.eucJP"); channel.connect(); is = channel.getInputStream(); os = channel.getOutputStream(); String[] result = new String[cmds.length]; for (int i = 0; i < cmds.length; i++) { result[i] = sendCommand(is, os, cmds[i]); } return result; } catch (JSchException e) { if (e.getMessage().contains("Auth fail")) { logger.error(dstIp+"服务器验证失败"); throw new Exception("Auth error"); } else { logger.error(dstIp+"服务器连接失败"); throw new Exception("Connect error"); } } catch (Exception e) { throw e; } finally { try { is.close(); } catch (IOException e) { } try { os.close(); } catch (IOException e) { } channel.disconnect(); session.disconnect(); } } /** *执行Shell脚本并返回结果 * */ private static String sendCommand(InputStream is, OutputStream os, String cmd) throws IOException { logger.info("开始执行脚本!"); os.write(cmd.getBytes()); os.flush(); StringBuffer sb = new StringBuffer(); int beat = 0; while (true) { if (beat > 3) { break; } if (is.available() > 0) { byte[] b = new byte[is.available()]; is.read(b); sb.append(new String(b)); beat = 0; } else { if (sb.length() > 0) { beat++; } try { Thread.sleep(sb.toString().trim().length() == 0 ? 1000 : 300); } catch (InterruptedException e) { } } } return sb.toString(); } }
package com.save.until; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import com.jcraft.jsch.ChannelExec; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException;import com.jcraft.jsch.Session; /** * SSH工具类 * */ public class SSHExcuteCommandHelper { Session session = null; ChannelExec openChannel = null; /** * @param host 主机ip * @param name 用户名 * @param pwd 密码 * @param port ssh端口 */ public SSHExcuteCommandHelper(String host, String user, String pwd, int port) { JSch jsch = new JSch(); try { session = jsch.getSession(user, host, port); java.util.Properties config = new java.util.Properties(); config.put("StrictHostKeyChecking", "no"); session.setTimeout(1000); session.setConfig(config); session.setPassword(pwd); } catch (JSchException e) { e.printStackTrace(); } } /** * 是否连接成功,调用如果不需要调用execCommand方法那么必须调用 disconnect方法关闭session * @return */ public boolean canConnection(){ try { session.connect(); return true; } catch (JSchException e) { e.printStackTrace(); return false; } } /** * 关闭连接 */ public void disconnect(){ if (openChannel != null && !openChannel.isClosed()) { openChannel.disconnect(); } if (session != null && session.isConnected()) { session.disconnect(); } } /** * 执行命令 * @param command * @return */ public String execCommand(String command) { StringBuffer result = new StringBuffer(); try { if(!session.isConnected()){ session.connect(); } openChannel = (ChannelExec) session.openChannel("exec"); openChannel.setCommand(command); //int exitStatus = openChannel.getExitStatus(); openChannel.connect(); InputStream in = openChannel.getInputStream(); BufferedReader reader = new BufferedReader( new InputStreamReader(in)); String tmpStr = ""; while ((tmpStr = reader.readLine()) != null) { result.append(new String(tmpStr.getBytes("gbk"), "UTF-8")).append("\n"); } } catch (Exception e) { e.printStackTrace(); result.append(e.getMessage()); } finally { disconnect(); } return result.toString(); } /** * 解析 * @param result * @return */ public List
> parseResult(String result){ List
> parseResult = new ArrayList
>(); List list = null; // for (String line : result.split("\n")) { list = new ArrayList (); String[] columns = {}; //这个是针对df命令的 [Mounted on] 其实就一个,如果用空格就会分割出两个 if(line.contains("Mounted ")){ columns = line.replace("Mounted ", "Mounted-").split(" "); }else{ columns = line.split(" "); } for (String column : columns) { if (!" ".equals(column) && !"".equals(column)) { list.add(column); } } parseResult.add(list); } return parseResult; } //测试/* public static void main(String args[]) { SSHExcuteCommandHelper execute = new SSHExcuteCommandHelper("192.168.175.128", "root", "123456", 22); System.out.println("是否连接成功"+execute.canConnection()); String s = execute.execCommand("free -m"); System.out.println("解析前"); System.out.println(s); System.out.println("解析后"); List
> parseResult = execute.parseResult(s); for (List l : parseResult) { System.out.println(l); } }*/ }
package com.save.until;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.text.SimpleDateFormat;import java.util.Date;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import com.save.action.InspAction;/** * * @author zhangzhuo * */public class WriteUntil { final static Logger logger = LoggerFactory.getLogger(WriteUntil.class); /** * 新建文件夹,并创建文件写入数据 */ public static void createFile(String basePath,String filePath, String filename, String input) { String[] dirs = filePath.split("/"); String tempPath = basePath; for (String dir : dirs) { if (null == dir || "".equals(dir)) continue; tempPath += "\\" + dir; } //文件夹判断 File dir = new File(tempPath);//"d:\\test_dir" if (dir.exists()) { if (dir.isDirectory()) { logger.info("文件夹存在"); } else { logger.info("同名文件存在,无法创建目录"); } } else { logger.info("文件夹不存在,开始创建"); dir.mkdirs(); } //文件判断 File file = new File(tempPath+filename);//"d:\\test_file.txt" if (file.exists()) { logger.info(filename+"已存在"); } else { logger.info(filename+"文件不存在,开始创建"); try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } //写入数据 //方法一、每次写入覆盖之前的 /* try { FileOutputStream fos = new FileOutputStream(tempPath+filename); fos.write(input.getBytes()); fos.close(); } catch (Exception e) { e.printStackTrace(); }*/ try { FileOutputStream fos = new FileOutputStream(tempPath+filename, true); fos.write(input.getBytes()); fos.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } //测试/* public static void main(String[] args) { //createFile("E:\\log", "2014/16/2/", "\\2020.txt", "hahha"); Date date = new Date(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd"); String dateString = formatter.format(date); System.out.println(dateString); }*/}
applicationContext.xml
config.properties
#测试用服务器192.168.175.128={"username":"root","password":"123456","shell":"ps -ef|grep mongo\n,df -h\n, free -m\n, top\n","server":"mongod"}192.168.175.129={"username":"root","password":"123456","shell":"ps -ef|grep redis\n,df -h\n, free -m\n, top\n","server":"mongod"}
log4j.properties
#指定根Logger,及日志输出级别#大于等于该级别的日志将被输出( DEBUG < INFO < WARN < ERROR < FATAL ),设为OFF可以关闭日志 log4j.rootLogger=INFO, A1,A2 #指定log输出目的,这里设为输出日志到指定目录的文件my.log中 log4j.appender.A1=org.apache.log4j.FileAppender log4j.appender.A1.File=E:\\save\\log\\xj.log#指定日志信息的格式 log4j.appender.A1.layout=org.apache.log4j.PatternLayoutlog4j.appender.A1.layout.ConversionPattern=%r %d{yyyy-MM-dd HH:mm:ss} %c %p -%m%n #把A2输出到控制台 log4j.appender.A2=org.apache.log4j.ConsoleAppender log4j.appender.A2.layout=org.apache.log4j.PatternLayout log4j.appender.A2.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %c %p -%m%n
pom.xml:
4.0.0 com.save save-xj war 0.0.1-SNAPSHOT save-xj Maven Webapp http://maven.apache.org com.jcraft jsch 0.1.53 com.alibaba fastjson 1.2.31 org.springframework spring-context 3.1.1.RELEASE org.springframework spring-context-support 3.1.1.RELEASE org.springframework spring-tx 3.1.1.RELEASE org.springframework spring-web 3.0.5.RELEASE org.quartz-scheduler quartz 1.8.5 log4j log4j 1.2.17 org.slf4j slf4j-log4j12 1.7.21 com.sun.mail javax.mail 1.4.4 javax.mail 1.4 org.apache.commons commons-lang3 3.4 org.apache.tomcat.maven tomcat7-maven-plugin 2.2 8080 /