Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
[AWS CDK] API Gatewayの自動デプロイができない! 解消したらCDKコントリ...
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
酢ろう
November 04, 2024
Technology
960
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
660
Other Decks in Technology
See All in Technology
AIAU_UMEMOGU_ninomiya_slide
ninomiya_ii
0
150
プロダクト開発から業務改善コンサルまで。事業全体へ「染み出す」ことで広がるエンジニアの可能性
ham0215
0
140
小さく始める AI 活用推進 ― 日経電子版 Web チームの事例/nikkei-tech-talk47
nikkei_engineer_recruiting
0
280
2026 TECHFRESH 畢業分享會 - AI-Native 重塑軟體工程與虛擬講師
line_developers_tw
PRO
0
1.2k
フィジカル版Github Onshapeの紹介
shiba_8ro
0
270
AIソロプレナー時代に2ヶ月で20人増員した事業創造会社の開発組織の話
miyatakoji
0
680
攻撃者視点で考えるDetection Engineering
cryptopeg
3
1.9k
【NRUG vol.18】なぜ多くのオブザーバビリティ導入は失敗するのか
nrug_member
0
180
2026TECHFRESH畢業分享會 - Lightning Talk - E起 See See : 電商推薦讀心術? 數據說了算
line_developers_tw
PRO
0
1.2k
LayerXにおけるセキュリティ管理の現在地と次の一手
tosho
0
230
20260619 私の日常業務での生成 AI 活用
masaruogura
1
220
Chainlitで作るお手軽チャットUI
ynt0485
0
260
Featured
See All Featured
Everyday Curiosity
cassininazir
0
230
The Mindset for Success: Future Career Progression
greggifford
PRO
0
360
Stewardship and Sustainability of Urban and Community Forests
pwiseman
0
230
A Soul's Torment
seathinner
6
2.9k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Joys of Absence: A Defence of Solitary Play
codingconduct
1
390
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
Code Reviewing Like a Champion
maltzj
528
40k
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
730
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
250
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
160
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
34
2.8k
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