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を利用した方法があったが社内インフラ的な理由により断念
capistrano-git-copyという
deploy_via :copy
の動作を再現してくれるgemがあるが、ステージングサーバーからデプロイサーバーにデプロイするという動作上ステージングサーバー→デプロイサーバーに対応するgitリポジトリがないことに気付く。ステージングサーバー内にgitリポジトリを用意すれば解決はしそうだが…サーバー担当氏曰くサーバーの構造的な問題があるらしく断念gitによるscmを捨てcapistrano-withrsyncというローカルでpullしたソースをrcyncしてくれるgemも試してみたが失敗
そもそもステージングサーバーにはmasterブランチ以外のブランチも自動デプロイできるようになりたい。ステージング兼テストサーバーみたいになっている
要望としてデプロイサーバーにはmasterブランチの内容だけ自動デプロイできるようになりたい。
解決案
代替案としてcapistrano-scm-copyというローカルのソースをtarしてそのままUP&展開してくれるようなGemがあったので利用する。
gem 'capistrano-scm-copy'
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設定ファイルを以下のように設定しておく
set :linked_files, %w{ public/healthcheck.txt }
これでステージングからのデプロイ時は自動的にlinkをはってくれるようになります。
ちなみにpublic以下に.htaccessなどを仕込んでいる場合も
set :linked_files, %w{ public/healthcheck.txt public/.htaccess }
と、スペース&追記で大丈夫です。
まとめ
Capistrano導入のおかげでデプロイはめちゃ早&安定になりましたが、このままじゃよくないなと思う次第…
リポジトリをどこかのリモートサーバーに移行するか、ステージングサーバーにデプロイ専用のリポジトリを作るなど模索してみたいです。
注釈
capistrano-scm-copyの導入資料で
require 'capistrano/copy'
をしろという記述が見かけられますが、それをやると圧縮&コピーが二度行われてしまうので記述する必要はありません。
この記述がなくともちゃんとデプロイは動作します。記述を省いた結果
cap aborted!
LoadError: cannot load such file -- capistrano/copy.rb
というエラーになる場合はbundlerから読み込めていない可能性が高いです。
bundle exec cap 環境名 deployで実行しましょう。