CDK v0.33から利用するほどのヘビーユーザー

大越雄太氏(以下、大越):「全AWSアカウント×全CDKアプリでLegacyテンプレートをMigrationした話」をします。よろしくお願いします。今回は5つの章に分けて話を進めます。では「ことの始まり」からお話ししていきたいと思います。

知らない方が大半だと思いますが、実は私たちjustInCase(株式会社justInCaseTechnologies)は、CDK v1発表前からキャッチアップをしており、バージョン0.33から利用を開始している超ヘビーユーザーです。

当時の「Slack」のスクリーンショットをスライドに貼りましたが、これだけではわからないと思うので、CDKの歴史と並べてみました。まず、2018年9月にバージョン0.8のファーストパブリックリリースが公開されました。その後、2019年7月にバージョン1が、2019年11月にバージョン1.18のCDK for Javaが一般公開されました。

それに対して私たちjustInCaseは、2019年2月からCDKのキャッチアップを開始しています。さらに4ヶ月後の2019年6月には、CDK v0.33をKotlinから動かすことに成功しました。ここからjustInCase内でのCDK利用が活発化します。

なぜそこまでCDKをKotlinで動かすことにこだわるのかというと、当時のjustInCaseは、Kotlinユーザーが大半を占めており、KotlinでCDKを書くために「AWS-CDK-Kotlin-DSL」を独自で開発するほどでした。これはawesome-cdkのLanguage Supportの欄の一番上に載るほど、有名なライブラリに育ちました。

CDK v2化で発生するようになったエラー

大越:そんなヘビーな使い方をしている私たちですが、CDK v1に対してはみなさんと同じつらさを強く感じていました。ライブラリが個別なので、CDKリポジトリが大量にあるとバージョンアップが大変だったり、Constructの抽象度が低くて新規開発のたびに同じコードを書く羽目になったり。そんなつらさを感じていたjustInCaseのエンジニアだからこそ、CDK v2の発表を知った時、世界で一番幸せを感じていたのかもしれません。

そんな幸せムードの中、とある古参メンバーが「CDK v2も出てきたことだし、当社でもアップグレードを進めていきましょう!」と提案し、「機能開発のついでに、v2化をしてみました」というプルリクを出してくれました。このプルリクは、問題なくリリースが完了しました。しかし翌日から、それ以外のCDKリポジトリのデプロイが失敗するようになってしまいました。

さて、この原因はいったいなんだったのでしょうか? それは、v2のためだけにbootstrapすると、なにもしていないv1のデプロイはエラーになるからでした。

エラーが発生した原因

大越:CDKを利用するためには、cdk bootstrapコマンドを使い、CDKToolkitというスタックを利用するリージョンにデプロイする必要があります。そのCDKToolkitのテンプレートには、大きく分けてModernテンプレートとLegacyテンプレートの2種類が存在します。もともとはLegacyテンプレートのみでしたが、のちにModernテンプレートが登場しました。現在はどちらも生成・利用が可能です。

今回、この2つの違いについては触れませんが、詳しく知りたい方はスライドの「クラスメソッド」の記事を読んでもらえればと思います。

さらに、CDK v1の初期はLegacyテンプレートしか利用できないので、justInCaseではLegacyテンプレートでのデプロイが行われていました。なお、CDK v1のデフォルト設定では、Modernテンプレートを利用したデプロイのサポートはしていません。

またCDK v2では、Legacyテンプレートによるデプロイのサポートもされていませんでした。なので、CDK v2を利用するためにはModernテンプレートを利用する必要があります。これらの理由から、v2のためにbootstrapするだけではなくv1のModern化対応が必要なことに気がつくことができませんでした。これが(エラーの)主な原因です。

LegacyとModernのCDKアプリを共存させるメリット・デメリット

大越:しかし、AWSがそんな仕様を認めるのだろうか? 僕は気になって調べてみました。結論から言うと、LegacyとModernのCDKアプリを共存させることは可能です。共存させる方法について。cdk deployの前にModernとLegacyのCDKToolkitを作成しておき、デプロイ時にどのCDKToolkitを利用するのかをオプションで指定する必要があります。

共存した場合のメリット・デメリットとしては、既存のv1をほぼそのまま使えるというメリットがあります。ただし、リージョンごとにLegacyとModernの2つのCDKToolkitをデプロイする必要があるほか、cdk deployの時、オプションでわざわざどのCDKToolkitを使うのかを指定しなければならなくなってしまいます。

完全移行のメリット・デメリット

