電通総研 テックブログ

電通総研が運営する技術ブログ

AWS AppConfigとLambdaとNext.jsを用いてFeatureFlagを実装してみた

みなさんこんにちは。エンタープライズ第一本部の鈴木です。
この記事では、以前記事にまとめたFeature Flag(フィーチャーフラグ)を取り入れたアプリケーションを、勉強を兼ねてAWS上に構築してみたため、記事にまとめていきます。
Feature Flagについては、以前私がまとめた以下のリンク先の記事を参考にしていただければ幸いです。

Feature Flagという開発手法についてまとめる

1.はじめに

今回は、以下2つの目的をもとに簡単なデモアプリを作成しています。

  • Feature Flagを導入したアプリケーションを実装してみる。
  • Feature Flagを管理することができるAWSのサービス「AWS AppConfig」の使用感を確認する。

また、今回構築するシステムの全体構成は以下のとおりです。

構成図

今回重要となってくるのがAppConfigというAWSのサービスです。詳しくは次章でまとめますが、Feature Flagの管理に利用しています。
また、アプリケーションはLambdaにホストします。今回の目的はFeature Flagを用いたアプリケーションを実装することであり、構成をシンプルにするため、CloudFront + Lambdaのサーバーレス構成としています。LambdaはECSなどサーバーが必要なサービスと比較して、実行回数と実行時間に応じて料金が発生するため、今回のようなとりあえず試してみるミニマムな開発では良い選択だと思います。また、アプリケーションはコンテナで作成しているため、ECSなどサーバーありのホストに移行する際も比較的容易に移行できるのもメリットです。

アプリケーションは、フロントエンド/バックエンドともにNext.jsで構築しています。主要なパッケージのバージョンは以下のとおりです。

パッケージ バージョン
Next.js 15.3.1
node.js v22-slim
@aws-sdk/client-appconfigdata 3.798.0

アプリケーションはLambdaにホストしていますが、通常LambdaではWebアプリケーションをそのまま動作させることができません。しかし、Lambda Web AdapterというAWSが提供するツールを導入することで、実装をほとんど変更することなくWebアプリケーションをLambda上で実行できるようにしています。本筋からは外れるため詳細は記載しませんが、「構築手順」に実装方法は記載しているため、参考にしてみてください。

肝心のFeature Flagは、アプリケーションからAWS SDKを用いてAppConfigから取得しています。実装や設定方法は「構築手順」に記載しています。

2.AWS AppConfigについて

この章では、先ほどから登場しているAppConfigについて紹介します。

AWS AppConfigとは、Feature Flagを管理するAWSのサービスで、開発者が完全なコードをデプロイすることなく、アプリケーションの動作を迅速かつ安全に変更することができます。
2019年12月に、AWS Systems Managerの機能の1つとしてリリースされたサービスで、もともとAmazonAWS内で使われていた仕組みを一般向けにリリースしたサービスのようです。

詳しく知りたい方は、以下のドキュメントを参照してください。
参考:AWS AppConfig とは何ですか? - aws.amazon.com

Feature Flagの管理にAppConfigを利用するメリットとしては、以下が挙げられます。

  • Feature Flagの設定・バージョン・環境の管理を自前で実装することなく、マネージドに管理することができる。
  • CloudWatchと連携することで、予期せぬ動作を検知して、自動でロールバックすることが可能である。
    • 例えば、Feature Flagのリリース後にCloudWatch のアラームがトリガーされた場合、AppConfig が自動的に変更をロールバックするように設定できる。これにより、アプリケーション更新によるユーザーへの影響を最小限に抑えることが可能である。
  • Feature Flagごとにバリアントを設定することができ、特定のユーザーIDや地理的位置などの条件をもとに評価を返却することができる。また、コンテキスト値に基づいてトラフィックを分割することも可能で、カナリアリリースやA/Bテストの実現が可能である。
  • Feature Flagのリリース戦略をリリース単位で指定することができる。

3.構築内容

3-1.アプリケーション

今回は以下の3つのFeature Flagを持つ、簡単なTODOアプリを作成しました。

  • isNewFeatureEnabled(trueの時、「新機能が有効になっています」と文言が表示される)
  • isTodoReorderEnabled(trueの時、TODOリストを入れ替えられる機能を有効にする)
  • isTodoMemoEnabled(trueの時、メモ機能を有効にする)

app

アプリケーションはサーバーサイドレンダリングで、Feature Flagは初期表示の際にappconfig.tsを用いてAppConfigから取得するようにしています。

// app/page.tsx
import getAppConfigConfiguration from "./appconfig";
import TodoApp from "./components/TodoApp";

