AWS AppConfigで実現する安全な段階的リリース 〜レガシーシステムのモダナイゼーション事例〜

こんにちは、ADWAYS DEEEでアプリケーションエンジニアをしている中利です。

AWS AppConfigというサービスがチームで進めていたプロジェクトの段階リリースにとても便利だったので、今回はその活用事例を紹介します。

AWS AppConfigとは?

AWS AppConfig(以下AppConfig)は、AWSが提供するアプリケーション設定管理サービスです。フィーチャーフラグを動的に管理でき、アプリケーションの再デプロイなしで設定を変更できます。

主な特徴

  • 動的設定変更: アプリケーションの再起動なしで設定を更新
  • 段階的デプロイ: 設定を段階的に適用可能
  • 設定の検証: デプロイ前の設定内容を検証
  • 監視とロールバック: CloudWatchとの連携で監視と自動ロールバック

利用の背景

私が所属していたチームでは、レガシーシステムのモダナイゼーションを進めていました。対象サービスのモダナイゼーションには以下の要件がありました。

  1. 段階的移行: 対象となるシステムは影響範囲が大きかったため、新しいシステムへの移行を段階的に実施する必要がありました。実際の作業では、リクエストが増える月末月初の移行作業を避け、1か月かけて徐々にモダンシステムへ流れるリクエストの量を増やしていきました。
  2. IDベースでのシステムの振り分け: メッセージのIDによって柔軟にレガシーシステムとモダンシステムを振り分けたいという要件がありました。特定のIDのメッセージを新システムに流すことで、段階的な移行を実現できます。
  3. 迅速な切り替え: 問題発生時に素早い切り替えができることも安定したサービスの提供のために重要でした。

これらの要件を満たすためのリリース方法を検討していた際、AWSソリューションアーキテクトの方に相談する機会がありました。その中でAppConfigをご提案いただき、要件との親和性の高さから導入を決定しました。

実装方法

今回の要件のように、特定の条件に応じて異なる設定を提供するためには、AppConfigのマルチバリアント機能フラグルールを使用します。マルチバリアント機能フラグを利用するためにはAppConfig Agentが必要となるため、これを導入します。

ECSからAppConfigを使う場合、以下のようにECSのサイドカーコンテナとしてAppConfig Agentを利用します。

ソースコードの実装

AppConfigの実装例をサンプルコードを使って説明します。ECSのサイドカーコンテナとしてAppConfig Agentを利用する場合の例を示します。あくまでイメージを掴んでもらう説明のため、実際の実装詳細については公式ドキュメントをご覧ください。

AppConfigを使わない場合

if (id == 1 || id == 2 || id == 3) {
    sendToModern()
} else {
    sendToLegacy()
}

AppConfigを使う場合

featureFlag := getFlag(id)
if featureFlag {
    sendToModern()
} else {
    sendToLegacy()
}

func getFlag(id int) bool {
    client := http.DefaultClient
    urlStr := fmt.Sprintf("http://localhost:2772/applications/%s/environments/%s/configurations/%s", 
        applicationID, environmentID, configurationProfileID)
    url, err := url.ParseRequestURI(urlStr)
    if err != nil {
        return false
    }
    
    request := &http.Request{
        Method: http.MethodGet,
        Header: http.Header{
            contextHeader: []string{fmt.Sprintf("id=%d", id)},
        },
        URL: url,
    }
    
    response, err := client.Do(request)
    responseBody, err := io.ReadAll(response.Body)
    var appConfigResponse appConfigResponse
    err = json.Unmarshal(responseBody, &appConfigResponse)
    return appConfigResponse.FeatureFlag.Enabled
}

localhost:2772はECSのサイドカーコンテナで動作するAppConfig Agentのエンドポイントです。

コードは長くなりますが、一度この変更を加えると、モダンシステムを利用するIDの対象をAppConfig側のデプロイのみで変更できるようになります。

フィーチャーフラグの設定例

コンソール画面からフィーチャーフラグを追加することもできますが、AWS CLIを使ってデプロイする場合はJSONファイルで設定を記述します。

{
  "flags": {
    "feature_flag": {
      "name": "Feature Flag",
      "description": "サンプルフィーチャーフラグ"
    }
  },
  "values": {
    "feature_flag": {
      "_variants": [
        {
          "enabled": true,
          "name": "ToModern",
          "rule": "(in $id [\"1\",\"2\",\"3\"])"
        },
        {
          "enabled": false,
          "name": "ToLegacy"
        }
      ]
    }
  },
  "version": "1"
}

デプロイ方法

デプロイ方法として私が所属していたチームでは、以下の3つの候補を検討しました。

  1. AWS CLIを使用
  2. コンソール画面からデプロイ
  3. Terraformを使用

