あしたのチーム Tech Blog

はたらくすべての人に「ワクワク」を届けるべく、日々奮闘するエンジニアの日常をお伝えします。

アプリケーションによるインフラへの密結合を環境変数で解消した話

f:id:snaka72:20220121141905p:plain

engineer.ashita-team.com

(この記事はあしたのクラウド のインフラ移行に関する連載記事です)

年が明けて心機一転やっていく気持ちの @snaka です。

さて、あしたのクラウドのインフラ移行についての連載スタートを宣言して結構日が経ってしまいましたが続けていきます。

今回は、あしたのクラウドのインフラ移行を行う際にそれを阻んでいた問題とそれをどのように解決していったかという点についてご紹介できたらと考えています。

インフラ移行を阻害していた要因

インフラ移行を阻害していた要因とは RAILS_ENV の値によってアプリケーションの挙動を切り替えていた箇所が複数存在していたことでした。

インフラ移行前、あしたのクラウドには本番環境とは別に以下のような環境が存在していました。

  • デモ環境
    • 正式に契約する前のお客様にデモとして実際の操作感などを見ていただくための環境
    • この環境の安定性・信頼性は契約にも直結するため、ある意味こちらも「本番」と呼んでも良いかもしれません
  • ステージング環境
    • 修正したプログラム等を本番環境へリリースする前に動作確認するための環境
  • 社内レビュー環境
    • 開発者がスプリントで実装した機能などを社内のステークホルダーに見てもらうために利用する環境

これらの環境ではそれぞれ異なるDBの接続先やファイルを保存するストレージの種類などが異なるため、 RAILS_ENV の値でロジックを切り替えるという対応をしていました。

なぜ RAILS_ENV でアプリケーションの挙動を切り替えると問題か?

RAILS_ENV でアプリケーションの挙動を切り替えると何が問題なのでしょうか?

あしたのクラウドでは以下のような問題が発生していました。

  • 本番環境でしか再現できないバグに遭遇する
  • 環境が増えるたびにアプリケーション側の修正が必要になる

これらについて以下で詳しく説明します。

本番環境でしか再現できないバグに遭遇する

例えば、あしたのクラウドでは社員情報としてプロフィール画像や任意のファイルをアップロードする機能がありますが、それらアップロードされたファイルの格納先が環境毎に異なっていました。

これらのアップロード先ごとにファイルの格納方法が異なるため、アプリケーション側では以下のようにロジックを分岐することで対応していました。

case
when Rails.env.production?
   # 1) 本番環境向けの S3 バケットに保存するロジック
when Rails.env.staging?
   # 2) ステージング環境向けの S3 バケットに保存するロジック
when Rails.env.demo?
   # 3) デモ環境のローカルストレージに保存するロジック
end

これでは、1) のロジックを確認したくてもそのロジックは RAILS_ENVproduction の場合にしか動作しないことになります。

かと言って本番環境以外(ステージング環境や開発者個人の環境)で production で動作させると、DB や Redis などの接続先が本番環境に向いてしまうことになります。

そうなると本番のデータに影響を与えかねない *1 ためそのような方法をとるのもリスクが高くそのようなこともできません。

上記の理由により、前述のロジックを本番以外で動作確認するのは難しく本番でしか発生しないバグが混入することが稀にありました。

環境が増えるたびにアプリケーション側の修正が必要になる

前述のロジックを見ればすぐに分かると思いますが、環境が増える毎にロジックの分岐を追加する必要があります。

例えば、「レビュー環境」を新たに追加したいという場合に以下のようにロジックの分岐が追加になります。

case
when Rails.env.production?
   # 1) 本番環境向けの S3 バケットに保存するロジック
when Rails.env.staging?
   # 2) ステージング環境向けの S3 バケットに保存するロジック
when Rails.env.demo?
   # 3) デモ環境のローカルストレージに保存するロジック
when Rails.env.review?   ### 追加 ###
   # 4) レビュー環境のローカルストレージに保存するロジック