export default async function Home() {
  const featureFlags = await getAppConfigConfiguration();

  const initialTodos = [
    { id: 1, text: "お肉を買う" },
    { id: 2, text: "ニンジンを買う" },
    { id: 3, text: "ジャガイモを買う" },
    { id: 4, text: "ルーを買う" },
  ];

  return <TodoApp initialTodos={initialTodos} featureFlags={featureFlags} />;
}
// appconfig.ts
import {
  AppConfigDataClient,
  GetLatestConfigurationCommand,
  StartConfigurationSessionCommand,
  StartConfigurationSessionCommandInput,
} from "@aws-sdk/client-appconfigdata";

// エラー等でFeature Flagが取得できなかった場合のデフォルト値
const FALLBACK_CONFIG = {
  isTodoReorderEnabled: { enabled: false },
  isTodoMemoEnabled: { enabled: false },
  isNewFeatureEnabled: { enabled: false },
};

async function getAppConfigConfiguration() {
  try {
    // AppConfig クライアント用の設定値を環境変数から取得する。
    const region = process.env.AWS_REGION || "ap-northeast-1";
    const applicationIdentifier = process.env.APPCONFIG_APPLICATION_ID;
    const environmentIdentifier = process.env.APPCONFIG_ENVIRONMENT_ID;
    const configurationName = process.env.APPCONFIG_CONFIGURATION_NAME;

    // AppConfig クライアントを生成
    const client = new AppConfigDataClient({ region });

    // AppConfig とのセッションを作成する。
    const sessionParams: StartConfigurationSessionCommandInput = {
      ApplicationIdentifier: applicationIdentifier,
      EnvironmentIdentifier: environmentIdentifier,
      ConfigurationProfileIdentifier: configurationName,
    };

    const session = await client.send(
      new StartConfigurationSessionCommand(sessionParams)
    );

    if (!session?.InitialConfigurationToken) {
      console.warn(
        "AppConfig セッションの初期トークンが取得できませんでした。"
      );
      return FALLBACK_CONFIG;
    }

    // AppConfigからFeature Flagを取得する。
    const configCommand = new GetLatestConfigurationCommand({
      ConfigurationToken: session.InitialConfigurationToken,
    });

    const response = await client.send(configCommand);

    if (!response.Configuration) {
      console.error(
        "AppConfig から構成を取得できませんでした"
      );
      return FALLBACK_CONFIG;
    }

    // 取得した値をJSON形式に変換する。
    const config = JSON.parse(response.Configuration.transformToString());
    console.info("AppConfig の構成取得に成功:", config);
    return config;
  } catch (error) {
    console.error("AppConfig 構成取得中にエラーが発生しました:", error);
    return FALLBACK_CONFIG;
  }
}

export default getAppConfigConfiguration;

また、アプリケーションをLambda上で動作させるにあたりStandaloneモードでビルドしています。これは、Lambdaのファイルサイズ上限(250MB)を超えないようにサイズを小さくしたり、初期化を早くしてLambdaのコールドスタートの影響をなるべく小さくしたりするために採用しています。
Standaloneモードでビルドするためには、next.config.tsoutput:standaloneを追加します。

// next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  output: "standalone",
};

export default nextConfig;

LambdaにはDockerビルドしたコンテナをデプロイし、そのコンテナを起動するようにします。
以下にDockerfileと、サーバーを起動させるために使用するスクリプトrun.shの実装例を載せておきます。

補足として、DockerfileにLambda Web Adapter用の設定をしています。
このようにCOPYコマンドの設定を1行付け足すことで、Next.jsのようなフレームワークを使用したWebアプリケーションをLambda上で動作させることができます。
本筋から外れるため説明は省略させていただきますが、詳しく知りたい方は以下のドキュメントを参照してください。
参考:Lambda Web Adapter でウェブアプリを (ほぼ) そのままサーバーレス化する - aws.amazon.com

// Dockerfile
FROM node:22-slim AS builder
WORKDIR /app
COPY . .
EXPOSE 3000 
RUN npm install && npm run build

# -----------------------------
FROM node:22-slim AS runner

# Lambda Web Adapter用の設定
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 /lambda-adapter /opt/extensions/lambda-adapter

ENV PORT=3000 NODE_ENV=production
WORKDIR /app
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/run.sh ./run.sh
RUN ln -s /tmp/cache ./.next/cache
RUN chmod +x ./run.sh
RUN apt-get update && apt-get install -y bash

ENTRYPOINT ["./run.sh"]
# run.sh
#!/bin/bash
[ ! -d '/tmp/cache' ] && mkdir -p /tmp/cache

exec node server.js

3-2.AWS

この章では、AppConfigとLambdaを中心に、AWS環境の構築例を紹介させていただきます。
※ CloudFrontまわりの設定は本筋から外れるため、設定内容を省略させていただきます。

Lambda

はじめにLambda関数を設定します。