検討の結果、以下の理由からAWS CLIを使用することに決定しました。

  • 設定値をコードとして管理したい
  • Terraformでは新しいバージョンの作成が難しい
  • GitHub Actionsのワークフローからmakeコマンドを実行する形式にすることで、GitHubからのデプロイが可能
  • チームで利用しているECS、LambdaもGitHub Actionsでデプロイしているため、同じ感覚でデプロイができる

これにより、コードベースでの管理と、既存のCI/CDパイプラインとの統合を実現できました。

エンジニア以外のチームメンバーの方が設定を変更したい場合や、コード管理をする必要がない場合は、AWSコンソール画面からのデプロイがおすすめです。コンソール画面は使いやすいUIになっているので、技術的な知識がなくても安心して設定を変更できます。

AWS CLIを使ったデプロイ

チームではAWS CLIを使ったデプロイを採用し、以下のようなMakefileを作成しました。

deploy:
    $(eval version := $(shell $(MAKE) create-hosted-configuration-version | jq -r '.VersionNumber'))
    $(MAKE) start-deployment configuration-version=$(version)

create-hosted-configuration-version:
    @aws appconfig create-hosted-configuration-version \
        --application-id $${APPCONFIG_APP_ID} \
        --configuration-profile-id $${APPCONFIG_CFG_PROF_ID} \
        --content-type "application/json" \
        --content file://./$${APPCONFIG_ENV}/$${APPCONFIG_CONFIGURATION_FILE_NAME} \
        --cli-binary-format raw-in-base64-out \
        outfile

start-deployment:
    aws appconfig start-deployment \
        --application-id $${APPCONFIG_APP_ID} \
        --environment-id $${APPCONFIG_ENV_ID} \
        --deployment-strategy-id $${APPCONFIG_DEPLOY_STG_ID} \
        --configuration-profile-id $${APPCONFIG_CFG_PROF_ID} \
        --configuration-version $(configuration-version)

CI/CDパイプラインの構築

GitHub Actionsを使用して以下のワークフローを構築しました。

- name: Deploy AppConfig
  run: |
    make deploy
  env:
    APPCONFIG_APP_ID: ${{ inputs.app_id }}
    APPCONFIG_ENV_ID: ${{ inputs.env_id }}
    APPCONFIG_CFG_PROF_ID: ${{ inputs.cfg_prof_id }}
    APPCONFIG_DEPLOY_STG_ID: ${{ inputs.deploy_stg_id }}
    APPCONFIG_ENV: ${{ inputs.env }}
    APPCONFIG_CONFIGURATION_FILE_NAME: ${{ inputs.config_file }}

AppConfigを導入して良かったこと

1. 迅速な設定変更

ECSのデプロイ(通常5-10分)と比較して、AppConfigの設定変更は数秒で完了します。

2. リスクの軽減

  • 段階的な設定適用により、問題の影響範囲を限定
  • 即座のロールバックが可能
  • 設定変更の監視とアラート

3. 運用効率の向上

  • コンソールからワンクリックで設定変更可能
  • 設定変更履歴の管理

4. 開発とリリースの並行

今回モダナイゼーションしたシステムでは、ある特定のIDをもつレコードに対しては特別なロジックを使った処理が必要でした。

AppConfigを導入したことで、IDベースでモダンシステムとレガシーシステムの振り分けが可能になりました。これにより、特定のIDに対する特別な処理の開発を完了させる前に、他のIDに対する段階的なリリースを開始できました。開発とリリースを並行して進められるようになり、プロジェクト全体の開発効率が大きく向上しました。

当初AppConfigを導入することを決断した際には、開発とリリースを並行して進められるという利点は想定していませんでした。しかし、実際の運用を通じて、この機能が開発効率の向上に大きく貢献することが分かりました。このように、導入時の想定以上の価値を見出すことができたのは、大きな収穫でした。

まとめ

AWS AppConfigは、まだ広く知られているツールではありませんが、以下の理由からおすすめできます。

  • 簡単な導入: 既存のアプリケーションに最小限の変更で導入可能
  • 迅速な設定変更: アプリケーションの再デプロイなしで設定を更新
  • リスク管理: 段階的デプロイとロールバック機能
  • 運用効率: 設定管理の自動化と監視

特に、マイクロサービスアーキテクチャや段階的リリースが必要なプロジェクトでは、AppConfigの導入を検討する価値があります。

設定管理でお困りの際は、ぜひAppConfigの利用を検討してみてください!

謝辞

本記事で紹介したAWS AppConfigの導入にあたり、AWSソリューションアーキテクトの方には貴重なアドバイスをいただきました。要件に合わせた最適なソリューションをご提案いただき、導入の意思決定から実装まで親身にサポートしていただきました。この場を借りて深く感謝申し上げます。

参考リンク

AppConfigについてより詳しく知りたい方、実際に導入を検討されている方は、以下の公式ドキュメントをご参照ください。

公式ドキュメント