堅牢なクラウドネイティブアプリケーションのための設定の外部化
Min-jun Kim
Dev Intern · Leapcell

現代のソフトウェアにおける設定の課題
現代のソフトウェア開発の目まぐるしい世界では、アプリケーションはもはや単一の機械に縛られたモノリシックな巨大なものではありません。それらは分散され、スケーラブルであり、多くの場合動的なクラウド環境で実行されます。この移行は、柔軟性と回復性の面で計り知れない利点をもたらしますが、特にアプリケーションが設定をどのように管理するかという点で複雑さももたらします。データベース認証情報、APIキー、または環境固有の設定をアプリケーションのコードベースに直接ハードコードすることは、最初は便利かもしれませんが、すぐに壊れやすく、柔軟性のないシステムにつながります。単純な設定変更のためにアプリケーション全体を再デプロイしたり、開発、ステージング、本番環境間での異なる設定に苦労したり、バージョン管理で機密情報が漏洩するリスクを冒したりすることは、この実践が引き起こす頭痛の種のごく一部です。ここで、12ファクターアプリの方法論の「設定」原則が明るく輝き、設定とコードを分離するための戦略的なアプローチを提供し、アプリケーションをよりポータブル、スケーラブル、そして安全にします。
外部化された設定の理解
外部化された設定の力を完全に理解するために、議論を導くためのいくつかの重要な用語を定義しましょう。
- 設定: これは、デプロイ(ステージング、本番、開発など)間で変化する可能性のあるものを指します。これには、データベース認証情報、外部サービスAPIキー、ホスト名、ポート番号、フィーチャーフラグ、環境固有のロギングレベル、およびその他のデプロイ固有の値が含まれます。アプリケーション内の環境間で変化しない内部設定(ビジネスロジックパラメータなど)は明示的に除外されます。
- コードベース: これは、アプリケーションのソースコードを含む、バージョン管理された単一のリポジトリです。12ファクタ原則は、リビジョン管理で追跡される1つのコードベースを持ち、多くのデプロイを行うことを強調しています。
- 環境変数: これらは、プロセスに設定を渡すために広く採用されている、言語にとらわれず、オペレーティングシステムにとらわれない標準です。これらは、アプリケーションプロセス外に設定でき、実行時にアプリケーションによってアクセスできる動的なキーと値のペアです。
外部化された設定の核となる考え方は単純です。アプリケーションコードは、異なる環境間で一定でなければなりません。変化するのは、各環境に固有の設定値だけです。この分離により、コンパイルまたはパッケージ化された同じアプリケーションアーティファクトをどこにでもデプロイでき、その動作は、実行時に注入された外部設定にのみ基づいて適応します。
設定分離の原則と実践
この原則では、すべての設定は環境変数に保存されるべきであると規定されています。このアプローチは、いくつかの説得力のある利点を提供します。
- 厳格な分離: 環境変数は、コードと設定の間の厳格な分離を強制し、機密情報をコードベースに誤ってコミットすることを不可能にします。
- 言語とOSに依存しない: 環境変数は普遍的なメカニズムであり、事実上すべてのプログラミング言語とオペレーティングシステムでサポートされています。これにより、多様なテクノロジースタック全体でのポータビリティが促進されます。
- 変更の容易さ: 本番環境の設定値を更新することは、環境変数を変更してアプリケーションを再起動する(またはサポートされている場合は動的に変更を検出するようにする)のと同じくらい簡単です。コードを変更したり再ビルドしたりする必要はありません。
- セキュリティ: データベースパスワードやAPIキーのような機密情報は、コードベース内にプレーンテキストで保存するのではなく、デプロイ環境(例:Kubernetessecrets、クラウドプロバイダーのシークレットマネージャー)によって安全に管理し、環境変数として注入できます。
この概念を、PythonとFlaskを使用した実践的な例で説明しましょう。ただし、これらの原則は、あらゆるバックエンドフレームワークに普遍的に適用されます。
データベースに接続するシンプルなFlaskアプリケーションを考えてみましょう。
悪い実践(ハードコードされた設定):
# app.py from flask import Flask from sqlalchemy import create_engine app = Flask(__name__) # ハードコードされたデータベース認証情報 - ダメ! DATABASE_URL = "postgresql://user:password@localhost:5432/mydatabase_dev" engine = create_engine(DATABASE_URL) @app.route('/') def hello(): # 例のデータベース操作 with engine.connect() as connection: result = connection.execute("SELECT 1").scalar() return f"Hello from Flask! DB query result: {result}" if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5000)
このコードは、データベースURLをアプリケーションに直接結合しています。これを本番環境で実行するには、DATABASE_URL
を変更して再ビルド/再デプロイする必要があります。
良い実践(環境変数による外部化された設定):
# app.py import os from flask import Flask from sqlalchemy import create_engine app = Flask(__name__) # 環境変数からデータベースURLを取得 # ローカル開発用の適切なデフォルトを提供するが、本番環境では上書きされることを想定 DATABASE_URL = os.environ.get("DATABASE_URL", "postgresql://dev_user:dev_pass@localhost:5432/mydatabase_dev") engine = create_engine(DATABASE_URL) @app.route('/') def hello(): with engine.connect() as connection: result = connection.execute("SELECT 1").scalar() return f"Hello from Flask! DB query result: {result}" if __name__ == '__main__': # 環境変数からポートを取得、デフォルトは5000 port = int(os.environ.get("PORT", 5000)) app.run(debug=True, host='0.0.0.0', port=port)
これで、アプリケーションはDATABASE_URL
とPORT
を環境変数から取得します。
環境変数の設定方法:
-
ローカル開発(シェル):
export DATABASE_URL="postgresql://myuser:mypass@prod_db_host:5432/mydatabase_prod" export PORT=8080 python app.py
-
ローカル開発(
.env
ファイルとpython-dotenv
):python-dotenv
のようなライブラリを使用して、ローカル開発中に.env
ファイルから環境変数をロードし、シェルの履歴を散らかしたり、設定を忘れたりするのを防ぐことができます。.env
ファイルは、常にバージョン管理から除外する必要があります(例:.gitignore
経由)。.env
ファイル:DATABASE_URL="postgresql://dev_user:dev_pass@localhost:5432/mydatabase_dev" PORT=5000 SOME_API_KEY="your_dev_api_key_here"
app.py
(python-dotenv
を使用):import os from dotenv import load_dotenv # 'pip install python-dotenv'を忘れないでください from flask import Flask from sqlalchemy import create_engine load_dotenv() # これは.envファイルから環境変数をロードします app = Flask(__name__) DATABASE_URL = os.environ.get("DATABASE_URL") # これで.envまたは実際の環境変数からロードされます # ... 残りのコード
-
コンテナ化された環境(Docker):
docker run -p 8080:8080 -e DATABASE_URL="postgresql://prod_user:prod_pass@prod_db_host:5432/mydatabase_prod" -e PORT=8080 my-flask-app:latest
-
オーケストレーションシステム(Kubernetes、Docker Compose): Kubernetesでは、Deployment設定に環境変数を定義でき、機密データにはしばしば
Secrets
を参照します。Docker Composeは、docker-compose.yml
のenvironment
セクションを使用します。例
docker-compose.yml
:version: '3.8' services: web: build: . ports: - "8080:5000" environment: - DATABASE_URL=postgresql://user:password@db:5432/mydatabase_prod - PORT=5000 depends_on: - db db: image: postgres:13 environment: POSTGRES_DB: mydatabase_prod POSTGRES_USER: user POSTGRES_PASSWORD: password
本番シナリオでは、APIキーやデータベース認証情報のような機密データについては、通常、クラウドプロバイダー(例:AWS Secrets Manager、Google Secret Manager、Azure Key Vault)が提供するシークレット管理ツールや、HashiCorp Vaultのような外部ソリューションと統合します。これらのツールは、シークレットを実行時にアプリケーションに環境変数として(またはファイルとしてマウントして)注入し、デプロイ設定ファイルでの直接の公開を回避することで、セキュリティをさらに強化します。
成果:堅牢でポータブルなアプリケーション
12ファクターアプリの設定原則を採用することで、大幅なメリットが得られます。アプリケーションは、設定の問題がコードバグから分離されているため、本質的に堅牢になります。ポータビリティを獲得し、同じアプリケーションバイナリが、環境変数を調整するだけで、開発者のラップトップからステージングサーバー、そして最終的には本番クラスターまで、さまざまな環境でシームレスに実行できるようになります。この一貫性は、環境ドリフトを最小限に抑え、「私のマシンでは動作する」症候群を軽減します。さらに、機密資格情報がソースコードやデプロイメントマニフェストに刻み込まれなくなるため、セキュリティが強化されます。
要するに、外部化された設定を採用することは、アプリケーションを緊密に結合された環境依存のアーティファクトから、あらゆる環境で繁栄できる柔軟で適応性のあるコンポーネントに変え、現代のクラウドネイティブバックエンド開発の基盤となります。