end

ただし、レビュー環境のインフラ構成はデモ環境と同様の構成となっていたため、上記のようなコードの変更は行わず、環境変数RAILS_ENVdemo に設定することでロジックの修正を回避していました。

これはこれで、実際の環境の用途(レビュー)と異なる設定名(demo)を割り当てていることで現実世界と実装との間に齟齬が発生して開発者を混乱させる要因でもありました。

Rails Guilde の功罪

私自身 Rails の案件に携わりはじめた10年くらい前はこのような設計を行っていましたし、このような設計の Rails アプリケーションをいくつか見てきました。

なぜ、アンチパターンとも言えるこのような設計が多くのプロジェクトで見られるのでしょうか?

その原因のひとつとなっているのが Rails Guide の以下の記述ではないかと考えています。

"staging"環境をサーバーに追加したいのであれば、config/environments/staging.rbというファイルを作成するだけで済みます。その際にはなるべくconfig/environmentsにある既存のファイルを流用し、必要な部分のみを変更するようにしてください。
Rails アプリケーションを設定する - Railsガイド

確かに、プロダクトの規模や開発期間の制約などによってはこれでも問題無いとは思います。

ですが、あしたのクラウドのコードベースの規模や開発チームの規模ではこの設計は問題を引き起こす要因となっていました。

しかし、ここ数年でこのような設計はアンチパターンであるという認識が広まってきているように見受けられ、Rails staging 環境 というキーワードで検索してみてもこのような設計は避けましょうという趣旨のブログにヒットするようになりました。

今後このような設計の Rails アプリケーションに遭遇することが無いことを願っています。

The Twelve-Factor App という考え方

では、このような設計はどのように改善したら良いのでしょうか?

その指針となるのが The Twelve-Factor App です。

12factor.net

The Twelve-Factor App は Heroku の中の人である Adam Wiggins 氏が提唱した、移植性・柔軟性の高いモダンな Web アプリケーションを構築するための方法論をまとめたものです。

先にあげた Rails の問題は III. 設定 のページに以下のように記載されています。

グループは、Railsにおけるdevelopment、test、production環境のように、デプロイの名前を取って名付けられる。この方法はうまくスケールしない。... (中略) ... 結果として設定が組み合わせ的に爆発し、アプリケーションのデプロイの管理が非常に不安定になる。

あしたのクラウドではまさにこの問題に直面しているところでした。

ということで、以下のようにインフラ移行の戦略を立てました。

  1. 移行後の新しいインフラでは基本的に RAILS_ENVproduction で動作させる前提とする
  2. アプリケーション側では外部環境 (DB や S3 バケット名など) に関する情報は基本的に環境変数を参照するように変更する
    • 特定の環境だけ挙動を変更したい場合も環境変数の値によって挙動を切り替えるようにする
  3. stagingdemo の場合のロジックの分岐はインフラ移行が完了するまでの間残しておいて、移行が完了した後不要となったタイミングで削除する

移行の前準備としては 2. の修正を行い現行の環境で問題無く動作することを確認しました。

このように RAILS_ENV に依存して挙動を変えるようなコードを洗い出しそれに対応する環境変数の追加とロジックの修正を経て、新しい環境への移行が行える状態となったのでした。

さいごに

この記事ではあしたのクラウドのインフラ移行を阻害していたアプリケーションの問題と、それを改善するための方法論として参考となった The Twelve-Factor App について軽く紹介し、実際にどのように移行に臨んだのかということをお話しました。

個人的に The Twelve-Factor App はコンテナで動作するアプリケーションを設計する場合にも有用だと感じており、Web アプリケーションを開発する人は一度は読んでみることをおすすめします。

*1:当然セキュリティグループやネットワーク設定によって環境が分離されていることが保証されていれば問題無いと判断できますが、当時のインフラ構成には不明なところも多く問題無いと判断できる状況ではありませんでした