share facebook facebook2 twitter menu hatena pocket slack

2015.03.03 TUE

lsyncdで双方向同期するなら、delete='running' がいい

WRITTEN BY今岡 久敏

こんにちは、cloudpack今岡 です。

lsyncd を使って、2台のサーバ上のファイルを双方向からの同期を検証していた時に、予期せぬファイル削除が発生しました。その謎と対処方法について迫ります。

lsyncdで双方向同期とは?

lsyncd は自分の特定のディレクトリを見張り、変更(ファイルの追加、変更、削除等)のイベントが発生した場合、それをトリガーに他のサーバへ伝搬したりすることができます。しかし、 lsyncd はあくまでも自サーバのファイル変更を捕まえるだけなので、相手のサーバに起こった変更はわかりません。

そこで、例として2台のサーバでお互いに lsyncd をかけることにより、2台が全く同じ状況を作ります。下図のような構成です。(絵はAWSですが、別にAWSに限った話ではないです。)
lsyncdで双方向同期するなら、delete='running' がいい: 構成イメージ図

lsyncインストール

CentOS 6系の場合は、下記を参考にインストールできます。

このエントリにも記載があるように、initスクリプトがミスっていますので、修正が必要です。 /etc/lsyncd.conf に設定ファイルを置く場合は、ここの修正は不要ですが、上記を参考にアレンジしましょう。

設定ファイル

lsyncd は 2.1 系で大きく設定ファイルが変更されています。 2.0系の設定ファイルはまず動かないと思います。ここはマニュアル読むしか無いです(日本語の2.1系の情報は少ないと言われています。)

とりあえず、設定例を載せておきます。

settings {
   logfile = "/var/log/lsyncd/lsyncd.log",
   statusFile = "/var/log/lsyncd/lsyncd-status.log",
   statusInterval = 20
}

sync {
    default.rsync,
    delay = 0,
    source="/tmp/lsync-test",
    target="[user]@[相手ホスト]:/tmp/lsync-test/",
    rsync = {
        rsh = "/usr/bin/ssh -i [ssh-key] -o StrictHostKeyChecking=no"
    }
}

/tmp/lsync-test にファイルを書いたり消したりして、動作を確認しましょう。問題無いことが分かります。(循環が発生しそうですが、大丈夫でした)

障害時を想定し、片系を止める

ためしに図の サーバA を止めてみます。すると、LBはすべてのリクエストをサーバBに流れます。サーバBに対して書き込まれたファイルは、 lsyncd によりサーバAに伝搬しようとしますが、Aは死んでいるので受け取れません。その時 lsyncd はどうなるのか。

答えは、リトライを繰り返すです。BはAが死んでいても、思いを伝えようと、何度も何度もリトライします。健気です、切ないです。。
lsyncdで双方向同期するなら、delete='running' がいい: 障害時を想定し、片系を停止
ですが、この動作を利用すれば、このままAが復帰した暁には、Bの思いはAに届きそうです。やったね!サーバB!

無慈悲な現実、サーバAが復帰すると、サーバBのファイルが消された!

なんと無残な!この事象の発生メカニズムはこうです。

  1. Aが死んだ
  2. Bにリクエストが全部行く、これにより AとBに差分が生まれた
  3. BはAに同期を通うとするが、Aは死んでいるので受け取れない
  4. Aが復帰した
  5. B->Aの同期が届く前に A->B の同期が発生した場合、 2.で生じた差分が、DeleteとなってBに伝搬する!

lsyncdで双方向同期するなら、delete='running' がいい: 障害から復帰した場合の無慈悲な現実
5については、B->Aの同期が早く届く場合もあるので、100%ではありませんが、実際に確認することは出来ました。これを何とかしましょう。

delete='running'を使おう!

上記にデフォルトの動作として delete=true のかわりに delete="running" とすれば、スタートアップ時の削除は抑制できるよーと書いてあります。早速適応して、試すとうまく行きました。

settings {
   logfile = "/var/log/lsyncd/lsyncd.log",
   statusFile = "/var/log/lsyncd/lsyncd-status.log",
   statusInterval = 20
}

sync {
    default.rsync,
    delay = 0,
    source="/tmp/lsync-test",
    target="[user]@[相手ホスト]:/tmp/lsync-test/",
    delete="running",
    rsync = {
        rsh = "/usr/bin/ssh -i [ssh-key] -o StrictHostKeyChecking=no"
    }
}

めでたしめでたし。

と・・・油断させといて・・・

実はこれで終わりではないです。確かにファイル喪失という最悪の事態は回避できますが、今度はB単独時に削除されたファイルを復帰させてしまう可能性があります。

  1. A,B 同期が取れてる状態で、x.txt というファイルがあった
  2. A死亡
  3. Bでx.txtを削除
  4. A復帰
  5. A が x.txt をBに伝搬!

これを抑制するには init = false を指定しましょう、これもマニュアルに書いてあり、扱いは要注意とのことです。

元記事はこちらです。
lsyncdで双方向同期するなら、delete='running' がいい

今岡 久敏

「常に新しいモノの方が、古いモノより優れている、というマインドを持てなくなった時、それはエンジニアとしての死を意味する」え、誰の言葉だって?俺の言葉だよ。