2026年10月 Flociアップデートまとめ、Lambda durable functionsとCognitoのMFAが入った

2026年10月 Flociアップデートまとめ、Lambda durable functionsとCognitoのMFAが入った

AWSエミュレータFlociの2.2.0と2.3.0の更新をまとめました。Lambda durable functions、CognitoのTOTP MFA、S3のバケットポリシー評価、EventBridgeの再試行とDLQを2.3.0で動かし、SageMakerなど新サービス6件やリリース日程の変更も取り上げます。
2026.10.30

こんにちは。サービス開発部の武田です。

LocalStackの代替として登場したオープンソースのAWSエミュレータ「Floci」を、毎月取り上げています。直近の3本は次のとおりです。

前回からまた1ヵ月が経ち、2.2.0と2.3.0が出ました。今回はこの1ヵ月の更新をまとめたうえで、次の4つを2.3.0で動かしました。

  • Lambda durable functions
  • CognitoのTOTP MFA
  • S3のバケットポリシー評価とBlock Public Access
  • EventBridgeの再試行とデッドレターキュー

動かす中で、AWSと挙動が違う点を2つ見つけてissueにしました。うち1件は、起票した日にメンテナーの修正がmainに入りました。

この1ヵ月の主な変化(2026年10月30日時点)

前回記事(2.1.0時点)から現在(2.3.0)までの変化をサマリーすると、次のとおりです。

  • リリース 2回(2.2.0が10月6日、2.3.0が10月15日)。2.1.0と2.3.0の間に 1,303コミット
  • リリースノートの項目数は、2.2.0が744件、2.3.0が309件
  • GitHubスターが 26,051 → 27,280 へ
  • Dockerイメージ(amd64、圧縮サイズ)が 80.6MB → 86.8MB へ
  • リリースの日程が、毎月15日と月末の自動カットに変わった
  • 新サービスはSageMaker、Timestream for InfluxDB、CodeArtifact、DMS、Redshift Serverless、Data Lifecycle Manager
  • http://localhost:4566/をブラウザで開いたときのランディングページが、ダッシュボードになった

リリースと対応範囲の数字

