設定とは何か、そしてそれが思っている以上に重要な理由

あなたは今まさに小さなアップデートをデプロイしようとしている。コードはレビュー済み、テストはパス、パイプラインもグリーン。しかし、アプリケーションが本番環境で起動すると、データベースに接続できない。ログを確認すると、接続文字列がステージングサーバーを指している。誰かが3ヶ月前にハードコードしたもので、今まで誰も気づかなかった。

これはコードのバグではない。ロジックは正しい。問題は、簡単に変更できるべき値がプログラム内部に埋め込まれていたことだ。デプロイが失敗したのはコードが間違っているからではなく、設定が後回しにされていたからだ。

設定の正体

設定とは、アプリケーションの実行に必要な情報のうち、環境間や時間の経過とともに変化するもので、コードの変更を必要としないものを指す。データベースのホスト名、APIキー、タイムアウト値を変更するためにソースファイルを編集して再デプロイしなければならないなら、あなたは設定とコードを混同している。

最も単純な例はデータベース接続だ。あなたのラップトップでは、データベースはローカルまたはコンテナ内で動作している。本番環境では、異なるアドレス、認証情報、データベース名を持つ専用サーバー上で動作する。これらの値がソースコードに直接書かれている場合、異なる環境にデプロイするたびにファイルを編集する必要がある。それは面倒で、エラーが発生しやすく、不必要なリスクを生み出す。

しかし、設定はデータベースの認証情報だけにとどまらない。

設定の一般的な種類

APIキーとシークレット

決済ゲートウェイ、メールプロバイダー、クラウドストレージプラットフォームなどの外部サービスは、環境ごとに一意のトークンを提供する。開発用APIキーは本番用キーとは異なる。本番環境が誤って開発用キーを使用すると、いくつかの悪いことが起こる。テストデータと本番データが混ざる、開発用のレート制限に本番トラフィックがヒットする、またはセキュリティ境界が完全に破られる。

シークレットはセキュリティ上の影響があるため、設定の中でも特別なカテゴリだ。保存時の暗号化、アクセス制限、ローテーションポリシーが必要である。他の設定値と同じように扱うのは間違いだ。

フィーチャーフラグ

フィーチャーフラグは、アプリケーションを再デプロイすることなく機能のオン/オフを切り替えるスイッチだ。チームが新しいチェックアウトフローをユーザーの10%だけにロールアウトしたいとする。フラグの値(機能がアクティブかどうか、誰に対してか)は設定である。コード変更なしで変化し、同じ環境内のユーザー間でも異なる可能性がある。

フィーチャーフラグは、設定と実行時の意思決定の境界を曖昧にする。ロジックを変更せずに振る舞いを制御するという意味で設定だが、動的であり、設定ファイルではなく別のシステムで管理されることが多い。

環境固有の値

環境ごとに異なり、見落とされがちな小さな値は多い。クラウドストレージのバケット名、内部サービスURL、ファイルパス、ポート番号、ログレベルなどがこれに該当する。開発環境では問題をデバッグするために冗長なログが必要だが、本番環境ではディスク容量を節約しノイズを減らすために簡潔なログが必要だ。これらの値は、ソースコードに触れることなく環境ごとに調整可能でなければならない。

非機能パラメータ

タイムアウト時間、最大リクエストサイズ、スレッドプールサイズ、リトライ制限、ヘルスチェック間隔は、アプリケーションの動作方法に影響を与えるものであり、何をするかには影響しない。5秒のデータベースタイムアウトは開発環境では問題なく動作するかもしれないが、ネットワークレイテンシが高い本番環境では障害を引き起こす可能性がある。そのタイムアウトがハードコードされている場合、たった一つの数値を変更するためだけに再デプロイしなければならない。

これらのパラメータは、初期開発時にはチューニングの詳細のように見えて無視されがちだ。しかし本番環境では、アプリケーションがトラフィックの急増、ネットワーク問題、または低速な依存関係に耐えられるかどうかを決定する。

コードと設定の線引き

境界は常に明確とは限らない。パートナーAPIのURLを考えてみよう。それは設定か、それともコードか? 文脈による。アプリケーションが常に1つのパートナーとのみ通信し、そのURLが決して変わらないなら、コードに書くのも実用的かもしれない。しかし、パートナーを切り替える可能性がある場合、または環境によって異なるパートナーを使用する場合、それは設定になる。

実用的な経験則:ある値が環境間で変化する、またはデプロイなしで変化するなら、設定として扱う。ある値がアプリケーションが実行されるすべての場所で同じビジネスロジックを定義するなら、コードとして扱う。

しかし、ニュアンスもある。変更は稀だが、変更時に重要となる値もある。データベース接続文字列は何年も変わらないかもしれないが、変更時に間違えるとアプリケーション全体がダウンする。変更頻度は一つの要素だが、エラーの影響も同様に重要だ。

別の考え方:設定とは、コードレビューとデプロイパイプラインなしで変更できるようにしたいものだ。タイムアウトの変更にビジネスロジックの変更と同じプロセスが必要なら、不必要な摩擦を生み出している。設定はコードよりも簡素な手続きで調整可能であるべきだ。

設定ミスがコードバグより危険な理由

コードバグは通常、特定の機能やコードパスに影響する。ロールバックしてロジックを修正し、再デプロイできる。影響範囲は、そのコードを使用する機能に限定されることが多い。

設定ミスは、すべてに一度に影響する可能性がある。間違ったデータベースURLはアプリケーション全体をダウンさせる。誤設定されたタイムアウトはすべてのリクエストを失敗させる。誤って設定されたフィーチャーフラグは、未完成の機能を全ユーザーに公開する。設定ミスは、グローバルで即時的であり、インフラストラクチャの問題のように見えるためコード問題よりも診断が難しい傾向がある。

設定はコードよりもテストされることが少ない。チームはロジックに対して単体テストや統合テストを書くが、設定値をテストするチームはどれだけいるだろうか? デプロイ前に本番データベースURLに到達可能かを検証する自動チェックを持っているチームは? 設定はコードのように見えないため、品質ゲートをすり抜けることが多い。

設定管理のための実践的チェックリスト

すべてのチームに複雑な設定管理システムが必要なわけではない。しかし、明確な境界線はすべてのチームに利益をもたらす。現在のプロジェクトに適用するための短いチェックリストを以下に示す。

  • 同じビルドアーティファクトを、変更せずに開発、ステージング、本番にデプロイできますか?
  • シークレットは他の設定と分離され、暗号化とアクセス制御が適用されていますか?
  • コードデプロイなしでタイムアウトやリトライ制限を変更できますか?
  • デプロイ前に設定値を検証する自動チェックはありますか?
  • どの設定値が存在し、それらが何を意味するかについての単一の情報源はありますか?

これらのいずれかに「いいえ」と答えた場合、あなたはいつか本番インシデントを引き起こす設定負債を抱えている。

具体的な教訓

設定は後回しにすべき詳細ではない。コードと同じ規律を必要とするデリバリー上の関心事だ。まず、アプリケーション内で環境間または時間の経過とともに変化するすべての値を特定することから始めよう。それらの値をソースコードから分離する。シークレットは安全に保存する。デプロイ前に設定を検証する。そして、間違った設定値はほとんどのコードバグよりも速く、より大きな損害を引き起こす可能性があることを覚えておいてほしい。なぜなら、それはシステム全体に一度に影響するからだ。設定が要求する敬意をもって扱えば、デプロイはロジックとは無関係な理由で失敗しなくなる。