摘要:主要是將一個服務集群部署到遠端的服務器上,具體服務器的連接信息會通過接口傳入。本來部署是人工來完成的,無非是將一些必須的文件到目標服務器上,然后遠程登錄,執行一些安裝的操作,齊活。
介紹
前段時間接了一個比較特殊的需求,需要做一個用于部署服務的服務。主要是將一個k8s服務集群部署到遠端的服務器上,具體服務器的連接信息會通過接口傳入。
本來部署是人工來完成的,無非是將一些必須的文件scp到目標服務器上,然后ssh遠程登錄,執行一些安裝的操作,齊活。安裝的流程沒什么問題,主要是這些步驟需要使用代碼來實現,也就是需要一個支持SSH的client庫來執行這些操作
最終選用了JSch(Java Secure Channel),官網介紹:
JSch is a pure Java implementation of SSH2.實現
JSch allows you to connect to an sshd server and use port forwarding, X11 forwarding, file transfer, etc., and you can integrate its functionality into your own Java programs. JSch is licensed under BSD style license.
為了完成部署服務的任務,需要解決幾個問題:
SSH連接到遠端的服務器
在服務器上執行指令
使用scp命令傳輸文件
編輯服務器上的文件,主要是為了修改一些配置文件
這里介紹下幾個主要的工具方法
遠程ssh連接先定義一個Remote類,用于記錄服務器登錄信息
@Data public class Remote { private String user = "root"; private String host = "127.0.0.1"; private int port = 22; private String password = ""; private String identity = "~/.ssh/id_rsa"; private String passphrase = ""; }
這里填充了一些默認值,平時用的時候方便一些
JSch使用Session來定義一個遠程節點:
public static Session getSession(Remote remote) throws JSchException { JSch jSch = new JSch(); if (Files.exists(Paths.get(remote.getIdentity()))) { jSch.addIdentity(remote.getIdentity(), remote.getPassphrase()); } Session session = jSch.getSession(remote.getUser(), remote.getHost(),remote.getPort()); session.setPassword(remote.getPassword()); session.setConfig("StrictHostKeyChecking", "no"); return session; }
測試一下:
public static void main(String[] args) throws Exception { Remote remote = new Remote(); remote.setHost("192.168.124.20"); remote.setPassword("123456"); Session session = getSession(remote); session.connect(CONNECT_TIMEOUT); if (session.isConnected()) { System.out.println("Host({}) connected.", remote.getHost); } session.disconnect(); }
正確的輸入了服務器地址和密碼后,連接成功。
這里要提一下,JSch會優先使用填入的ssh_key去嘗試登錄,嘗試失敗后才會使用password登錄,這點和平時使用ssh命令的交互是一致的,好評~遠程指令
接下來就是編寫一個通用的方法,用于在Session上執行命令
public static ListremoteExecute(Session session, String command) throws JSchException { log.debug(">> {}", command); List resultLines = new ArrayList<>(); ChannelExec channel = null; try{ channel = (ChannelExec) session.openChannel("exec"); channel.setCommand(command); InputStream input = channel.getInputStream(); channel.connect(CONNECT_TIMEOUT); try { BufferedReader inputReader = new BufferedReader(newInputStreamReader(input)); String inputLine = null; while((inputLine = inputReader.readLine()) != null) { log.debug(" {}", inputLine); resultLines.add(inputLine); } } finally { if (input != null) { try { input.close(); } catch (Exception e) { log.error("JSch inputStream close error:", e); } } } } catch (IOException e) { log.error("IOcxecption:", e); } finally { if (channel != null) { try { channel.disconnect(); } catch (Exception e) { log.error("JSch channel disconnect error:", e); } } } return resultLines; }
測試一下:
public static void main(String[] args) throws Exception { Remote remote = new Remote(); remote.setHost("192.168.124.20"); remote.setPassword("123456"); Session session = getSession(remote); session.connect(CONNECT_TIMEOUT); if (session.isConnected()) { System.out.println("Host({}) connected.", remote.getHost()); } remoteExecute(session, "pwd"); remoteExecute(session, "mkdir /root/jsch-demo"); remoteExecute(session, "ls /root/jsch-demo"); remoteExecute(session, "touch /root/jsch-demo/test1; touch /root/jsch-demo/test2"); remoteExecute(session, "echo "It a test file." > /root/jsch-demo/test-file"); remoteExecute(session, "ls -all /root/jsch-demo"); remoteExecute(session, "ls -all /root/jsch-demo | grep test"); remoteExecute(session, "cat /root/jsch-demo/test-file"); session.disconnect(); }
執行后,日志輸出如下內容:
Host(192.168.124.20) connected. >> pwd /root >> mkdir /root/jsch-demo >> ls /root/jsch-demo >> touch /root/jsch-demo/test1; touch /root/jsch-demo/test2 >> echo "It a test file." > /root/jsch-demo/test-file >> ls -all /root/jsch-demo total 12 drwxr-xr-x 2 root root 4096 Jul 30 03:05 . drwx------ 6 root root 4096 Jul 30 03:05 .. -rw-r--r-- 1 root root 0 Jul 30 03:05 test1 -rw-r--r-- 1 root root 0 Jul 30 03:05 test2 -rw-r--r-- 1 root root 16 Jul 30 03:05 test-file >> ls -all /root/jsch-demo | grep test -rw-r--r-- 1 root root 0 Jul 30 03:05 test1 -rw-r--r-- 1 root root 0 Jul 30 03:05 test2 -rw-r--r-- 1 root root 16 Jul 30 03:05 test-file >> cat /root/jsch-demo/test-file It a test file.
執行結果令人滿意,這些常見的命令都成功了
再次好評~
scp操作官方給了很詳細的示例scpTo+scpFrom,再次好評~
scpTo:
public static long scpTo(String source, Session session, String destination) { FileInputStream fileInputStream = null; try { ChannelExec channel = (ChannelExec) session.openChannel("exec"); OutputStream out = channel.getOutputStream(); InputStream in = channel.getInputStream(); boolean ptimestamp = false; String command = "scp"; if (ptimestamp) { command += " -p"; } command += " -t " + destination; channel.setCommand(command); channel.connect(CONNECT_TIMEOUT); if (checkAck(in) != 0) { return -1; } File _lfile = new File(source); if (ptimestamp) { command = "T " + (_lfile.lastModified() / 1000) + " 0"; // The access time should be sent here, // but it is not accessible with JavaAPI ;-< command += (" " + (_lfile.lastModified() / 1000) + " 0 "); out.write(command.getBytes()); out.flush(); if (checkAck(in) != 0) { return -1; } } //send "C0644 filesize filename", where filename should not include "/" long fileSize = _lfile.length(); command = "C0644 " + fileSize + " "; if (source.lastIndexOf("/") > 0) { command += source.substring(source.lastIndexOf("/") + 1); } else { command += source; } command += " "; out.write(command.getBytes()); out.flush(); if (checkAck(in) != 0) { return -1; } //send content of file fileInputStream = new FileInputStream(source); byte[] buf = new byte[1024]; long sum = 0; while (true) { int len = fileInputStream.read(buf, 0, buf.length); if (len <= 0) { break; } out.write(buf, 0, len); sum += len; } //send "