0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ECS上のSpringBootアプリケーションからサイドカーのAppConfig Agentを使ってAppConfigのFeature Flagを取得する方法

0
Posted at

はじめに

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のtypeAWS.AppConfig.FeatureFlagsを指定: これにより、Feature Flags用のスキーマ(flagsvalues)で設定を管理できます
  • デプロイ戦略を即時反映に設定: フラグの切り替えは即座に反映させたいため、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:StartConfigurationSessionappconfig: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リソースの属性をタスク定義内で {{ tfstate aws_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_versionaws_appconfig_deploymentは、値に変更がなくてもterraform applyのたびに新しいバージョンが作成・デプロイされます。これはTerraformのリソースの性質上避けられないため、CI/CDでapplyを頻繁に実行する場合は留意してください
  • ポーリング間隔とキャッシュ: AppConfig AgentはPOLL_INTERVAL(デフォルト45秒)の間隔でAppConfigをポーリングし、結果をローカルにキャッシュします。フラグを変更してデプロイしても、アプリケーションに反映されるまで最大でPOLL_INTERVAL秒の遅延があります。即時性が求められるユースケースではこの間隔を短くすることを検討してください
  • Feature Flagsのenabledattributesの関係: Feature Flags形式では、enabledfalseのフラグはattributes(本記事の例ではmessagecontent)がレスポンスに含まれません。文字列などの属性値を常に取得したい場合は、enabledtrueに固定しておく必要があります
  • essential: falseの設定: サイドカーが停止してもメインコンテナが停止しないようにしていますが、その代わりアプリ側で必ずフォールバック処理を実装してください
  • IAM権限の最小化: AppConfig Agentが必要とする権限は StartConfigurationSessionGetLatestConfiguration の2つのみです。Resourceも必要なApplicationに絞ることを推奨します
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?