有效地將多個客戶端(位於防火牆後面)同步到伺服器
像許多團隊一樣,我們現在也有人在家工作。這些遠端客戶端位於防火牆後面(我們無法控制),並且它們沒有靜態 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
命令的所有選項。
若干想法
- 您的腳本(大概)以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
因此,我無法有效地測試對您的腳本可能進行的更改。你期待在這裡看到什麼? 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:')
- 如果我們可以看到您的
netstat
命令的預期輸出,則可以使用它awk
來簡化行處理