需求:有大量的linux机器需要进行发布更新以及其他命令操作,而且网段和本地不通,需要通过代理的方式进行,
按以往的方式,只能一个一个手动上去执行命令,或者传文件上去,这样耗费大量的时间。这个时候想到用代码
来实现这种重复操作,
关键技术:session的登录,xhell通道代理的建立。
具体实现步骤
一 :建立本地代理
由于不在同一网段,需要用工具建立一个通道(代理),可以选择SecureCRT,xhell,小红帽等工具建立一个能远程到目标服务器的通道(代理,默认端口设置为1080,使用socks进行动态转发)
二:代码实现
博主用的框架是springboot,主要方便jar包的引入
1:关键jar包的引入,pom.xml文件
需要实现Jsch包中的UserInfo,UIKeyboardInteractive接口,用以保存用户信息,以及进行键盘交互式认证并执行命令
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.54</version>
</dependency>
方便调试运行,写成测试类,
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
主类全部代码
package com.hg.test;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelShell;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.ProxySOCKS5;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UserInfo;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.util.StringUtils;
@Slf4j
public class Testsshkylin2 extends BaseTest{
private int timeout = 60000;
private ExecutorService ES = Executors.newFixedThreadPool(50);
@Test
public void test() throws IOException {
String con = read("/******");//此处我写成了json,方便读取,这个用户可以自定义,只要能读取就行,里面主要存放用户名,密码,以及个别判断字段
ObjectMapper objectMapper = new ObjectMapper();
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, Server.class);
List<Server> list = objectMapper.readValue(con, javaType);//此处,我新建了一个实体类Server,方便参数的读取,
exec(list);
}
public void exec( List<Server> list) throws IOException {
List<Callable<Integer>> tasks = new ArrayList<Callable<Integer>>();
list
.stream().filter(server ->!server.isHh())
.forEach(s -> {
tasks.add(buile(s,s.name));
});
long ss = System.currentTimeMillis();
try {
List<Future<Integer>> results = ES.invokeAll(tasks);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("执行任务消耗了 :" + (System.currentTimeMillis() - ss) + "毫秒");
}
private Callable<Integer> buile(Server s,String name){
return () -> {
try {
readRemote(s,name);
} catch (SocketTimeoutException e) {
log.error("{}\t 网络不通", s.ip);
} catch (Exception e) {
String err=e.getLocalizedMessage();
if(err.equalsIgnoreCase("Auth cancel"))
return 1;
log.error("{}\t{}", s.ip, e.getLocalizedMessage());
}
return 1;
};
}
public void readRemote(final Server s,String staname) throws JSchException, IOException, InterruptedException {
String host = s.ip;
String username = s.user;
String pass = s.pass;
JSch jsch = new JSch();
Session session = jsch.getSession(username, host);
session.setProxy(new ProxySOCKS5("127.0.0.1"));//路由用本地
session.setPassword(pass); // 设置密码
session.setUserInfo(new MyUserInfo(s)); //需要实现Jsch包中的UserInfo,UIKeyboardInteractive接口,用以保存用户信息,以及进行键盘交互式认证并执行命令。
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");//在代码里需要跳过检测。否则会报错找不到主机
session.setConfig(config); // 为Session对象设置properties
session.setTimeout(timeout); // 设置timeout时间
session.connect(); // 通过Session建立与远程服务器的连接回话
ChannelShell channelShell = (ChannelShell) session.openChannel("shell");
PipedInputStream pipeIn = new PipedInputStream();
PipedOutputStream pipeOut = new PipedOutputStream(pipeIn);
channelShell.setInputStream(pipeIn);
ByteArrayOutputStream output = new ByteArrayOutputStream();
channelShell.setOutputStream(output);
channelShell.connect(timeout);
output.reset();
pipeOut.write(("\n").getBytes());
wateForuser(output);
//命令最好一个一个执行,不然可能会出现,先后顺序错乱
sendFile(session,new File("D:\\shellwork\\d.xml"));//上传文件
exec("service cloud_ds restart",pipeOut,output);//服务重启
String status=exec("systemctl status cloud_ds",pipeOut,output);//查看服务状态获取返回值
log.info("===================="+status);
pipeOut.close();
pipeIn.close();
channelShell.disconnect();
session.disconnect();
}
private void sendFile(Session session, File file) {
log.info("put file {}", file.getName());
try {
Channel channel = session.openChannel("sftp");
channel.connect();
ChannelSftp c = (ChannelSftp) channel;
int mode = ChannelSftp.OVERWRITE;
c.put(file.getAbsolutePath(), "./", mode);
c.quit();
c.disconnect();
channel.disconnect();
} catch (Exception e) {
log.error("上传文件失败 {} -{} -{}", session.getHost(), file.getName(), e.getLocalizedMessage());
}
}
private String exec(String cmd, PipedOutputStream pipeOut, ByteArrayOutputStream output) throws IOException {
pipeOut.write((cmd + "\n").getBytes());
return wateFor("#", output);
}
private void wateForuser(ByteArrayOutputStream output) {
long start = System.currentTimeMillis();
while (true) {
String str = new String(output.toByteArray());
if (StringUtils.hasText(str)) {
if (str.indexOf("#") >= 0 || str.indexOf("$") >= 0) {
output.reset();
return;
}
}
if (System.currentTimeMillis() - start > timeout) {
throw new RuntimeException("登录时等待超时");
} else {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private String wateFor(String con, ByteArrayOutputStream output) {
long start = System.currentTimeMillis();
String str = null;
while (true) {
str = output.toString();
if (str.indexOf(con) >= 0) {
output.reset();
return str;
}
if (System.currentTimeMillis() - start > timeout) {
throw new RuntimeException("等待超时");
} else {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
}
}
}
private static class MyUserInfo implements UserInfo {
private Server server;
private MyUserInfo(Server ip) {
this.server = ip;
}
public String getPassphrase() {
System.out.println("getPassphrase");
return null;
}
public String getPassword() {
log.info("{}\t密码错误", server.ip);
return null;
}
public boolean promptPassword(String s) {
// System.out.println("promptPassword:" + s);
return true;
}
public boolean promptPassphrase(String s) {
System.out.println("promptPassphrase:" + s);
return false;
}
public boolean promptYesNo(String s) {
System.out.println("promptYesNo:" + s);
return true;//notice here!
}
public void showMessage(String s) {
// System.out.println("showMessage:" + s);
}
}
}
大致就是这样,具体可以尝试一下,