こんにちは。 弁護士ドットコム株式会社 Professional Tech Lab の伊藤 (@michimani) です。
Professional Tech Lab は今年の 2 月に新しく作られたチームで、主に AI(特に自然言語処理)を用いたプロダクトの開発や研究をしています。私はその中で、新しいプロダクトの開発や既存プロダクトへの AI を活用した機能の実装を担当しています。
この記事では、 Azure App Configuration のリソース作成方法とアプリケーションからリソースへアクセスする方法について説明します。 また実際にアプリケーションが提供している機能の利用可否を再起動・再デプロイなしで切り替えるアーキテクチャの例についても紹介します。
- はじめに
- App Configuration とは
- App Configuration リソースの作成方法
- Python のコードから App Configuration リソースへアクセスする方法
- 挙動を確認してみる
- 実際のアプリケーションではどのような使い方が想定できるか
- まとめ
はじめに
この記事は 弁護士ドットコム Advent Calendar 2023 の 18 日目の記事です。
昨日は こがさん @poemnによる プロジェクトマネージャーになって半年、やったこと、そして意識したこと。 でした。
App Configuration とは
App Configuration は Azure が提供しているサービスのひとつで、アプリケーションの設定情報を管理するためのサービスです。設定情報は key-value 形式で管理されており、アプリケーションは REST API を通じて設定情報にアクセスできます。
アプリケーションの設定情報は環境変数として保持できますが、その場合は設定情報を変更するたびにアプリケーションを再起動・再デプロイする必要があります。App Configuration を使うことで設定情報をアプリケーションから切り離し、再起動・再デプロイすることなくアプリケーションの挙動を変更できます。
App Configuration リソースの作成方法
App Configuration には、任意の値を key-value 形式で保存できる Key and value と、 その中でも特に真偽値を保存できる Feature という概念があります。今回は Feature を利用します。
Azure Portal や Azure CLI による App Configuration のリソース作成方法については公式ドキュメント 1 に記載されています。 なので、ここでは Terraform 2 を使ってリソースを定義してみます。
terraform { required_providers { azurerm = { source = "hashicorp/azurerm" version = "=3.84.0" } } required_version = "= 1.6.5" } provider "azurerm" { features {} }
resource "azurerm_resource_group" "main" { name = "sample-rg" location = "japaneast" } resource "azurerm_app_configuration" "main" { name = "sample-app-configuration" resource_group_name = azurerm_resource_group.main.name location = "japaneast" sku = "standard" } data "azurerm_client_config" "current" {} resource "azurerm_role_assignment" "main_dataowner" { scope = azurerm_app_configuration.main.id role_definition_name = "App Configuration Data Owner" principal_id = data.azurerm_client_config.current.object_id } resource "azurerm_app_configuration_feature" "feature" { configuration_store_id = azurerm_app_configuration.main.id name = "sushi-service" enabled = true lifecycle { ignore_changes = [ enabled, ] } depends_on = [ azurerm_role_assignment.main_dataowner, ] } output "appconfig_endpoint" { value = azurerm_app_configuration.main.endpoint description = "endpoint of app configuration" }
azurerm_app_configuration_feature.enabled
の値は Terraform の管理外で変更するため ignore_changes
に設定しています。
Python のコードから App Configuration リソースへアクセスする方法
App Configuration へのアクセスは REST API を通じて行うことができますが 3 、 今回は Python 用の SDK を使ってアクセスします。必要なモジュールは下記のとおりです。
azure-appconfiguration
: Azure/azure-sdk-for-python - GitHubazure-identity
: Azure/azure-sdk-for-python - GitHub
これらのモジュールを利用して、 Feature リソースを取得・更新するコードを書いてみます。
App Configuration クライアントの作成
Feature リソースを取得・更新するためには、まず App Configuration クライアントを作成する必要があります。 App Configuration のクライアント作成時には App Configuration のエンドポイントが必要です。
エンドポイントの値は terraform output
で確認するか、下記の Azure CLI コマンドで確認できます。
az appconfig show \ --resource-group 'sample-rg' \ --name 'sample-app-configuration' \ --query 'endpoint' \ --output tsv
今回はエンドポイントの値を環境変数 APP_CONFIGURATION_ENDPOINT
に設定するものとして、下記のようにクライアントを作成します。
from os import environ from azure.identity import DefaultAzureCredential def init_app_configuration_client() -> AzureAppConfigurationClient | None: endpoint = environ.get("APP_CONFIGURATION_ENDPOINT", "") if len(endpoint) == 0: print("APP_CONFIGURATION_ENDPOINT is not set") return None credential = DefaultAzureCredential() return AzureAppConfigurationClient(base_url=endpoint, credential=credential)
DefaultAzureCredential()
では、実行環境の認証情報を利用してクライアントを生成します。ローカルで実行する場合は Azure CLI の認証情報を利用しますが、 Azure 上の Container Apps などで実行する場合は各リソースに設定された Identity を利用します。
Feature リソースの取得
Feature リソースには .appconfig.featureflag/
という prefix が自動で付与されています。
対象の Feature にアクセスするには Feature 名に prefix を付けた値を Key として get_configuration_setting
メソッドで取得します。
from json import loads as json_loads from traceback import format_exc from azure.appconfiguration import AzureAppConfigurationClient from azure.core import exceptions def is_enabled_feature(client: AzureAppConfigurationClient, feature_name: str) -> bool: key = ".appconfig.featureflag/{}".format(feature_name) try: feature_flag = client.get_configuration_setting(key=key) if feature_flag is None: return False return json_loads(feature_flag.value)["enabled"] except exceptions.ResourceNotFoundError: print(f"key: {key} is not found") return False except Exception: print(format_exc()) return False
Feature リソースは ConfigurationSetting
として返されるので、 value
プロパティを Python Object に変換して enabled
の値を取り出しています。4
Feature リソースの更新
Feature リソースの更新は set_configuration_setting
で行います。
このメソッドは引数に ConfigurationSetting
型のオブジェクトを受け取り、更新後の ConfigurationSetting
型のオブジェクトを返します。
下記の実装では ConfigurationSetting
を継承している FeatureFlagConfigurationSetting
型のオブジェクトを渡しています。
from json import loads as json_loads from traceback import format_exc from azure.appconfiguration import AzureAppConfigurationClient, FeatureFlagConfigurationSetting from azure.core import exceptions def update_enabled( client: AzureAppConfigurationClient, feature_name: str, enabled: bool ) -> bool | None: key = ".appconfig.featureflag/{}".format(feature_name) try: feature_setting = FeatureFlagConfigurationSetting( feature_id=feature_name, enabled=enabled, ) new_config_setting = client.set_configuration_setting(feature_setting) if new_config_setting is None: return None return json_loads(new_config_setting.value)["enabled"] except exceptions.ResourceNotFoundError: print(f"key: {key} is not found") return None except Exception: print(format_exc()) return None
挙動を確認してみる
Feature の取得・更新するコードを実際に動かしてみます。
import sys from json import loads as json_loads from os import environ from traceback import format_exc from typing import Final from azure.appconfiguration import ( AzureAppConfigurationClient, FeatureFlagConfigurationSetting, ) from azure.core import exceptions from azure.identity import DefaultAzureCredential def init_app_configuration_client() -> AzureAppConfigurationClient | None: endpoint = environ.get("APP_CONFIGURATION_ENDPOINT", "") if len(endpoint) == 0: print("APP_CONFIGURATION_ENDPOINT is not set") return None credential = DefaultAzureCredential() return AzureAppConfigurationClient(base_url=endpoint, credential=credential) def update_enabled( client: AzureAppConfigurationClient, feature_name: str, enabled: bool ) -> bool | None: key = ".appconfig.featureflag/{}".format(feature_name) try: feature_setting = FeatureFlagConfigurationSetting( feature_id=feature_name, enabled=enabled, ) new_config_setting = client.set_configuration_setting(feature_setting) if new_config_setting is None: return None return json_loads(new_config_setting.value)["enabled"] except exceptions.ResourceNotFoundError: print(f"key: {key} is not found") return None except Exception: print(format_exc()) return None def is_enabled_feature(client: AzureAppConfigurationClient, feature_name: str) -> bool: key = ".appconfig.featureflag/{}".format(feature_name) try: feature_flag = client.get_configuration_setting(key=key) if feature_flag is None: return False return json_loads(feature_flag.value)["enabled"] except exceptions.ResourceNotFoundError: print(f"key: {key} is not found") return False except Exception: print(format_exc()) return False FEATURE_NAME: Final[str] = "sushi-service" if __name__ == "__main__": client = init_app_configuration_client() if client is None: sys.exit(1) enabled = is_enabled_feature(client, FEATURE_NAME) if enabled: print("sushi service is available") else: print("sushi service is NOT available") new_enabled = update_enabled(client, FEATURE_NAME, not enabled) print(f"Updated feature flag: {new_enabled}")
このコードを実行すると、実行するたびに Feature の値が切り替わることを確認できます。
$ python main.py sushi service is available Updated feature flag: False $ python main.py sushi service is NOT available Updated feature flag: True
実際のアプリケーションではどのような使い方が想定できるか
App Configuration の基本的な使い方がわかったところで、実際のアプリケーションでどのような使い方が想定できるかを考えてみます。例えば下記のようなアーキテクチャが考えられます。
まず、 Azure OpenAI Service にリクエストを送信するアプリケーションがあります。このアプリケーションは、 Azure App Configuration の Feature を参照して、 Azure OpenAI Service にリクエストを送信するかどうかを判断します。
Service Alerts では Azure OpenAI Service の特定のメトリクスを監視し、アラート状態が切り替わった際に Function Apps の関数を呼び出します。監視するメトリクスとしては、 GPT-4 によって処理されたトークン数などが考えられます。 この関数では、トリガーされたときの Service Alerts のアラート状態に応じて App Configuration の Feature の値を更新します。
Azure Cache for Redis を置いているのは、アプリケーションで取得した Feature の値をキャッシュするためです。 App Configuration には一定期間あたりのリクエスト数に制限があり、Standard SKU では 1 時間あたり 30,000 リクエストが上限となっています。5
この制限を超えてリクエストすると、 App Configuration は 429 Too Many Requests を返します。 このような状況を避けるためにアプリケーションは優先的に Redis から値を取得します。そして、キャッシュが存在しない場合のみ App Configuration にリクエストし、取得した値を Redis に保存します。
ただし、このキャッシュ時間が長いと App Configuration を利用するメリットである機能の利用可否をリアルタイムに切り替えることができなくなってしまうので設定する時間には注意が必要です。
まとめ
以上、 Azure App Configuration を使ってアプリケーションの再起動・再デプロイなし機能の利用可否を切り替える方法を紹介しました。
このような仕組みは フィーチャーフラグ や リリースフラグ などと呼ばれており、デプロイとリリースの分離や迅速なリリース・ロールバックの実現に効果的です。
弊社ではクラウドサインの開発において AWS の CloudWatch Evidently を利用していますが、App Configuration は広義での同類サービスと言えます。 クラウドサインでの CloudWatch Evidently 利用については SRE NEXT 2023 で登壇された SRE チーム上田さんの発表内で触れられているので、ぜひそちらもご覧ください。
明日は @dskymd によるブログです。お楽しみに。
- クイックスタート: Azure App Configuration ストアを作成する | Microsoft Learn↩
- hashicorp/azurerm | Terraform Registry↩
- Azure App Configuration REST API | Microsoft Learn↩
-
feature_flag.enabled
としても値を取得できますが、静的解析ツールの型チェックが通らないためvalue
を変換してから取得しています。↩ - 価格 - App Configuration | Microsoft Azure↩