Heroku Review Appsで「使える」レビュー環境構築

こんにちは。grooves エンジニアの福井(@bary822)です。普段はCrowd Agentを開発しています。

今回はCrowd Agentのリリースフローが抱えていたボトルネックををHeroku Review Appsを使って解決した方法をご紹介します。

似たような課題をお持ちの方に解決のヒントを与えることができればと思いながらこの記事を書いています。

リリースフローのボトルネック

Crowd Agentではスクラム開発を採用しています。

スクラムでは開発者はPO(Product Owner)が定義する受け入れ条件を満たす機能を実装することを期待されていますが、Crowd Agentでは「POデモ」と呼ばれる受け入れチェックによりこれが正しく実現されていることを担保しています。

f:id:grooves:20200915175947j:plain

実はここにボトルネックが存在します。それはStagingが1つしかないことです。

複数の機能開発が並行して進んだ場合、POデモも機能毎に実行する必要がありますが、一度に使えるStaging環境は1つのみです。既にStagingが使われていた場合、他の開発者はPOデモが終わるのを待たなければなりません。

f:id:grooves:20200915175830j:plain

また、POが多忙でPOデモに使うことの出来る時間が限られていることもあり、デモが終わる度にStagingにデプロイし直すことも現実的ではありませんでした。

作って壊せる検証環境

このボトルネックを解消するためにHeroku Review Appsを利用することにしました。

ご存知の方も多いかと思いますのでここではHeroku Review Appsについての詳細を書きませんが、いわゆる「作って壊せる検証環境」を構築できるものです。

GitHubとHerokuを連携させることにより、Pull Requestが作成/更新されたイベントをHerokuに通知し、予め定義したフローに沿って自動的にアプリケーションがデプロイされることができます。

Heroku Review Appsを導入することでStagingの空き待ち時間を無くし、同時に複数の機能をPOデモできる環境を構築することに成功しました。

f:id:grooves:20200915175836j:plain

これによりボトルネックは解消され、開発された機能がスムーズに本番環境にリリースされる状態をつくることができました。

と、ここまで流暢に書いてきましたが、実はこれが実現されるまでにはいくつもの困難がありました。

独自サブ-サブドメイン使えない問題

Herokuではデプロイされたアプリケーションに自動的にドメインが付与されます。例えば、crowd-agent.herokuapp.com のようなものです。 Heroku Review Appsでは、サブドメイン部(crowd-agentに当たる部分)を任意の値に変更することができますが、サブ-サブドメイン部(*.crowd-agent)は指定することができません。

Crowd Agentではユーザーの属性によってログイン後のドメインを分けていたため、サブ-サブドメイン部を任意の値にコントロールし、同じアプリケーションにルーティングさせる必要がありました。

f:id:grooves:20200915175843j:plain

公式ドキュメントを読み進めていくと、どうやら独自ドメインを使うと解決できることがわかりました。

*.herokuapp.comは内部的に*.herokudns.comというドメインへのCNAMEレコードをもっているため、同様に独自ドメインからもCNAMEレコードを作成して紐付けるというものです。 確かに独自ドメインであればサブ-サブドメインのレコードも自由に設定することができます。

f:id:grooves:20200915175850j:plain

これはHerokuの設定ファイルである app.jsonpostdeployエントリにDNS登録作業を行なうスクリプト実行コマンドを指定することでデプロイ語に自動的にすることができます。

app.json

"postdeploy": "bin/rails heroku:review_apps_setup"

lib/tasks/heroku.rake

aws_client = Aws::Route53::Client.new(access_key_id: AWS_ACCESS_KEY, secret_access_key: AWS_SECRET_ACCESS_KEY)
heroku_client = PlatformAPI.connect_oauth(HEROKU_API_TOKEN)

