Capistranoを使って、オートスケール(Auto Scaling)したAWS EC2インスタンスに自動でセルフデプロイ(self-deploy)して最新のソースコードを反映させる方法『Bootstrapパターン』


# オートスケール(Auto Scaling)
AWSでは、ある状態のEC2インスタンスを記録したAMI（マシンイメージ、スナップショットのようなもの）を作成することができます。
作成したAMIは、サーバの負荷に応じて台数を変化させる「オートスケール(Auto Scaling)」機能によって新しくサーバを立ち上げるときに使用されます。

![6wNg0ISJczU5Pz1m-67124.png](https://qiita-image-store.s3.amazonaws.com/0/14932/01db6d31-97a2-3a79-6b81-9878ecfe56b6.png "6wNg0ISJczU5Pz1m-67124.png")
[CDP:Scale Outパターン](http://aws.clouddesignpattern.org/index.php/CDP:Scale_Out%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)

ここで問題なのが、AMIはある時点でのEC2インスタンスの状態をそのまま記録しただけのものなので、例えばソースコードが更新されていった場合に最新の状態とずれていってしまうことです。

# Bootstrapパターン

この問題の解決法として『Stampパターン』と『Bootstrapパターン』の2つが主に知られています。

Stampパターンとは、ソースコードなどが更新された場合は、単純にまた新しいマシンイメージを作り直すというものです。
これは直感的な方法ではありますが、少しでも変更が入った場合は毎回マシンイメージを作り直す必要が出てきてしまいます。

Bootstrapパターンでは、マシンイメージからサーバを起動したタイミングで最新のソースコードなどを自動で取得するように設定しておくことで、毎回マシンイメージを作り直す必要がなくなります。

![6wNg0ISJczU5Pz1m-33D32.png](https://qiita-image-store.s3.amazonaws.com/0/14932/aeffb182-bae8-4e82-f7f8-f9d804a14b6e.png "6wNg0ISJczU5Pz1m-33D32.png")

[CDP:Bootstrapパターン](http://aws.clouddesignpattern.org/index.php/CDP:Bootstrap%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)

# Capistranoによるデプロイの仕組み

Capistranoは単純に説明すると、サーバにSSHログインしてからGitHubなどのリモートサーバから最新のソースコードを取得する仕組みです。

注目すべきは、Capistranoのコードを実行するのは手元のローカルPCであるということです。サーバのソースコードを最新にするので本来はサーバ内でいろいろと処理をする必要があるのですが、それをCapistranoがSSHログインして自動的にやってくれます。

![simple_capistrano.png](https://qiita-image-store.s3.amazonaws.com/0/14932/5461dacc-4da2-e9d7-306f-80a884c4f67c.png "simple_capistrano.png")

# CapistranoでBootstrapパターンを実装する際の問題点 → セルフデプロイ(self-deploy)
Capistranoは素晴らしいデプロイツールなのでぜひ活用したいのですが、Bootstrapパターンにおいて最新のソースコードを取得するのはそのサーバ自体です。CapistranoはローカルPCからサーバにSSHログインして使うツールなので、単純には使えません。

そこで解決策として、「自分自身（localhost）にSSHログインしてデプロイする」という方法があります。
多少非効率ではありますが、これならCapistranoをそのまま使ってBootstrapパターンを実装できます。

![self_deploy.png](https://qiita-image-store.s3.amazonaws.com/0/14932/e317b895-4302-c6f4-02dd-717185aa49ac.png "self_deploy.png")
[オートスケール時のデプロイを User Data と Capistrano を使って行う（BootStrap パターン）](http://dev.classmethod.jp/cloud/auto-scaling-deploy-userdata-capistrano/)

# 具体的な実装
## 1.EC2内で自分自身にSSHログインできるようにする
### 公開鍵を設置
```
[ec2-user@ip-x-x-x-x ~]$ ssh-keygen # パスワードなしで生成（何も入力せずEnter）
[ec2-user@ip-x-x-x-x ~]$ cd ~/.ssh
[ec2-user@ip-x-x-x-x .ssh]$ mv id_rsa id_for_localhost # 秘密鍵の名前を変更
[ec2-user@ip-x-x-x-x .ssh]$ mv id_rsa.pub id_for_localhost.pub # 公開鍵の名前を変更
[ec2-user@ip-x-x-x-x .ssh]$ cat id_for_localhost.pub >> authorized_keys # 生成した公開鍵でログインできるように追記（元からあるキーは削除しないでください！）
[ec2-user@ip-x-x-x-x .ssh]$ chmod 600 authorized_keys # 権限を変更
```

SSHの公開鍵認証について理解したい人は以下から。
[SSHの仕組み!ぼんやりとした理解だったものをすっきりさせようの会](http://nigohiroki.hatenablog.com/entry/2013/08/18/221434)

### SSHログインを確認
```
[ec2-user@ip-x-x-x-x ~]$ ssh -i ~/.ssh/id_for_localhost ec2-user@localhost
The authenticity of host 'localhost (127.0.0.1)' can't be established.
ECDSA key fingerprint is xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'localhost' (ECDSA) to the list of known hosts.
Last login: Tue Jul  8 13:05:16 2014 from localhost
 
       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|
 
https://aws.amazon.com/amazon-linux-ami/2014.03-release-notes/
[ec2-user@ip-x-x-x-x ~]$ 
```
このように表示されたら自分自身にログインできています。

### フィンガープリントのチェックを省略する
EC2 は AMI から起動すると毎回ホスト鍵が変わるため、Capistrano からログインを行う際に、再度フィンガープリントの確認が対話的に行われてしまい、デプロイが上手くいかなくなります。それを回避するために、フィンガープリントのチェックを省略するように設定します。``~/.ssh/config``に以下を記述してください。

```sh:~/.ssh/config
Host *
  StrictHostKeyChecking no
  UserKnownHostsFile /dev/null
```

## 2.セルフデプロイのためのRailsアプリケーションの設定
サーバ内には、通常のRailsアプリケーションとデプロイ用の2つを用意する必要があります。今回は以下のパスにします。
通常のRails（デプロイ先）: ``/var/www/rails/webapp``
デプロイ用（デプロイ実行）: ``/var/www/rails/deploy``
ソースコードの内容は同じですが、前者はCapistranoからデプロイして、後者はGitHubからクローンします（お好みの方法で構いません）。

### local環境のデプロイファイルを作成
```ruby:config/deploy/local_production.rb
server 'localhost', user: 'ec2-user', roles: %w(web app db)

set :stage, 'local'
set :branch, 'master'
set :rails_env, :production

set :ssh_options, {
  keys: %w(~/.ssh/id_for_localhost),
}
```

```ruby:config/environments/local_production.rb
require 'production.rb' # stagingまたはproductionと同じ。
```

### プライベートリポジトリの場合、GitHubからソースコードを取得できるように秘密鍵を設置
プライベートリポジトリの場合は認証を通す必要があるので、秘密鍵をサーバにアップロードして``~/.ssh/config``に設定を記述しておきます。今回は秘密鍵は``id_for_github``という名前で配置します。

```sh:~/.ssh/config
Host *
  StrictHostKeyChecking no
  UserKnownHostsFile /dev/null

Host github.com
  HostName github.com
  IdentityFile ~/.ssh/id_for_github
  User git
```

[capistrano3で突然deploy時githubのprivate repositoryにssh接続できなくなった](http://qiita.com/rainbowaffro/items/3bcb13c462ae3b7dfc06)

### セルフデプロイが成功するかどうか確認
```
[ec2-user@ip-x-x-x-x ~]$ cd /var/www/rails/deploy
[ec2-user@ip-x-x-x-x deploy]$ bundle install
[ec2-user@ip-x-x-x-x deploy]$ bundle exec cap local deploy
DEBUG [f55ceada] Running /usr/bin/env [ -d /usr/local/src/rbenv/versions/2.3.0 ] as ec2-user@localhost
DEBUG [f55ceada] Command: [ -d /usr/local/src/rbenv/versions/2.3.0 ]
DEBUG [f55ceada] Finished in 0.103 seconds with exit status 0 (successful).
.
.
.
INFO [f64df576] Finished in 0.036 seconds with exit status 0 (successful).
[ec2-user@ip-x-x-x-x deploy]$
```
こんな感じで実行されていき、赤いエラー表示がなく終了していれば成功です。

### 起動時にセルフデプロイを実行
あとは起動時に実行するシェルスクリプトを書いていきます。以下のファイルを設置します。

```bash:/etc/init.d/startup
#!/bin/bash
# chkconfig: 2345 99 10
# description: startup script

######################
# /etc/init.d/startup
######################

# log path
LOG_DIR=/home/ec2-user/log
LOG_FILE=$LOG_DIR/startup.log

# make log directory
mkdir -p $LOG_DIR

# self deploy (ec2-userで実行)
su - ec2-user /home/ec2-user/bin/self-deploy.sh production >> "$LOG_FILE" 2>&1 # staging | production

# change owner of log directory
chown ec2-user:ec2-user -R $LOG_DIR
```

```bash:/home/ec2-user/bin/self-deploy.sh
#!/bin/bash

####################################
# /home/ec2-user/bin/self-deploy.sh
####################################

# path
app_path=/var/www/rails/webapp/current
deploy_path=/var/www/rails/deploy

# environment
rails_env=$1

# change directory
cd $deploy_path

#️ export $PATH
export PATH

# update rails app for deploy
echo "=== [${rails_env}] update rails app for deploy ==="
if [ ${rails_env} = "production" ] ; then
  git fetch origin master
  git reset --hard origin/master
elif [ ${rails_env} = "staging" ] ; then
  git fetch origin develop
  git reset --hard origin/develop
fi
echo ""

# bundle install for deploy
echo "=== [${rails_env}] bundle install for deploy ==="
bundle install --path vendor/bundle #--quiet
echo ""

# self-deploy
echo "=== [${rails_env}] self-deploy to localhost ==="
bundle exec cap local_$rails_env deploy
echo ""
```

``/etc/init.d/startup``を起動時に実行するために、以下のように``chkconfig``コマンドを実行します。

```
[ec2-user@ip-x-x-x-x ~]$ sudo chmod 755 /etc/init.d/startup
[ec2-user@ip-x-x-x-x ~]$ sudo chmod 755 /home/ec2-user/bin/self-deploy.sh
[ec2-user@ip-x-x-x-x ~]$ sudo chkconfig --add startup
[ec2-user@ip-x-x-x-x ~]$ sudo chkconfig startup on
[ec2-user@ip-x-x-x-x ~]$ chkconfig --list | grep startup # 以下のように出力されればOK
startup	0:off	1:off	2:on	3:on	4:on	5:on	6:off
```

以上です。EC2インスタンスを再起動してみて、``/home/ec2-user/log/startup.log``にログが書き込まれていれば成功です。

# まとめ
CapistranoをAutoScalingを使うような実践環境で使う場合は必須のノウハウだと思ったのでまとめてみました。
誰かのお役に立てれば幸いです。質問等はコメント欄によろしくお願いします。