Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
[AWS CDK] API Gatewayの自動デプロイができない! 解消したらCDKコントリ...
Search
酢ろう
November 04, 2024
Technology
1.1k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
[AWS CDK] API Gatewayの自動デプロイができない! 解消したらCDKコントリビュートできた話
#cm_cdk_special
酢ろう
November 04, 2024
More Decks by 酢ろう
See All by 酢ろう
WingLangでインフラとアプリを同時にコード化!
engineer12taro
0
730
Other Decks in Technology
See All in Technology
SDDの運用にめげずに向き合った話
sansantech
PRO
1
140
メルペイ 会計システム概要と歴史
mewuto
0
200
ハードウェアコンペでもAI駆動開発が進んでいる話
iotengineer22
1
570
AI駆動開発の時代になったのでトヨタ生産方式から見直す
terurou
5
5.2k
3つのアプローチで目指す チームを強くするAI駆動開発
thkt
0
140
自律型 AI をセキュアに実装!Gemini と MIG で作る動的コード実行環境
recruitengineers
PRO
1
230
初対面のメンバーがチームを組み、Claude Codeでゼロからサービスをリリース
mkdev10
1
110
BedrockとLambdaで作る リアルタイム進行型推理ゲーム
kawametho
0
210
自作バイナリを動かす「入口」8選
puhitaku
1
1.1k
予測不能な実行エージェントを安全にサクッと試せるサンドボックス環境の話
sansantech
PRO
0
130
ブラウザとサーバーから考えるAI時代のフロントエンド監視
shogo4131
2
540
Execution in the Kingdom of Agents: Reflections on Abstraction and Complexity
bcantrill
0
880
Featured
See All Featured
Joys of Absence: A Defence of Solitary Play
codingconduct
1
530
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.4k
Un-Boring Meetings
codingconduct
0
440
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
0
260
Code Reviewing Like a Champion
maltzj
529
40k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
232
55k
The Language of Interfaces
destraynor
162
27k
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
AI: The stuff that nobody shows you
jnunemaker
PRO
10
1.1k
[SF Ruby Conf 2025] Rails X
palkan
3
1.4k
We Have a Design System, Now What?
morganepeng
55
8.4k
GraphQLの誤解/rethinking-graphql
sonatard
75
12k
Transcript
API Gatewayの自動デプロイができない! → 解消したらCDKコントリビュートできた話 クラスメソッドのCDK事情大公開スペシャル#1 塚本太朗 1
自己紹介 塚本太朗 リテールアプリ共創部 エンハンスチーム 2023年5月入社(1年半!) 最近ハマっていること HUNTER×HUNTERをちゃんと読む 継承戦編が難しすぎる X: @9Lgo1
2
💬 今日話すこと3行 チームで困っていた謎の挙動を解決したら… → CDK調査の勘所が身につき… → CDKコントリビュートもできた! 3
📇 目次 どんな問題があったか? 問題の原因を紹介 解消方法 CDKの挙動調査Tips CDKコントリビュートを紹介 まとめ 4
cdk deployしても、API Gatewayが更新されな い! 😞 5
😞 どんな問題があったか? CDKでデプロイ (GitHub Actions) API Gatewayが自動デプロイされないので、手動でデプロイを行う CDKでデプロイ 〜 手動デプロイの間に瞬断が発生する
定期的に 🌙深夜リリースが必要だった 6
😀 改善した結果 深夜リリースの対応が不要に! メンバーのリソースを削減 深夜リリースによる身体的ダメージを抑制 7
原因はスタック分割! ✂️ 8
🔍 原因 API GatewayのリソースとLambdaのリソースが別スタックで定義さ れていた 9
🔍 原因: 実装ソース(一部抜粋) /* ApiGatewayのStack */ export class ApiStack extends cdk.Stack
{ constructor(scope: Construct, id: string, props?: cdk.StackProps) { const api = new RestApi(this, "base-api", {}); } } /* LambdaのStack */ export class LambdaStack extends cdk.Stack { constructor(scope: Construct, id: string, props: LambdaStackProps) { // ⭐ API GatewayをfromXXXXAttributeで取得する const apiGateway = RestApi.fromRestApiAttributes(this, "api", { restApiId: props.apiId, rootResourceId: props.rootId, }); // API GatewayとLambdaの紐付けを行う apiGateway.root.addResource("hoge").addMethod("GET", hogeIntegration); } } 10
🔍 原因: 実装ソース(一部抜粋) const api = new RestApi(this, "base-api", {}); /*
ApiGatewayのStack */ export class ApiStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { } } /* LambdaのStack */ export class LambdaStack extends cdk.Stack { constructor(scope: Construct, id: string, props: LambdaStackProps) { // ⭐ API GatewayをfromXXXXAttributeで取得する const apiGateway = RestApi.fromRestApiAttributes(this, "api", { restApiId: props.apiId, rootResourceId: props.rootId, }); // API GatewayとLambdaの紐付けを行う apiGateway.root.addResource("hoge").addMethod("GET", hogeIntegration); } } 10
🔍 原因: 実装ソース(一部抜粋) // ⭐ API GatewayをfromXXXXAttributeで取得する const apiGateway = RestApi.fromRestApiAttributes(this,
"api", { restApiId: props.apiId, rootResourceId: props.rootId, }); /* ApiGatewayのStack */ export class ApiStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { const api = new RestApi(this, "base-api", {}); } } /* LambdaのStack */ export class LambdaStack extends cdk.Stack { constructor(scope: Construct, id: string, props: LambdaStackProps) { // API GatewayとLambdaの紐付けを行う apiGateway.root.addResource("hoge").addMethod("GET", hogeIntegration); } } 10
🔍 原因: 実装ソース(一部抜粋) // API GatewayとLambdaの紐付けを行う apiGateway.root.addResource("hoge").addMethod("GET", hogeIntegration); /* ApiGatewayのStack */
export class ApiStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { const api = new RestApi(this, "base-api", {}); } } /* LambdaのStack */ export class LambdaStack extends cdk.Stack { constructor(scope: Construct, id: string, props: LambdaStackProps) { // ⭐ API GatewayをfromXXXXAttributeで取得する const apiGateway = RestApi.fromRestApiAttributes(this, "api", { restApiId: props.apiId, rootResourceId: props.rootId, }); } } 10
🔍 原因: 実装ソース(一部抜粋) /* ApiGatewayのStack */ export class ApiStack extends cdk.Stack
{ constructor(scope: Construct, id: string, props?: cdk.StackProps) { const api = new RestApi(this, "base-api", {}); } } /* LambdaのStack */ export class LambdaStack extends cdk.Stack { constructor(scope: Construct, id: string, props: LambdaStackProps) { // ⭐ API GatewayをfromXXXXAttributeで取得する const apiGateway = RestApi.fromRestApiAttributes(this, "api", { restApiId: props.apiId, rootResourceId: props.rootId, }); // API GatewayとLambdaの紐付けを行う apiGateway.root.addResource("hoge").addMethod("GET", hogeIntegration); } } 10
⛏️さらに深掘り: CDKのソースを追ってみる Method, Resourceの作成時、デプロイメントのIDを更新するように なっている。 aws-cdk-lib/aws-apigateway/lib/method.tsの抜粋 export class Method extends
Resource { constructor(scope: Construct, id: string, props: MethodProps) { // RestApiのlatestDeploymentがある時... const deployment = props.resource.api.latestDeployment; if (deployment) { deployment.node.addDependency(resource); // ロジカルIDを変更する deployment.addToLogicalId({ method: { ...methodProps, integrationToken: bindResult?.deploymentToken, }, }); } 11
⛏️さらに深掘り: CDKのソースを追ってみる Method, Resourceの作成時、デプロイメントのIDを更新するように なっている。 aws-cdk-lib/aws-apigateway/lib/method.tsの抜粋 // RestApiのlatestDeploymentがある時... const deployment
= props.resource.api.latestDeployment; if (deployment) { export class Method extends Resource { constructor(scope: Construct, id: string, props: MethodProps) { deployment.node.addDependency(resource); // ロジカルIDを変更する deployment.addToLogicalId({ method: { ...methodProps, integrationToken: bindResult?.deploymentToken, }, }); } 11
⛏️さらに深掘り: CDKのソースを追ってみる Method, Resourceの作成時、デプロイメントのIDを更新するように なっている。 aws-cdk-lib/aws-apigateway/lib/method.tsの抜粋 // ロジカルIDを変更する deployment.addToLogicalId({ method:
{ ...methodProps, integrationToken: bindResult?.deploymentToken, }, }); export class Method extends Resource { constructor(scope: Construct, id: string, props: MethodProps) { // RestApiのlatestDeploymentがある時... const deployment = props.resource.api.latestDeployment; if (deployment) { deployment.node.addDependency(resource); } 11
⛏️さらに深掘り: CDKのソースを追ってみる Method, Resourceの作成時、デプロイメントのIDを更新するように なっている。 aws-cdk-lib/aws-apigateway/lib/method.tsの抜粋 export class Method extends
Resource { constructor(scope: Construct, id: string, props: MethodProps) { // RestApiのlatestDeploymentがある時... const deployment = props.resource.api.latestDeployment; if (deployment) { deployment.node.addDependency(resource); // ロジカルIDを変更する deployment.addToLogicalId({ method: { ...methodProps, integrationToken: bindResult?.deploymentToken, }, }); } 11
⛏️さらに深掘り: CDKのソースを追ってみる fromRestApiAttributeで取得した IRestApi には latestDeployment が設定されていない aws-cdk-lib/aws-apigateway/lib/restapi.tsの抜粋 export class
RestApi extends RestApiBase { public static fromRestApiAttributes(scope: Construct, id: string, attrs: RestApiAttributes): IRestApi { class Import extends RestApiBase { public readonly restApiId = attrs.restApiId; public readonly restApiName = attrs.restApiName ?? id; public readonly restApiRootResourceId = attrs.rootResourceId; public readonly root: IResource = new RootResource(this, {}, this.restApiRootResourceId); } return new Import(scope, id); } } 12
⛏️さらに深掘り: CDKのソースを追ってみる 再び Methodクラスの実装 export class Method extends Resource {
constructor(scope: Construct, id: string, props: MethodProps) { // RestApiのlatestDeploymentがある時... const deployment = props.resource.api.latestDeployment; if (deployment) { // ⭐️ここに入ってこない! deployment.node.addDependency(resource); // ロジカルIDを変更する deployment.addToLogicalId({ method: { ...methodProps, integrationToken: bindResult?.deploymentToken, }, }); } } } 13
⛏️さらに深掘り: CDKのソースを追ってみる 再び Methodクラスの実装 // RestApiのlatestDeploymentがある時... const deployment = props.resource.api.latestDeployment;
if (deployment) { // ⭐️ここに入ってこない! export class Method extends Resource { constructor(scope: Construct, id: string, props: MethodProps) { deployment.node.addDependency(resource); // ロジカルIDを変更する deployment.addToLogicalId({ method: { ...methodProps, integrationToken: bindResult?.deploymentToken, }, }); } } } 13
⛏️さらに深掘り: CDKのソースを追ってみる 再び Methodクラスの実装 export class Method extends Resource {
constructor(scope: Construct, id: string, props: MethodProps) { // RestApiのlatestDeploymentがある時... const deployment = props.resource.api.latestDeployment; if (deployment) { // ⭐️ここに入ってこない! deployment.node.addDependency(resource); // ロジカルIDを変更する deployment.addToLogicalId({ method: { ...methodProps, integrationToken: bindResult?.deploymentToken, }, }); } } } 13
💡 解決方法 毎回新しいDeploymentを作成するため、専用のスタックを用意した ※ 0から作るなら、この実装はあまりオススメできません 14
💡 解決方法: コード紹介 // ApiDeploymentStack export class ApiDeploymentStack extends cdk.Stack
{ constructor(scope: Construct, id: string, props: ApiDeploymentStackProps) { // api-stackで定義したAPI Gateway const apiGateway = RestApi.fromRestApiAttributes(this, "api", { restApiId: props.apiId, rootResourceId: props.rootId, }); // Deploymentを定義 const deployment = new Deployment(this, "base-api-deployment", { api: apiGateway, retainDeployments: true, }); deployment.addToLogicalId(new Date().toISOString()); // ⭐️ Deploymentを更新 // Stageを定義し、deploymentを紐づける const stage = new Stage(this, "base-api-stage", { deployment, stageName: "prod", }); } } 15
💡 解決方法: コード紹介 // api-stackで定義したAPI Gateway const apiGateway = RestApi.fromRestApiAttributes(this,
"api", { restApiId: props.apiId, rootResourceId: props.rootId, }); // ApiDeploymentStack export class ApiDeploymentStack extends cdk.Stack { constructor(scope: Construct, id: string, props: ApiDeploymentStackProps) { // Deploymentを定義 const deployment = new Deployment(this, "base-api-deployment", { api: apiGateway, retainDeployments: true, }); deployment.addToLogicalId(new Date().toISOString()); // ⭐️ Deploymentを更新 // Stageを定義し、deploymentを紐づける const stage = new Stage(this, "base-api-stage", { deployment, stageName: "prod", }); } } 15
💡 解決方法: コード紹介 // Deploymentを定義 const deployment = new Deployment(this,
"base-api-deployment", { api: apiGateway, retainDeployments: true, }); deployment.addToLogicalId(new Date().toISOString()); // ⭐️ Deploymentを更新 // ApiDeploymentStack export class ApiDeploymentStack extends cdk.Stack { constructor(scope: Construct, id: string, props: ApiDeploymentStackProps) { // api-stackで定義したAPI Gateway const apiGateway = RestApi.fromRestApiAttributes(this, "api", { restApiId: props.apiId, rootResourceId: props.rootId, }); // Stageを定義し、deploymentを紐づける const stage = new Stage(this, "base-api-stage", { deployment, stageName: "prod", }); } } 15
💡 解決方法: コード紹介 // ApiDeploymentStack export class ApiDeploymentStack extends cdk.Stack
{ constructor(scope: Construct, id: string, props: ApiDeploymentStackProps) { // api-stackで定義したAPI Gateway const apiGateway = RestApi.fromRestApiAttributes(this, "api", { restApiId: props.apiId, rootResourceId: props.rootId, }); // Deploymentを定義 const deployment = new Deployment(this, "base-api-deployment", { api: apiGateway, retainDeployments: true, }); deployment.addToLogicalId(new Date().toISOString()); // ⭐️ Deploymentを更新 // Stageを定義し、deploymentを紐づける const stage = new Stage(this, "base-api-stage", { deployment, stageName: "prod", }); } } 15
🕵️♀️ Tips: 役に立った調査方法 CDKのソースコードを解析する(今までの流れ) CloudTrailでデプロイ時の流れを確認する 16
🕵️♀️ CloudTrailでデプロイ時の流れを確認する デプロイ時のリソース作成・更新などを時系列で確認 正常動作と異常動作の差を確認できる 17
🌱 0から作れるなら? RestApiを作成したスタックで、LambdaのIDを参照する 参考: AWS CDKでAPI Gateway+Lambdaを作成する際のベストな スタック構成について Lambdalithの構成を検討する 参考: LambdalithとSingle
purpose Lambdaは1つのAPI Gateway で共存できる 18
🎉 CDKコントリビュートもできました! https://github.com/aws/aws-cdk/pull/29691 19
🎉CDKコントリビュートもできました!: 対応内容 今回紹介した問題が起こるサンプルコードがCDKの公式ドキュメン トで紹介されていた そのサンプルコードの下に注意文言を記載するPRを作成 対応内容はシンプルですが、レビュアーの方とのやり取りで色々勉 強になりました!! 20
得られた知見 他スタックから fromXXX などでインポートしたリソースが、予想外 の挙動を引き起こすかもしれない。 CloudTrailを活用して、CDKデプロイ時の挙動を分析できる。 CDKで謎の挙動をする時はソースを見てみる。読みやすいので、意 外と早く原因が特定できる。 21
まとめ 慎重にスタック分割をしよう!メリット・デメリットを把握した上 で慎重に行う。 CDKの構成があまり良くない時は、OSSコントリビュートのチャン ス転がってくるかも! とはいえ…サーバーレス構成をCDKで実装する時は、さまざまな制 限に注意して構成を考えよう。 22
ご清聴ありがとうございました!質問あればお願 いします! 23
参考 aws-cdk AWS CDKでAPI Gateway+Lambdaを作成する際のベストなスタック 構成について LambdalithとSingle purpose Lambdaは1つのAPI Gatewayで共存で
きる 今回対応したPR 登壇の元ブログ 24
この登壇の元ブログ 25
26