AWS CDKリファクタリングの罠と解決策
AWS CDKにおけるインフラリファクタリングの課題と「cdk refactor」の登場背景
AWS CDKを使ってインフラ構築をしていると、プロジェクトが成長するにつれて「このリソースの名前、もっとわかりやすくしたいな」「このデータベース、別のスタックに切り出したいな」と思う場面がよくあります。ソフトウェア開発において、コードの振る舞いを変えずに内部構造を整理する「リファクタリング」は日常茶飯事ですよね。
しかし、CDKにおけるインフラのリファクタリングには、これまで大きな壁がありました。このセクションでは、私たちが直面していた課題と、それを打ち破るために登場した「cdk refactor」の背景についてお話しします。
従来のCDKリファクタリング時のリスク(リソースの置き換えによるデータ損失)
CDKでコードを書いていると、コンストラクト(AWSリソースを定義するための部品)に付けた名前を変えたり、別のスタックに移動したりしたくなることがあります。しかし、これをそのままデプロイすると重大なトラブルに繋がる可能性があります。
CDKの裏側では、コードがCloudFormationのテンプレートに変換されています。CloudFormationは「論理ID」という内部の識別子を使ってリソースを管理していますが、CDKのコードでコンストラクトの名前を変えると、この論理IDも一緒に変わってしまいます。
CloudFormationは論理IDが変わると、「古いリソースが削除されて、新しいリソースが作成された」と勘違いします。つまり、ただ名前を変えただけなのに、実環境では「削除&再作成」が行われてしまうのです。
Warning
ステートフルなリソース(状態を持つリソース)でこの置き換えが発生すると、データ損失やサービス中断に直結します。例えば、DynamoDBのテーブル名をコード上で変更しただけで、本番のテーブルが一旦削除され、中のデータがすべて消えてしまうリスクがあります。RDS(データベース)なども同様で、保持しているデータが消え去る大事故につながりかねません。
この「コードを綺麗にしたいだけなのに、本番環境を壊すかもしれない」という恐怖心が、CDKエンジニアの心理的ハードルを大きく引き上げていました。
CloudFormationリファクタリングの煩雑な手順
「では、安全にリソースを保持したまま名前を変えるにはどうすればいいのか?」という話になります。実は、CloudFormation側には「リファクタリングAPI」という、リソースを削除せずに論理IDを変更する機能自体は以前から存在していました。
しかし、CDKからこの機能を使おうとすると、非常に多くの手間がかかりました。具体的には、以下のようなステップを踏む必要がありました。
- CDKのコードを修正する
cdk diffで差分と新しい論理IDを確認するcdk synthでテンプレートファイル(JSON/YAML)を出力する- 出力されたテンプレートファイルを手作業で開き、対象リソースのメタデータを現在の状態に合わせて手動修正する
- 「どの古い論理IDを、どの新しい論理IDにマッピングするか」という定義ファイルを手作業で作成する
- CloudFormationのリファクタリングAPIを実行する
- 再度
cdk diffを行い、メタデータの差分だけが反映されているか確認する cdk deployを実行する
これだけの工程があり、しかもテンプレートやマッピング定義の修正は「手作業」です。7〜10ステップにも及ぶ複雑な手作業の中で、少しでも設定を間違えれば元も子もありません。そのため、多くのエンジニアは「面倒だから、今の変な名前のまま我慢しよう」となりがちでした。
プレビュー版「cdk refactor」コマンドがもたらす開発体験の向上
こうした長年の課題を一気に解決してくれる機能として、2024年9月に「cdk refactor」コマンドがプレビューリリースされました。
このコマンドを使うと、先ほど挙げた7〜10ステップの複雑な手作業が、わずか2〜3ステップに短縮されます。基本的には「CDKのコードを修正する」→「cdk refactorコマンドを実行する」だけです。面倒なテンプレートのメタデータ修正や、マッピング定義ファイルの作成は、すべてコマンドが裏側で自動的に計算して処理してくれます。
これがもたらす最大の価値は、単なる「作業時間の短縮」ではありません。エンジニアの「心理的ハードル」を下げてくれる点にあります。
「リファクタリングしたいけど、手間がかかるしミスしたくないからやめておこう」という葛藤がなくなります。インフラコードも普通のアプリケーションコードと同じように、いつでも気軽に整理・改善できるようになったことは、CDKを使った開発体験(DX)を根底から変える大きな一歩だと言えます。
cdk refactorとは?デプロイ済みリソースを保持したまま構成を変更する仕組み
ソフトウェア開発において「リファクタリング」という言葉があるように、インフラのコードでも「外から見た動作は変えずに、中身のコードをより綺麗で扱いやすい形に再構成したい」という場面がよくあります。cdk refactorは、まさにそのための仕組みです。
一言で言えば、「すでにデプロイされているAWSリソースをそのまま残した状態で、CDKコードの構造だけを安全に変えるための機能」です。前のセクションでお話ししたような、うっかりリソースが消えてしまうリスクを抱えることなく、コードの整理整頓ができるようになります。
コンストラクトの名前変更やスタック間移動を安全に実行する仕組み
CDKでインフラを定義する際、私たちは「コンストラクト」と呼ばれる部品を組み合わせてスタック(デプロイの単位)を作ります。ここで、後から「このコンストラクトの名前をもっと分かりやすくしたい」と思ってIDを変更したり、「データベースの定義をネットワーク用のスタックから切り離して、専用のスタックに移動したい」と思ったりすることがあります。
しかし、先述の通り、これらの変更を行うとCloudFormationの論理IDが変わることで、リソースの「置き換え」が発生してしまいます。
cdk refactorは、この問題を解決するために生まれました。コマンドを実行すると、CDKがコードの変更をインテリジェントに検出し、裏側でCloudFormationが提供している「リファクタリングAPI」と連携します。これにより、CloudFormation側でリソースを管理するための内部ID(論理IDと呼びます)を更新するだけで、実際のAWS上のリソースそのものは手付かずで保持されます。
コードとデプロイ状態の比較によるリソースマッピングの自動計算
では、cdk refactorはどのようにして「古いリソース」と「新しいコード上のリソース」が同一であることを証明しているのでしょうか。
コマンドが実行されると、まず「現在書かれているCDKコード」と「すでにAWS上にデプロイされている状態」の2つを比較します。そして、「どのリソースが、コードのどの場所に移動したのか」という対応付け(マッピング)を自動で計算します。
このとき、システムは非常に厳密なチェックを行います。移行前と移行後で「リソースの種類と数が全く同じで、プロパティ(設定値)なども一切変わっていないこと」を確認します。つまり、配置場所(スタックやコンストラクトの階層)だけが変わったことを確かめるのです。
Important
もし比較の結果、リソースの追加・削除、あるいは設定値の変更が一つでも含まれていると、cdk refactorは安全を優先してエラーを出し、処理を拒否します。これにより、「移動するつもりだったのに、うっかり設定も変えていて意図しない再作成が起きた」という事故を未然に防ぐようになっています。
あいまいなマッピングを解決するためのフォールバック機能
自動計算はとても便利ですが、すべてのケースで機械が正しい答えを導き出せるとは限りません。
例えば、スタック内に「同じ設定のDynamoDBテーブル」が2つあったとします。その両方を別のスタックに移動させようとした場合、機械側では「どちらのテーブルが、どちらの移動先に対応するのか」を一意に決めることができず、処理が止まってしまうことがあります。これを「マッピングのあいまいさ」と呼びます。
こうした場合のために、手動でマッピングを指定する「フォールバック機能」が用意されています。--override-fileというオプションを使い、JSON形式のファイルを用意します。
例えば、以下のようにファイルに記述してコマンドに渡すと、機械が迷っていた対応関係を明示的に解決できます。
{
"environments": [
{
"account": "123456789012",
"region": "ap-northeast-1",
"resources": {
"OldStack.TableA": "NewStack.UserTable",
"OldStack.TableB": "NewStack.LogTable"
}
}
]
}
自動計算に任せっきりにせず、このように人間が確実なマッピング情報を提供することで、複雑で大規模なリファクタリングであっても安全かつ確実に実行することが可能になります。
cdk refactorの代表的なユースケース
ここまで仕組みについて解説してきましたが、「実際に自分のプロジェクトでどう役立つの?」と気になりますよね。cdk refactorは、多くのエンジニアがインフラコードを書いていく中で直面する「変えたいけど変えられない」という壁を突破してくれます。代表的な3つのユースケースを見ていきましょう。
モノリシックな巨大スタックの分割による懸念事項の分離
サービスの初期開発では、ネットワーク(VPCやサブネット)からデータベース、アプリケーションのサーバーまで、なんでも1つのスタックに詰め込んでしまいがちです。いわゆる「モノリシックなスタック」です。しかし、システムが成長してチーム開発になってくると、「ネットワークの変更なのにアプリケーションまでデプロイ時間が長くなる」「データベース層だけ独立して権限を分けたい」といった課題が出てきます。
例えば、Webアプリケーションスタックに定義されていたDynamoDBのテーブルを、独立した「データベーススタック」に移動させたいとします。従来の方法では、スタックからDynamoDBの定義を消して別スタックに移すと、CloudFormationが「古いテーブルを削除して、新しいテーブルを作る」と解釈してしまいます。結果として、ユーザーのデータがすべて消えるという致命的な事態に繋がりかねません。
cdk refactorを使えば、この移動を「テーブルの実体はそのままに、管理しているスタックだけを変更する」という形で安全に実行できます。既存のテーブルに保存されている数万件のデータも、サービスを止めることなく完全に保持されたまま移行が完了します。
Note
DynamoDBやRDSなどの「ステートフルなリソース(データや状態を保持するリソース)」を移動させる際、データが保持されることは本番環境の安定性に直結します。cdk refactorの最大の恩恵は、このステートフルなリソースの移動が恐れなくなる点にあります。
L3コンストラクトへのアップグレードやライブラリの安全な切り替え
CDKには「コンストラクト」という部品の概念があり、L1(CloudFormationの直接的な置き換え)、L2(デフォルト値が設定された便利な部品)、L3(複数のL2を組み合わせて作られたより高度な部品)という階層があります。システムが複雑になるにつれて、バラバラだったL2のコンストラクトを束ねて、独自のL3コンストラクトを作りたくなるケースがよくあります。
また、コミュニティが提供するサードパーティ製の便利なライブラリ(Construct Hubなどで公開されているもの)を導入したり、バージョンアップしたりする機会もあるでしょう。しかし、ライブラリをアップデートすると、内部で生成される「論理ID(CloudFormationがリソースを識別するための内部名)」が予告なく変更されることがあります。
論理IDが変わると、先ほどと同じように「新しいリソースが作られた」とみなされて置き換えが発生します。cdk refactorは、コード上のコンストラクト階層がどう変わっても、裏側で生成されるAWSリソースのセットが同一であれば、論理IDの変更を「ただの名前の変更」として安全に同期してくれます。これにより、ライブラリのアップグレードによる意図しないリソースの再作成を防ぐことができます。
Tip
L3コンストラクトへの切り替えは、インフラ構成を見直す絶好のタイミングです。単にリソースをまとめるだけでなく、ビジネスロジックやチームの責任分割に合わせてコンストラクトを再グループ化すると、今後の保守が格段に楽になります。
コードベースの可読性向上を目的としたコンストラクト名の変更
プロトタイプの段階では、「Table1」「QueueA」「LambdaFunction2」のように、適当な名前を付けてリソースを定義してしまうこと、ありませんか?このようなケースは開発初期によく見られます。最初はそれで良くても、後からコードを読み返したときに「これ、どの注文データを入れるテーブルだっけ?」と迷うことが増えてきます。
チーム開発において、コードの可読性は非常に重要です。後からビジネス要件に合わせて「UserOrderTable」や「PaymentNotificationQueue」のように明確な名前にリファクタリングしたいのは当然の欲求です。
しかし、CDKではコンストラクトのIDを変更すると、それがそのままCloudFormationの論理IDの変更に繋がるため、リソースの置き換えリスクを伴います。そのため、多くのエンジニアが「名前を変えるのは怖いから、今の適当な名前のままコメントで誤魔化そう」と妥協してきました。
cdk refactorがあれば、こうした名前の変更をリソースの置き換えリスクを一切気にすることなく実行できます。コードベース全体の見通しが良くなり、新しいメンバーがプロジェクトに参加した際の理解度も劇的に向上します。「コードは読まれるためにある」という原則を、インフラコードの世界でもストレスなく実践できるようになるのです。
cdk refactorコマンドの基本的な使い方と主要オプション
ここまで仕組みやユースケースを見てきましたが、「じゃあ実際にどうコマンドを打てばいいの?」というのが気になりますよね。ここからは、cdk refactorコマンドの具体的な使い方と、知っておくと作業がぐっと楽になる主要なオプションについて一緒に見ていきましょう。
プレビュー機能の有効化(—unstable=refactor)とコマンドの基本構文
まず大前提として、cdk refactorコマンドを利用するには、必ず--unstable=refactorというオプションを付ける必要があります。2024年9月時点でこの機能はプレビューリリースとなっており、通常の安定版コマンドとして隠れている状態です。そのため、このオプションを付けないと「そんなコマンドは知らないよ」とエラーになってしまいます。
基本的なコマンドの構文は以下のようになります。
npx cdk refactor --unstable=refactor [対象のスタックID]
対象となるスタックの指定方法も覚えておきたいポイントです。例えば、DatabaseStackとNetworkStackの間でリソースを移動させたい場合は、スペース区切りで複数のスタックIDを渡すことができます。
npx cdk refactor --unstable=refactor DatabaseStack NetworkStack
また、スタックIDを一切指定せずにコマンドを実行すると、CDKアプリに含まれる「すべてのスタック」がリファクタリングの対象になります。特定のスタックだけを安全に操作したい場合は、必ずスタックIDを明記するようにしましょう。なお、リソースの移動先となるスタックを指定し忘れた場合でも、移動元のスタックを指定していれば、CDK側が賢く判断して自動的に移動先を処理対象に含めてくれます。
Important
cdk refactorコマンドを実行する際は、必ず--unstable=refactorオプションを指定してください。これがないとコマンドが認識されません。これはプレビュー機能であることを開発者に意識づけ、本番環境での不用意な実行を防ぐための安全装置として設計されています。
実行前のドライラン(—dry-run)による変更内容の事前検証
インフラのリファクタリングにおいて一番怖いのは「意図していなかった変更が勝手に適用されてしまうこと」です。手動でのテンプレート編集は、意図しない変更が加わるリスクを伴います。
そのような事態を防ぐために有用なのが--dry-runオプションです。以下のようにコマンドに付与して実行してみてください。
npx cdk refactor --unstable=refactor --dry-run DatabaseStack
このオプションをつけると、実際のCloudFormationへのリクエスト送信や変更適用は一切行われず、CDKが計算した「リソースのマッピング結果」だけをコンソールに表示してくれます。例えば「DatabaseStackのTableOldという論理IDを、NewDatabaseStackのTableNewという論理IDにマッピングしますよ」といった内容が、実際にどう解釈されているのかを事前に把握できます。
スタックが数個程度ならまだしも、モノリシックな巨大なスタックを複数に分割するような複雑なリファクタリングでは、想定外のリソースまで移動対象に含まれていることがあります。ドライランで出力されたマッピング結果を眼で見て確認する習慣をつけておけば、そうしたミスを未然に防ぐことができます。
Tip
複雑なリファクタリングを行う際は、まず--dry-runでマッピング結果を確認し、内容に問題がないことを確認してから、オプションを外して本番実行するという2段階のステップを踏むことをベストプラクティスとしておすすめします。
CI/CDパイプラインでの自動実行と強制実行(—force)の活用
cdk refactorコマンドは、デフォルトの設定だとマッピング計算が終わった後に「この内容でリファクタリングを実行しますか?(y/n)」といった確認プロンプトが表示され、ユーザーがキーボードでyを入力してはじめて実際の処理が走ります。
この確認プロンプトをスキップして、一気にリファクタリングを進行させたい場合は--forceオプションを使います。
npx cdk refactor --unstable=refactor --force DatabaseStack
このオプションが真価を発揮するのは、GitHub ActionsやGitLab CIなどのCI/CDパイプライン上で自動実行したい場合です。パイプライン上では対話型のプロンプト入力ができないため、通常であればエラーで止まってしまいます。--forceをつけることで、確認の手間を省いて自動化の流れの中に組み込むことができます。
ちょっと補足ですが、公式ドキュメントによると、CI/CDなどの非インタラクティブな環境では、そもそもCDK CLIがプロンプトを表示せずに自動的に処理を続行する仕様になっています。つまり、厳密にはパイプライン実行時に--forceが必須というわけではありません。しかし、明示的にオプションをつけておくことで、パイプラインのコードを見た他の開発者にも「ここは対話的な確認なしで進める意図だよ」というのが伝わりやすくなります。
一方で、手元のローカルターミナルで作業しているときに--forceを使うケースもあります。例えば、先ほど紹介した--dry-runで十分に検証を終えていて「もう間違いない」と確信がある場合などです。何度も同じスタックに対してリファクタリングを試行錯誤している時などは、確認プロンプトをスキップできることで作業効率がぐっと上がります。ただし、手元での実行で--forceを使う場合は、油断して事前検証を飛ばしてしまわないように注意してくださいね。
cdk refactorを安全に実行するための注意点と制約事項
ここまでcdk refactorの便利さや具体的な使い方を見てきましたが、とても便利な分、油断すると思わぬ落とし穴にはまることもあります。この機能はあくまで「移動や名前の変更」に特化した安全設計になっており、何でもかんでもリファクタリングしてくれる魔法の杖ではありません。
最後のセクションでは、cdk refactorを安全に使うための重要な制約と、実際に手を動かす際の心構えについて整理しておきます。
リソースの追加・削除・変更が含まれる場合のエラーによる保護機能
cdk refactorのコアな仕様として、許可されている操作は「リソースの移動」と「名前の変更(=論理IDの変更)」のみです。逆に言えば、これら以外の変更が含まれていると、コマンドはエラーを出して処理を完全に拒否します。
例えば、DynamoDBのテーブルを別のスタックに移動する際に、「ついでに課金モードをプロビジョンドからオンデマンドに変えよう」とプロパティ(リソースの設定値)を変更したとします。この状態でコマンドを実行すると、CDKは「設定が変わっているよ」と検知してエラーを吐き出します。同様に、リソースを一つでも増やしたり減らしたりした場合も、即座にエラーになります。
一見すると「厳しすぎるかな」と感じるかもしれませんが、これは非常に優秀な安全装置です。リソースの設定を変えながら場所も移動してしまうと、CloudFormation側で「古いリソースを削除して、新しい設定のリソースを別の場所に作り直す」という、最もやってはいけない置き換えが発生してしまう可能性があります。エラーで止めてくれるおかげで、私たちが意図しないデータ損失やサービス中断から守られているんです。
Warning
「移動」と「設定変更」を同時に行うことはできません。必ず、cdk refactorを使ってリソースの移動だけを先に完了させ、その後に通常のcdk deployで設定変更を行うというように、手順を分けて実行するようにしてください。
実行前に移行元と移行先のスタックをデプロイしておく必要性
リソースをスタック間で移動させる場合、やってしまいがちな失敗の一つに「移行先のスタックをデプロイし忘れる」というものがあります。
実は、cdk refactorコマンドが正しくマッピングを計算するためには、移動前のスタック(移行元)と、移動後のスタック(移行先)の両方が、すでにAWS上にデプロイ済みである必要があります。
具体的な正しい手順は以下のようになります。
- 移行元のスタック(リソースが入っているもの)をデプロイ済みの状態にしておく
- 移行先となる新しいスタックをコードに追加し、中身は空の状態で
cdk deployしておく - ローカルのコード上で、リソースの定義を移行元から移行先に移動させる
cdk refactorコマンドを実行する
もし手順2を飛ばして、空のスタックをデプロイせずにコードだけ書き換えてcdk refactorを実行してしまうと、CDKは「移動先のスタックがAWS上に存在しないよ」と判断してエラーになります。マッピングの計算は、現在のローカルコードと、AWS上に実際に存在するデプロイ済みの状態を比較して行われるため、スタック間でリソースを移動させる場合は、必ず空の移行先スタックを事前にcdk deployで作成しておくことが前提となります。
プレビュー段階であることへの留意点と本番環境での利用に向けた心構え
何度もお伝えしている通り、cdk refactorコマンドは現在「プレビューリリース」という位置づけです。これは、今後のCDKのアップデートにおいて、コマンドのオプションや挙動が予告なく変更される可能性があることを意味します。
そのため、いきなり重要な本番環境のインフラに適用するのはリスクが高いです。特にDynamoDBやRDSのような、データを保持するステートフルなリソースが絡むリファクタリングでは、一歩間違えるとビジネスに致命的な影響を与えかねません。
本番環境で利用する場合は、以下のステップを踏むことを強くおすすめします。
まず、開発環境やステージング環境で対象のリソースを再現し、そこで何度かテストを行います。その際、必ず--dry-runオプションを付けて実行し、コンソールに出力されるマッピング結果が自分の意図した移動先と完全に一致しているかを、人間の目で丁寧に確認してください。
Caution
本機能はプレビュー版です。本番環境に適用する前は、必ず開発環境等で--dry-runを用いた十分なテストを実施してください。特にデータベースなどのステートフルなリソースを扱う際は、バックアップを取得した上で、慎重に検証を重ねることをお勧めします。
制約がいくつかあるとはいえ、これらはすべて「安全にリファクタリングを行うためのガードレール」です。ルールを正しく理解して守れば、これほど心強い味方はありません。ぜひ、小さなリソースから少しずつ試して、安全なインフラ運用に役立ててみてくださいね。