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
720
Other Decks in Technology
See All in Technology
AIに任せた品質は、誰が見立てるのか - AI時代のテストマネジメント
nakanao
3
2.2k
Azure Serverless 2026:Production-ready な AI エージェント基盤 / Azure Serverless 2026: Production-Ready AI Agent Platform
miyake
2
240
絵ではじめるKubernetesセキュリティ
aoi1
4
680
HHKBエバンジェリストになる方法
941
0
110
負債のメタファと2026年 / Debt Metaphor in Agentic Engineering Age 202609 Edition
twada
PRO
10
4.1k
.NET WebAssemblyで実現するクライアントサイドAI推論:NuGetからViteまで、2つのエコシステムを繋ぐビルド戦略
yamachu
0
200
今話題のAI「Jev」って何? 宇宙最速で学ぶ会
minorun365
PRO
29
16k
LTのテーマ どうきめてる?〜5つの型と私のやり方〜
yama3133
1
110
Genieを崇めよ
kameitomohiro
0
170
データ界隈LT祭 第1回LT登壇
taromatsui_cccmkhd
2
1.4k
越境するなら専門用語を使うな高校校歌 / If you wanna cross border, you shouldn't use jargon
vtryo
0
150
TinyGo 開発サイクルを高速化する:Go で作るエミュレータ入門
zozotech
PRO
1
750
Featured
See All Featured
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
203
76k
DevOps and Value Stream Thinking: Enabling flow, efficiency and business value
helenjbeal
1
390
Building Applications with DynamoDB
mza
96
7.2k
ラッコキーワード サービス紹介資料
rakko
1
4.9M
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
240
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
3
1.2k
Large-scale JavaScript Application Architecture
addyosmani
515
110k
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
510
Odyssey Design
rkendrick25
PRO
2
810
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.6k
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.8k
Heart Work Chapter 1 - Part 1
lfama
PRO
10
36k
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