大越:それに対し、完全移行のメリット・デメリットの場合。まずデメリットですが、短期的な作業が少し大変です。利用するAWSアカウントとリージョンすべての環境でCDKToolkitをmodernへ変更する必要があります。また、CDK v1リポジトリのすべてでmodernフラグを設定する必要があります。

しかし、この2つに関しては一度やってしまえば終わりなので、長期的な作業はほぼありません。AWSアカウント作成時のCDKToolkitのキッティングに関しても楽になるし、エンジニアがcdk deployする時に余計なオプションを付けないで済むというメリットもあります。また、誤ってv1が使われるのを防ぐことでv2への移行を後押しできることもメリットです。

ちなみにv1は、2022年6月にメンテナンスモードに移行します。メンテナンスモードに移行してしまうとL2以上のConstructがアップデートされなくなるようなので、そこは注意してもらいたいと思います。

これらの理由や、社内からのアップグレードに対する熱い希望もあり、全AWS環境の完全移行の覚悟を決めました。

cloudformation_includeで管理する方法

大越:「マイグレーションを通して学んだTips」について。話したいTipsは2つあります。cloudformation_includeの便利さについてと、bootstrapする時のテンプレートのバージョンについてです。まずは、cloudformation_includeの便利さについてお話しします。

justInCaseのKotlin CDKでは、Modernテンプレートに対応していないバージョン1.20からバージョン1.64を利用しています。この問題を解消するためには、Kotlin CDKをなんとかアップグレードするか、TypeScriptへの移行を行うかという2択で、とても悩みました。

マイグレーション時に残っていたKotlin CDKのリポジトリは全部で4つで、リポジトリあたり500行から1,000行ぐらいのKotlinのコードがありました。これらKotlin CDKのアップグレードは1.63で止まっているし、それらすべてのKotlinコードをTypeScriptへ書き換えるだけでも何百時間もかかってしまう。そんな悩みを抱えながら、数日間思考を巡るに巡らせ、結局CDKは「CloudFormation」のテンプレートを出力して、スタックをデプロイしているだけだということに気がつきました。

そして、cloudformation_includeで管理する方法を思いつきました。CloudFormationモジュールを使えば、既存スタックのCloudFormationテンプレートをファイルとして管理できる。スライドのソースコードのようにテンプレートファイルとして読み込めば、CDK側で利用することが可能です。これについては、v1、v2のどちらにもあったので実行することは可能だと思います。

また、CloudFormationを利用しているリポジトリや、エンジニアが行方不明になり管理できなくなってしまったCDKリポジトリなど、そういったものが残念ながらいくつかあると思います。そのような場合でもcloudformation_includeを使えば、簡単にダウンタイムなしで移行することができます。

また、環境ごとにテンプレートやリソースが異なっていることが必ずあると思います。例えばDBのサイズやドメインですね。そのような場合は、商用環境のテンプレートをもとに各環境向けの定義で上書きしてデプロイすることができます。これを使うことで、扱いの難しいCloudFormationの拡張構文やKotlinをイチから学ぶことなく、とてもスムーズにTypeScriptに移行できました。

Bootstrapする時のtemplateバージョン

大越:2つ目は、bootstrapする時のテンプレートバージョンについてです。実は、bootstrapを実行するCDK CLIのバージョンによって展開されるModernテンプレートの内容が異なります。v2を使っていれば基本的に大丈夫だと思いますが、v1を使っている時はかなり注意が必要です。

v2で生成したCloudFormationテンプレートには、Modernテンプレートのバージョンが6以上でないとエラーになるルールが追加されています。(スライドを指して)こちらが自動的に追加されるルールです。これについては、実際に手元でdiffコマンドを実行するとどのようなルールが追加されるのかがわかるので、一度実行してみるといいと思います。

これらの理由から、bootstrapする場合はCDK CLIのバージョンが最新かどうかを確認してから実行することで、この問題を事前に回避できます。以上がbootstrapする時のテンプレートバージョンについてでした。

CDKを使う人には最高の環境を作ることができた

大越:マイグレーションを終えて。CDKを使う人にとっては、justInCaseは最高の環境を作ることができたと思います。bootstrapも最新のバージョンで、すべてのAWSアカウントで実施済みです。これからjustInCaseに入る人は、LegacyやModernを気にせず開発に集中することができます。

この話を聞いてCDKをぜひ業務で使ってみたいという方に向けた情報があるようなので、小笠原さんにバトンタッチしたいと思います。

justInCase社からのお知らせ

小笠原寛明氏(以下、小笠原):お約束の宣伝タイムです。もともとv1には10個以上のリポジトリがありましたが、順次v2へのアップデートを進めていて、ここで話していない知見もけっこうあります。

