【发布时间】:2014-04-07 21:51:00
【问题描述】:
我需要在许多远程机器上运行 Java 程序。我在循环中使用 ssh 并调用运行 Java 程序的远程脚本。
你可以想象,这是用于测试集群上的分布式系统。
问题是,在我输入第一个 ssh 会话的密码后,脚本立即挂起。这可能是 bash 错误,因为 Java 程序在本地运行良好。
确切的结构是这样的,一个运行许多远程 bash 脚本的本地 bash 脚本。每个远程脚本编译并运行一个 Java 程序。这个 Java 程序启动一个单独的线程来做一些工作。当收到 SIGINT 信号时,通知 Java 线程以便它可以干净地退出。
我做了一个简化的工作示例。
编辑:下面的代码现在可以工作(为后代修复)
如果你想回答,请不要对代码的结构做太多改动,否则它不会像原来的那样,我也无法理解问题所在。
手动运行的 Bash 脚本
#!/bin/bash
function startBatch()
{
#the problem was using -n
ssh -f "$1" "cd $projectDir;./startBatch.sh $2"
}
function stopBatch()
{
#the problem was using -n
ssh -f "$1" "pkill -f jnode_.*"
}
projectDir=NetBeansProjects/Runner
#start nodes
nodeNumber=0
while read node; do
startBatch "$node" "$nodeNumber"
nodeNumber=$(($nodeNumber + 1))
done < ./nodes.txt
sleep 3
#stop nodes
while read node; do
stopBatch "$node"
done < ./nodes.txt
由其他脚本运行的 Bash 脚本
#!/bin/bash
#this is a simplified working example
myNumber=$1
$(exec -a jnode_"$myNumber" java -cp build/classes runner.Runner "$myNumber.txt")
这是上述脚本的简化版本。如果您想要正确的日志记录,请检查已接受答案的第二部分。
#!/bin/bash
batchNumber=$1
procNumber=0
batchSize=3
while [ "$procNumber" -lt "$batchSize" ]; do
procName="$batchNumber"_"$procNumber"
#this line was no good
#$(exec -a jnode_"$procName" java -cp build/classes runner.Runner "$procName.txt" &)
#this line works fine
exec -a jnode_"$procName" java -cp build/classes runner.Runner "$procName.txt" 1>/dev/null 2>/dev/null &
procNumber=$(($procNumber + 1))
done
Java Runner(启动线程的东西)
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
public class Runner {
public static void main(String[] args) throws FileNotFoundException, InterruptedException {
//redirect all outputs to a given file
PrintStream output = new PrintStream(new File(args[0]));
System.setOut(output);
System.setErr(output);
//controlled object
final MyRunnable myRunnable = new MyRunnable();
//shutdown the controlled process on command
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
myRunnable.stop = true;
}
});
//run the process
new Thread(myRunnable).start();
}
}
Java MyRunnable(正在运行的线程)
public class MyRunnable implements Runnable {
public boolean stop = false;
@Override
public void run() {
while (!stop) {
try {
System.out.println("running");
Thread.sleep(1000);
} catch (InterruptedException ex) {
System.out.println("interrupted");
}
}
System.out.println("stopping");
}
}
不要在你的 Java 程序中使用 System.exit(),否则关闭钩子将不会被正确调用(或完全执行)。从外部发送 SIGINT 消息。
正如在 cmets 中提到的,输入密码可能很无聊。无密码的 RSA 密钥是一种选择,但我们可以做得更好。让我们添加一些安全功能。
创建公钥/私钥对
ssh-keygen -t rsa
Enter file in which to save the key (home/your_user/.ssh/id_rsa): [input ~/.ssh/nameOfKey]
Enter passphrase (empty for no passphrase): [input a passphrase not weaker than your ssh password]
将公钥添加到远程主机的authorized_keys文件中,这样就可以进行认证了。
#first option (use proper command)
ssh-copy-id user@123.45.67.89
#second option (append the key at the end of the file)
cat ~/.ssh/nameOfKey.pub | ssh user@123.45.67.89 "cat >> ~/.ssh/authorized_keys"
现在,如果我们使用 ssh-agent,我们可以让密码只被询问一次(在执行第一个命令时)。请注意,它会询问密码(创建密钥时输入的密码),而不是实际的 ssh 密码。
#activate the agent
eval `ssh-agent`
#add the key, its passphrase will be asked
ssh-add ~/.ssh/keyName1
#add more keys, if needed
ssh-add ~/.ssh/keyName2
您现在拥有一个非常简单但功能强大的分布式系统测试框架。玩得开心。
【问题讨论】:
-
我知道这不是您问题的直接答案,但一种可能性是在您的机器上创建一个无密码密钥 (
ssh-keygen -t rsa),然后将公钥粘贴到每个的authorized_keys2远程机器,那么您在从您的机器连接时不必处理密码。 SSH 密码提示有时会对脚本交互性造成严重破坏。带有相关的安全陷阱,但它们可能与您的情况无关。
标签: java bash shell ssh remote-access