Slide 1

Slide 1 text

アプリケーションコンフィグの設計 パターン
 
 森 雅智 / @morimorihoge 2020/11/27 1
 銀座Rails #27
 ※スライドは発表後に #ginzarails で公開します 


Slide 2

Slide 2 text

About Me
 ● 森 雅智: @morimorihoge
 ● BPS株式会社でRailsの受託開発チームをやってたり、週1大学非常勤で Web開発を教えてたりします
 ● Ruby/Rails歴は11年くらい。Web開発は17年くらい
 ● 銀座Ralis #10でActiveRecordでVIEWを使おうという話をしました
 ● 最近の銀座Railsでは出張Railsウォッチという枠を頂いて発表しています
 About BPS & TechRacho
 ● Web受託開発や電子書籍製品開発をやっている会社です
 ● TechRachoという自社技術Blogを運営しています
 ○ 3年半ほど前から平日毎日更新してます
 ○ https://techracho.bpsinc.jp/ ● お仕事相談、転職相談、TechRachoへのご意見など気軽にどうぞ
 ○ https://www.bpsinc.jp/ 2


Slide 3

Slide 3 text

アプリケーションの「設定」どうしてますか?
 サービスが成長するにつれて増えていく色々な「設定」
 例)外部サービスの接続情報、S3 Bucket ARNなどのインフラ情報
    消費税率、祝日、リスト項目の選択肢
    システム内の動作モード切替フラグなど
 必須の設定項目が増えすぎると環境構築時の初期設定が匠の技化してしまっ たり、どこにどんな設定項目があって動いているのかよく分からなくなりがち (Config Hell)
 3


Slide 4

Slide 4 text

この発表の目指すところ
 アプリケーションの「設定」を設計するにあたって、どのような設定の種類や実装方法が あるのかをまとめ、それぞれのメリット・デメリットを洗い出していきたい
 4


Slide 5

Slide 5 text

取っかかりのための12-Factor App
 2017年当時、HerokuにいたAdam Wigginsが現代的なWebアプリケーションやSoftware as a Serviceを作るのにあたりまとめた方法論集
 どちらかというとインフラ~DevOpsエンジニアの視点で書かれているが、恐らく現代の Railsエンジニアはその辺りを兼務していることが多いと思うので必読
 コンテナ化されたモダンなインフラ環境でまさに参考にできることが多い
 今日はここからIII: Configurationをピックアップ
 5
 https://12factor.net/ja/


Slide 6

Slide 6 text

12-Factor Appにおける「設定」の定義
 ● アプリケーションの 設定 は、デプロイ(ステージング、本番、開発環境など)の間で 異なり得る唯一のものである
 ● 認証情報を漏洩させることなく、コードベースを今すぐにでもオープンソースにする ことができるなら、アプリケーションの設定が正しく外部に分離できている
 12-Factor Appでは設定を環境変数に格納することを勧めている(利点は後述)
 一方で、アプリケーション内部の設定については12-Factor Appは定義の対象外として いる
 -> 今回の発表ではこちらも対象としていく
 6


Slide 7

Slide 7 text

本発表における「設定」の想定範囲
 外部から調整可能な何らかのデータを渡すことにより、アプリケーションの動作を変更し 得るもの、とする。
 ● 環境変数や引数など、プロセス実行時に引き渡されるもの
 ● プログラムの外からファイルやURLとして渡されて読み込むもの
 ● DBの特定テーブルに入っていて都度読み出す設定情報など
 どれも広義の設定として考える
 7


Slide 8

Slide 8 text

設定の注入方法
 8


Slide 9

Slide 9 text

色々な設定注入方法一覧
 9
 ● ソースコード内定数
 ● コマンドライン実行時引数
 ● 環境変数
 ● 定数型設定ファイル(YAML、JSONなど)
 ● ソースコード形式設定ファイル(config/initializersなど)
 ● DBやKVSの設定情報用テーブル
 ● その他の方式


Slide 10

Slide 10 text

ソースコード内定数
 ソースコード内に定数として設定を記述する方式。いわゆるmagic numberに相当する
 ● 設定値を変更するにはソースコードの変更が必要なため、扱いづらい
 ● 外部から設定値を変更するのも困難なため、テストもし辛い
 10


Slide 11

Slide 11 text

コマンドライン実行時引数
 RakeタスクやRails runner経由で実行する場合は、実行時コマンドに設定を渡すこともで きる
 ● rakeやrailsコマンドが実行できるシェルが取れる場合に柔軟に利用可能
 ● Rails runner経由であればRubyコードも書けるので、Time.nowなどの取得も可能
 11
 rails runner "HogeService.call(from: Time.now)"

Slide 12

Slide 12 text

環境変数
 12-Factor Appでもオススメされる設定方法。RubyではENVから取る
 ● PaaSやCIツールが設定をサポートしているケースが多く、つなぎ込みという点では 最も柔軟かつ汎用性が高い
 ● Rails以外のソフトウェアと設定値を共有したい場合にも便利
 ● 文字列しか渡せないため、複雑なデータ構造を渡すのは不向き
 12