コンソール画面からLambdaを開き、「関数を作成」から新しい関数を作成します。
以下のように「コンテナイメージ」を選択し、ECRにデプロイ済みのコンテナイメージを選択して作成します。

Lambda1

次に、Lambda実行ロールへAppConfigに対するアクセス権限を追加します。
具体的には、実行ロールにAppConfigに対する権限を付与するポリシーを追加します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowAppconfigForLambda",
            "Effect": "Allow",
            "Action": [
                "appconfig:StartConfigurationSession",
                "appconfig:GetLatestConfiguration"
            ],
            "Resource": "arn:aws:appconfig:<region>:<account-id>:application/*"
        }
    ]
}

AppConfig

次にAppConfigを設定します。

コンソール画面からAppConfigを開き、設定を作成します。
「設定オプション」を「機能フラグ」にして、「設定プロファイル名」を入力して次へ進みます。

AppConfig1

次にFeature Flagを設定します。
以下の図のように、値を設定します。「フラグ名」はAppConfig上でFeature Flagを識別する名前で、「フラグキー」はアプリケーションから設定を取得する際のキーとなる値です。
「バリアント」については「基本フラグ」を選択していますが、「マルチバリアントフラグ」を設定すると、特定のユーザーIDや地理的位置などの条件をもとに評価を返却したり、コンテキスト値に基づいてトラフィックを分割したりすることも可能です。
最後にフラグ値をオン/オフで選択します。

AppConfig2

必要なFeature Flagを設定したら「保存してデプロイを続ける」をクリックして、デプロイに進みます。

AppConfig3

デプロイの画面に進んだら、まずはじめに「環境を作成」から環境を作成します。
環境を判別できる名前を付けたら「環境を作成」をクリックします。今回は「dev」としています。

AppConfig4

AppConfig5

デプロイの画面に戻ってきたら、「環境」に先ほど作成した環境名が設定されていることを確認します。
そして、デプロイ戦略を選択します。デフォルトでいくつかデプロイ方法が選択できますが、「デプロイ戦略を作成」から自分でデプロイ戦略を作成することができます。今回は「AppConfig.AllAtOnce」を選択します。
「デプロイを開始」をクリックしてFeature Flagの設定をデプロイします。デプロイは数秒で完了します。これでアプリケーションからFeature Flagの設定を取得できる状態となりました。

AppConfig6

AppConfig7

4.動作検証

構築が完了したため、動作検証します。

まずはじめに、設定したFeature Flagが全てオフ(false)の場合で確認します。
アプリケーションを起動すると「3-1.アプリケーション」で記載したフラグが全てfalseの場合と一致しています。
CloudWatchに出力されているログを確認すると、AppConfigから取得したフラグ値が全てfalseとなっていることが確認できます。

test1

test2

test3

では設定を変更して、機能をリリースします。
AppConfigから、先ほど作成したアプリケーションの設定を開きます。
各フラグの値をオン(true)に変更し、「Save version」をクリックして設定を保存します。
その後に遷移する画面で、保存したバージョンの設定値が表示されていることを確認して、「デプロイを開始」をクリックします。

test4

test5

デプロイの画面で、環境名、バージョン、デプロイ戦略を選択して「デプロイを開始」をクリックします。
デプロイは数秒で完了します。状態が「ベーキング」となったことを確認して、アプリケーションの動作検証をします。

test6

test7

アプリケーションを起動すると「3-1.アプリケーション」で記載したフラグが全てtrueの場合と一致しています。
CloudWatchに出力されているログを確認すると、AppConfigから取得したフラグ値が全てtrueとなっていることが確認できます。

test8

test9

以上のように、アプリケーションコードのデプロイを挟まずに、AppConfigからFeature Flagを更新するだけで新機能をリリースすることができました。

5.まとめ

今回は簡単なデモアプリを作成して、Feature Flagの実装やAppConfigの使用感を検証しました。

まずFeature Flagについては、アプリケーションコードのデプロイをすることなく、AppConfigの更新のみで新機能の有効/無効を切り替えられるのはとても良いと思いました。
機能のリリースとデプロイの分離ができることで、任意のタイミングでデプロイを挟まずに機能リリースができるため、開発サイクルを早めることができると思いました。

AppConfigについては、Feature Flagのバージョン管理や環境管理が可能で、AWSを利用している場合はとても良いと思いました。
プロジェクトでAWSを利用している場合、Feature Flagを導入する際には、選択肢の1つに含める価値は十分あるのではないでしょうか。

最後まで読んでいただきましてありがとうございました。

私たちは一緒に働いてくれる仲間を募集しています!

電通総研 キャリア採用サイト 電通総研 新卒採用サイト

執筆:@suzuki.takuma、レビュー:@miyazawa.hibiki
Shodoで執筆されました