社内Git環境からAWSのセキュリティグループにCapistrano3で自動デプロイするまでに起こった問題と解決

Capistranoを用いた自動デプロイ環境を構築時、AWS上のサーバーに直接デプロイできませんでした。プライベートな社内Gitを利用しているゆえに発生した問題なので、デプロイできるようになるまでに発生した特有の問題と解決策について記載していきます。どんどん追記していく形で、長い記事にしようと考えています。

#### 注意
筆者はインフラ素人です。
今回提示した方法でとりあえず動きましたが、細かな環境の違いであっさり動かなくなると思われるので、鵜呑みにはしないでください。

といいますか、もっといい方法があればぜひ教えて下さいお願いします。

## 開発環境
* Capistrano 3.7.2
* Ruby 2.3.1
* Rails 5.0.0.1

## AWS上のプライベートIPしか持たないデプロイサーバー(EC2)に直接デプロイできない
今回のアプリは負荷分散などのため、ELBで同じプライベートサブネットを持つ複数のEC2に振り分ける構成になっています。いわゆるセキュリティグループというやつです。よってデプロイサーバーはプライベートIPアドレスしか持っておらず、ローカルマシンから直接デプロイができない状態にありました。

ではいままでどのようにデプロイしていたのかというと、ステージングサーバーとして、同じプライベートサブネットにありつつもグローバルIPをもつEC2を一つ用意しておき、ローカルからそこにSSH接続してあれこれ、さらにそのステージングサーバーからSSH接続してデプロイサーバーにアクセスしてあれこれするという手法をとっていました。

なので、Capistranoによるデプロイも

ローカルからステージングにデプロイ( `bundle exec cap deploy staging` )

↓　ステージングにsshアクセス

ステージング上から本番サーバーにデプロイ(`bundle exec cap deploy production` )
という方法をとることにしました。

## 問題1.ローカル→ステージングでgit-pullタイムアウトする
**問題**
ローカルマシンでCapistranoをインストールし、ssh接続設定やgitリポジトリ指定などを終わらせ意気揚々と`bundle exec cap deploy staging`するもステージングサーバー上でgitリポジトリへのpullが発生しタイムアウト

**発生原因**
Capistrno3ではデフォルトでソースコードをアプリケーションサーバ（デプロイ先）で取得する動作となっている。今回利用しているリポジトリは社内のローカルなリポジトリであり、当然ステージングサーバーでそれを取得できるはずもなかった。

ちなみにCapistrano2の時代は、`deploy_via :copy`などでローカルでpullしたソースをupするような挙動が用意されていたらしいが、どうやらCapistranoの思想に反しているらしく無効になってしまった。

**試行錯誤**

