和许多团队一样,我们现在有人在家工作。这些远程客户端位于防火墙后面(我们无法控制),并且它们没有静态 IP 地址。简而言之,我们无法直接通过 SSH 访问这些客户端。但是,客户端可以通过 SSH 访问我们的服务器。 (由于其他原因,所有客户端和服务器上都已经设置了强化 SSH。)
我们的要求是在每个客户端上保持一组文件(在几个不同的目录中)同步,并且高效地做到这一点。我试图避免让每个客户端rsync
每 NN 秒运行一个命令。当服务器上的任何相关文件发生更改时,最好通知客户端。
此外,我们的实现只能使用 SSH、rsync、inotify 工具以及 bash 或 Python(以及 awk、cut 等工具)。具体来说,我们不能使用NextCloud、OwnCloud、SyncThing、SeaFile等。
服务器上唯一开放的传入端口用于 SSH,我们希望维护或更新的唯一软件包是发行版存储库中的核心软件包。
我们的想法是让每个客户端建立一个到服务器的反向 SSH 隧道。然后服务器可以运行这样的脚本:
#!/bin/bash
while true; do
inotifywait -r -e modify,attrib,close_write,move,create,delete /path/to/source/folder
for port_user in "$(netstat -Wpet | grep "ESTABLISHED" | grep 'localhost.localdomain:' | grep 'sshd:' | cut -d ':' -f2-3 | cut -d ' ' -f1,4)"; do
uport=$(echo $port_user | cut -d ' ' -f1)
uu=$(echo $port_user | cut -d ' ' -f2)
sudo -u $uu rsync -avz -e "ssh -p $uport -i /home/$uu/.ssh/id_ed25519" /path/to/source/folder $uu@localhost:/path/to/destination/folder
done
done
我正在寻求反馈。首先,上面的bash脚本可以改进或者清理吗?cut
例如,我似乎不得不使用太多的语句。
编辑:以下是对 roaima 的精彩问题和评论的答复。
文件服务器上的脚本以 root 身份运行。客户端上的脚本不是。
& 7.这是我的 netstat 命令的示例输出
netstat -Wpetl
tcp 0 0 localhost.localdomain:22222 0.0.0.0:* LISTEN myuser 42137 8381/sshd: myuser
“你有竞争条件......” - 谢谢。我们暂时忽略这个问题。
“你有一个遗漏问题......” - 再次谢谢你。我相信这在客户端很容易解决。这是将在用户登录时启动的客户端脚本:
#!/bin/bash
synchost=sync.example.com
syncpath="path/to/sync/folder"
uu=$(logname)
uport=222222 #hard code per client device
# initial sync upon connecting:
rsync -avzz -e "ssh -i /home/$uu/.ssh/id_ed25519" /"$syncpath"/ $uu@$synchost:/"$syncpath"
# loop until script is stopped when user logs out
while true; do
inotifywait -r -e modify,attrib,close_write,move,create,delete /"$syncpath"
rsync -avzz -e "ssh -i /home/$uu/.ssh/id_ed25519" /"$syncpath"/ $uu@$synchost:/"$syncpath"
done
还有一个按需脚本,用户可以随时运行以强制同步。这是上面没有循环的脚本while
。
- 这是服务器脚本的当前版本:
syncpath="path/to/sync/folder"
while true; do
inotifywait -r -e modify,attrib,close_write,move,create,delete /"$syncpath"
netstat -Wpetl | grep "LISTEN" | grep 'localhost.localdomain:' | grep 'sshd:' | while read proto rq sq local remote state uu inode prog
do
uport=${local#*:}
sudo -u $uu rsync -avzz -e "ssh -p $uport -i /home/$uu/.ssh/id_ed25519" /"$syncpath"/ $uu@localhost:/"$syncpath"
done
done
- “您应该考虑对客户端的每个 ssh/rsync 进行超时,这样,如果它们在您尝试传输时断开连接,您最终不会阻止其他人”。
这是一个很好的建议。但是,某些有效rsync
更新的运行时间可能比平均时间长得多。您能否建议一种适当的方法来处理正常和必要的长时间rsync
更新,同时还处理更新期间客户端断开连接的罕见情况?
我有一个想法,可以通过一种非常非常简单的方式解决超时以及(大部分)竞争条件。首先,每个用户登录时的初始客户端同步应处理长时间运行的更新操作。因此,服务器端同步操作时间不会有这么长的右尾。我们可以优化超时参数和睡眠时间,并使用如下方法:
syncpath="path/to/sync/folder"
while true; do
inotifywait -r -e modify,attrib,close_write,move,create,delete /"$syncpath"
netstat -Wpetl | grep "LISTEN" | grep 'localhost.localdomain:' | grep 'sshd:' | while read proto rq sq local remote state uu inode prog
do
uport=${local#*:}
timeout 300s sudo -u $uu rsync -avzz -e "ssh -p $uport -i /home/$uu/.ssh/id_ed25519" /"$syncpath"/ $uu@localhost:/"$syncpath"
done
sleep 90
netstat -Wpetl | grep "LISTEN" | grep 'localhost.localdomain:' | grep 'sshd:' | while read proto rq sq local remote state uu inode prog
do
uport=${local#*:}
timeout 900s sudo -u $uu rsync -avzz -e "ssh -p $uport -i /home/$uu/.ssh/id_ed25519" /"$syncpath"/ $uu@localhost:/"$syncpath"
done
done
最后一条评论。命令显示的参数rsync
不是最终的。感谢您的建议,但我们也打算花一些时间评估命令的所有选项rsync
。
答案1
一些想法
您的脚本(大概)以 root 身份运行,因此
netstat -Wpet
可以运行并且sudo -u ${user}
操作得到简化。使用反向连接,例如
ssh -R 20202:localhost:22 centralserver
我无法从线路获取端口和用户组合netstat | grep | grep | cut ...
。netstat -Wpet | grep "ESTABLISHED" | grep sshd: tcp 0 36 centralserver:ssh client:37226 ESTABLISHED root 238622975 15198/sshd: roaima
因此,我无法有效地测试您的脚本可能发生的更改。你期待在这里看到什么?
您存在竞争条件,因此如果在
inotifywait
完成后更改了第二个文件,则在更改另一个文件之前,该文件可能不会传播到所有目标系统。解决此问题的方法可能是侦听来自单个实例的事件,并对每个事件
inotifywait
运行一组传输。rsync
但是,根据更新频率,这可能会使您客户端的网络连接饱和您有一个遗漏问题,因为在一组更改之后连接的客户端将不会收到这些更改,直到下一个文件更改。如果更新如此重要,您需要考虑某种方式在连接后立即更新客户端副本
ssh
您应该考虑每个/到客户端的超时rsync
,这样如果它们在您尝试传输时断开连接,您最终不会阻止其他人给定这样的代码片段
bash
,您可以用cut
变量操作(%
、#
和/
运算符)替换语句while read -r proto recvq sendq localaddrport foreignaddrport state user inode pidprogram name do localaddr="${localaddrport%:*}" localport="${localaddrport#*:}" foreignaddr="${foreignaddrport%:*}" foreignport="${foreignaddrport#*:}" pid="${pidprogram%/*}" program="${pidprogram#*/}"; program="${program%:}" echo "Foreign address = $foreignaddr and port = $foreignport" echo "PID = $pid, program = $program" echo "Name = $name" done < <(netstat -Wpet | grep '\<localhost.localdomain:.*\<ESTABLISHED\>.*/sshd:')
如果我们可以看到
netstat
命令的预期输出,则可以用来awk
简化行处理