こういうL3 Constructが使えるとか、こういう「Construct Hub」を使うと便利だとか、そういったものがあるので、それらを内側から知りたいという方は、うちの会社に来てもらえればたくさんお話しできると思います。

「カジュアル面談とか大変」という人のために、たくさん情報発信をしています。技術ブログのリンクをTwitterに貼りますが、毎週金曜日の夜に「クラウドのニュース、読むぜ」というのをTwitterスペースをやっているので、そちらに来てもらってたくさんお話しできるとうれしいです。CDKを使う人、たくさん話しましょう。よろしくお願いします。

大越:お願いします。発表は以上です。ありがとうございました。

小笠原:ありがとうございました。

質疑応答

吉田真吾氏(以下、吉田):ありがとうございました。すばらしい創意工夫ですね。

小笠原:ありがとうございます。

大越:ありがとうございます。

吉田:できなかったらヤバかったみたいなことがいくつかあったと思うのですが、なんとか載せきれたという感じですね。今アカウントには、LegacyテンプレートとModernテンプレートのCDKToolスタックがそれぞれ、いろいろなリージョンに載っているという感じですか。

大越:いえ、Legacyテンプレートを使うアプリはすべてModernテンプレートを使うかたちに移行したので、うちの場合はLegacyテンプレートは気にしなくて大丈夫です。

吉田:了解です。

小笠原:これ、大変だったんですよ。最初のリポジトリが10個ぐらいあって、全部にフラグを立てたうえで、対象のリージョンがまた10アカウント以上、3リージョン以上あって、それら1個1個に対して全部v2でbootstrapするというのを真面目にやったんですよ。

大越:そうですね。

吉田:大越さん、大変でしたね。

大越:大変でした。

吉田:新居田さん、質問は来ていますか?

新居田晃史氏(以下、新居田):はい。「現在もCDKはKotlinで作成しているのでしょうか?」。いかがでしょうか。

小笠原:お答えすると、やめました。というのは、もうCDK v2の進化を見てL2、L3のConstructもたくさん出てくるので、これはこっちに乗っかったほうがいいだろうということで全部TypeScriptに移りました。さらに進んだこともやっています。例えばアプリケーションのフロントエンド、バックエンドとインフラ全部をTypeScriptでモノレポで管理するようなこともやっていて、なかなかいいです。

なので、TypeScriptを持ってきたのは正解だったと思います。ついでに語ると、JAWSの話なのにアレですが、CIに「GitHub Actions」を使うことが今増えていると思うんです。一方で「ECS」などのデプロイはコアの部分だから、AWSで、「CodePipeline」でやりたいというケースもあると思っています。そういう場合、インフラに修正を加えようとすると、GitHub ActionsとAWS側の修正を同時に加えなきゃいけないんです。

というわけで、リポジトリのライフサイクルとインフラのライフサイクルが一緒になってしまうので、まとめたほうがいいという実感があります。

吉田:まとめたうえで、例えばECS側に変更が加わった時は、GitHub Actionsはフロントで使っている。

小笠原:実は全部で使っているんですよ。

吉田:全部で使っているんですか?

小笠原:フロントのデプロイとバックエンドのデプロイの両方で使っています。

吉田:じゃあ、すべてのプッシュのイベントに関してGitHub Actionsが走るかたちになるんですね。

小笠原:もちろん変更されているワークスペースがどこかというのは見ますが、一応走っています。そのGitHub Actions側でコンテナのイメージを固めて「ECR」にプッシュして、ECRのプッシュをトリガーにしてCodePipelineが走るみたいな。

吉田:なるほど。

小笠原:全部CodePipelineでやったらいいじゃんと思われるかもしれません。理由が2つあって、1つはGitHubのリポジトリごとのWebhookの個数の上限が20までということ。もう1つは、1個のコンテナイメージを複数の環境にデプロイする場合に、それら全部でコンテナイメージを管理するのか、それとも1ヶ所からプッシュしてしまうのか、どちらのほうが効率がいいのかということですね。

吉田:今のコンテナのイメージに関しては、基本的には1ヶ所に上げて、1ヶ所から複数のリージョンにばらまいているんですか?

小笠原:1ヶ所に上げるまではやっていません。今は1ヶ所でビルドして、それを各箇所にプッシュしていますが、将来的には1ヶ所にプッシュして、クロスアカウントでECRを読みにいくようにしたいと思います。

吉田:わかりました。質問は以上でしょうか。

新居田:そうですね。

吉田:追加があれば、Twitterで引き続き回答してもらいたいと思います。justInCaseの小笠原さんと大越さん、ありがとうございました。

大越:ありがとうございました。

新居田:ありがとうございました。