OpsWorks with Rails 環境でDB Migration問題をなんとかする

  • 1
    いいね
  • 0
    コメント

はじめに

AWS OpsWorksではChef11を選択すると、Railsスタックをデフォルトで提供しており、エンジニアが自前でChefなど用意しなくてもHeroku感覚でAWSをPaaSとして利用することが出来ます。

  • Herokuほどフルマネージドは要件上出来ないが、インフラはある程度管理したい
  • Chef実行準備が面倒くさい
  • コードのDeploy機能とデプロイ記録とりたい
  • CloudWatch Logs統合 (ログPath設定を画面から出来る)
  • AWSビジネスサポートに入っていると、困っている時に技術的なことを直接チケットを投げて聞ける

こんなメリットたちがあります。正直最後のメリットが一番のような気もしますが・・・

DB Migration問題?

このサービスには致命的な問題があります。なんと複数インスタンスへのデプロイ時に、RailsのDB Migrationと、Unicornの再起動の同期が全く取れていません。 そのため、複数インスタンスを選択した状態で、DB Migrationを実行すると、Migration以外のノードはソース反映後すぐにUnicorn再起動がかかり、高確率で不完全な状態でデプロイされてしまいます。

OpsWorks上でのMigration実行の仕組み

OpsWorks経由で「Migrate YES」を選択してデプロイを行うと rake の db:migrate が実行されます。
https://github.com/aws/opsworks-cookbooks/blob/2209f35fc0e3fd7a75af2e3d0715d425c6a55783/deploy/attributes/deploy.rb#L79

複数ノードがある状態で、「Migrate YES」を選択してデプロイを行う場合、グループ内で任意のノード1個が選択され(基本的にはグループで一番最初のもの)、そのノードに対して内部的にchefの変数に default[:deploy][application][:migrate] = true が設定入り、その後、全ノードに同時にcookbookが実行されます。

デプロイ中は、基本的に全てのインスタンスはそれぞれ勝手にChefが実行されるだけです。そのため、これが原因でマイグレーション同期されない問題がおきています。小さなDBマイグレーションであれば問題なかったりしますが、ちょっとでも時間がかかるようになるとアウトです。規模によりますが、Railsのデプロイを1回実行するのに少なくとも3〜4分くらいかかりますので、ダウンタイム発生となります

不完全な状態でのUnicorn再起動を回避する方法 by AWS

AWSサポート等にもヒアリングした所、以下2点の方針が一般的との回答。

  1. Migration用のレイヤーを作っていただき、先にそちらでMigration込みで一旦ソースを反映して頂く
  2. Chefのデプロイフックがあるので、それを利用して何かしら処理を入れてもらう

うーむ、自前でやらねば・・・・ということで素直に上記を対応します。
(1)に関してはただの運用フロー寄りの問題なので、一旦今回は詳細を割愛します。

デプロイフック実装

OpsWorksはDocument Root配下に deploy ディレクトリ配下に before_symlink.rb (シンボリックリンク張替え直前) で以下のチェック入れます。

Chef::Log.info("Running deploy/before_symlink.rb")
current_release = release_path
env = node[:deploy][:app_rails][:rails_env]

execute "rake aws:verify_migration" do
  cwd current_release
  command "bundle exec rake aws:verify_migration"
  environment new_resource.environment.merge({ "RAILS_ENV" => env })
end

Rakeタスクを適当に作ります。

namespace :aws do
  task verify_migration: :environment do
    raise StandardError if 
  ActiveRecord::Migrator.needs_migration?
  end
end

これでとりあえず、不完全な状態でUnicornが再起動される問題だけ回避しました。before_symlinkの段階で、verify_migrationを実行し、マイグレーションが残っている状態の時は対象インスタンスの反映を途中中断させるようにします。「カラムの削除の時はどうするのか」みたいな懸念はありますが、一旦これだけでも入れておけば大きい所はカバーできそうです。

最後に

そもそもChefは諦めて、コンテナ運用(GKE + Spinnaker) に早く移行したほうがいいかもしれない