はじめに
AWS AppConfigは、アプリケーションの設定を動的に管理・配信するためのサービスです。Feature Flagsなど、アプリケーションを再デプロイせずに挙動を変更したいユースケースに最適です。
本記事では、ECS上で動作するSpring Bootアプリケーションから、サイドカーとして配置したAppConfig Agentを経由してAppConfigの値を取得する方法を、インフラ構築からアプリケーション実装まで一気通貫で解説します。
やりたいこと
本記事で扱うユースケースは、AppConfigでFeature Flagsを管理し、その値に応じてアプリケーションの挙動を動的に制御するというものです。
具体的には、AppConfigに以下の 3つの値 を持たせます。
| 値 | 型 | 説明 |
|---|---|---|
flag |
boolean(Feature Flag) | 機能の有効/無効を制御するフラグ |
message |
string(Feature Flagの属性) | フラグがONのときにユーザーに表示するメッセージ |
AppConfigのFeature Flags形式で保存されるJSONは以下のような構造になります。
{
"flag": { "enabled": true, "content": "This is a sample message." }
}
booleanのフラグは { "enabled": true/false } というオブジェクトで表現され、文字列を持たせたい場合はattributesとしてcontentのようなフィールドを追加する形になります。このFeature Flags特有のスキーマ構造は、後述のTerraformやJavaのモデルクラスに影響します。
なぜAppConfig Agentを使うのか
AppConfigの値を取得するにはAWS SDKを直接使う方法もありますが、AppConfig Agentをサイドカーとして使う方法には以下のメリットがあります。
- SDKレス: アプリケーション側にAWS SDKの依存が不要。単純なHTTP GETで値を取得できる
- ローカルキャッシュ: Agentがバックグラウンドでポーリング・キャッシュするため、アプリからのリクエストは常にローカルのキャッシュに対して行われる
-
低レイテンシー:
localhost:2772へのHTTPリクエストなので、AWSへの直接APIコールと比べて非常に高速 - 言語非依存: サイドカーなのでアプリの言語やフレームワークに依存しない
全体アーキテクチャ
┌─────────────────────────────────────────────┐
│ ECS Task │
│ │
│ ┌──────────────┐ ┌───────────────────┐ │
│ │ Spring Boot │ │ AppConfig Agent │ │
│ │ Application │───▶│ (sidecar) │ │
│ │ │HTTP│ localhost:2772 │ │
│ └──────────────┘ └─────────┬─────────┘ │
│ │ │
└────────────────────────────────┼────────────┘
│ ポーリング
▼
┌───────────────────┐
│ AWS AppConfig │
│ (Feature Flags) │
└───────────────────┘
AppConfig Agentは定期的に(デフォルト45秒間隔)AWS AppConfigから最新の設定をポーリングし、ローカルにキャッシュします。Spring Bootアプリケーションは http://localhost:2772 に対してHTTP GETリクエストを送るだけで、キャッシュされた最新の設定値を取得できます。
1. インフラ構築(Terraform)
AppConfigリソースの作成
まず、AppConfig Application、Environment、Configuration Profile、およびHosted Configuration Versionを作成します。
# AppConfig Application
resource "aws_appconfig_application" "main" {
name = "my-app-appconfig"
description = "AppConfig for feature flags"
}
# AppConfig Environment
resource "aws_appconfig_environment" "main" {
name = "my-app-appconfig-environment"
description = "Main environment for AppConfig"
application_id = aws_appconfig_application.main.id
}
# AppConfig Configuration Profile(Feature Flags型)
resource "aws_appconfig_configuration_profile" "feature_flags" {
application_id = aws_appconfig_application.main.id
name = "my-app-appconfig-feature-flags"
description = "Feature flags"
location_uri = "hosted"
type = "AWS.AppConfig.FeatureFlags"
}
# Hosted Configuration Version(Feature Flagsの実際の値)
resource "aws_appconfig_hosted_configuration_version" "feature_flags" {
application_id = aws_appconfig_application.main.id
configuration_profile_id = aws_appconfig_configuration_profile.feature_flags.configuration_profile_id
content_type = "application/json"
content = jsonencode({
flags = {
flag = {
name = "flag"
attributes = {
content = {
constraints = {
type = "string"
}
}
}
}
}
values = {
flag = {
enabled = var.flag_enabled # true or false
content = var.flag_message # 表示するメッセージ文字列
}
}
version = "1"
})
}
# カスタムデプロイ戦略: 即時反映(デプロイ時間0分、ベイク時間0分)
resource "aws_appconfig_deployment_strategy" "immediate" {
name = "my-app-appconfig-immediate"
description = "Immediate deployment with no bake time"
deployment_duration_in_minutes = 0
final_bake_time_in_minutes = 0
growth_factor = 100
growth_type = "LINEAR"
replicate_to = "NONE"
}
# デプロイ
resource "aws_appconfig_deployment" "feature_flags" {
application_id = aws_appconfig_application.main.id
configuration_profile_id = aws_appconfig_configuration_profile.feature_flags.configuration_profile_id
configuration_version = aws_appconfig_hosted_configuration_version.feature_flags.version_number
deployment_strategy_id = aws_appconfig_deployment_strategy.immediate.id
environment_id = aws_appconfig_environment.main.environment_id
}
ポイントは以下の通りです。
-
Configuration Profileの
typeにAWS.AppConfig.FeatureFlagsを指定: これにより、Feature Flags用のスキーマ(flagsとvalues)で設定を管理できます -
デプロイ戦略を即時反映に設定: フラグの切り替えは即座に反映させたいため、
deployment_duration_in_minutes = 0としています -
contentのJSON構造:flagsでフラグの定義(スキーマ)、valuesで実際の値を記述します
IAMポリシーの追加
ECSタスクロールにAppConfigへのアクセス権限を付与します。
# AppConfigから設定を取得するための権限
resource "aws_iam_policy" "task_appconfig" {
name = "my-app-appconfig-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"appconfig:StartConfigurationSession",
"appconfig:GetLatestConfiguration"
]
Resource = "arn:aws:appconfig:ap-northeast-1:${data.aws_caller_identity.current.account_id}:application/*"
}
]
})
}
resource "aws_iam_role_policy_attachment" "task_appconfig" {
policy_arn = aws_iam_policy.task_appconfig.arn
role = aws_iam_role.task_role.name
}
AppConfig Agentが必要とするIAMアクションは appconfig:StartConfigurationSession と appconfig:GetLatestConfiguration の2つです。
2. ECSタスク定義にサイドカーを追加(ecspresso)
ECSへのデプロイには ecspresso を使用しています。ecspressoの tfstate plugin を使うと、Terraformで管理しているリソースの属性をタスク定義から直接参照できます。
ecspresso.yml(tfstate pluginの設定)
# ecspresso.yml
region: ap-northeast-1
cluster: my-cluster
service: my-service
service_definition: ecs-service-def.json
task_definition: ecs-task-def.jsonnet
plugins:
- name: tfstate
config:
url: s3://my-tfstate-bucket/appconfig/terraform.tfstate
タスク定義(ecs-task-def.jsonnet)
{{ tfstate }} でTerraformリソースの属性を直接参照し、AppConfig Agentサイドカーを追加します。
// ecs-task-def.jsonnet
{
containerDefinitions: [
// ===== springbootコンテナ =====
{
// ... image など ...
environment: [
// ... 既存の環境変数 ...
// ▼ AppConfig用の環境変数を追加(tfstateから参照)
{
name: 'APPCONFIG_APPLICATION',
value: '{{ tfstate `aws_appconfig_application.main.name` }}',
},
{
name: 'APPCONFIG_ENVIRONMENT',
value: '{{ tfstate `aws_appconfig_environment.main.name` }}',
},
{
name: 'APPCONFIG_CONFIGURATION_PROFILE',
value: '{{ tfstate `aws_appconfig_configuration_profile.feature_flags.name` }}',
},
],
// ... logConfiguration など ...
},
// ===== AppConfig Agent サイドカー =====
{
name: 'appconfig-agent',
image: 'public.ecr.aws/aws-appconfig/aws-appconfig-agent:2.x',
essential: false, // サイドカーなのでessentialはfalse
environment: [
{
name: 'SERVICE_REGION',
value: 'ap-northeast-1',
},
{
// 起動時にプリフェッチする設定パスを指定
name: 'PREFETCH_LIST',
value: '/applications/{{ tfstate `aws_appconfig_application.main.name` }}/environments/{{ tfstate `aws_appconfig_environment.main.name` }}/configurations/{{ tfstate `aws_appconfig_configuration_profile.feature_flags.name` }}',
},
{
// ポーリング間隔(秒)。デフォルトは45秒
name: 'POLL_INTERVAL',
value: '45',
},
],
portMappings: [
{
containerPort: 2772,
hostPort: 2772,
protocol: 'tcp',
},
],
// ... logConfiguration など ...
},
],
// ... タスクロールなどタスク全体の設定 ...
}
ポイント:
-
tfstate plugin: Terraformリソースの属性をタスク定義内で
{{ tfstateaws_appconfig_application.main.name}}のように直接参照でき、リソース名をハードコードする必要がありません -
PREFETCH_LIST: Agentの起動時に事前にフェッチする設定パスを指定します。これにより、アプリケーションの初回リクエスト時にもキャッシュが利用可能になります -
essential: false: AppConfig Agentが停止してもタスク全体が停止しないようにします。アプリ側でフォールバック処理を入れることで可用性を担保します -
POLL_INTERVAL: ポーリング間隔を秒数で指定します(デフォルト45秒) -
webappコンテナへの環境変数追加:
APPCONFIG_APPLICATION等をwebappコンテナにも渡します。Spring Bootのapplication.ymlで${APPCONFIG_APPLICATION}のようにプレースホルダーとして参照するためです
3. Spring Bootアプリケーション実装
3-1. 設定ファイル(application.yml)
application:
infra:
appconfig:
agentBaseUrl: http://localhost:2772
configurationPath: /applications/${APPCONFIG_APPLICATION}/environments/${APPCONFIG_ENVIRONMENT}/configurations/${APPCONFIG_CONFIGURATION_PROFILE}
connectTimeout: 2000
readTimeout: 2000
agentBaseUrlは常に http://localhost:2772 です。同一ECSタスク内のサイドカーなので、localhostでアクセスできます。
3-2. AppConfigService(値取得サービス)
AppConfig Agentに対してHTTP GETリクエストを送り、設定値を取得するサービスクラスです。Spring 6のRestClientを使用しています。
@Service
public final class AppConfigService {
private final RestClient restClient;
private final String configurationPath;
@Autowired
public AppConfigService(
@Value("${application.infra.appconfig.agentBaseUrl}") final String agentBaseUrl,
@Value("${application.infra.appconfig.configurationPath:}") final String configurationPath,
@Value("${application.infra.appconfig.connectTimeout}") final int connectTimeout,
@Value("${application.infra.appconfig.readTimeout}") final int readTimeout
) {
final var requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(connectTimeout);
requestFactory.setReadTimeout(readTimeout);
this.restClient = RestClient.builder().baseUrl(agentBaseUrl).requestFactory(requestFactory).build();
this.configurationPath = configurationPath;
}
public FeatureFlagConfig getFeatureFlagConfig() {
try {
return Objects.requireNonNull(
restClient.get().uri(configurationPath).retrieve().body(FeatureFlagConfig.class)
);
} catch (final Exception e) {
LOGGER.warn("Failed to fetch AppConfig, returning default config", e);
return new FeatureFlagConfig(); // デフォルト値(全フラグOFF)を返却
}
}
}
ポイント:
- フォールバック処理: リクエスト失敗時はデフォルト値(全フラグOFF)を返却し、サイドカーが不調でもアプリケーションは正常に動作します
- タイムアウト設定: connect/readともに2秒に設定し、Agentの応答がない場合は速やかにフォールバックします
3-3. FeatureFlagConfig(レスポンスモデル)
AppConfig Feature Flagsのレスポンスは以下のようなJSON構造です。
{
"flag": { "enabled": true, "content": "This is a sample message." }
}
各フラグが { "enabled": bool } というネスト構造を持つため、それに対応するJava Recordを定義します。
@JsonIgnoreProperties(ignoreUnknown = true)
public record FeatureFlagConfig(
@Nullable MessageFlag flag
) {
// nullの場合はデフォルト値で初期化
public FeatureFlagConfig {
flag = flag != null ? flag : new MessageFlag();
}
public FeatureFlagConfig() {
this(new MessageFlag());
}
@JsonIgnoreProperties(ignoreUnknown = true)
public record MessageFlag(boolean enabled, @JsonProperty("content") String content) {
public MessageFlag() { this(false, ""); }
}
}
取得した FeatureFlagConfig を使って、フラグの状態に応じた処理(リクエストのブロック、メッセージの表示など)をアプリケーション側で自由に実装できます。
4. AWS CLIでAppConfigの現在値を取得する
GitHub Actions等のCI/CDパイプラインやターミナルから、AppConfigの現在の設定値を確認したい場合は、以下のAWS CLIコマンドで取得できます。
# 1. セッショントークンを取得
SESSION_TOKEN=$(aws appconfigdata start-configuration-session \
--application-identifier "my-app-appconfig" \
--environment-identifier "my-app-appconfig-environment" \
--configuration-profile-identifier "my-app-appconfig-feature-flags" \
--region ap-northeast-1 \
--query 'InitialConfigurationToken' \
--output text)
# 2. 最新の設定値を取得
aws appconfigdata get-latest-configuration \
--configuration-token "$SESSION_TOKEN" \
--region ap-northeast-1 \
/tmp/appconfig-config.json
# 3. 取得したJSONから値を参照
jq . /tmp/appconfig-config.json
まとめ
本記事では、ECS上のSpring BootアプリケーションからAppConfig Agentサイドカーを使ってAWS AppConfigの値を取得する方法を解説しました。
設計上のポイント
| 観点 | 方針 |
|---|---|
| 値の取得 | AppConfig Agent(サイドカー)経由で localhost:2772 にHTTP GET |
| フォールバック | Agent取得失敗時はデフォルト値(全フラグOFF)を返却 |
| デプロイ戦略 | 即時反映(deployment_duration = 0, bake_time = 0) |
| ECSデプロイ | ecspressoでJsonnetタスク定義を管理 |
注意点
-
terraform applyのたびにデプロイが実行される:
aws_appconfig_hosted_configuration_versionとaws_appconfig_deploymentは、値に変更がなくてもterraform applyのたびに新しいバージョンが作成・デプロイされます。これはTerraformのリソースの性質上避けられないため、CI/CDでapplyを頻繁に実行する場合は留意してください -
ポーリング間隔とキャッシュ: AppConfig Agentは
POLL_INTERVAL(デフォルト45秒)の間隔でAppConfigをポーリングし、結果をローカルにキャッシュします。フラグを変更してデプロイしても、アプリケーションに反映されるまで最大でPOLL_INTERVAL秒の遅延があります。即時性が求められるユースケースではこの間隔を短くすることを検討してください -
Feature Flagsの
enabledとattributesの関係: Feature Flags形式では、enabledがfalseのフラグはattributes(本記事の例ではmessageのcontent)がレスポンスに含まれません。文字列などの属性値を常に取得したい場合は、enabledをtrueに固定しておく必要があります -
essential: falseの設定: サイドカーが停止してもメインコンテナが停止しないようにしていますが、その代わりアプリ側で必ずフォールバック処理を実装してください -
IAM権限の最小化: AppConfig Agentが必要とする権限は
StartConfigurationSessionとGetLatestConfigurationの2つのみです。Resourceも必要なApplicationに絞ることを推奨します