個人的にベストなS3との連携方法

今までS3との連携はs3fsしか使った事がなかったんですが、s3fsの速度があまりに遅くてNGで今回他の方法を探ってみました。今回s3fsの他にもs3cmdも使ってみたんですが、最終的にlsyncdAWS CLIを利用する方法に落ち着きました。この方式安定していて個人的にとても気に入ってます^^

公式のAWS CLIがやっぱりおすすめ

AWS CLIのS3系のコマンドはかなり使えます。たとえば、下記のようにするとローカルのディレクトリをS3の任意の場所に同期してくれます。

$ aws s3 sync --exact-timestamps --delete /path/to/local/source s3://bucket/path/to/target

このsync以外にもget、put、removeなど基本的な操作が簡単に実行できるようになっています。詳しくはマニュアルがあります。そして何よりAWS公式のツールなので安心感があります。

lsyncdと組み合わせて即時処理する

ただ、このコマンドをcron等で定期的に実行するようにするとS3にアップロードできるまでに定期実行を待つため時間がかかってしまいます。そこでlsyncdと組み合わせて利用するようにしました。lsyncdはOSのファイルの変更を検知してrsync等を実行するデーモンなんですが、Luaというスクリプト言語で設定が記述できるようになっていて、rsync以外にも自分で処理を組み込むことができるようになっています。

lsyncdのAPIはマニュアルに詳しく記載があります。APIの利用を低抽象度のものからLayer1からLayer4に分けて説明されているのですが、今回ぼくはLayer2のインタフェースを利用して実装しました。

lsyncd.conf

settings {
    logfile = "/var/log/lsyncd.log",
    statusFile = "/tmp/lsyncd.stat",
    statusInterval = 5,
    maxProcesses = 10,
}

handleCreate = function(event)
  if event.isdir then
    return
  end
  if string.match(event.sourcePathname, "directory-to-ignore") then
    return
  end
  spawnShell(event, "aws s3 cp " .. event.sourcePath .. " " .. event.targetPath)
end

handleDelete = function(event)
  if event.isdir then
    return
  end
  if string.match(event.sourcePathname, "directory-to-ignore") then
    return
  end
  spawnShell(event, "aws s3 rm " .. event.targetPath)
end

awscli_sync = {
  maxProcesses = 10,
  onStartup = "aws s3 sync --exact-timestamps --delete ^source ^target/",
  onCreate = handleCreate,
  onModify = handleCreate,
  onDelete = handleDelete
}

sync {
  awscli_sync,
  source = "/path/to/source",
  target = "s3://bucket1"
}

設定ファイルに直接コーディングできるのが良いですね。この設定で自分でテストした限りではファイルの作成時・変更時・移動時・削除時ともに想定通りに動いてくれました。/var/log/lsyncd.logを見てると即時にファイルがアップされたり消されたりしてるのが分かるので見ていて気持ち良いです。

ちなみに下記のコードですが、

  if string.match(event.sourcePathname, "directory-to-ignore") then
    return
  end

これは一時ファイル等で一瞬で作成の後に消えるディレクトリ・ファイルがあった場合にうまくいかなかったので、そういった一時領域のディレクトリ等のパスを正規表現で判定して無視するようにしました。WordPressの場合、プラグインのインストール時にupgradeフォルダに一瞬大量にファイルが作成されるようでそれを無視するように設定しました。

他の連携方法について

最後に今回試した他の方法について見送った理由を書いておきます。

s3fs

s3fsはS3をファイルシステムとしてマウントする方式のツールです。一旦マウントすると通常のファイルシステムと同じように使えるため抽象度が高く論理的にとても扱いやすいです。

https://code.google.com/p/s3fs/wiki/FuseOverAmazon

見送った理由
  • 速度が遅かった
  • 動作に不安な点があった

S3をマウントする形になるのでSANやNFSのような共有ディスクのイメージで利用するのにお手軽に導入できます。送受信されるファイルが少なければ良いのですが、ファイルが多い場合など速度の遅さが目につきました。これが今回は致命的でした。

また、これは自分の理解不足によるところも大きいかと思いますが、動作に不安なところがありました。不安だったところはファイルのパーミッション(マウント時に000?になる事がありました)、lsyncdでrsync先をs3fsでマウントしたディレクトリにした場合にrsyncは成功するのにエラーログが出る(rsync: failed to set times on “/s3fs1/.”: Input/output error (5))、解消方法が分からないなどです。

s3cmd

s3cmdはS3と連携するためのコマンドラインツールです。AWS-CLIのS3と同じような形でsync, get, put, removeなどが実行できます。

http://s3tools.org/s3cmd

syncのコマンド例

$ s3cmd -c /root/.s3cfg -P -r --delete-removed sync /path/to/source s3://bucket1/path/to/target
見送った理由
  • 日本語名のファイルがうまく扱えなかった

これは環境に依存する問題かもしれませんが、少なくとも自分の環境(MacOS, CentOS6, Amazon Linux)だと最新版の1.6は全滅でした。日本語のファイルの場合、「ERROR: Parameter problem: Nothing to upload.」というエラーが発生して失敗します。過去のバージョン1.01だとMacOSでは成功しましたが、CentOS・Amazon Linuxでは解消できませんでした。回避策はいろいろ調べてみたんですがどれも効果なく、これが致命的で今回は採用を見送りました。

というわけで今回はAWS-CLIを利用した連携方法に落ち着きました。またAWSの新サービスEFSが使えるようになると変わってくるかもしれませんが、それまではAWSでのファイル共有は個人的にはこの方式でいこうかなと思います。

ちなみにアップロード時はlsyncdで即時で実行できるんですが、ダウンロードする側のEC2はcronで定期的にaws s3 syncを実行するようにしてます。これもさらに改善できると本当は良いのですが…。