本日のお題
Serverless Framework(以下「SFW」)において、API GatewayのAPIキーを作成する方法、及びその際の注意点について。
なお前回記載した通り、今回のお題は前編と後編の2回に分けており、この記事はその後編になります。
- 前編:作成方法(これは前回)
- 後編:注意点と対策(今回はココ)
参考
TL; DR
provider.apiGateway.apiKeys
配列の末尾以外のAPIKeyを削除すると、予期しない挙動をするケースがある- 対策としては、
enabled:false
で無効化するか、resources.Resources
に生CFnテンプレートを書く- もちろん、削除しないのが一番いい
- APIキーの値を設定する可能性がある場合、APIキー名は設定しない方がよい。(必ずではない)
Serverless FrameworkでAPIキーを作成する際の注意点
先に結論から言うと、「apiKeys
配列で上にある(=末尾ではない)APIキーを削除する場合は注意が必要」となります。
そしてその際の挙動は「他のAPIキーに名前(=name)を設定しているものがあるかどうか」によって変わります。
ケース1:名前(=name)を設定していない場合
まず、現在のAPIキーに名前(=name)を設定しているものがない状態だったとします。
provider: apiGateway: apiKeys: - description: 削除するAPIキー - description: 削除しないAPIキー1 - description: 削除しないAPIキー2
この状態で、一番上の「description: 削除するAPIキー」を削除してデプロイすると、最新のAPIキーは下記の状態となります。(1個ずつ上にスライドする感じ)
- エラーは発生しない(=デプロイ自体は成功する)
- 「description: 削除しないAPIキー1」のキーの値(=value)が、デプロイ前の「description: 削除するAPIキー」の値になっている
- 同様に「description: 削除しないAPIキー2」のキーの値(=value)が、デプロイ前の「description: 削除しないAPIキー1」の値になっている
ケース2:名前(=name)を設定している場合
次に、現在のAPIキーに名前(=name)を設定しているものがある状態だったとします。
provider: apiGateway: apiKeys: - description: 削除するAPIキー - name: noDeletedKey1 description: 削除しないAPIキー1 - description: 削除しないAPIキー2
この状態で、一番上の「description: 削除するAPIキー」を削除してデプロイすると、下記の状態となります。
- エラーが発生する(=デプロイ自体が失敗する)
- 「ApiKey:noDeletedKey1 already exists」というエラーが表示される。(=名前の重複エラー)
対策
上記の現象は、簡潔に言うと「CloudFormation(以下CFn)の論理IDのズレ」が原因で起こります。
これの回避策としては、下記2点となります。
enabled:false
を設定して対応するresources.Resource
セクションに生CFnテンプレートでAPIキーを定義する
なおこの「CFnの論理IDのズレ」については、この下の「参考:論理IDのズレが起こる理由」で説明しているので、興味があれば一度読んでみてください。(単にAPIキーを作るだけなら、特に知らなくても問題ありません。)
対策1:enabled:false を設定して対応する
これが一番手っ取り早いです。
enabled:false
を設定すれば、そのAPI Keyは無効になりますし、API Key自体が削除されるわけではないので、先述の「論理IDのズレ」も発生しません。
ただし「使わなくなったAPIキーがずっと残り続ける」という問題があります。(許容できるなら問題なし)
対策2:resources.Resources セクションに生CFnテンプレートでAPIキーを定義する
生CFnテンプレートなら、論理IDを自分で設定でき、使わなくなったAPIキーを削除しても論理IDが変更されることはないので、これも「論理IDのズレ」は起こりません。
例えばこんな感じです。
resources: Resources: # APIキーの設定 CfnApiGatewayApiKey1: Type: 'AWS::ApiGateway::ApiKey' Properties: Description: APIキー1 StageKeys: # 「ApiGatewayRestApi」は、Serverless FrameworkがAPI Gatewayに対して付与する固定の論理ID - RestApiId: !Ref ApiGatewayRestApi - StageName: dev CfnApiGatewayApiKey2: Type: 'AWS::ApiGateway::ApiKey' Properties: Description: APIキー2 StageKeys: - RestApiId: !Ref ApiGatewayRestApi - StageName: dev # 使用料プランを作成した場合、使用料プランとAPIキーの紐づけ設定 CfnApiGatewayUsagePlanKey1: Type: AWS::ApiGateway::UsagePlanKey' Properties: KeyId: !Ref CfnApiGatewayApiKey1 KeyType: 'API_KEY' # 「ApiGatewayUsagePlan」は、Serverless Frameworkが使用料プランに対して付与する固定の論理ID UsagePlanId: !Ref ApiGatewayUsagePlan CfnApiGatewayUsagePlanKey2: Type: 'AWS::ApiGateway::ApiKey' Properties: KeyId: !Ref CfnApiGatewayApiKey2 KeyType: 'API_KEY' UsagePlanId: !Ref ApiGatewayUsagePlan
ただし、この方法を実施する場合、以下の問題があります。
- APIキー(必要に応じて使用料プランとの紐づけ設定も)を自分で1から生CFnテンプレートを書く必要がある
- 使用料プラン自体はServerless Frameworkの
usagePlan
で定義すればOK
- 使用料プラン自体はServerless Frameworkの
- 初回デプロイ時のみ、必ず「ApiKeyのデプロイステージが不正(=存在しない)」というエラーが出る
参考:論理IDのズレが起こる理由
Serverless Frameworkでは、serverless.ymlをCloudFormation(以下CFn)テンプレートに変換する際、apiKeys
の配列に対して、上から順に「ApiGatewayApiKey1」「ApiGatewayApiKey2」「ApiGatewayApiKey3」...のように、「ApiGatewayApiKey+連番」という論理IDを付けます。
なので最初の状態は、CFnテンプレートは以下のようになります。
ApiGatewayApiKey1: Type: 'AWS::ApiGateway::ApiKey' Properties: Description: 削除するAPIキー StageKeys: # 「ApiGatewayRestApi」は、Serverless FrameworkがApi Gatewayに対して付与する固定の論理ID - RestApiId: !Ref ApiGatewayRestApi - StageName: dev ApiGatewayApiKey2: Type: 'AWS::ApiGateway::ApiKey' Properties: Description: 削除しないAPIキー1 StageKeys: - RestApiId: !Ref ApiGatewayRestApi - StageName: dev ApiGatewayApiKey3: Type: 'AWS::ApiGateway::ApiKey' Properties: Description: 削除しないAPIキー2 StageKeys: - RestApiId: !Ref ApiGatewayRestApi - StageName: dev
この状態で一番上の「description: 削除するAPIキー」を削除すると、最新のCFnテンプレートは以下のようになります。
ApiGatewayApiKey1: Type: 'AWS::ApiGateway::ApiKey' Properties: Description: 削除しないAPIキー1 StageKeys: - RestApiId: !Ref ApiGatewayRestApi - StageName: dev ApiGatewayApiKey2: Type: 'AWS::ApiGateway::ApiKey' Properties: Description: 削除しないAPIキー2 StageKeys: - RestApiId: !Ref ApiGatewayRestApi - StageName: dev
つまりCFnテンプレート的には「ApiGatewayApiKey1&2のDescriptionだけが変更された」という扱いになってしまうので、「ケース1:名前(=name)を設定していない場合 」で説明した現象が発生します。(内部的には「削除するAPIキー」自体、削除されていない)
また「ケース2:名前(=name)を設定している場合」に関してですが、一番上の「description: 削除するAPIキー」の削除前後の最新のCFnテンプレートは以下のようになります。
# 削除前 ApiGatewayApiKey1: Type: 'AWS::ApiGateway::ApiKey' Properties: Description: 削除するAPIキー StageKeys: - RestApiId: !Ref ApiGatewayRestApi - StageName: dev ApiGatewayApiKey2: Type: 'AWS::ApiGateway::ApiKey' Properties: Description: 削除しないAPIキー1 Name: noDeletedKey1 StageKeys: - RestApiId: !Ref ApiGatewayRestApi - StageName: dev ApiGatewayApiKey3: Type: 'AWS::ApiGateway::ApiKey' Properties: Description: 削除しないAPIキー2 StageKeys: - RestApiId: !Ref ApiGatewayRestApi - StageName: dev #削除後 ApiGatewayApiKey1: Type: 'AWS::ApiGateway::ApiKey' Properties: Description: 削除しないAPIキー1 Name: noDeletedKey1 StageKeys: - RestApiId: !Ref ApiGatewayRestApi - StageName: dev ApiGatewayApiKey2: Type: 'AWS::ApiGateway::ApiKey' Properties: Description: 削除しないAPIキー2 StageKeys: - RestApiId: !Ref ApiGatewayRestApi - StageName: dev
この状態でデプロイしようとすると、CFnは下記の挙動を取ります。
- 先頭の定義から順に処理を実施する
- 「ApiGatewayApiKey1」にNameが新規に設定されたから、既存のAPIキー(=「description: 削除するAPIキー」のAPIキー) の削除→再生成(=置き換え)を実行しようとする
- でも前回の「ApiGatewayApiKey2」で作成した「Name: noDeletedKey1」のキーがまだ存在している
- Nameが重複しているのでエラー
結果、名前の重複エラーとなります。
ちなみに、名前を指定した際にAPIキーの削除→再生成が実施されるのは、CFnの仕様のようです。
- 名前を指定すると、置き換えを必要とする更新を実施できない
- 置き換えを実施する場合、名前を変更する必要がある
- 今回は新たに名前を指定したので「置き換えを実施する必要がある」と判断された
If you specify a name, you cannot perform updates that require replacement of this resource. You can perform updates that require no or some interruption. If you must replace the resource, specify a new name.
前回の記事の中で「value
を設定する場合、name
は設定しないことが推奨されている」と記載しましたが、この制約があるからだと思います。
告知
今週の2023/6/24(土) (AWS Dev Day 2023の翌日)に開催される「Reject Day 2023」(主催:JAWS-UG&AWS Startup Community)において「Mobageの監視環境をAWSで構築する話」という内容でお話しさせて頂くことになりました。
内容ですが、YAPC::Kyoto 2023でお話しした内容をベースに、もう少し開発者よりな話をしたいと思いますので、皆様よろしくお願いします。
それでは、今回はこの辺で。