はじめに
相変わらずCDKを触る機会が多めな宮本です。今回は比較的最近リリースされたばかりのCloudFront KeyValueStoreのCDK管理についての記事です。待望のCloudFront Functionsで使えるデータストアということで無邪気に喜んでましたが、IaCで管理しようとすると結構癖がありました。
CloudFront KeyValueStore
Amazon CloudFront KeyValueStoreは、CloudFront Functions内で利用可能なその名の通りキーバリュー方式のデータストアです。リリースされたのも昨年と、かなり新しいサービスです。
今までCloudFront Functions上で扱うデータは、CloudFrontから渡されるリクエストまたはレスポンス情報を除きコードに直書きする以外に保持する術がありませんでした。そのため、たとえばパスごとのリダイレクト情報を複数書こうとすればそれだけコード行数が膨大になり、コードの挙動そのものがどんどん下の方に追いやられ可読性が落ちるという問題がありました。
KeyValueStoreが登場したことで、CloudFront Functionsでもロジックとデータを完全に分離することが可能になりました。データとロジックを混在させなければならないという部分に正直辟易としていたので、この機能追加はとてもありがたいです。
詳しくは 公式ドキュメント や こちらの記事 をご覧ください。
コード
実装してみたコードの紹介です。PythonのCDKを用いて記述しています。
また、省略のためにCloudFront FunctionとKeyValueStoreの作成のみ実施しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
import json import random import string from aws_cdk import aws_cloudfront as cloudfront # functionのコードにKVSのIDを埋め込むためにファイル読み込み cloudfornt_function_src_path = "src/index.js" with open(cloudfornt_function_src_path, "r") as file: file_content = file.read() # json形式のvalueを文字列化するためにファイル読み込み kvs_file_path = "kvs.json" with open(kvs_file_path, "r") as file: row_kvs_items = json.load(file) kvs_items = [ { "key": kvs_item["key"], "value": json.dumps(kvs_item["value"], separators=(",", ":")), } for kvs_item in row_kvs_items ] def rand_suffix(seed: str): """ kvs名のsuffix用ランダム文字列を生成する関数 seedを与えることでkvsのデータが変わらない場合は同じsuffixが生成されるようにする """ random.seed(seed) suffix = "".join(random.choices(string.ascii_letters + string.digits, k=4)) # seedをリセット random.seed() return suffix class CloudFrontStack(Stack): def __init__( self, scope: Construct, construct_id: str, **kwargs ) -> None: super().__init__(scope, construct_id, **kwargs) # KVSの初期値文字列作成 kvs_source = json.dumps({"data": kvs_items}) keyValueStore = cloudfront.KeyValueStore( self, "KeyValueStore", # KVSのsuffixとしてKVSに格納する値をseedとしたランダム文字列を生成 # 格納する値が変わった場合にKVSの名称を変更して再デプロイする key_value_store_name=f"kvs-{rand_suffix(kvs_source)}", source=cloudfront.ImportSource.from_inline(kvs_source), ) cf_func = cloudfront.Function( self, "CFFunc", key_value_store=keyValueStore, code=cloudfront.FunctionCode.from_inline( code=file_content.replace( "<KEY_VALUE_STORE_ID>", keyValueStore.key_value_store_id ) ), runtime=cloudfront.FunctionRuntime.JS_2_0, ) function_associations = [ cloudfront.FunctionAssociation( event_type=cloudfront.FunctionEventType.VIEWER_REQUEST, function=cf_func, ) ] |
CloudFront Functionsのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import cf from "cloudfront"; const kvsId = "<KEY_VALUE_STORE_ID>"; // This fails if the key value store is not associated with the function const kvsHandle = cf.kvs(kvsId); async function handler(event) { const request = event.request; const key = request.uri; let value; try { value = await kvsHandle.get(key, { format: "json" }); } catch (err) { value = ""; } console.log(value) // 処理いろいろ return request; } |
KeyValueStoreの初期値を入れたjsonファイルの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
[ { "key": "/hoge", "value": { "url": "https://example.com/hoge", "statusCode": 301, } }, { "key": "/fuga", "value": { "url": "https://example.com/fuga", "statusCode": 302, } }, ] |
CDKによる管理
他のリソースと同じく、CDKを用いてCloudFront KeyValueStoreの操作が可能です。が、いくつか欠点もとい注意点があります。先に示したコードでは、その対策を入れています。
- CloudFront Functions側からの指定がコード直書き以外不可
- CloudFront Functions側からvalueをjson文字列として読み込むことはできるが、初期値のファイル投入時には必ず文字列として投入する必要がある
- CDKからはKeyValueStoreに保存されたデータの更新をする方法がない
CloudFront Functions側からの指定がコード直書き以外不可
これについては、すでにクラスメソッドさんの記事に対策がまとまっています。Cloudfront Functions内からKeyValueStoreにアクセスするために、IDを指定する必要があります。しかしこのIDはKeyValueStoreのリソースが作成されなければわからず、その上でコードに直書きする必要があるので、デプロイ時にコードに埋め込むように実装する必要があります。
CloudFront Functions側からvalueをjson文字列として読み込むことはできるが、初期値のファイル投入時には必ず文字列として投入する必要がある
KeyValueStoreのValueがjsonとして解釈することができる場合、CloudFront Functions側でjsonを解釈したobjectとして読み込むことができます。しかし初期値として投入する場合は、あくまでvalue部分は文字列である必要があります。
文字で書くとわかりづらいですが↓ということです。
1 2 3 4 5 6 7 8 9 10 11 12 |
// 以下のようなデータをKeyValueStoreの初期値として入れたい場合は {"hoge":{"fuga":"hogefuga"}} // この形式のjsonファイルを用意する必要がある { "data": [ { "key": "hoge", "value": "{\"fuga\":\"hogefuga\"}" } ] } |
jsonファイルを使ってKeyValueStoreの初期値を管理することができるのは便利なのですが、valueにjsonを入れたい場合は文字列化する必要があるのが厄介です。折角jsonファイルで管理できると思ったのに、見づらくなってしまいます。このあたりはCDKなので、前処理としてjsonファイルを読み込んで文字列化することで解決できます。
一方で、valueに入れるのがただの文字列である場合は以下の変換は不要です。ImportSource.from_asset()
を使うことで、直にファイルを指定し読み込むことができます。ただし、この初期値を入れたファイルの内容を変更する予定があるのであれば、後述のエラー対策として、ファイルデータ自体は読み込みハッシュ値などを使ってseedとして利用することが良いと思います。
1 2 3 4 5 6 7 |
kvs_items = [ { "key": kvs_item["key"], "value": json.dumps(kvs_item["value"], separators=(",", ":")), } for kvs_item in row_kvs_items ] |
CDKからはKeyValueStoreに保存されたデータの更新をする方法がない
これはさらに厄介です。例えばKeyValueStoreでリダイレクトルールを管理しようとした場合、IaCで管理するならルール自体もファイルに保存しておきたいです。しかし、このKeyValueStoreの初期値を決めたファイルに変更を加えた場合、次のようなエラーが発生して更新ができません。
1 2 |
CloudFormation cannot update a stack when a custom-named resource requires replacing. Rename <KVS名> and update the stack again. |
これについては、少々強引ですが初期値を変えるたびに毎回KeyValueStoreの名称を変えて作り直すと解決するようです。参考にしたコードでは毎回ランダムに名前を割り振っていましたが、初期値を定義するjsonファイルの内容をseedにすることで、値が変わらない限り無駄なデプロイが走らないようにすることが可能です。
ただし、この時完全にKeyValueStoreは作り直しになるため、もしコンソールやSDKから内部の値を変更していた場合は完全に失われてしまうためご注意ください。
また、今回はPythonでCDKを書いているため簡単にseedの指定ができます。一方でJavaScriptではデフォルトで乱数のseedを固定することができないため、追加でライブラリを入れる必要がありさらに一手間必要です。
さいごに
現時点でCloudFront KeyValueStoreをCDKで管理する方法についてまとめました。機能としてはありがたいのですが、若干引っかかるポイントが多く今後改善されると嬉しいです。
ちなみに今回はCDKで紹介しましたが、一応TerraformでもKeyValueStore自体の作成は可能です。ただし、初期値として投入できるファイルを指定することはできません。issueは出ているので、早めに追加されると嬉しいです。
参考
- Amazon CloudFront KeyValueStore – Amazon CloudFront
- CloudFront KeyValueStoreがリリース。CloudFront Functionsからキーバリューストアを利用可能に! | DevelopersIO
- AWS CDK v2.124.0 で CloudFront KeyValueStore の Functions への関連付けがサポートされました | DevelopersIO
- [New Resource]: Amazon CloudFront KeyValueStore · Issue #34512 · hashicorp/terraform-provider-aws
- Replacing custom paths results in CFN update error · Issue #2 · ssennettau/CloudFrontRedirector-Construct
- [Enhancement]: Add support for
import_source
foraws_cloudfront_key_value_store
· Issue #36524 · hashicorp/terraform-provider-aws