
- 概要
- はじめに
- マネージド変換とカスタム変換の違い
- 事前準備
- 検証:カスタム変換定義を作って実行する
- カスタム変換定義の管理(ドラフト保存から公開までの仕組み)
- 実践してわかったハマりポイント
- まとめ
- 付録
概要
本記事では、AWS Transform custom を使って「カスタム変換定義」を自作し、Serverless Framework v3 から AWS CDK v2(TypeScript)への移行を試してみた結果をまとめました。
本記事でわかること
- マネージド変換とカスタム変換定義の違い
atx(AWS Transform custom エージェント)を使ったカスタム定義の作成〜実行〜公開の流れ- Serverless Framework → CDK 移行の自動化事例(生成コードの中身・デプロイ結果)
- 変換にかかったコスト(所要時間・費用感)
- 実際に使ってみてよかった点と、気になった点
はじめに
こんにちは。アプリケーションサービス本部 ディベロップメントサービス4課の大橋です。
突然ですが、みなさん AWS Transform custom はご存知でしょうか。
AWS Transform custom は、コードのモダナイゼーションを AI エージェントが自動でやってくれるサービスです。「Python 3.8 → 3.12 に上げたい」「AWS SDK v1 → v2 に移行したい」といった定番パターンは、コマンド一発でコード変換してくれます。
(ちなみに、私も最初勘違いしていたのですが "Transfer" じゃなくて "Transform" です)
弊社でもこのサービスの検証を進めており、これまで以下のブログでマネージド変換(AWS が公式に用意している変換定義)を使う方法を紹介してきました。
今回はその続きとして、マネージド変換を使う側ではなく、カスタム変換定義を自分で作るほうを試してみました。
題材にしたのは、Serverless Framework v3 から AWS CDK v2(TypeScript)への移行です。Serverless Framework v3 はすでに EOL しており、CDK へ移したいけれど、対象が多くて手作業ではつらい、という場面は実際にありそうです。そういうときにカスタム変換定義が使えないか、今回試してみることにしました。なお、今回主に変換対象にしたのは Python のアプリケーションコードそのものではなく、serverless.yml を中心としたインフラ定義です。
マネージド変換とカスタム変換の違い
まず整理として、マネージド変換とカスタム変換の違いを確認しておきます。
マネージド変換は AWS が公式に用意している変換定義です(一覧はこちら)。atx custom def list を実行すると現在 13 種類あり、AWS/ プレフィックスがついています。
$ atx custom def list
Python/Node.js/Java のバージョンアップや SDK 移行など、よくあるパターンが揃っています。設定ファイル(config.yaml)を書けばすぐ使えるのがいいところです。
一方、カスタム変換定義はユーザーが自分で作るものです。今回の「Serverless Framework → CDK」のように、マネージド変換にはないパターンを扱いたいときに向いています。組織内で似た移行を何度もやりたい場合にも使いやすそうです。
| 観点 | マネージド変換 | カスタム変換 |
|---|---|---|
| 提供元 | AWS(AWS/ プレフィックス) |
自分で定義 |
| セットアップ | 不要、すぐ使える | atx で対話的に作成 |
| 対応パターン | ランタイムアップグレード等の定番 | 何でも作れる |
| 再現性・共有 | そのまま使う | ドラフト保存から公開までを経てチーム共有 |
| 向いているケース | 定番パターンを手軽に | 同じ変換を複数リポジトリに横展開 |
カスタム変換定義が向いていそうだと感じたのは、たとえば次のようなケースです。
- マネージド変換にないパターンを変換したいとき
- 同じ変換を複数のリポジトリに展開したいとき
今回の Serverless Framework v3 → CDK の移行は、前者のパターンですね。
逆に、マネージド変換で足りる内容を1リポジトリだけ変換したいなら、わざわざカスタム変換定義を作らなくてもよさそうです。
また、カスタム変換定義には Knowledge Items という仕組みがあり、変換の実行を通じて得られた学びを次回以降に活かせるのも特徴です。
では、実際にどんなコードを変換したのかを見ていきます。今回は Serverless Framework v3 で作ったシンプルな API アプリを題材にしました。
事前準備
※ AWS Transform custom のセットアップ手順は本記事では扱いません。必要な場合は、公式ドキュメント を参照してください。
検証環境
atxバージョン: 1.7.1- Node.js: v24.8.0
- 実行環境: WSL2(Ubuntu 24.04 / Linux 5.15)
- AWS Transform custom の実行リージョン: us-east-1
- 変換後の CDK デプロイ先リージョン: ap-northeast-1
変換対象のプロジェクト構成
まず、変換対象となる Serverless Framework v3 のサンプルプロジェクトを用意しました。
AWS Lambda(Python 3.12)+ Amazon API Gateway(HTTP API)+ Amazon DynamoDB という、よくある構成です。エンドポイントは2つで、GET /items では Amazon DynamoDB から全件取得し、POST /items ではアイテムを登録します。
serverless.yml
service: sample-api frameworkVersion: '3' provider: name: aws runtime: python3.12 region: ap-northeast-1 stage: ${opt:stage, 'dev'} environment: TABLE_NAME: !Ref SampleItemsTable iam: role: statements: - Effect: Allow Action: - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem Resource: !GetAtt SampleItemsTable.Arn - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*' functions: getItems: handler: src/get_items.handler events: - httpApi: path: /items method: get putItem: handler: src/put_item.handler events: - httpApi: path: /items method: post resources: Resources: SampleItemsTable: Type: AWS::DynamoDB::Table Properties: TableName: sample-items-${self:provider.stage} AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH BillingMode: PAY_PER_REQUEST GetItemsLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: /aws/lambda/sample-api-${self:provider.stage}-getItems RetentionInDays: 30 PutItemLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: /aws/lambda/sample-api-${self:provider.stage}-putItem RetentionInDays: 30
ディレクトリ構成はこのとおりです。
sample-serverless-app/ ├── serverless.yml # インフラ定義(変換対象) ├── src/ │ ├── get_items.py # GET /items ハンドラー │ └── put_item.py # POST /items ハンドラー ├── seed/ │ └── seed_data.py # サンプルデータ投入スクリプト └── requirements.txt
atx はコードを直接読み込んで変換を実行するため、git init でリポジトリ化しておく必要があります。
検証:カスタム変換定義を作って実行する
1. atx を起動して変換定義を作成する
準備ができたら atx を起動します。
$ atx
起動するとこんな画面が出てきます。Kiro CLI と似たインターフェースで、リージョンとデフォルトで信頼されているツールが表示されます。
█████╗ ██╗ ██╗███████╗ ██╔══██╗██║ ██║██╔════╝ ███████║██║ █╗ ██║███████╗ ██╔══██║██║███╗██║╚════██║ ██║ ██║╚███╔███╔╝███████║ ╚═╝ ╚═╝ ╚══╝╚══╝ ╚══════╝ ████████╗██████╗ █████╗ ███╗ ██╗███████╗███████╗ ██████╗ ██████╗ ███╗ ███╗ ╚══██╔══╝██╔══██╗██╔══██╗████╗ ██║██╔════╝██╔════╝██╔═══██╗██╔══██╗████╗ ████║ ██║ ██████╔╝███████║██╔██╗ ██║███████╗█████╗ ██║ ██║██████╔╝██╔████╔██║ ██║ ██╔══██╗██╔══██║██║╚██╗██║╚════██║██╔══╝ ██║ ██║██╔══██╗██║╚██╔╝██║ ██║ ██║ ██║██║ ██║██║ ╚████║███████║██║ ╚██████╔╝██║ ██║██║ ╚═╝ ██║ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ┌──────────────────────────────────────────────────────────────────────────────┐ │ Region: us-east-1 │ └──────────────────────────────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────────────────────────┐ │ Trusted Tools │ │ │ │ Built-in Trusted Tools │ │ Configuration: Built-in read-only tools │ │ • file_read │ │ • get_transformation_from_registry │ │ • list_available_transformations_from_registry │ │ • document_manager (add operations only) │ │ • editor (view and find_line operations only) │ └──────────────────────────────────────────────────────────────────────────────┘ Welcome to AWS Transform. You can discover, create, and execute transformations (AWS-managed or custom ones published to your registry). How can I help?
日本語で応答できるか試したところ、問題なかったので日本語で進めました。
「新しいカスタム変換定義を作りたい」と伝えると、エージェントがまずレジストリ(変換定義を登録・管理する場所。atx custom def list で一覧を確認できます)に同様の変換がないか自動確認してくれます。
> 新しいカスタム変換定義を作りたいです。Serverless Framework v3 の serverless.yml を AWS CDK v2 (TypeScript) に変換する定義です。 どのような変換を作成するか理解しました。まず、既存のレジストリに同様の変換がないか確認します。 🔧 Using tool: list_available_transformations_from_registry (trusted) Found 13 transformation definitions - 13 AWS Managed transformations (変換一覧省略) レジストリに類似の変換は見つかりませんでした。新しく作成しましょう。 効果的な変換定義を作成するために、以下の情報があると役立ちます: 1. serverless.yml で主に使用しているリソースはどのようなものですか? 2. カスタムプラグインや特殊な設定はありますか?
このように答えました。
> 特定のリポジトリに依存しない、Serverless Framework v3 の一般的な構成を CDK v2(TypeScript)に変換できる汎用的な定義を作りたいです。 > 今回サンプルとして提供するリポジトリの使用リソースは、AWS Lambda(Python)、Amazon API Gateway(HTTP API)、Amazon DynamoDB、IAM、Amazon CloudWatch Logs です。プラグインは使用していません。 了解しました。汎用的な定義をベースに、サンプルリポジトリで使われているリソースをカバーする形で作成しますね。 (変換定義ファイルを生成中...) トランスフォーメーション定義を作成しました。以下の内容をカバーしています: - Implementation Steps: 8 つのフェーズに分けて段階的に変換 - Phase 1: serverless.yml の解析 - Phase 2: CDK プロジェクトのスキャフォールディング - Phase 3: プロバイダー設定のマッピング(region, stage, environment, API Gateway 等) - Phase 4: Lambda 関数とイベントソースのマッピング - Phase 5: IAM パーミッションのマッピング(grant メソッド優先) - Phase 6: カスタム CloudFormation リソースの変換 - Phase 7: Serverless Framework 変数の CDK 置換 - Phase 8: パッケージ/デプロイ設定 - Validation: TypeScript コンパイル、cdk synth 成功、リソース網羅性の確認など 10 項目
ここで実際に生成されたのは transformation_definition.md という英語の定義ファイルです。
余談:定義ファイルは英語のままにした
視認性のために日本語化できないかも試してみました。
> 私の視認性のために、定義ファイルを日本語で書き直してください。 > もし日本語だと精度が下がるおそれがあるなどの懸念があれば、そのままで大丈夫です。 定義を確認しました。 ご懸念について正直にお伝えします。トランスフォーメーション定義は、コード変換を実行する AI エージェントへの指示書として使われます。そのため、英語で記述されている方が変換の精度と信頼性が高くなります。日本語に書き直すと、以下のような問題が生じる可能性があります: - 技術用語や構文のニュアンスが翻訳で失われるリスク - 変換エンジンが英語の指示に最適化されているため、精度が低下する可能性 - CDKやServerless Frameworkなどの技術固有の概念が不自然な表現になるリスク 現在の定義はそのまま維持しました。
正直なエージェントですね。英語のままで進めることにしました。
生成された transformation_definition.md は約 1,400 語の英語ドキュメントです。冒頭はこんな感じです。
# Serverless-Framework-v3-to-AWS-CDK-v2-TypeScript ## Objective Convert Serverless Framework v3 projects (serverless.yml and associated configuration) into AWS CDK v2 TypeScript projects, replacing the declarative YAML-based infrastructure definition with a programmatic, type-safe CDK stack while preserving the same cloud resources, runtime behavior, and deployment topology. ## Implementation Steps ### Phase 1: Analysis of the Serverless Framework Project ### Phase 2: CDK Project Scaffolding ### Phase 3: Provider-Level Configuration Mapping ### Phase 4: Function and Event Mapping ### Phase 5: IAM Permission Mapping ### Phase 6: Custom CloudFormation Resources ### Phase 7: Variable and Reference Resolution ### Phase 8: Package and Deployment Configuration ## Validation / Exit Criteria 1. The CDK project compiles without TypeScript errors 2. cdk synth produces a valid CloudFormation template without errors ...(10 項目)
自然言語で書かれた変換の指示書ですね。「どう解析して、どう変換して、何を検証するか」が詳細に定義されています。
2. コードへの変換を実行する
「コードに適用する」を選ぶと、まず変換定義がレジストリにドラフト状態で保存されます。
🔧 Using tool: save_transformation (trusted) Successfully saved as draft the new transformation 'Serverless-Framework-v3-to-AWS-CDK-v2-TypeScript' with version 3fy6s6dh9dgbsei4j6m89pel ドラフトとして保存しました。次にコードリポジトリに適用します。 I'll begin orchestrating the Serverless Framework v3 to AWS CDK v2 TypeScript transformation. Let me start by executing Phase 1. **Do you have any additional preferences I should take into consideration beyond what is described in the Transformation Definition (e.g., specific version requirements, build or validation commands, etc.)?**
ここで突然英語に切り替わりました。変換定義の作成フェーズ(対話モード)は日本語で応答してくれていたのに、変換実行フェーズに入った途端に英語になります。どうやら変換実行エンジンは英語前提で動いているようです。
ここで「デフォルトで進めてください」と返すと変換が始まります。
そして変換対象のリポジトリパスを入力すると、エージェントがコードを解析して変換計画を立ててくれます。
I've created the transformation plan. Here's a summary: ## Transformation Plan Summary: Serverless Framework v3 → AWS CDK v2 TypeScript ### Codebase Analysis - Service: `sample-api` — HTTP API(API Gateway v2)+ Lambda(Python 3.12)+ DynamoDB - Functions: 2 Lambda functions (getItems → GET /items, putItem → POST /items) - Resources: DynamoDB table (PAY_PER_REQUEST), CloudWatch Logs x2 - Lambda code: Python — will NOT be modified(Python コードは変換しない) ### Plan (5 Steps) | Step | Title | |------|-------| | 1 | Scaffold CDK v2 TypeScript project | | 2 | DynamoDB table & CloudWatch Logs | | 3 | Lambda functions & IAM | | 4 | HTTP API routes | | 5 | Cleanup & final validation | Each step is independently verifiable via `npm run build && npx cdk synth`.
計画の中に Lambda code: Python — will NOT be modified という記述があります。atx はコードを解析した上で「Python コードはそのまま、インフラ定義だけ変換する」という判断を自動でしてくれています。
「進めてください」と伝えると、Step 1 から順に変換が実行されます。各ステップ完了のたびに cdk synth で確認しながら、git にコミットして進んでいきます。
Step 1: Scaffold CDK v2 TypeScript project — Build status: Success ✅ Step 2: DynamoDB table & CloudWatch Logs — Build status: Success ✅ Step 3: Lambda functions & IAM — Build status: Success ✅ Step 4: HTTP API routes — Build status: Success ✅ Step 5: Cleanup & final validation — Build status: Success ✅
3. 変換結果を確認する
結論として、今回のサンプル構成に対する変換結果はかなり良い感じでした。atx の変換過程で cdk synth の成功が確認できただけでなく、生成された CDK コードを使って手元で cdk deploy まで実行し、API の疎通確認もできました。
生成されたファイルとディレクトリ構成
まず、完了後の git ログにはステップごとのコミットが残っていました。
$ git log --oneline 3a0bf7e Step 5: Update .gitignore, clean up Serverless artifacts, and final validation Build status: Success e94e7b3 Step 4: Create HTTP API (API Gateway v2) with Lambda integrations and routes Build status: Success fd883a4 Step 3: Create Lambda functions with environment variables and IAM permissions Build status: Success 31cb95d Step 2: Create DynamoDB table construct and CloudWatch Logs in the CDK stack Build status: Success 5070ad5 Step 1: Scaffold CDK v2 TypeScript project and install dependencies Build status: Success
作成されたのは以下のファイルです。
sample-serverless-app/ ├── bin/ │ └── sample-api.ts # CDK アプリのエントリポイント(新規) ├── lib/ │ └── sample-api-stack.ts # CDK スタック本体(新規) ├── src/ │ ├── get_items.py # 変更なし │ └── put_item.py # 変更なし ├── package.json # CDK 依存関係(新規) ├── tsconfig.json # TypeScript 設定(新規) ├── cdk.json # CDK コンテキスト(新規) ├── .gitignore # CDK 向けに更新 ├── README.md # CDK デプロイ手順に更新 └── serverless.yml # 残存(参考として)
Python のコードは一切変更されていません。変換されたのはインフラ定義だけです。
変換された CDK コードの中身
肝心の lib/sample-api-stack.ts を見てみましょう。以下は実際に生成されたコードの一部抜粋です。途中は中略しています。
import * as cdk from 'aws-cdk-lib'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as logs from 'aws-cdk-lib/aws-logs'; import * as apigwv2 from 'aws-cdk-lib/aws-apigatewayv2'; import * as apigwv2_integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations'; export class SampleApiStack extends cdk.Stack { constructor(scope: Construct, id: string, props: SampleApiStackProps) { super(scope, id, props); const stage = props.stage; // DynamoDB const table = new dynamodb.Table(this, 'SampleItemsTable', { tableName: `sample-items-${stage}`, partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, }); // Lambda 関数 const getItemsFn = new lambda.Function(this, 'GetItemsFunction', { functionName: `sample-api-${stage}-getItems`, runtime: lambda.Runtime.PYTHON_3_12, handler: 'get_items.handler', code: lambda.Code.fromAsset('src'), environment: { TABLE_NAME: table.tableName }, }); // ...中略(putItemFn の定義など) // IAM: grant メソッドで必要な範囲の権限をまとめて付与 table.grantReadData(getItemsFn); table.grantReadWriteData(putItemFn); // HTTP API const httpApi = new apigwv2.HttpApi(this, 'HttpApi', { apiName: `sample-api-${stage}`, }); httpApi.addRoutes({ path: '/items', methods: [apigwv2.HttpMethod.GET], integration: new apigwv2_integrations.HttpLambdaIntegration('GetItems', getItemsFn), }); // エンドポイントを出力 new cdk.CfnOutput(this, 'HttpApiEndpoint', { value: httpApi.apiEndpoint }); } }
比較:Serverless Framework と CDK の書き方
変換前後の対応を具体的に見てみましょう。
IAM 権限の書き方
# 変換前(serverless.yml) iam: role: statements: - Effect: Allow Action: - dynamodb:Scan - dynamodb:GetItem - dynamodb:PutItem Resource: !GetAtt SampleItemsTable.Arn
// 変換後(CDK) table.grantReadData(getItemsFn); // Scan/GetItem をカバー table.grantReadWriteData(putItemFn); // +PutItem をカバー
イベントトリガーの書き方
# 変換前(serverless.yml) events: - httpApi: path: /items method: get
// 変換後(CDK) httpApi.addRoutes({ path: '/items', methods: [apigwv2.HttpMethod.GET], integration: new HttpLambdaIntegration('GetItems', getItemsFn), });
Serverless Framework の変数参照の書き方
# 変換前(serverless.yml) TableName: sample-items-${self:provider.stage} environment: TABLE_NAME: !Ref SampleItemsTable
// 変換後(CDK) tableName: `sample-items-${stage}`, environment: { TABLE_NAME: table.tableName },
${self:provider.stage} のような Serverless Framework 固有の変数構文が TypeScript のテンプレートリテラルに置き換わり、!Ref が CDK のリソース参照(table.tableName)に変わっています。
実際にデプロイして動作確認してみた
さらに今回は、atx の最終バリデーションだけでなく、生成された CDK コードを使って実際に cdk deploy まで試しました。その結果、SampleApiStack-dev のデプロイは成功し、出力された HTTP API エンドポイントに対して GET /items と POST /items の両方が期待どおりに動作することを確認できました。
今回のサンプルでは、変換された CDK コードをそのままデプロイし、実際に API の動作確認までできました。少なくともこの構成では、生成されたコードが十分使えるものになっていると感じました。
最終バリデーションでは 10 項目すべてが PASS しています。
Validation successful - All exit criteria passed.
- TypeScript コンパイル: ✅ PASS
- cdk synth 成功(17 リソース生成): ✅ PASS
- 全 Lambda 関数が CDK construct として存在: ✅ PASS
- 全イベントトリガーが CDK ルートとして存在: ✅ PASS
- 全リソース(DynamoDB, CloudWatch Logs)が CDK construct として存在: ✅ PASS
- IAM 権限が元の iamRoleStatements 以上をカバー: ✅ PASS
- 環境変数が Lambda 関数に正しく設定: ✅ PASS
- CloudFormation Outputs が存在: ✅ PASS
- CloudFormation リソース数が Serverless Framework と同等: ✅ PASS
- ${self:...}, ${opt:...} 構文がコードに残っていない: ✅ PASS
とはいえ、今回うまくいったからといって、どんな Serverless Framework プロジェクトでも同じように変換できるとは限りません。今回試したのは比較的シンプルな構成でしたが、plugin を使っていたり、サービス構成がより複雑だったりする場合にどうなるのかは、もう少し検証が必要そうです。
4. 今回の所要時間と費用感
AWS Transform custom は Agent Minutes という単位で課金されます(最新の料金はこちら)。2026年4月時点では $0.035 / agent minute です。Agent Minutes は、サーバー側でエージェントが実際に作業している時間を表します。複数のエージェントが並列で動くことがあるため、実時間より大きくなる場合があります。
今回の変換結果は以下のとおりです。
| 現実時間 | Agent Minutes | 費用概算 | |
|---|---|---|---|
| WSL2(タイムアウト多発) | 約 65 分(参考値) | 59.1 AM | 約 $2.07(約 310 円) |
| EC2(Knowledge Items 確認のための再実行) | 約 26 分 | 47.9 AM | 約 $1.68(約 250 円) |
カスタム変換定義の管理(ドラフト保存から公開までの仕組み)
レジストリとは、AWS 側が管理する変換定義のストアです。ローカルではなくクラウド上にあり、マネージド変換もカスタム変換もここで管理されています。
変換が完了すると、変換定義はドラフト状態でレジストリに保存されています(変換実行前に自動保存されています)。
atx custom def list を実行しても、ドラフト状態の定義はリストに表示されません。公開してはじめて他のユーザーから見えるようになります。
なお、カスタム変換定義のスコープは AWS アカウント単位です。公開した定義は同じ AWS アカウント内のユーザー全員が利用できますが、別の AWS アカウントには共有されません。
$ atx custom def publish \ -n Serverless-Framework-v3-to-AWS-CDK-v2-TypeScript \ --tv 3fy6s6dh9dgbsei4j6m89pel ✓ Published the transformation 'Serverless-Framework-v3-to-AWS-CDK-v2-TypeScript' with version '3fy6s6dh9dgbsei4j6m89pel' in the registry.
公開後に atx custom def list を実行すると、AWS マネージド変換の一覧の末尾に自分の変換定義が表示されます。
│ Serverless-Framework-v3-to-AWS-CDK-v2-TypeScript │ Convert Serverless Framework v3 serverless.yml ... │ 3fy6s6dh9dgbsei4j6m89pel │
同じ AWS アカウントのユーザーであれば、以下のコマンドで使えるようになります。
$ atx custom def exec \ -n Serverless-Framework-v3-to-AWS-CDK-v2-TypeScript \ -p /path/to/your/repo \ -x -t
実践してわかったハマりポイント
WSL2 で接続タイムアウトが多発した
今回の検証環境では WSL2 上で atx のバックエンドとの接続が頻繁に切れました。
Unexpected error occurred: Socket timed out without establishing a connection within 10000 ms Fatal error: Socket timed out without establishing a connection within 10000 ms
原因として、今回の検証環境では WSL2 の仮想ネットワーク経由の接続が atx の使用する長時間ストリーミング接続と相性が悪かった可能性があります。
今回は --resume で都度再開しながら進めました。ステップ完了ごとに git コミットされるため、再開後は続きから実行できます。最終的には EC2 上で問題なく動作することを確認できているので、UNIX / Linux ネイティブの環境で実行した方が安全かもしれません。
また WSL2 では日本語入力時に、IME の変換候補が誤って繰り返し入力されることがありました。下図のように同じ文章が何度も貼り付けられてしまいます。(地味にストレスでした)