Slide 13

Slide 13 text

定義型設定ファイル(YAML、JSONなど)
 `rubyconfig/config` gem(旧rails_config)などがメジャーどころ
 ● ArrayやHashなどの文字列以外の構造データを格納することができる
 ● 階層構造に対応するのでnamespace的な整理が可能
 13
 ※以下は`rubyconfig/config` gemを使ったサンプル

Slide 14

Slide 14 text

ソースコード形式設定ファイル
 config/initializers/*に置かれるようなRubyコードとして実行時に初期化・設定のために 読み込まれる方式
 ● pureなRubyコードが書けるため、値の設定だけでなく初期化の重いオブジェクトの 生成なども可能
 ● lambdaやProcも設定することができるため、定数による設定ファイルでは書けない ロジック(Time.now.yesterdayなど)も記述できる
 ○ 既存のライブラリの設定値にlambdaやProcを設定してちゃんと動くかは作りに依る 
 起動前の事前設定が必要なRails系Gemなどはこの形式を取っているものが多い
 14


Slide 15

Slide 15 text

DBやKVSの設定情報用テーブル
 アプリケーション設定情報用のkey-valueデータを格納したテーブルを用意し、必要に応 じて参照・更新する
 ● 古くからある方式で、feature toggleなどに良く利用される
 ● プロセスの再起動なしに値の更新が可能
 ● DBアクセスが発生するため、DBサーバーが遠かったり設定値が多すぎると速度面 が問題になるかもしれない
 15


Slide 16

Slide 16 text

その他
 フロントエンド系ではサーバーサイドを介さずに外部サービスで設定を行うこともある が、ここでは非サーバーサイド系ということで対象外としておく
 ● 例:Google TagManagerで条件に合わせて表示する内容をJavaScriptで切り替えて A-Bテストを行うなど
 他にもAWSなどのインフラサイドの設定のみでcanary deployするようなことも最近はでき たりするが、こちらも完全にクラウド側の設定になってしまうので今回は対象外としてお きます
 16


Slide 17

Slide 17 text

各手法の特徴や使いどころを見ていく
 17


Slide 18

Slide 18 text

ざっくり整理してみる
 18
 読み込みタイミング 設定値の編集方法 再読込方法 リクエスト処理時 オーバーヘッド ソース内定数 プロセス起動時 ソース修正 プロセス再起動 低 コマンドライン引数 プロセス起動時 実行コマンド変更 プロセス再起動 低 環境変数 プロセス起動時 環境設定更新 プロセス再起動 低 YAML/JSON プロセス起動時 ファイル修正 プロセス再起動 低 ソースコード設定ファ イル プロセス起動時 ソース修正 プロセス再起動 低 DB/KVSテーブル 値参照時 DB値更新 DB値参照 小 次ページから切り口ごとに見ていきます 


Slide 19

Slide 19 text

読み込みタイミングによる違い
 多くの手法はプロセス実行時読み込みとなるが、それはすなわち設定値の変更に deployが必要になるということ(まさに12-Factor Appの「設定」の定義)
 19
 読み込みタイミング 設定値の編集方法 再読込方法 リクエスト処理時オー バーヘッド ソース内定数 プロセス起動時 ソース修正 プロセス再起動 低 コマンドライン引数 プロセス起動時 実行コマンド変更 プロセス再起動 低 環境変数 プロセス起動時 環境設定更新 プロセス再起動 低 YAML/JSON プロセス起動時 ファイル修正 プロセス再起動 低 ソースコード設定ファイ ル プロセス起動時 ソース修正 プロセス再起動 低 DB/KVSテーブル 値参照時 DB値更新 DB値参照 小 多くの手法はプロセス実行時読み込みとなるが、それはすなわち設定値の変更に deployが必要になるということ(まさに12-Factor Appの「設定」の定義)
 -> deployせずに設定変更したければ、DB/KVSを使うのが良さそう 


Slide 20

Slide 20 text

設定変更方法による違い
 設定変更にソース修正が必要な方法を使う場合、運用上のニーズである設定変更が機 能開発のソース変更と混じる
 -> branchのmerge運用がカオスになりやすい要因の一つ
 20
 読み込みタイミング 設定値の編集方法 再読込方法 リクエスト処理時オー バーヘッド ソース内定数 プロセス起動時 ソース修正 プロセス再起動 低 コマンドライン引数 プロセス起動時 実行コマンド変更 プロセス再起動 低 環境変数 プロセス起動時 環境設定更新 プロセス再起動 低 YAML/JSON プロセス起動時 ファイル修正 プロセス再起動 低 ソースコード設定ファイ ル プロセス起動時 ソース修正 プロセス再起動 低 DB/KVSテーブル 値参照時 DB値更新 DB値参照 小 ※YAML/JSON方式は当該configをcommitするかにも依る
 -> deployせずに設定変更したければ、DB/KVSを使うのが良さそう 


Slide 21

Slide 21 text

API KEYなどの秘密情報管理の観点
 21
 参照・更新権限 ソース内定数 リポジトリへのアクセス権限次第 コマンドライン引数 設定されている場所や実行方法によるが、 リポジトリのCI設定ファイルなどに書かれている場合はそのファイルの書き方依存となる ※Secure Variable的な機能があればよりセキュアに設定できる 環境変数 設定されている場所や実行方法によるが、 リポジトリのCI設定ファイルなどに書かれている場合はそのファイルの書き方依存となる ※Secure Variable的な機能があればよりセキュアに設定できる YAML/JSON リポジトリに置く場合はcredentials機能などを使わないととても危険 リポジトリ管理外に置くのであれば、当該設定ファイルのアクセス権限依存 ※AWS S3などであればpolicy設定などで細かく設定できる ソースコード設定ファイル 基本的にここに生で書くことはなさそう? もしやる場合はYAML/JSONの場合と同様 DB/KVSテーブル DB/KVSへのアクセス権限次第 どのケースでも、shellが取れてrails consoleが使えると見られない情報はないので、踏み台サー バーなどでshell環境を用意する場合は権限に要注意


Slide 22

Slide 22 text

その他のトピック
 22


Slide 23

Slide 23 text

configからconfigを作るという選択肢
 ファイル形式の設定しか受け入れてくれないが、設定を環境変数で渡したいというケー ス(DockerでApacheやMySQLのコンテナを使おうとするとありがち)
 Dockerだとentrypoint.shの中でenvsubst(gettextパッケージ内)などのテンプレートエン ジンを使ってテンプレート化されたconfigに環境変数を実行時展開して読み込ませるとい う手が使える
 # buildに含めるのでも良いが、その場合設定変更時に毎回buildが必要
 -> 参考: envsubstを使ってDockerで設定ファイルに環境変数を埋め込めこむ汎用的な パターン
  https://qiita.com/minamijoyo/items/63ae57b99d4a4c5d7987
 23
 # entrypoint.sh . /container.env EXTRACT_VARS=’$VAR_A $VAR_B...’ envsubst “$EXTRACT_VARS” < httpd.conf.tpl > /etc/apache2/conf/httpd.conf apache2-foreground やや冗長だが、こういうやり方もあるということで・・・ 


Slide 24

Slide 24 text

複数の設定方式に同時対応する
 OSSなどで色々な環境で動作させることを想定する場合、複数の設定方式に対応すると より柔軟な使い方ができる
 よく見るのは以下のように環境変数・設定ファイルに同時対応しているもの
 ※上に行くほど優先度が高く、設定がなければ順に下の値を見に行く
 ● 環境変数の設定値
 ● 設定ファイルの設定値
 ○ この中にさらに優先順位があることもある(例:`config` gem) 
 ● アプリケーションのデフォルト設定値
 24


Slide 25

Slide 25 text

デフォルト値問題
 可能ならデフォルト値を設定し、デフォルトでいい感じに動くようにしておきたい
 ※これはRailsのCoC(Convention over Configuration)原則にも通じる
 デフォルト値がある場合でも、設定ファイルを使うのであればリポジトリに全パラメータの デフォルト値が記述されたデフォルト設定ファイルがあるととても親切
 ※rubocop-todo.yml みたいなイメージ。「その設定パラメータがある」ことが明示的に分 かることで得られる情報がかなり増えるのと、既存設定とのdiffを見ることで何がカスタマ イズすべきパラメータなのかを調べたりできる
 25


Slide 26

Slide 26 text

環境変数に全部乗せるのは難しい問題
 環境変数は文字列しか設定できないので、配列やハッシュなどの構造を持ったデータは 扱いづらい(不可能ではないが・・・)
 すべてのconfigを環境変数に出すというのは現実的ではないので、開発環境や動作環 境によって切り替えることが多いものは環境変数、そうでないものは設定ファイルに入れ るなどの対応は必要
 一般的にはWebサーバーレベルの挙動や外部連携周りは環境変数、アプリケーション 内の振る舞いに関連する部分は設定ファイルに記述することが多いように思える
 ・・・が、アプリケーション内の振る舞いでも環境変数で渡したいデータは出てくるケース があるので、この辺りはニーズに応じて調整するしかなさそう?
 26


Slide 27

Slide 27 text

まとめ
 Railsにおけるアプリケーションコンフィグの設計パターンを整理し、解説してみました
 いろいろな実装方法があることを知ることで、新しい設定値を追加しようとしたときにどの 手法が最適化を考える一助になれば幸いです
 設定情報はアプリケーションが育てば育つほど増えていく傾向があるので、将来の自分 や同僚が発狂しないようにしたいものですね
 27


Slide 28

Slide 28 text

次回以降もブラッシュアップしていきます
 感想・リクエストなどあればTwitter
 #ginzarails
 @morimorihoge
 までお声かけください
 28