有效地将多个客户端(位于防火墙后面)同步到服务器

有效地将多个客户端(位于防火墙后面)同步到服务器

和许多团队一样,我们现在有人在家工作。这些远程客户端位于防火墙后面(我们无法控制),并且它们没有静态 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 的精彩问题和评论的答复。

  1. 文件服务器上的脚本以 root 身份运行。客户端上的脚本不是。

  2. & 7.这是我的 netstat 命令的示例输出

netstat -Wpetl
tcp 0 0 localhost.localdomain:22222 0.0.0.0:* LISTEN  myuser 42137  8381/sshd: myuser
  1. “你有竞争条件......” - 谢谢。我们暂时忽略这个问题。

  2. “你有一个遗漏问题......” - 再次谢谢你。我相信这在客户端很容易解决。这是将在用户登录时启动的客户端脚本:

#!/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

  1. 这是服务器脚本的当前版本:
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
  1. “您应该考虑对客户端的每个 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

一些想法

  1. 您的脚本(大概)以 root 身份运行,因此netstat -Wpet可以运行并且sudo -u ${user}操作得到简化。

  2. 使用反向连接,例如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
    

    因此,我无法有效地测试您的脚本可能发生的更改。你期待在这里看到什么?

  3. 您存在竞争条件,因此如果在inotifywait完成后更改了第二个文件,则在更改另一个文件之前,该文件可能不会传播到所有目标系统。

    解决此问题的方法可能是侦听来自单个实例的事件,并对每个事件inotifywait运行一组传输。rsync但是,根据更新频率,这可能会使您客户端的网络连接饱和

  4. 您有一个遗漏问题,因为在一组更改之后连接的客户端将不会收到这些更改,直到下一个文件更改。如果更新如此重要,您需要考虑某种方式在连接后立即更新客户端副本

  5. ssh您应该考虑每个/到客户端的超时rsync,这样如果它们在您尝试传输时断开连接,您最终不会阻止其他人

  6. 给定这样的代码片段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:')
    
  7. 如果我们可以看到netstat命令的预期输出,则可以用来awk简化行处理

相关内容