リリースの日程は、2.3.0の直前に変わりました(#5698)。これまでは第1・第3火曜日でしたが、毎月15日と月末の00:30(米国中部時間)にRelease Cutワークフローが自動で走り、mainからX.Y.Zタグを打ちます。CONTRIBUTING.mdにこの日程と、mainは毎日nightlyタグで公開されることが書かれています。

リリースノートの内訳は、2.2.0がBug Fixes 499件、Features 237件、Performance Improvements 8件です。2.3.0はBug Fixes 249件、Features 59件、Performance Improvements 1件です。READMEの互換性テスト件数は2,576件のままです。

対応サービス数は、公式ブログの「State of the Flock #004」(10月15日)が125、floci.ioのトップページが126で、掲載値が一致していません。手元の2.3.0では、後述するダッシュボードに「127 running」と表示されました。これはFlociに登録されたサービスのうち設定で有効になっている件数で、個々のサービスの動作確認を意味する数字ではありません。

2.2.0で入った新サービスのうち、SageMaker(#1949)はDockerでトレーニングジョブを実行します。Timestream for InfluxDB(#3683)はInfluxDB 2.xのコンテナを使います。CodeArtifactは管理API(#3713)に加えて、Maven(#4059)、npm(#4336)、PyPI(#4998)をサイドカー経由で提供します。

ダッシュボード

http://localhost:4566/をブラウザで開いたときのページは、6月の記事で取り上げたランディングページです。1.5.26で入ったときは、エンドポイント・アカウント・バージョンと有効なサービス数、「Open Floci UI」ボタンだけの画面でした。2.3.0でこれがダッシュボードになりました(#5594)。シェル・AWS CLI・boto3向けの設定スニペット、起動フェーズの表示、稼働中のサービスの一覧、DynamoDBのバックエンドが加わっています。別ページだった起動待ちの画面も、この中に統合されました。

floci-2026-10-lambda-durable-functions-cognito-mfa-update_1.jpg

ルートへのGETはS3のListBucketsとして処理されるパスで、Acceptにtext/htmlを含むリクエストのときだけダッシュボードが返ります。SDKやCLIによるバケット一覧の取得は従来どおりです。「Open Floci UI」を押すと、Webコンソール(floci-ui)がサイドカーとして起動します。

検証環境

docker-compose.ymlを用意して起動します。durable functionsの再起動の確認のために、永続化を有効にしています。永続化先はイメージのVOLUMEである/app/dataです。

services:
  floci:
    image: floci/floci:2.3.0
    ports:
      - "4566:4566"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - floci-data:/app/data
    environment:
      FLOCI_STORAGE_MODE: persistent
volumes:
  floci-data:
$ export AWS_ENDPOINT_URL=http://localhost:4566
$ export AWS_DEFAULT_REGION=us-east-1
$ export AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test

S3の節だけは、認可を評価させるために次の2つを追加した別のコンテナで試しました。どちらもデフォルトでは無効です。

    environment:
      FLOCI_SERVICES_S3_ENFORCE_AUTH: "true"
      FLOCI_SERVICES_IAM_ENFORCEMENT_ENABLED: "true"

AWS CLIは2.36.30です。

Lambda durable functionsで待機と再開を確認する

Lambda durable functionsは、2025年12月にAWSが発表した機能です。ハンドラの中の処理をステップに分け、ステップの結果をLambdaがチェックポイントとして保存します。待機や外部からのコールバックの間は呼び出しを終了し、あとの呼び出しでチェックポイントを再生して続きから処理します。

Flociでは2.2.0で、DurableConfigの保存(#5007)、チェックポイント・待機・ステップの再試行(#5047)、コールバック(#5100)が入りました。2.3.0では、別のdurable functionを呼ぶchained invoke(#5164)が入っています。ドキュメントには、AWSのDurable Execution SDKがそのまま動くと書かれています。

SDKを同梱してデプロイする

今回使うPython 3.14のAWSマネージドランタイムにはDurable Execution SDKが同梱されています。Flociが関数の実行に使うpublic.ecr.aws/lambda/pythonイメージには入っていないので、デプロイパッケージに同梱します。PyPIのaws-durable-execution-sdk-python(3.0.0)はboto3に依存するので、zipは17MBになりました。

$ uv pip install --target pkg aws-durable-execution-sdk-python==3.0.0
$ cp lambda_function.py pkg/
$ (cd pkg && zip -qr ../function.zip .)

関数は、AWSのドキュメントの例に沿って、注文の検証・決済・待機・確定の4段階にしました。

import time
from aws_durable_execution_sdk_python import DurableContext, durable_execution, durable_step
from aws_durable_execution_sdk_python.config import Duration

@durable_step
def validate_order(step_context, order_id):
    return {"orderId": order_id, "status": "validated", "at": time.time()}

@durable_step
def process_payment(step_context, order_id):
    return {"orderId": order_id, "status": "paid", "amount": 99.99, "at": time.time()}

@durable_step
def confirm_order(step_context, order_id):
    return {"orderId": order_id, "status": "confirmed", "at": time.time()}

@durable_execution
def lambda_handler(event, context: DurableContext):
    order_id = event["orderId"]
    print(f"handler invoked for {order_id}")
    validation_result = context.step(validate_order(order_id))
    payment_result = context.step(process_payment(order_id))
    context.wait(Duration.from_seconds(event.get("waitSeconds", 10)))
    confirmation_result = context.step(confirm_order(order_id))
    return {"orderId": order_id, "status": "completed",
            "steps": [validation_result, payment_result, confirmation_result]}

create-functionに--durable-configを付け、--publishでバージョンを発行します。durable functionの呼び出しにはバージョンかエイリアスの指定が必要で、以下ではorder-durable:1を使います。

$ aws lambda create-function --function-name order-durable \
    --runtime python3.14 --handler lambda_function.lambda_handler \
    --role arn:aws:iam::000000000000:role/lambda-role \
    --zip-file fileb://function.zip --timeout 60 \
    --durable-config '{"ExecutionTimeout":300,"RetentionPeriodInDays":1}' \
    --publish

wait前後の実行と履歴

invokeに--durable-execution-nameを付けて呼びます。同期呼び出し(RequestResponse)では、10秒の待機を含む実行が終わるまで応答を待ちます。

$ aws lambda invoke --function-name order-durable:1 \
    --durable-execution-name order-1 \
    --payload '{"orderId":"order-12345","waitSeconds":10}' \
    --cli-binary-format raw-in-base64-out out.json
{
    "StatusCode": 200,
    "ExecutedVersion": "1",
    "DurableExecutionArn": "arn:aws:lambda:us-east-1:000000000000:function:order-durable:1/durable-execution/order-1/b4438095-..."
}
$ cat out.json
{"orderId": "order-12345", "status": "completed", "steps": [{"orderId": "order-12345", "status": "validated", "at": 1793155188.36}, {"orderId": "order-12345", "status": "paid", "amount": 99.99, "at": 1793155188.37}, {"orderId": "order-12345", "status": "confirmed", "at": 1793155199.07}]}

応答まで45秒かかり、そのうち34秒はLambdaコンテナの初回起動です。ウォーム状態で別の実行名を指定して新しい実行を始めると、10秒の待機を含めて12秒でした。

実行の履歴はget-durable-execution-historyで取れます。イベントの種類だけを抜き出すと、次のとおりです。

$ aws lambda get-durable-execution-history --durable-execution-arn "$ARN" \
    --query 'Events[].[EventType,Name]' --output text
ExecutionStarted        order-1
StepStarted             validate_order
StepSucceeded           validate_order
StepStarted             process_payment
StepSucceeded           process_payment
WaitStarted             None
InvocationCompleted     None
WaitSucceeded           None
StepStarted             confirm_order
StepSucceeded           confirm_order
InvocationCompleted     None
ExecutionSucceeded      order-1

InvocationCompletedが2回あり、waitの前後でハンドラが2回起動しています。CloudWatch Logsのhandler invoked forも2行でした。2回目の起動では、完了済みのvalidate_orderとprocess_paymentは再実行されず、保存された結果が使われています。結果に入っているatのタイムスタンプが1回目と同じ値であることで確認できます。

バージョンやエイリアスを指定せずに呼ぶと、InvalidParameterValueExceptionで拒否されます。メッセージはYou cannot invoke a durable function using an unqualified ARN.です。

同じ--durable-execution-nameで呼び直したときの応答は、ペイロードで変わります。同じペイロードなら既存の実行のARNと結果が200で返り、ペイロードが違うとDurableExecutionAlreadyStartedExceptionです。AWSのInvoke APIのリファレンスにも同じ動作が書かれています。

ステップの再試行とコールバック

別の関数を用意し、3回目で成功するステップに再試行の設定(RetryStrategyConfig、最大5回、間隔1〜2秒)を付けました。そのあとはwait_for_callbackで外部からのコールバックを待ちます。呼び出すと、履歴にStepFailedが2回入ったあとStepSucceededになり、その間にハンドラは3回起動しました。失敗したステップのあとにInvocationCompletedが入り、次の呼び出しで再試行される流れです。

続くwait_for_callbackでは、履歴のCallbackStartedイベントにコールバックIDが入ります。IDを使って成功を送ると実行が再開し、get-durable-executionのStatusはSUCCEEDED、Resultのapprovalには送った値が入っていました。

$ aws lambda send-durable-execution-callback-success --callback-id "$CALLBACK_ID" \
    --result '{"approved":true,"by":"manager"}'

コールバックの期限も見ました。heartbeat_timeoutの120秒を超えて何も送らないと、履歴はCallbackTimedOutからExecutionFailedに進みます。エラーはCallbackTimeoutError: Callback timed out on heartbeatでした。完了したコールバックにもう一度成功を送ると、CallbackTimeoutExceptionで拒否されます。AWSのAPIリファレンスでも、期限切れと完了済みの両方でこの例外が返ると説明されています。

Flociを再起動しても待機が続く

ドキュメントには、タイマーは永続化され、バックグラウンドの処理が期限を監視して再開すると書かれています。60秒待機する実行を始めて、待機中にdocker compose restartでFlociを再起動しました(6秒ほど)。再起動直後の状態はRUNNINGのままで、開始から60秒後にWaitSucceededが入り、SUCCEEDEDで終わりました。履歴ではWaitStartedのあとにInvocationCompletedが2つ並び、再起動直後にハンドラが一度起動してチェックポイントを再生し、待機の続きに入ったことがわかります。

確認したのは、永続化モードでdocker compose restartした場合です。強制終了や、永続化していない場合は試していません。

未対応として挙げられているのは、Step FunctionsやEvent Source Mappingからの起動、ペイロードの暗号化、EventBridgeへのイベント発行です。1実行あたり3,000操作と100MBの永続化ペイロードという上限もまだ入っていません。

CognitoのOPTIONAL MFAでTOTPを使う

Cognitoはこの1ヵ月で認証に関する更新が集中しました。2.2.0ではソフトウェアトークン(TOTP)MFAの登録とチャレンジ(#4620)と、USER_AUTHフロー(#3930)が入りました。PKCE付きのマネージドログイン(#4238)とフェデレーションサインイン(#3748)も2.2.0です。2.3.0では、MFAがOPTIONALのプールでのTOTP(#5101)とパスキー(#5102)が入っています。OAuthトークンエンドポイントのrefresh_tokenグラント(#4948)とトークン失効エンドポイント(#4936)も2.3.0です。

このうちTOTPを、OPTIONALのプールで登録から再ログインまで通しました。

TOTPを登録してユーザー単位で有効化する

プールをOPTIONALで作り、ソフトウェアトークンMFAを有効にします。アプリケーションクライアントはUSER_PASSWORD_AUTHを許可し、ユーザーaliceに恒久パスワードを設定しておきます。

$ aws cognito-idp create-user-pool --pool-name app-pool --mfa-configuration OPTIONAL \
    --query UserPool.Id --output text
us-east-1_cfea00a4f
$ aws cognito-idp set-user-pool-mfa-config --user-pool-id us-east-1_cfea00a4f \
    --mfa-configuration OPTIONAL --software-token-mfa-configuration Enabled=true

MFAを登録していない状態でログインすると、チャレンジなしでトークンが返ります。そのアクセストークンでTOTPのシークレットを取得します。認証アプリケーションの代わりにPythonでコードを計算して、登録を確定します。

$ aws cognito-idp initiate-auth --client-id "$CLIENT" --auth-flow USER_PASSWORD_AUTH \
    --auth-parameters USERNAME=alice,PASSWORD="$PASSWORD" \
    --query 'AuthenticationResult.AccessToken' --output text > access-token.txt
$ aws cognito-idp associate-software-token --access-token "$(cat access-token.txt)"
{
    "SecretCode": "6VLWV5..."   # 32文字のBase32
}
$ aws cognito-idp verify-software-token --access-token "$(cat access-token.txt)" --user-code "$(python3 totp.py "$SECRET")"
{
    "Status": "SUCCESS"
}

totp.pyは、RFC 6238のとおりにHMAC-SHA1で現在の30秒枠の6桁コードを計算します。$CLIENTと$SECRETはcreate-user-pool-clientとassociate-software-tokenの応答の値です。$SESSIONはinitiate-authの応答から取ります。

import base64, hmac, struct, sys, time
secret = sys.argv[1]
key = base64.b32decode(secret + '=' * (-len(secret) % 8), casefold=True)
counter = int(time.time()) // 30
h = hmac.new(key, struct.pack('>Q', counter), 'sha1').digest()
o = h[-1] & 15
print(f"{(struct.unpack('>I', h[o:o+4])[0] & 0x7fffffff) % 1000000:06d}")

登録を確定した直後にもう一度ログインしても、チャレンジは出ませんでした。OPTIONALのプールでは、ユーザー単位の設定(set-user-mfa-preference)で有効にするまでチャレンジが出ない、というAWSのドキュメントの説明と同じです。有効化すると、admin-get-userのPreferredMfaSettingがSOFTWARE_TOKEN_MFAになります。

$ aws cognito-idp set-user-mfa-preference --access-token "$(cat access-token.txt)" \
    --software-token-mfa-settings Enabled=true,PreferredMfa=true

チャレンジに応答する

有効化のあとにログインすると、トークンの代わりにSOFTWARE_TOKEN_MFAチャレンジが返ります。

$ aws cognito-idp initiate-auth --client-id "$CLIENT" --auth-flow USER_PASSWORD_AUTH \
    --auth-parameters USERNAME=alice,PASSWORD="$PASSWORD"
{
    "ChallengeName": "SOFTWARE_TOKEN_MFA",
    "Session": "dXMtZWFzdC0x...",
    "ChallengeParameters": {
        "USERNAME": "alice"
    }
}
$ aws cognito-idp respond-to-auth-challenge --client-id "$CLIENT" --challenge-name SOFTWARE_TOKEN_MFA \
    --session "$SESSION" --challenge-responses USERNAME=alice,SOFTWARE_TOKEN_MFA_CODE="$CODE" \
    --query 'AuthenticationResult.TokenType'
"Bearer"

誤ったコードは、登録時のverify-software-tokenでもチャレンジへの応答でもCodeMismatchExceptionになります。

ChallengeParametersのキーは、AWSではUSER_ID_FOR_SRPです。2.3.0はUSERNAMEで返していて、このキーを読んで応答を組み立てるクライアントは値を取れません。issue #6074として報告されていて、修正(#6086)は10月20日にmainへ入りました。2.3.0には含まれていません。

同じコードを2回使えてしまう

ログインに使った6桁のコードを、新しいログインのチャレンジ(別のSession)にもう一度送ると、Flociは同じようにトークンを返しました。

RFC 6238は、一度認証に成功したコードを再び受け付けてはならないと定めています。2.3.0の実装は、コードが現在の時間枠か前後1枠のどれかに一致するかだけを見ていて、使ったコードを記録していません。

AWSの動作は、実際のユーザープール(Liteティア)に同じ手順を流して確かめました。1回目は認証でき、同じコードの2回目は拒否、次の30秒枠のコードは認証できました。

An error occurred (ExpiredCodeException) when calling the RespondToAuthChallenge operation: Your software token has already been used once.

この差はissue #6804にしました。起票した10月28日のうちに、メンテナーの修正(#6808)がmainへ入っています。直前に受理したコードの時間枠をユーザー単位で保存し、同じ枠か前の枠のコードはExpiredCodeExceptionで拒否します。verify-software-tokenに渡したコードは数えず、認証アプリケーションを登録し直すとリセットされます。2.3.0には含まれていません。

S3のバケットポリシー評価とBlock Public Access

S3の認可は、2.2.0で認証済みの呼び出し元に対するバケットポリシーの評価(#3632)と、データプレーンでのBlock Public Accessの強制(#4253)が入りました。2.3.0では、設定していないバケットのGetPublicAccessBlockが、2023年4月以降のAWSのデフォルトと同じ4項目すべてtrueを返すようになりました(#5434)。

IAMユーザーへの許可と明示的なDeny

IAMユーザーreaderをポリシーなしで作り、そのアクセスキーでオブジェクトを取得すると拒否されます。バケットポリシーでhello.txtのs3:GetObjectだけを許可すると、そのオブジェクトだけ取得できます。

{"Version":"2012-10-17","Statement":[{"Sid":"ReaderGetsHello","Effect":"Allow",
  "Principal":{"AWS":"arn:aws:iam::000000000000:user/reader"},
  "Action":"s3:GetObject","Resource":"arn:aws:s3:::policy-bucket/hello.txt"}]}
# reader のアクセスキーで実行
$ aws s3 cp s3://policy-bucket/hello.txt -
hello from policy-bucket
$ aws s3 cp s3://policy-bucket/private/secret.txt -
download failed: s3://policy-bucket/private/secret.txt to - An error occurred (403) when calling the HeadObject operation: Forbidden
$ echo x | aws s3 cp - s3://policy-bucket/new.txt
upload failed: - to s3://policy-bucket/new.txt An error occurred (AccessDenied) when calling the PutObject operation: User is not authorized to perform: s3:PutObject

Principal: "*"に対するprivate/*の明示的なDenyを足しました。バケットの所有者(testのルート認証情報)がprivate/secret.txtを取得してもAccessDeniedになります。許可対象のhello.txtは引き続き取得できます。

明示的に設定したBlock Public Access

署名なしのリクエスト(curl)で、公開ポリシーの有無とBlock Public Accessの組み合わせを見ました。Block Public Accessをすべてfalseで明示設定した状態から始めます。

$ curl -s -o /dev/null -w '%{http_code}\n' http://localhost:4566/policy-bucket/hello.txt
403
$ aws s3api put-bucket-policy --bucket policy-bucket --policy file://public.json   # Principal "*" に hello.txt の GetObject を許可
$ curl -s -w '  %{http_code}\n' http://localhost:4566/policy-bucket/hello.txt
hello from policy-bucket  200
$ aws s3api put-public-access-block --bucket policy-bucket \
    --public-access-block-configuration BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=true
$ curl -s -o /dev/null -w '%{http_code}\n' http://localhost:4566/policy-bucket/hello.txt
403

今回の比較では、RestrictPublicBucketsを有効にすると匿名アクセスは403になり、ルート認証情報での取得は成功しました。BlockPublicPolicyを有効にしてから公開ポリシーを設定し直すと、put-bucket-policyの時点でAccessDeniedになります。アカウントレベルの設定(s3control put-public-access-block)も合成され、別のバケットへの公開ポリシーの設定が拒否されました。

試したのはこのAllow、Deny、Block Public Accessの組み合わせで、条件キーやクロスアカウントのポリシーは試していません。

新規バケットのデフォルト値は報告されるだけで強制されない

新しく作ったバケットは、2.3.0以降はget-public-access-blockで4項目ともtrueを返します。ところが、同じバケットに公開ポリシーを設定すると成功し、匿名アクセスも成功します。前の節で設定したアカウントレベルのBlock Public Accessは削除してから試しています。

$ aws s3control delete-public-access-block --account-id 000000000000
$ aws s3api create-bucket --bucket fresh-bucket
$ aws s3api get-public-access-block --bucket fresh-bucket --query PublicAccessBlockConfiguration --output text
True    True    True    True
$ aws s3api put-bucket-policy --bucket fresh-bucket --policy file://public-fresh.json
$ echo $?
0
$ echo hello > hello.txt && aws s3 cp hello.txt s3://fresh-bucket/hello.txt
$ curl -s -w '  %{http_code}\n' http://localhost:4566/fresh-bucket/hello.txt
hello  200

同じ4項目をput-public-access-blockで明示的に設定すると、同じポリシーはAccessDeniedになります。報告されるデフォルト値と、強制に使われる値が食い違っています。ソースを見ると、強制時には保存されたバケットの設定とアカウントの設定を合成していて、両方とも未設定ならブロックなしになり、取得APIが返すデフォルト値は使われません。デフォルト値を返す変更(#5434)のコメントには、変更するのはGetPublicAccessBlockの応答だけで、書き込み経路の強制は変えないと書かれています。

AWSでは、新規バケットに公開ポリシーを設定すると、Block Public Accessを理由に拒否されます。実際のアカウントで作ったバケットでの結果は次のとおりです。

An error occurred (AccessDenied) when calling the PutBucketPolicy operation: User: ... is not authorized to perform: s3:PutBucketPolicy on resource: "arn:aws:s3:::..." because public policies are prevented by the BlockPublicPolicy setting in S3 Block Public Access.

Flociのテストで通った公開ポリシーが、AWSへデプロイすると拒否されます。issue #6803にして、デフォルト値を強制時にも適用する変更を提案しています。以前のバージョンで永続化したバケットも設定なしの扱いのため、アップグレード後に公開ポリシーが拒否されるようになる影響があります。意図した設計の変更になるため、PRの前にissueで方針を相談しています。

関連して、get-bucket-policy-statusは2.3.0ではPolicyStatusが空で返ります。IsPublicを返す修正(#6397)はmainに入っています。

EventBridgeの再試行とデッドレターキュー

2.2.0で、EventBridgeのターゲットに設定した再試行ポリシーとデッドレターキュー(DLQ)が実行されるようになりました(#4736)。ターゲットが落ちているときの再試行と、失敗したイベントの送り先を確認するために、API Destinationを相手に試しました。

ステータス500を返す送信先へイベントを送る

常にステータス500を返すHTTPサーバーを、Flociと同じDockerネットワークにeb-echoという名前で起動しました。API Destinationの接続はAPIキー認証(x-api-keyヘッダー)です。ルールのターゲットに、再試行2回と、作成済みのSQSキューeb-dlqをDLQとして設定します。$CONN_ARNと$DEST_ARNは作成時の応答にあるARNです。

$ aws events create-connection --name echo-conn --authorization-type API_KEY \
    --auth-parameters '{"ApiKeyAuthParameters":{"ApiKeyName":"x-api-key","ApiKeyValue":"secret-123"}}'
$ aws events create-api-destination --name echo-dest --connection-arn "$CONN_ARN" \
    --invocation-endpoint http://eb-echo:8080/orders --http-method POST
$ aws events put-rule --name order-rule --event-pattern '{"source":["blog.orders"]}'
$ aws events put-targets --rule order-rule --targets '[{
    "Id":"echo","Arn":"'"$DEST_ARN"'","RoleArn":"arn:aws:iam::000000000000:role/eb-role",
    "RetryPolicy":{"MaximumRetryAttempts":2,"MaximumEventAgeInSeconds":60},
    "DeadLetterConfig":{"Arn":"arn:aws:sqs:us-east-1:000000000000:eb-dlq"}}]'

イベントを1つ送ると、eb-echoのログにPOSTが3回記録されました。初回と再試行2回で、間隔は1秒と2秒でした。

$ aws events put-events --entries '[{"Source":"blog.orders","DetailType":"OrderPlaced","Detail":"{\"orderId\":\"o-200\"}"}]'
07:41:23 POST /orders api-key=secret-123 body={"version":"0","id":"bb37de11-...","source":"blog.orders",...
07:41:24 POST /orders api-key=secret-123 body=(同じイベント)
07:41:26 POST /orders api-key=secret-123 body=(同じイベント)

AWSの再試行間隔にはジッターがあるので、Flociの1秒と2秒が同じとは限りません。確認できたのは、設定した回数だけ再試行されることと、接続に設定したAPIキーがヘッダーに付くことです。

再試行を使い切ったあとのDLQのメッセージ

3回目が失敗したあと、DLQにメッセージが入っていました。本文は元のイベントで、メッセージ属性に失敗の理由が入ります。

$ aws sqs receive-message --queue-url http://localhost:4566/000000000000/eb-dlq \
    --message-attribute-names All --query 'Messages[0].MessageAttributes.*.StringValue'
[
    "arn:aws:events:us-east-1:000000000000:rule/order-rule",
    "arn:aws:events:us-east-1:000000000000:api-destination/echo-dest/f030e44a-...",
    "API destination echo-dest got HTTP 500 from http://eb-echo:8080/orders",
    "MaximumRetryAttempts",
    "2",
    "ERROR_FROM_TARGET"
]

属性名はRULE_ARN、TARGET_ARN、ERROR_MESSAGE、EXHAUSTED_RETRY_CONDITION、RETRY_ATTEMPTS、ERROR_CODEです。AWSのドキュメントにあるDLQのメッセージ属性と一致します。

ドキュメントによると、再試行待ちのイベントはメモリ上に保持され、Flociを再起動すると失われます。

前回の宿題

前回の記事で挙げていた4件は、いずれも2.2.0に修正や実装が入りました。2.3.0で動かした結果をまとめます。

前回の内容 2.3.0での状態
ローカルCAの証明書に鍵識別子(SKI/AKI)がなく、Python 3.13のデフォルト設定で検証に失敗(#4621) #4644で修正。2.1.0の永続化データのまま2.3.0を起動すると、同じ鍵でCAが再発行されてフィンガープリントが変わる。古いca.pemでは失敗し、取得し直したca.pemでPython 3.13から接続できた
Logs Insightsの複合filter(and/or)が0件でCompleteになる。ドキュメントの記述を直すPR(#4717) ドキュメントは2.2.0で更新。動作は変わらず、複合filterは0件でComplete、likeやstatsはFailed。FilterLogEventsのフィルターパターン構文の対応(#5903)はmainのみ
AppSyncのリゾルバーがnightlyでClassNotFoundException #4729で修正。VTLのユニットリゾルバーがNONEとDynamoDBのデータソースで動いた。テンプレートのversionは2018-05-29だけで、2017-02-28は拒否される。GraphQLエンジンはサイドカーfloci/floci-sidecar-graphqlに移り、スキーマの検証ではじめて必要になったときに起動する
CloudFrontでクエリ文字列とビューアのヘッダーがオリジンに届かない #4230で、キャッシュポリシーとオリジンリクエストポリシーに沿って転送されるようになった。ポリシーで限定したヘッダーとクエリ文字列、Cookieは届き、それ以外は転送されなかった。AWSマネージドのキャッシュポリシーは2.3.0では未登録で、追加(#6698)はmainのみ

Cloud Controlのlist-resourcesに対応するリソースタイプは、前回と同じ9種類です。

その他の更新

次の表は、リリースノートに記載されている更新のうち、手元では短い確認にとどめたか、動かしていないものです。

分類 内容 バージョン
SQS MessageRetentionPeriodでメッセージが期限切れになる(#4706)。60秒のキューで、送信から75秒後には取得できなくなった 2.2.0
Logs サブスクリプションフィルターでLambda、Kinesis、Firehoseへ配信(#5021) 2.2.0
Step Functions Distributed MapのItemReaderがCSV(#3603)、JSONL(#3602)、listObjectsV2(#3460)に対応。ItemBatcher(#3604) 2.2.0
DynamoDB DynamoDB Localをバックエンドにするオプション(#4504)。ベクトルインデックスとSearchVectors(#4215) 2.2.0
ECS / EKS ECSのタスクメタデータエンドポイントv4(#4120)とExecuteCommand(#4121)。EKSのポッドへのIAMロールの関連付け(#4012、#4124)とアドオン管理(#4038) 2.2.0
RDS SQL Server(#3752)、リードレプリカ(#3813)、Aurora Serverless v2の一時停止と再開(#4232) 2.2.0
core AWSの全パーティションに対応する基盤(#4317)と、パーティションごとのホスト名(#4451)とARN(#4444)。Docker呼び出しのタイムアウト設定(#5431)。公開イメージのアテステーションにcosignで署名(#3914) 2.2.0 / 2.3.0
CloudFormation ロールバック失敗後の更新拒否とContinueUpdateRollback(#4949)。LambdaのDurableConfig(#5218) 2.3.0
API Gateway デプロイ済みREST APIのOpenAPIエクスポート(#5131、2.3.0)。Cognitoユーザープールオーソライザーの強制(#4323、2.2.0) 2.2.0 / 2.3.0
IAM aws:username、aws:userid、aws:PrincipalType(#5568、2.3.0)。ポリシー変数の解決(#5639、2.3.0)は、Resourceに${aws:username}を含むポリシーで、ユーザー自身のプレフィックスへの書き込みだけが成功した。仮想MFAデバイスのTOTP検証(#4820、2.2.0) 2.2.0 / 2.3.0

2.3.0のあと、mainには10月30日時点で887コミットが積まれています。Lambda Web Functions(10月12日にAWSが発表)とAurora DSQLはこの中にあります。CloudFrontのマネージドキャッシュポリシーとFilterLogEventsのフィルターパターンも同じです。CognitoのUSER_ID_FOR_SRPとTOTPコードの再利用、S3のPolicyStatusの修正もここにあり、いずれも未リリースです。LocalStack互換の環境変数の変換をエントリポイントのシェルスクリプトからJavaに移す変更(#5851)も入っています。mainの実装では、JVMやネイティブバイナリで直接起動する場合にも変換が適用されます。

まとめ

2.2.0以降へ更新するときに手を入れる場所は2つです。2.1.0のローカルCAを信頼している環境ではca.pemを取り直します。AppSyncのVTLリクエストテンプレートは、2.3.0が対応するversionの2018-05-29にします。S3の公開ポリシーをFlociでテストしている場合は、Block Public Accessを明示的に設定しないとAWSと結果が変わります。

今回出したissueは、S3のデフォルトのBlock Public Accessが強制されない件(#6803)と、CognitoのTOTPコードが再利用できる件(#6804)の2つです。後者は起票した日にメンテナーが修正し(#6808)、mainに入っています。前者は方針の回答待ちです。次のリリースが出た時点で、この2件と、mainに入っているLambda Web Functions、Aurora DSQL、CognitoのUSER_ID_FOR_SRPの修正を確認する予定です。

本記事の数値やリリース内容は、次の一次情報をもとにしています。

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事