hostname = "pr-#{heroku_app_name}.crowd-agent-review.com"

 domains = [nil, :admin, :agent, :company].map do |subdomain|
   hostname_with_subdomain = subdomain.present? ? [subdomain, hostname].join('.') : hostname

   # Heroku上にドメインを作成
   heroku_client.domain.create(heroku_app_name, hostname: hostname_with_subdomain)
   heroku_cname = heroku_client.domain.info(heroku_app_name, hostname_with_subdomain)['cname']

   # Route53用の設定を定義
   config = {
     hosted_zone_id: '/hostedzone/XXXXXXXX, # crowd-agent-review.com
     change_batch: {
       changes: [
         action: 'CREATE',
         resource_record_set: {
           name: hostname_with_subdomain, type: 'CNAME', ttl: 3600, resource_records: [{ value: heroku_cname }]
         }
        ],
        comment: "Review environment host for branch #{branch}"
      }
    }

    # Route53にレコードを作成
    aws_client.change_resource_record_sets(config)

    [subdomain || 'root', hostname_with_subdomain]
 end

 hash = {}
 domains.each { |item| hash[:"APP_#{item[0].upcase}_DOMAIN"] = item[1] }

 # Herokuにドメインを登録
 heroku_client.config_var.update(heroku_app_name, **hash)

 puts "App is successfully built!"

動作が遅すぎて使い物にならない問題

無事にドメインが設定され、ようやくブラウザから操作できるようになったReview Appsですが、実はあまり活用されていませんでした。なぜなら動作が遅すぎて全く使い物にならなかったからです。

どのくらい遅かったかと言うと、レンダリングするまでに30秒近くかかるページもあるほどでした。これでは開発者もPOも使ってくれません。

どこにボトルネックがあるかは明確でした。App - DB間のレイテンシです。

初期構成では実装コストを下げるために、Staging環境で利用しているDBをReview Appと共有していました。 しかし、Heroku上のAppはUS(Virginia)リージョンに、Staging DBはTokyoリージョンに配置されています。

動作が遅くなっていた原因は、この通信に大きなレイテンシが発生していることでした。

一旦整理すると、初期構成の全体像は以下のようになります。

f:id:grooves:20200915180014j:plain

HerokuのAppがVirginiaリージョンに配置されていることは以下のコマンドで確認しました。

# Herokuの認証情報はあらかじめexportしておく
$ curl -n -X GET https://api.heroku.com/regions/us -H "Accept: application/vnd.heroku+json; version=3"
{
  "country":"United States",
  "created_at":"2012-11-21T20:44:16Z",
  "description":"United States",
  "id":"59accabd-516d-4f0e-83e6-6e37577XXXX",
  "locale":"Virginia",
  "name":"us",
  "private_capable":false,
  "provider":{
    "name":"amazon-web-services",
    "region":"us-east-1"
  },
  "updated_at":"2016-08-09T22:03:28Z"
}

これを解決するために、まずはAppをTokyoリージョンに移せないか検討しましたが、コスト(お金)の面から難しいことがわかりました。

Review AppsをUS以外のリージョンで使うためには、Private Spacesという有料の機能を使う必要があります。今回の場合、レイテンシ解消で得られる恩恵は$1000/月(2020年9月現在)という価格を下回っていると判断し、導入を見送ることにしました。

この代替案として、DBをUS(Virginia)リージョンに移すことを検討しました。 Staging DBを共有するのではなく、全Review Appsで共通の専用DBをVirgniaリージョンに設置するというものです。

f:id:grooves:20200915180018j:plain

これによりレイテンシは改善され、ページがレンダリングされる時間は1/10まで短縮されました!

また毎デプロイ後、自動的にDBのテーブルセットアップやテスト用データの作成を行なうようにしました。これでデプロイ後すぐにPOデモを依頼することが可能になりました。

app.json

"postdeploy": "bin/rails heroku:review_apps_setup && bin/rails db:migrate db:seed"

まとめ

今回はHeroku Review Appsを使ったリリースフロー上のボトルネック解消方法、また導入時に私達が直面した問題とその解決方法の一例も併せてご紹介しました。

現在Heroku Review Appsは開発からリリースまでのリードタイムを減少させる有効なツールとして、私達の開発になくてはならない存在となっています。

個人的には開発チームを支援するツールの導入・作成は大好物なので、このタスクに携わることができて大変嬉しく思っています。

最後に、Crowd Agent開発チームではアプリ開発に強みを持つエンジニアとインフラ技術に強みを持つエンジニアを募集しています。

開発メンバーは全員フルリモート勤務となりますので、都市部以外にお住まいの方、また都市部から地方への移住を検討している方からの応募も大歓迎です!

みなさまのエントリーお待ちしています!

jobs.forkwell.com

jobs.forkwell.com