複数のクライアント(ファイアウォールの背後)をサーバーに効率的に同期します。

複数のクライアント(ファイアウォールの背後)をサーバーに効率的に同期します。

多くのチームと同様に、現在自宅で働いている人がいます。これらのリモートクライアントはファイアウォールの背後にあり(制御できない)、静的IPアドレスはありません。つまり、SSHを介してこれらのクライアントに直接アクセスすることはできません。ただし、クライアントはSSHを介してサーバーにアクセスできます。 (他の理由で強化されたSSHは、すでにすべてのクライアントとサーバーに設定されています。)

私たちの要件は、各クライアントで同期されたファイルセット(複数のディレクトリにあります)を維持し、効率的に実行することです。私は各クライアントがrsyncNN秒ごとにコマンドを実行しないように努めています。サーバーの関連ファイルが変更された場合は、クライアントに通知することをお勧めします。

また、私たちの実装では、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完了後に2番目のファイルが変更されると、他のファイルが変更される前にそのファイルがすべてのターゲットシステムに伝播されない可能性があります。

    この問題に対する解決策は、単一インスタンスからイベントを受信し、inotifywait各イベントに対して一連の転送を実行することです。rsyncただし、更新の頻度によっては、クライアントのネットワーク接続が飽和する可能性があります。

  4. 一連の変更後に接続するクライアントは、次回までその変更を受け取らないため、欠落している問題があります。ファイルが変更されます。アップデートが非常に重要な場合は、接続したらすぐにクライアントのコピーを更新する方法を検討する必要があります。

  5. sshrsync転送中に接続が切断された場合は、他の人をブロックしないようにクライアント固有のタイムアウトを考慮する必要があります。

  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ライン処理を簡素化するために使用できます。

おすすめ記事