こんにちは、中山( @k1nakayama ) です。
最近、DevOpsの文脈でアドバイザリーを行う事が増えてきています。その中で、如何に開発生産性を向上させていくかという議論はよくある話です。この開発生産性の向上には、様々な指標があると思いますが、DORAのFour Keysを例に考えた場合、毎日複数回の本番環境へのデプロイを行ったり、変更におけるリードタイムを短い期間で実施していったり、更には変更後の失敗率を最小化させていき、失敗してしまったら即座に復旧させるという流れを作るには、フィーチャーフラグ(機能フラグ)を用いることで、実現しやすさが高まるのではないかと思います。
そこで、今回は、そんな機能フラグの機能を提供しているSystems Managerの一機能であるAWS AppConfigを使って、普段弊社でバックエンド開発に用いることが多い、Lambdaからの利用を例に試してみたいと思います。
- AppConfigを使用した機能フラグの使い方を知りたい方
- 開発生産性を向上させていきたい方
- そもそもフィーチャーフラグをどのような場面で使用するか知りたい方
フィーチャーフラグとは
新機能を公開する際に、まずは社内からのアクセス(IPアドレス)のみ、機能を有効化して使ってみると言う形で、ソースコード上に外部から取得できる情報を元に、機能の有効/無効を切り替えたりした経験はありませんか?まさにそれが、フィーチャーフラグです。
フィーチャーフラグは、機能のロールアウト(ユーザーに利用可能な状態として適用すること)をする際に、ソースコードを修正することなく、外部の情報を元に振る舞いを変更する事ができるフラグです。SaaSなどで、テナント毎に機能を有効にするか否かを選択できるものがありますが、あれも一種のフィーチャーフラグと言えるかと思います。
フィーチャーフラグの使い所
既に上記で使われ方を少し例に出しましたが、この記事では主に開発生産性向上を目的としたフィーチャーフラグの使い所について見ていけたらと思います。
皆さんは、下記のような経験をしたことはありませんか?
- 1つの機能をバックエンドとフロントエンドや、いくつかの小さな機能単位でフィーチャーブランチを分けて開発しており、関連する機能が全て作り終わらないとmainにマージできない
- mainにマージした(検証環境へデプロイした)けど、その機能の実機検証作業が終わる前に、緊急でProductionへマージする必要のある修正が発生し、検証前の機能は一旦revertすることが必要になった
- 本番環境で使用しているデータを使わなければ、機能が正しく動作しているか確認しにくい
- キャンペーンなどで、ロールアウトさせたいタイミングが決まっているため、ロールアウト=デプロイでは進めづらい
- 検証環境で確認した際は起きていなかった問題が、本番環境の実際のデータを使うと発生する
- 問題が発生した後、Git revertするのではなく、社内のメンバーだけはそのまま調査に使える状態にし、一般ユーザー向けには直ちに復旧させたい
フィーチャーフラグを使用することで、上記のような場面で問題を回避することが出来ます。
例えば、デプロイとロールアウトのタイミングが異なる場合に、その新たに開発した機能をフィーチャーフラグで有効化するかどうかを判断するようにしておきデプロイし、実際にロールアウトしたいタイミングでフィーチャーフラグを有効にすることで、回避できます。
また、フィーチャーフラグを一部のユーザーやアクセス元IPなどに対してだけ有効にするといった判断の仕方も可能なので、本番にデプロイしてしまい、ロールアウトの対象を開発メンバーのみに制限しておくと、本番環境のデータを使って直接開発した機能を検証することが出来ます。
そして、ロールアウト後に問題が発覚したら、フィーチャーフラグを無効化するだけで、ロールアウト前に戻すことが可能となり、瞬時に復旧させることが出来ます。
いかがですか?使ってみる価値がグッと増した方も多いのではないでしょうか?
AppConfigの機能フラグをLambdaから使ってみるための設定
ということで、早速設定をしていきましょう
AppConfigの設定をする
まずはAppConfigの設定を行います。マネジメントコンソールの検索窓に「AppConfig」と入力してAppConfigのコンソールに移動しましょう
続けてアプリケーション作成を行います。このアプリケーションは、名前の通りアプリケーション名やプロジェクト名を設定すると良いかと思います。
続いて、プロファイルを作成します。このプロファイルの概念について、ベストプラクティス的なものが見当たらず、確かなことが言いにくいのですが、使い方を考慮すると、マイクロサービスやプラットフォーム(Web / IOS / Androidなど)毎に設定すると良い気がします
続いて今回の肝となるフラグの追加を行います。まずは、下記のようにfeature1というフラグを設定し、ロールアウトを一部のユーザーに対してのみ行う想定でuser_ids属性も設定してみましょう
設定が保存されていない旨の表示になっているかと思いますので、保存しちゃいましょう
続いて環境の作成に進みます。一度アプリケーションの画面に戻り、環境タブを選択して環境を作成しましょう。環境とは本番環境、検証環境のようなステージを想定しています。
AppConfigの設定の最後に、これらの設定をデプロイします。Profile1の設定画面に戻り、デプロイを選択し、上記で作成したバージョンを環境にデプロイしましょう
ベーキングのステータスになればデプロイ完了です。ちなみに、ベーキングの間はデプロイをロールバックが可能な期間となります。
Lambda用IAM Role作成
上記で作成した機能フラグを、今回はLambdaで取得し動作を変えてみたいと思います。そのため、LambdaからAppConfigの機能フラグにアクセスするための権限を持ったIAM Roleを作成します。
要点としては、下記の2つのパーミッションを設定したLambda用IAM Roleを作成することになります。
- appconfig:StartConfigurationSession
- appconfig:GetLatestConfiguration
早速IAM Roleを作成していきます。まずはIAMのコンソールにてRoleの作成に進みます。AWSのサービスでサービスにLambdaを選択します。
許可の選択では、Lambdaの基本的な権限を与えるために、AWSLambdaBasicExecutionRoleを選択してください
一旦この状態でロールを作成します。ここでは「LambdaAppConfigRole」と名前を付けました。
続いて、許可の追加をインラインポリシーとして行います
ポリシーエディタをJSONを選択し、下記のJSONを設定してください
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Statement1",
"Effect": "Allow",
"Action": [
"appconfig:StartConfigurationSession",
"appconfig:GetLatestConfiguration"
],
"Resource": "*"
}
]
}
「AppConfigPolicy」としてポリシーを登録します
以上でLambda用のIAM Roleが作成できました
Lambda Functionの作成
新しいLambda Functionを作成します。一から作成で下記のように、上記で作成したRoleを選択して作成しましょう
今回LambdaからAppConfigを利用するために、AppConfig Lambda Extensionを利用したいと思います。そのため、レイヤーの追加で下記のように、AWS-AppConfig-Lambda-Extensionを追加しましょう
コードとして、下記を入力してください
import urllib.request
import json
def lambda_handler(event, context):
application_name = 'SampleApp'
environment_name = 'Prod'
profile_name = 'Profile1'
url = f'http://localhost:2772/applications/{application_name}/environments/{environment_name}/configurations/{profile_name}'
config = urllib.request.urlopen(url).read()
flags = json.loads(config.decode())
text = "新機能1はまだ実装されていません"
if flags["feature1"]["enabled"]:
if "user_ids" in flags["feature1"]:
if event['queryStringParameters']['user_id'] in flags["feature1"]["user_ids"]:
text = "新機能1が実装されています"
else:
text = "新機能1が実装されています"
return text
AppConfigで設定したアプリケーション名、環境名、プロファイル名は、5行目〜7行目の変数に与えて設定しましょう。(AppConfig Lambda Extensionによって内部的にプロキシサーバーのような形でデータを取得できる様になっています)
また、Function URLsを使用してリクエストをしたいと思いますので、Function URLsを発行しましょう
このコードでは、通常時は「新機能1はまだ実装されていません」というテキストが返されます。しかし、フィーチャーフラグが有効な状態の場合、「新機能1が実装されています」というテキストに置き換えられて返されます。
なお、このLambdaはFunction URLsによって、クエリパラメータにuser_idがついてリクエストされてくることを想定しており、フィーチャーフラグの属性としてuser_idsが存在している場合は、user_idsに指定されているIDのみがロールアウトの対象となります。
機能フラグを使ってみる
設定が一通り終わったところで、機能フラグを試してみましょう
まず、現状ですが下記の通り、feature1が存在し、フラグは無効となっています。
この状態で、発行されたFunction URLをリクエストしてみましょう
想定通りフラグが無効なので「新機能1はまだ実装されていません」と返ってきました
では、続いて一部のユーザーのみに、機能をロールアウトしてみましょう。現状feature1にはuser_idsの属性が設定されており、値として「user1,user3」が設定されています。user_idがuser1もしくはuser3の場合のみ機能がロールアウトされる設定になります。
それでは、フラグを有効にしてみましょう。フラグの右側にあるトグルをオンにします。変更した後は、新しいバージョンを保存し、新しいバージョンを発行してください。
保存しただけではまだ設定がデプロイされていないので、デプロイを行います。
デプロイが完了し、ベーキング状態になったら、再度URLをリクエストしてみましょう
想定通り、User1に対して機能がロールアウトされました。
今度はUser2に対して、機能がロールアウトされていないことを確認しましょう
User2に対しては機能がロールアウトされていないことが確認できましたね
では、最後に機能が正しく動いていることが確認できたので、全ユーザーにロールアウトしましょう
AppConfigのコンソールにて、feature1の属性からuser_idsを削除しましょう。削除後保存してデプロイを忘れずに行ってください。
デプロイ後、再度User2としてリクエストをしてみましょう
今度はUser2にもロールアウトがされました!
このすべての手順にて、1回もLambdaのソースコードを変更することなく、機能のロールアウトをコントロールできたことが分かりました
まとめ
今回は、LambdaからAppConfigの機能フラグを活用し、ロールアウトをコントロールすることを試してみました。
試してみておわかりいただけた通り、とても簡単に実装できる上、ユーザーへの細かなロールアウトの制御をソースコードの改変なしに実施することが出来ます。
これによって、開発生産性は間違いなく向上すると思いますので、まだ触ったことがないという方は、ぜひ試してみていただきたいです。
また、今回Lambda Extensionを使用して取得しましたが、これ以外にもLambda Powertoolsを使って取得する方法などもありますので、機会があればそちらも説明出来たらと思います。