我需要想法来创建一种通过多跳远程执行(任何)命令的方法。主要问题是中间跳数是可变的!所以我需要 1) 描述服务器的“拓扑”,然后 2) 有一个函数?/脚本可以在所有服务器上执行一些命令,根据需要使用中间跃点到达最终目的地。
拓扑示例:(但这也有所不同)
server A communicates with server B directly.
servers C and D are "behind" B.
server E is behing C
etc.... (many servers, many indirect ways to reach some of them, some have multiple ways)
Then from A, you can execute "some command" on E with:
ssh foo@B ssh bar@C ssh baz@E "some command"
or you can build intermediary tunnels connecting A to **C** and then:
ssh -P 1234 baz@E "some command"
我有一些“主跳”可以直接到达很多服务器
我希望能够通过一个不太复杂的命令在所有 5 台服务器上执行一个(可能很复杂的)命令,允许我(在脚本中)执行类似的操作
do_on_all_servers "for dir in /something* ; do cd \"\$dir\" && something using $thisLOCALvariable ; done"
# with an easy way to say "from X, ssh to specificuser@Y using this ssh command and this username" to describe each intermediary hops
# dir is a remote variable, thisLOCALvariable is at the initiating script level, and the amount of "\" needs to be adjusted if going through multiple hops...
附加约束:没有 GNU 工具,没有“nc”,只是普通(非常)旧的 ssh 和 awk 以及类似的旧工具,以实现可移植的方式。可移植性必须很高(必须避免使用漂亮的“1990?”选项和工具)。我不想通过在最终目标上复制脚本并通过该脚本的多个跃点进行 ssh 来完成此操作,但如果您认为它更简单/更好,这也可以。
现在我正在使用变量来连接 ssh ....但这对解决“我需要多少”问题没有帮助。
我的想法包括:将包含“shh user@host”的变量串在一起。或者也许使用这个非常简洁https://stackoverflow.com/a/15198031/1841533隧道方式,如果我能正确地做到这一点,则允许使用正确的本地端口“直接”到达最终目的地(但不确定这种方式在所有跃点上都可用)进行 scp 等。
答案1
您可以编写/使用 shell 函数包装ssh
来生成自定义配置文件,该文件使用嵌套ProxyCommands
来到达最终主机。然后该函数将调用ssh
并-F
指向生成的临时配置文件。
像这样的东西:
sssh() {
TMPF=$(mktemp);
echo -n "$1" |
awk '
BEGIN {RS=" "}
{ split($0,a,":"); u=a[1]; p=a[3]; h=a[2]
print "\nHost "h"\n\tUser "u"\n\tPort "p;
if (hop) print "\tProxyCommand ssh -q -W %h:%p "hop;
hop=h }
END {print "\nHost *\n\tControlMaster auto\n\tControlPath /tmp/%r@%h:%p"}
' > $TMPF; TMPA=($1); TMPAA=(${TMPA[-1]//:/ }); TMPH=${TMPAA[1]}; shift;
ssh -F $TMPF $TMPH $@;
rm $TMPF; }
当这样运行时:
sssh "user1:so.me.ho.st:1234 user2:router.internal:2345 user3:device.internal:3456" do_complex_command
会生成一个像这样的 tmp 文件:
Host so.me.ho.st
User user1
Port 1234
Host router.internal
User user2
Port 2345
ProxyCommand ssh -q -W %h:%p so.me.ho.st
Host device.internal
User user3
Port 3456
ProxyCommand ssh -q -W %h:%p router.internal
Host *
ControlMaster auto
ControlPath /tmp/%r@%h:%p
最后运行:
ssh -F /tmp/tmp.IUVSRrer45 device.internal do_complex_command
这将do_complex_command
在最后一个“最内部”主机上执行。如果您需要在所有中介上执行命令,则必须稍微调整该功能。
答案2
我找到了一个相当可移植的解决方案(不需要对 ssh 配置文件进行任何编辑,并且很容易包含在调用脚本本身中,并且非常易于使用):只需将 ssh 串在一起,最后一个调用 shell ( ksh 或 bash 对于“有点旧”的 UNIX 运行良好):
echo "for remotedir in /some/path/*/ ; do \
cd \"\$remotedir\" && \
something using $thisLOCALvariable \
; done" | ssh user1@host1 ssh user2@host2 ssh user3@host3 "bash"
# note: the 'obvious' approach:
# ssh u1@h1 ssh u2@h2 "cmd1 ; cmd2; ..."
# does NOT work : it would execute "cmd2; ..." on h1, not on h2 !!!
# only cmd1 would be executed on h2 ... BE CAREFUL ! (at least on a not-too-old cygwin)
这个例子表明我们可以混合本地变量和远程变量......并且需要“转义”远程变量,以及远程双引号等......需要小心以确保我们做到我们的意思去做。
但它非常方便,因为即使我们将 3 个或更多 ssh 嵌套串在一起,总是有相同的(只有 1!)级别的引用,这非常方便......它$(cmd)
与旧的`
(反引号)相比带来的类似)的做法:它简化了书写,而不必考虑嵌套的层次。
警告:将其作为脚本的参数(就像我在问题中问的那样on_all_servers "for dir in /something* ; do cd \"\$dir\" && something using $thisLOCALvariable ; done"
:)有点难以弄清楚,因为调用 shell 首先解释“\”和引号...所以现在我使用命令-行版本(或带有脚本的类似构造,但不是从其参数获取命令行的脚本)...
我仍然保留这个问题,希望有人能得到更好的解决方案(或对此的改进,即:允许将类似的命令作为参数传递给“调用脚本”)
编辑:我还发现了如何使用这种方法进行远程 tar 提取!这并不简单,因为需要将 stdin 提供给远程 shell,一旦到达正确的目录,然后本地 tar cf - 需要转发到远程 tar 的正确位置:这里是:
# the easy one, nothing special needed: from remote to local:
echo "cd /remote/path && tar cf - *" | ssh user1@host1 ssh user2@host2 ssh user3@host3 "bash" | ( cd /local/path && tar xvf - )
# and the hard one: the other way around: needs a trick:
( cd /local/path && tar cf - * ) | { { echo "cd /remote/path/ && tar xvf -" ; cat ;} | ssh user1@host1 ssh user2@host2 ssh user3@host3 "bash" ;}
#once again: this trick allows you to have 1, 2, or any level of ssh.
# PLEASE NOTE: I just run bash, and feed it the command, to avoid
# the command to be split between the remote hose and the first hop!
# ex of the way the 'obvious' way fails:
# ssh user1@host1 ssh user2@host2 "hostname ; hostname;"
# will return not twice the name "host2", but "host2" then "host1" !!
# which means the first part of the command was executed on the last hop,
# but then the rest of it was executed on the first hop!!! DANGEROUS!
# that is why I choose instead to use that way:
# echo "hostname; hostname" | ssh user1@host1 ssh user2@host2 "bash"
# this one properly executes the full (complex) command on host2