AutoScaling環境においてGitを使わないでEC2を冗長化する方法

#制約
Git禁止(協力会社がFTPしか使えないため)

#結論
1. Master Serverを建てる
2. Master Serverのファイルの更新をlsyncdで監視し、常にS3と同期する
3. 任意のタイミングでSlave Server(ELB配下の全EC2インスタンス)をS3と同期する


#イメージ図
だいたいこんな感じ

![AutoScaling環境においてGitを使わないでEC2を冗長化する方法](https://qiita-image-store.s3.amazonaws.com/0/189055/14deca70-27fe-f1ad-d321-d7512bfba3e1.png)


#Master　Server編

###1. S3バケットを作成する
公式サイトに詳しい方法が書いてあります。
[S3 バケットを作成する方法 - Amazon Simple Storage Service](http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/user-guide/create-bucket.html)

###2. AWS CLIを設定する
公式サイトに詳しい方法が書いてあります。
[AWS CLI の設定 - AWS Command Line Interface](http://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-getting-started.html)

###3. lsyncdをインストールする
EPELリポジトリを利用してインストールします。

```
$ sudo yum install --enablerepo=epel lsyncd
```

###4. lsyncdの設定を変更する
普通の書き方だとファイル名の末尾に`/(スラッシュ)`が混入してエラーが出たりうまくsyncしてくれなかったので、
下記のような感じでパースしてあげる必要があります。
[komeda-shinjiさんの記事](http://qiita.com/komeda-shinji/items/eaa5e5f71b0b6a760b57)がとても参考になりました。ありがとうございます。

```
$ sudo vim /etc/lsyncd.conf
```

```lua:/etc/lsyncd.conf

source_dir = "/var/www/source"
s3bucket = "files.hoge.com"
prefix = "source"

snscmd = "echo !!! error "

settings {
    logfile    = "/var/log/lsyncd.log",
    statusFile = "/var/log/lsyncd.status",
    nodaemon = false,
    statusInterval = 1,
    delay = 5,
}

cp = function(event)
  local src_path = event.sourcePathname
  local dst_path = event.targetPathname

  if (string.sub(event.source, -1, -1) == "/") then
      src_path = string.sub(event.source, 1, -2) .. event.pathname
  end
  if (string.sub(event.target, -1, -1) == "/") then
      dst_path = string.sub(event.target, 1, -2) .. event.pathname
  end
  local s3cmd = "aws s3 cp '" .. src_path .. "' '" .. dst_path .. "'"
  local msg_body = "command failed: " .. s3cmd
  local msg = " --message '" ..  string.gsub(msg_body, "'", "\"") .. "'"


  local runcmd = "rc=0 && [ -f '" .. src_path .. "' ] && for try in 1 2 3; do " .. s3cmd .. "; rc=$?; [ $rc -eq 0 ] && break; done || " .. snscmd .. msg .. " || :"
  spawnShell(event, runcmd)
end

rm = function(event)
  local src_path = event.sourcePathname
  local dst_path = event.targetPathname

  if (string.sub(event.source, -1, -1) == "/") then
      src_path = string.sub(event.source, 1, -2) .. event.pathname
  end
  if (string.sub(event.target, -1, -1) == "/") then
      dst_path = string.sub(event.target, 1, -2) .. event.pathname
  end

  local s3cmd = "aws s3 rm '" .. dst_path .. "'"
  local msg_body = "command failed: " .. s3cmd
  local msg = " --message '" ..  string.gsub(msg_body, "'", "\"") .. "'"
  local runcmd = "rc=0 && [ ! -f '" .. src_path .. "' ] && for try in 1 2 3; do " .. s3cmd .. "; rc=$?; [ $rc -eq 0 ] && break; done || " .. snscmd .. msg .. " || :"
  spawnShell(event, runcmd)
end

mv = function(event)
  local src_path = event.o.targetPathname
  local dst_path = event.d.targetPathname

  if (string.sub(event.o.target, -1, -1) == "/") then
      src_path = string.sub(event.o.target, 1, -2) .. event.o.pathname
  end
  if (string.sub(event.d.target, -1, -1) == "/") then
      dst_path = string.sub(event.d.target, 1, -2) .. event.d.pathname
  end
  local s3cmd = "aws s3 mv '" .. src_path .. "' '" .. dst_path .. "'"
  local msg_body = "command failed: " .. s3cmd
  local msg = " --message '" ..  string.gsub(msg_body, "'", "\"") .. "'"

  local runcmd = "rc=0 && [ -f '" .. src_path .. "' ] && for try in 1 2 3; do " .. s3cmd .. "; rc=$?; [ $rc -eq 0 ] && break; done || " .. snscmd .. msg .." || :"
  spawnShell(event, runcmd)
end

s3sync = {
    maxProcesses = 1,
    onCreate = cp,
    onModify = cp,
    onDelete = rm,
--  onMove = mv,
}

sync {
    s3sync,
    source = source_dir,
    target = "s3://" .. s3bucket .. "/" .. prefix,
}

```

###5. lsyncdを起動する

```
$ sudo /etc/rc.d/init.d/lsyncd start
$ sudo chkconfig lsyncd on
$ /etc/rc.d/init.d/lsyncd status
lsyncd (pid  12345) is running...
```


###6. Sourceを変更する

```
$ vim /var/www/source/hoge.txt
```

```text:/var/www/source/hoge.txt
hogehoge
```

###7. Sourceの変更がS3に反映しているか確認する

```
$ tailf /var/log/lsyncd.log
```


#Slave　Server編

###1. AWS CLIを設定する
公式サイトに詳しい方法が書いてあります。
[AWS CLI の設定 - AWS Command Line Interface](http://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-getting-started.html)

###2. S3のSourceをローカルに同期するシェルスクリプトを作成する

```
$ vim /usr/local/bin/s3sync.sh
```

```shell-session:/usr/local/bin/s3sync.sh
#!/bin/sh
SOURCE_DIR="/var/www"
S3_BUCKET="files.hoge.com"
PREFIX="source"

TARGET_MASTER="s3://${S3_BUCKET}/${PREFIX}/"
TARGET_LOCAL="${SOURCE_DIR}/${PREFIX}/"

aws s3 sync ${TARGET_MASTER} ${TARGET_LOCAL} --delete

```

###3. 同期スクリプトをEC2インスタンス起動時に実行するように設定する

```
$ vim /etc/rc.local
```

```shell-session:/etc/rc.local
s3sync.sh
exit 0
```

※ちなみに、rc.localに書かれたスクリプトはroot権限で実行されます。


###4. 任意のタイミングで同期スクリプトを実行する
　cronするなり、execするなり


#ボツ案

###Master Server+rsyncパターン
- Master ServerのEC2インスタンスがSPOFになってしまう
- 参考文献:[CDP:Clone Serverパターン - AWS-CloudDesignPattern](http://aws.clouddesignpattern.org/index.php/CDP:Clone_Server%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)
- 参考文献:[【AWS】rsyncでEC2インスタンスを同期 - Qiita](http://qiita.com/daikiichikawa/items/f04e6bd6e4d615dfcd4b)
- 参考文献:[同期設定(rsync)を今一度整理してみました - Qiita](http://qiita.com/mitzi2funk/items/9308db56829d7b4cb90d)

###s3Syncパターン
- 監視ツールを入れない場合、cronなどで定期的に同期する必要がある
- 参考文献:[【aws】オートスケーリング(Auto Scaling)を実際に動かしてみてec2の冗長化に入門する - とりあえずphpとか](http://kimagureneet.hatenablog.com/entry/2016/07/11/145044)
- 参考文献:[～AutoScalingのコンテンツ同期～ | ナレコムAWSレシピ](https://recipe.kc-cloud.jp/archives/8652)

###s3fsパターン
- 速度が遅い
- 動作が不安定
- パーミッションバグがある
- 参考文献:[AWS s3fsを使って複数EC2インスタンスに共有ストレージを作る – Hiro Fukami's Blog](https://hirofukami.com/2013/06/08/aws-s3fs-multi-ec2/)


###NFSパターン
- NFSインスタンス分のコストがかかる
- どうせなら、Amazon EFSの東京リージョンを待ちたい
- 参考文献:[CDP:NFS Sharingパターン - AWS-CloudDesignPattern](http://aws.clouddesignpattern.org/index.php/CDP:NFS_Sharing%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)

###AMIを最新にしてから起動するパターン
- ファイル更新の度に起動し直す必要がある
- 参考文献:[AutoScaling導入によって得たメリットと気をつけたいポイント - Qiita](http://qiita.com/maaaato/items/6b2d067022d9b02c1dae)