今回はクリップボード経由でのペーストで回避しました。
Knowledge Items が生成されなかった
カスタム変換定義には Knowledge Items という仕組みがあります。これは、変換の実行軌跡や開発者フィードバック、コード修正の内容などをもとに、継続学習の仕組みが非同期に抽出する学習項目です。Knowledge Items は最初は未承認の状態で作られ、承認すると次回以降の変換に使われます。
変換後に atx custom def list-ki で確認しましたが、2回実行しても生成されませんでした。
$ atx custom def list-ki -n Serverless-Framework-v3-to-AWS-CDK-v2-TypeScript No knowledge items found for transformation definition 'Serverless-Framework-v3-to-AWS-CDK-v2-TypeScript'.
EC2 上でスムーズに変換が完了した後も同様だったため、原因は特定できていません。Knowledge Items の生成条件については引き続き確認中です。
まとめ
今回は AWS Transform custom でカスタム変換定義を作り、Serverless Framework v3 から AWS CDK v2(TypeScript)への変換を試してみました。
これまでマネージド変換は使ったことがありましたが、カスタム変換定義はもっと手間がかかるものだと思っていました。実際に試してみると、用途を伝えるだけでそれに合った変換定義を自動で組み立ててくれて、思っていたよりかなり手軽でした。
変換結果も想像以上によく、cdk synth まで自動で確認してくれるのはかなり助かりました。今回のサンプルでは、最終的に cdk deploy と API の動作確認まで通せたので、少なくともこのくらいの構成なら十分実用的だと感じました。
一方で、WSL2 上での atx の体験はあまりよくなく、タイムアウトや日本語入力まわりは正直ちょっとつらかったです。また、 Knowledge Items が今回の検証では生成されなかった点も心残りでした。
それでも、マネージド変換にない移行パターンを扱いたいときには、カスタム変換定義はかなり面白い選択肢だと感じました。今回は atx と対話しながら変換定義を作りましたが、一度作ってしまえば、その後は非対話モードで複数リポジトリに横展開したり、CI/CD に組み込んだり、という運用もできそうです。みなさんもこの機会にぜひ使ってみてください!
付録
生成された transformation_definition.md(全文)
生成された transformation_definition.md は約 1,400 語の英語ドキュメントでした。本文に入れると長くなるため、付録として全文を載せておきます。
クリックして transformation_definition.md を開く
# Serverless-Framework-v3-to-AWS-CDK-v2-TypeScript ## Objective Convert Serverless Framework v3 projects (serverless.yml and associated configuration) into AWS CDK v2 TypeScript projects, replacing the declarative YAML-based infrastructure definition with a programmatic, type-safe CDK stack while preserving the same cloud resources, runtime behavior, and deployment topology. ## Summary Analyze the existing serverless.yml to identify all declared resources, functions, events, permissions, and environment configuration. Scaffold a new CDK v2 TypeScript project with the appropriate directory structure and dependencies. Map each Serverless Framework construct (functions, events, resources, provider-level settings) to its equivalent CDK L2 construct, preserving naming conventions, environment variables, IAM permissions, and event bindings. Migrate custom CloudFormation resources defined in the resources block. Validate that the resulting CDK stack synthesizes successfully and that the generated CloudFormation template covers all originally declared resources. ## Entry Criteria 1. The project uses Serverless Framework v3 with a valid serverless.yml (or serverless.yaml) as the primary configuration file. 2. The serverless.yml uses the aws provider. 3. The project targets one or more AWS Lambda functions with event sources such as HTTP API (API Gateway v2), REST API (API Gateway v1), S3, SQS, SNS, DynamoDB Streams, Schedule, or CloudWatch Events. 4. Node.js (>=16) and npm/yarn are available in the build environment for initializing the CDK project. 5. The AWS CDK v2 CLI (cdk) is installed or can be installed in the target environment. ## Implementation Steps ### Phase 1: Analysis of the Serverless Framework Project 1. Parse serverless.yml and identify the following top-level sections: service, provider, functions, resources, custom, and package. 2. For the provider section, extract: runtime, region, stage, memorySize, timeout, environment variables, IAM role statements (iamRoleStatements or iam.role.statements), log retention settings, API Gateway configuration (httpApi or apiGateway), and VPC settings if present. 3. For each entry in the functions section, extract: handler path, runtime override, memorySize override, timeout override, environment variables, event triggers (http, httpApi, s3, sqs, sns, schedule, stream, etc.), layers, reserved concurrency, and any function-level IAM permissions. 4. For the resources section, extract all raw CloudFormation resource definitions (Resources) and outputs (Outputs). 5. Note any references to Serverless Framework intrinsic variables such as ${self:service}, ${self:provider.stage}, ${opt:stage}, ${self:custom.*}, and ${aws:accountId}. ### Phase 2: CDK Project Scaffolding 6. Initialize a new CDK v2 TypeScript project in the target directory using `cdk init app --language typescript` or create the equivalent structure manually: - bin/<service-name>.ts (app entry point) - lib/<service-name>-stack.ts (main stack) - cdk.json - tsconfig.json - package.json 7. Add required CDK dependencies to package.json: - aws-cdk-lib (core CDK v2 library) - constructs - Any additional construct libraries if L3 constructs are used 8. If Lambda functions use Python, ensure the CDK project references the original Lambda source code directory (do not convert Python handler code to TypeScript; only the infrastructure definition is converted). ### Phase 3: Provider-Level Configuration Mapping 9. Create the main stack class in lib/<service-name>-stack.ts extending cdk.Stack. 10. Map provider.stage to a CDK context variable or stack parameter, making it configurable at synth/deploy time. 11. Map provider.region to the stack's env property in the app entry point (bin/<service-name>.ts). 12. Map provider.environment variables to a shared configuration object that will be applied to all Lambda functions unless overridden. 13. Map provider.memorySize and provider.timeout to default values applied to all Lambda functions. 14. Map provider.logRetentionInDays to a CloudWatch Logs retention policy applied to each function's log group using the logs.RetentionDays enum. 15. Map provider.httpApi (or provider.apiGateway) settings to an HttpApi (or RestApi) construct: - For HTTP API (API Gateway v2): use aws_apigatewayv2.HttpApi and aws_apigatewayv2_integrations.HttpLambdaIntegration. - For REST API (API Gateway v1): use aws_apigateway.RestApi and aws_apigateway.LambdaIntegration. - Preserve CORS configuration, authorization settings, and custom domain settings if defined. ### Phase 4: Function and Event Mapping 16. For each function defined in the functions section, create a lambda.Function construct: - Set runtime using lambda.Runtime (e.g., lambda.Runtime.PYTHON_3_12 for python3.12). - Set handler to the original handler value. - Set code using lambda.Code.fromAsset() pointing to the function's source directory. - Apply memorySize, timeout (as cdk.Duration.seconds()), and environment variables, merging function-level overrides with provider-level defaults. - If reservedConcurrentExecutions is specified, set the corresponding property. 17. For httpApi events, create an HttpLambdaIntegration and add a route to the HttpApi construct: - Map the method and path from the event definition. - If an authorizer is specified, configure the appropriate HttpAuthorizer. 18. For http events (REST API), create a LambdaIntegration and add the resource/method to the RestApi construct. 19. For schedule events, create an events.Rule with events.Schedule and add the Lambda function as a target. 20. For s3 events, use the s3.Bucket construct's addEventNotification method or create an s3_notifications.LambdaDestination. 21. For sqs events, create an SqsEventSource from aws-lambda-event-sources and add it to the function. 22. For sns events, subscribe the Lambda function to the SNS topic using sns_subscriptions.LambdaSubscription. 23. For stream events (DynamoDB Streams), create a DynamoEventSource from aws-lambda-event-sources with the appropriate startingPosition, batchSize, and other stream configuration. 24. If layers are referenced, create lambda.LayerVersion constructs or reference existing layer ARNs. ### Phase 5: IAM Permission Mapping 25. Map provider-level iamRoleStatements to explicit IAM policy statements: - For each statement, identify the Actions, Resources, and Effect. - Where resources reference Serverless Framework intrinsic variables (e.g., arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/${self:service}-*), convert them to CDK references using the actual construct ARNs (e.g., table.tableArn). 26. Prefer CDK grant methods where available (e.g., table.grantReadWriteData(fn), bucket.grantRead(fn), queue.grantSendMessages(fn)) over raw IAM policy statements. 27. If function-level permissions are specified, create a dedicated IAM policy and attach it to the specific function's role. ### Phase 6: Custom CloudFormation Resources 28. For each resource in the resources.Resources block that is NOT already represented by a CDK construct created in previous steps: - If a CDK L2 construct exists for the resource type, create the equivalent construct with matching properties. - If no L2 construct exists, use cdk.CfnResource or the corresponding L1 construct (Cfn-prefixed class). 29. Map CloudFormation Outputs defined in resources.Outputs to cdk.CfnOutput constructs. ### Phase 7: Variable and Reference Resolution 30. Replace all Serverless Framework variable references with CDK equivalents: - ${self:service} to the stack/service name constant. - ${self:provider.stage} or ${opt:stage} to the stage context variable or parameter. - ${self:custom.*} to TypeScript constants or configuration objects. - ${aws:accountId} to cdk.Aws.ACCOUNT_ID. - ${aws:region} to cdk.Aws.REGION. - CloudFormation intrinsic functions (Fn::GetAtt, Fn::Ref, Fn::Sub, Fn::Join) to CDK native references (construct attributes, cdk.Fn.sub, etc.). 31. Ensure all cross-resource references (e.g., a Lambda function referencing a DynamoDB table name via environment variable) use CDK construct attributes (e.g., table.tableName) rather than hardcoded strings. ### Phase 8: Package and Deployment Configuration 32. Map the package section settings: - package.individually: If true, ensure each function uses a separate lambda.Code.fromAsset() path. - package.exclude / package.include: Document in comments or use bundling options in lambda.Code.fromAsset() to replicate the packaging behavior. 33. Create or update cdk.json with appropriate context values for stage, region, and any custom configuration. 34. Add useful npm scripts to package.json: - "synth": "cdk synth" - "deploy": "cdk deploy" - "diff": "cdk diff" - "destroy": "cdk destroy" <possible_quality_improvement> If the source project uses Serverless Framework plugins (e.g., serverless-offline, serverless-webpack, serverless-python-requirements), additional implementation steps should be added to map plugin behaviors to CDK equivalents or alternative tooling. For example, serverless-python-requirements can be replaced with CDK's PythonFunction construct from @aws-cdk/aws-lambda-python-alpha or custom bundling commands in lambda.Code.fromAsset(). </possible_quality_improvement> <possible_quality_improvement> If the source project uses multiple stages with environment-specific configuration (e.g., different DynamoDB table names or API keys per stage), additional steps should detail how to implement stage-aware configuration in CDK using context values, SSM parameters, or separate environment files. </possible_quality_improvement> ## Validation / Exit Criteria 1. The CDK project compiles without TypeScript errors (npx tsc --noEmit succeeds). 2. cdk synth produces a valid CloudFormation template without errors. 3. Every Lambda function defined in the original serverless.yml has a corresponding lambda.Function construct in the CDK stack. 4. Every event trigger (HTTP API routes, schedules, S3 notifications, SQS subscriptions, DynamoDB streams, etc.) defined in serverless.yml has a corresponding CDK event mapping. 5. Every DynamoDB table, S3 bucket, SQS queue, SNS topic, or other resource defined in the resources section of serverless.yml has a corresponding CDK construct. 6. IAM permissions in the CDK stack cover at minimum the same actions and resources as the original iamRoleStatements. 7. Environment variables defined at both provider and function level in serverless.yml are present in the corresponding CDK Lambda function definitions. 8. CloudFormation Outputs defined in the original serverless.yml are present as CfnOutput constructs in the CDK stack. 9. The synthesized CloudFormation template resource count is comparable to what the Serverless Framework would generate (accounting for CDK-specific helper resources such as CDK metadata). 10. No Serverless Framework-specific variable syntax (${self:...}, ${opt:...}) remains in the CDK code; all references use native CDK or TypeScript equivalents.