* [SSH Remote Port Forwardingを利用した方法](http://www.techscore.com/blog/2014/12/03/capistrano3%E3%81%A7%E3%83%97%E3%83%A9%E3%82%A4%E3%83%99%E3%83%BC%E3%83%88%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%81%8B%E3%82%89%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%81%99%E3%82%8B/)があったが社内インフラ的な理由により断念


* [capistrano-git-copy](http://qiita.com/Ping/items/57133b6bbb0a3d705fed)という`deploy_via :copy`の動作を再現してくれるgemがあるが、ステージングサーバーからデプロイサーバーにデプロイするという動作上ステージングサーバー→デプロイサーバーに対応するgitリポジトリがないことに気付く。ステージングサーバー内にgitリポジトリを用意すれば解決はしそうだが…サーバー担当氏曰くサーバーの構造的な問題があるらしく断念


* gitによるscmを捨て[capistrano-withrsync](https://www.qoosky.io/techs/2d2f44607f)というローカルでpullしたソースをrcyncしてくれるgemも試してみたが失敗


* そもそもステージングサーバーにはmasterブランチ以外のブランチも自動デプロイできるようになりたい。ステージング兼テストサーバーみたいになっている
* 要望としてデプロイサーバーにはmasterブランチの内容だけ自動デプロイできるようになりたい。

**解決案**
代替案として[capistrano-scm-copy](http://qiita.com/nownabe/items/24551dc11e6ce4140b37)というローカルのソースをtarしてそのままUP＆展開してくれるようなGemがあったので利用する。

```ruby:Gemfile
  gem 'capistrano-scm-copy'
```

```ruby:deploy.rb
set :scm, :copy
append :linked_dirs, "log", "tmp"
```

デプロイ実行時にコンソール上で「scmでコピーじゃなくてgit使えよそれもう古いんだよ」という警告が出るが無視（本当にごめんなさい）。

すると…現在のアプリケーションをtar圧縮したのちにアップしてくれている！

あとはCapistrano3標準の動作ですね。staging環境向けのbundleなどが自動で実行され、`/デプロイディレクトリ/current`に対応するreleaceフォルダのシンボリックリンクを張ってくれる。

本番サーバーにデプロイするときは、このcurrentディレクトリに移動し、さらに`bundle exec cap deploy production`とすることでデプロイが実行される。もちろん、`config/deploy/production.erb`には対応するSSHキーなどの記述が必要。

**この方法の問題点**
* 転送する必要のないディレクトリ(tmpやlog)まで圧縮＆転送してしまっている(上記のdeploy.rbはそれがデプロイまではされないように個別設定している)。
* commit＆pushしてないファイルをUPしてしまうなどの現象が起きるリスクがある

いままでの手動デプロイに近い感覚でデプロイできる分、いままであった問題も同様に引き継いでいる印象…

## 問題2.本番サーバーにデプロイでアクセスエラー
**発生した問題**
ステージングサーバーから本番サーバーへのデプロイ後、直後はページは閲覧できるが、しばらくしてからアクセスすると「500 internal server error」となり、アクセスできなくなる。

**原因**
ELBのヘルスチェックに失敗している。チェック対象として、HTTP:80/healthcheck.txtにpingを送るよう設定してあったが、デプロイ時にこれを適切な位置`public/healthcheck.txt`に配置できていなかったのが原因。

**試行錯誤**
* そもそもpublic以下のディレクトリも都度デプロイする必要は感じなかったので、public自体をシンボリックリンクにしようとしたところ、capistrano3はデフォルトでpublic/assetsをシンボリックリンクとしているためエラーとなった。カスタマイズすれば変更できるが、無暗に拡張するのもよくないと考え別の方法に。

**解決案**
`public/healthcheck.txt`をシンボリックリンクとしてデプロイ毎にリンクを張るように設定する。

まずは本番サーバーの/デプロイディレクトリ/shared/public/にhealthcheck.txtを設置。

production設定ファイルを以下のように設定しておく

```config/deploy/production.rb
set :linked_files, %w{ public/healthcheck.txt }
```
これでステージングからのデプロイ時は自動的にlinkをはってくれるようになります。

ちなみにpublic以下に.htaccessなどを仕込んでいる場合も

```config/deploy/production.rb
set :linked_files, %w{ public/healthcheck.txt public/.htaccess }
```
と、スペース＆追記で大丈夫です。


##まとめ##
Capistrano導入のおかげでデプロイはめちゃ早＆安定になりましたが、このままじゃよくないなと思う次第…
リポジトリをどこかのリモートサーバーに移行するか、ステージングサーバーにデプロイ専用のリポジトリを作るなど模索してみたいです。


##注釈##
capistrano-scm-copyの導入資料で

```ruby:Capfile
require 'capistrano/copy'
```
をしろという記述が見かけられますが、それをやると圧縮＆コピーが二度行われてしまうので記述する必要はありません。
この記述がなくともちゃんとデプロイは動作します。記述を省いた結果
```cap aborted!
LoadError: cannot load such file -- capistrano/copy.rb
```
というエラーになる場合はbundlerから読み込めていない可能性が高いです。
bundle exec cap 環境名 deployで実行しましょう。